Fundamentals of Fluid Transfer

- Fixed some inconsistencies with a tanks' fluidhandler invalidation when resized
- Patched crashes in present fluid handling of the basin
- Tanks now slightly shade horizontal faces of the contained liquid
- Tanks no longer resend data every tick when filled gradually
- Introduced a new lerped value type with better design decisions
- Refactored Smart tileentity serialization to better support custom overrides in contained behaviours

- Pumps propagate flows in the pipe networks in front and behind itself.
- Pumps collect all possible in and outputs across the reachable pipe graph as endpoints
- Flows move across multiple branches of a pipe network when both are equally viable
- Open-ended pipes are treated as endpoints and leak fluid into and out of a block space
- Open endpoints serialize stateful information about fluid units gathered and held at the interface
- Open endpoints turn a fluid block into 1000 fluid units and back
- Open endpoints undo their transaction when their flow changes from pull to push
- Open endpoints cannot pull fluids back when a full liquid block was not placed yet
- Open endpoints waterlog blocks when the provided fluid is water
- A collision response is triggered when different types of fluids meet at open endpoints
- Fluids are transferred instantly by the throughput of a completed flow per tick
- Pumps cut flows when vital pipes are removed
- Pumps do not lose progress of finished flows when an unrelated part of the pipe network changes
- Pumps do not lose progress of finished flows when reversed
- Pumps distribute their throughput across available input flows evenly
- Pumps distribute gathered input fluid across outputs evenly
- Pumps expose furthest reachable pipefaces to other pumps for chained transfer
- Chained pumps with fully overlapping flow sections provide their endpoints at the entrance of the other pump
- Chained pumps with overlapping flow sections participate in two shared endpoints, one for each pump dominating the contested region
- Chained pumps with overlapping flow only transfer via the optimal of the two possible endpoints based on their speeds
- Chained pumps of equal speed pick one of the two available endpoints deterministically
- Pumps transfer without flows when no pipe is between the pump and the endpoint
- Pumps serialize and recover stateful information about held fluid units at open endpoints
- Chained pumps do not actively transfer when both are partaking with push flows (or both pulling)
- A pull flow originating from an inter-pump endpoint only commences when the corresponding push flow is completed
- Chained pumps re-determine the optimal flow when the speed of one is changed at runtime
- Throughput of chained pumps is determined by their weakest link in terms of speed
- Endpoints created for chained pumps is treated equally to other available endpoints when fluid is distributed
- Pipes do not contain a physical amount of fluid.
- Pipes never hold serialized vital stateful information about fluid transfer.
- Pipes synchronize local flow progress and fluid type to clients
- Flows in a pipe progress with the speed of the network flow
- A networks flow speed depends on the speed of the aggregated pump
- Pipe flows of different flow graphs of different pumps interact with each other
- A collision response is triggered when two different types of fluid meet within a pipe
- Pipes spawn particles to illustrate contained flows/liquids of flows
- The fluid transfer role is exposed through a TE behaviour with some callbacks and properties
- Open endpoints show particles when interacting with in-world fluids
This commit is contained in:
simibubi 2020-08-24 21:02:03 +02:00
parent 8e349380a5
commit 2040d66c3e
94 changed files with 3282 additions and 937 deletions

View file

@ -11,6 +11,7 @@ public enum AllSpecialTextures {
BLANK("blank.png"), BLANK("blank.png"),
CHECKERED("checkerboard.png"), CHECKERED("checkerboard.png"),
THIN_CHECKERED("thin_checkerboard.png"), THIN_CHECKERED("thin_checkerboard.png"),
CUTOUT_CHECKERED("cutout_checkerboard.png"),
HIGHLIGHT_CHECKERED("highlighted_checkerboard.png"), HIGHLIGHT_CHECKERED("highlighted_checkerboard.png"),
SELECTION("selection.png"), SELECTION("selection.png"),

View file

@ -43,6 +43,7 @@ import com.simibubi.create.content.contraptions.components.structureMovement.pul
import com.simibubi.create.content.contraptions.components.structureMovement.pulley.PulleyTileEntity; import com.simibubi.create.content.contraptions.components.structureMovement.pulley.PulleyTileEntity;
import com.simibubi.create.content.contraptions.components.turntable.TurntableTileEntity; import com.simibubi.create.content.contraptions.components.turntable.TurntableTileEntity;
import com.simibubi.create.content.contraptions.components.waterwheel.WaterWheelTileEntity; import com.simibubi.create.content.contraptions.components.waterwheel.WaterWheelTileEntity;
import com.simibubi.create.content.contraptions.fluids.FluidPipeTileEntity;
import com.simibubi.create.content.contraptions.fluids.FluidTankRenderer; import com.simibubi.create.content.contraptions.fluids.FluidTankRenderer;
import com.simibubi.create.content.contraptions.fluids.FluidTankTileEntity; import com.simibubi.create.content.contraptions.fluids.FluidTankTileEntity;
import com.simibubi.create.content.contraptions.fluids.PumpRenderer; import com.simibubi.create.content.contraptions.fluids.PumpRenderer;
@ -57,7 +58,11 @@ import com.simibubi.create.content.contraptions.relays.advanced.sequencer.Sequen
import com.simibubi.create.content.contraptions.relays.belt.BeltRenderer; import com.simibubi.create.content.contraptions.relays.belt.BeltRenderer;
import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity; import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
import com.simibubi.create.content.contraptions.relays.elementary.SimpleKineticTileEntity; import com.simibubi.create.content.contraptions.relays.elementary.SimpleKineticTileEntity;
import com.simibubi.create.content.contraptions.relays.encased.*; import com.simibubi.create.content.contraptions.relays.encased.AdjustablePulleyTileEntity;
import com.simibubi.create.content.contraptions.relays.encased.ClutchTileEntity;
import com.simibubi.create.content.contraptions.relays.encased.EncasedShaftRenderer;
import com.simibubi.create.content.contraptions.relays.encased.EncasedShaftTileEntity;
import com.simibubi.create.content.contraptions.relays.encased.SplitShaftRenderer;
import com.simibubi.create.content.contraptions.relays.gauge.GaugeRenderer; import com.simibubi.create.content.contraptions.relays.gauge.GaugeRenderer;
import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeTileEntity; import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeTileEntity;
import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeTileEntity; import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeTileEntity;
@ -86,7 +91,12 @@ import com.simibubi.create.content.logistics.block.mechanicalArm.ArmRenderer;
import com.simibubi.create.content.logistics.block.mechanicalArm.ArmTileEntity; import com.simibubi.create.content.logistics.block.mechanicalArm.ArmTileEntity;
import com.simibubi.create.content.logistics.block.packager.PackagerRenderer; import com.simibubi.create.content.logistics.block.packager.PackagerRenderer;
import com.simibubi.create.content.logistics.block.packager.PackagerTileEntity; import com.simibubi.create.content.logistics.block.packager.PackagerTileEntity;
import com.simibubi.create.content.logistics.block.redstone.*; import com.simibubi.create.content.logistics.block.redstone.AnalogLeverRenderer;
import com.simibubi.create.content.logistics.block.redstone.AnalogLeverTileEntity;
import com.simibubi.create.content.logistics.block.redstone.NixieTubeRenderer;
import com.simibubi.create.content.logistics.block.redstone.NixieTubeTileEntity;
import com.simibubi.create.content.logistics.block.redstone.RedstoneLinkTileEntity;
import com.simibubi.create.content.logistics.block.redstone.StockpileSwitchTileEntity;
import com.simibubi.create.content.logistics.block.transposer.LinkedTransposerTileEntity; import com.simibubi.create.content.logistics.block.transposer.LinkedTransposerTileEntity;
import com.simibubi.create.content.logistics.block.transposer.TransposerTileEntity; import com.simibubi.create.content.logistics.block.transposer.TransposerTileEntity;
import com.simibubi.create.content.schematics.block.SchematicTableTileEntity; import com.simibubi.create.content.schematics.block.SchematicTableTileEntity;
@ -95,6 +105,7 @@ import com.simibubi.create.content.schematics.block.SchematicannonTileEntity;
import com.simibubi.create.foundation.tileEntity.renderer.SmartTileEntityRenderer; import com.simibubi.create.foundation.tileEntity.renderer.SmartTileEntityRenderer;
import com.tterrag.registrate.util.entry.TileEntityEntry; import com.tterrag.registrate.util.entry.TileEntityEntry;
import com.tterrag.registrate.util.nullness.NonNullFunction; import com.tterrag.registrate.util.nullness.NonNullFunction;
import net.minecraft.tileentity.TileEntityType; import net.minecraft.tileentity.TileEntityType;
public class AllTileEntities { public class AllTileEntities {
@ -191,6 +202,11 @@ public class AllTileEntities {
.renderer(() -> PumpRenderer::new) .renderer(() -> PumpRenderer::new)
.register(); .register();
public static final TileEntityEntry<FluidPipeTileEntity> FLUID_PIPE = Create.registrate()
.tileEntity("fluid_pipe", (NonNullFunction<TileEntityType<FluidPipeTileEntity>, ? extends FluidPipeTileEntity>) FluidPipeTileEntity::new)
.validBlocks(AllBlocks.FLUID_PIPE)
.register();
public static final TileEntityEntry<FluidTankTileEntity> FLUID_TANK = Create.registrate() public static final TileEntityEntry<FluidTankTileEntity> FLUID_TANK = Create.registrate()
.tileEntity("fluid_tank", (NonNullFunction<TileEntityType<FluidTankTileEntity>, ? extends FluidTankTileEntity>) FluidTankTileEntity::new) .tileEntity("fluid_tank", (NonNullFunction<TileEntityType<FluidTankTileEntity>, ? extends FluidTankTileEntity>) FluidTankTileEntity::new)
.validBlocks(AllBlocks.FLUID_TANK) .validBlocks(AllBlocks.FLUID_TANK)

View file

@ -43,7 +43,7 @@ public class KineticDebugger {
VoxelShape shape = world.getBlockState(toOutline) VoxelShape shape = world.getBlockState(toOutline)
.getRenderShape(world, toOutline); .getRenderShape(world, toOutline);
if (te.getTheoreticalSpeed() != 0) if (te.getTheoreticalSpeed() != 0 && !shape.isEmpty())
CreateClient.outliner.chaseAABB("kineticSource", shape.getBoundingBox() CreateClient.outliner.chaseAABB("kineticSource", shape.getBoundingBox()
.offset(toOutline)) .offset(toOutline))
.lineWidth(1 / 16f) .lineWidth(1 / 16f)

View file

@ -178,7 +178,7 @@ public abstract class KineticTileEntity extends SmartTileEntity
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { protected void write(CompoundNBT compound, boolean clientPacket) {
compound.putFloat("Speed", speed); compound.putFloat("Speed", speed);
if (needsSpeedUpdate()) if (needsSpeedUpdate())
@ -202,7 +202,7 @@ public abstract class KineticTileEntity extends SmartTileEntity
compound.put("Network", networkTag); compound.put("Network", networkTag);
} }
return super.write(compound); super.write(compound, clientPacket);
} }
public boolean needsSpeedUpdate() { public boolean needsSpeedUpdate() {
@ -210,12 +210,13 @@ public abstract class KineticTileEntity extends SmartTileEntity
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
boolean overStressedBefore = overStressed;
clearKineticInformation(); clearKineticInformation();
// DO NOT READ kinetic information when placed after movement // DO NOT READ kinetic information when placed after movement
if (wasMoved) { if (wasMoved) {
super.read(compound); super.read(compound, clientPacket);
return; return;
} }
@ -235,14 +236,9 @@ public abstract class KineticTileEntity extends SmartTileEntity
overStressed = capacity < stress && StressImpact.isEnabled(); overStressed = capacity < stress && StressImpact.isEnabled();
} }
super.read(compound); super.read(compound, clientPacket);
}
@Override if (clientPacket && overStressedBefore != overStressed && speed != 0)
public void readClientUpdate(CompoundNBT tag) {
boolean overStressedBefore = overStressed;
super.readClientUpdate(tag);
if (overStressedBefore != overStressed && speed != 0)
effects.triggerOverStressedEffect(); effects.triggerOverStressedEffect();
} }
@ -450,7 +446,7 @@ public abstract class KineticTileEntity extends SmartTileEntity
public int getFlickerScore() { public int getFlickerScore() {
return flickerTally; return flickerTally;
} }
public static float convertToDirection(float axisSpeed, Direction d) { public static float convertToDirection(float axisSpeed, Direction d) {
return d.getAxisDirection() == AxisDirection.POSITIVE ? axisSpeed : -axisSpeed; return d.getAxisDirection() == AxisDirection.POSITIVE ? axisSpeed : -axisSpeed;
} }

View file

@ -58,21 +58,21 @@ public abstract class BlockBreakingKineticTileEntity extends KineticTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putInt("Progress", destroyProgress); compound.putInt("Progress", destroyProgress);
compound.putInt("NextTick", ticksUntilNextProgress); compound.putInt("NextTick", ticksUntilNextProgress);
if (breakingPos != null) if (breakingPos != null)
compound.put("Breaking", NBTUtil.writeBlockPos(breakingPos)); compound.put("Breaking", NBTUtil.writeBlockPos(breakingPos));
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
destroyProgress = compound.getInt("Progress"); destroyProgress = compound.getInt("Progress");
ticksUntilNextProgress = compound.getInt("NextTick"); ticksUntilNextProgress = compound.getInt("NextTick");
if (compound.contains("Breaking")) if (compound.contains("Breaking"))
breakingPos = NBTUtil.readBlockPos(compound.getCompound("Breaking")); breakingPos = NBTUtil.readBlockPos(compound.getCompound("Breaking"));
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override

View file

@ -3,7 +3,6 @@ package com.simibubi.create.content.contraptions.components.actors;
import java.util.UUID; import java.util.UUID;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
import net.minecraft.world.server.ServerWorld; import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.common.util.FakePlayer;

View file

@ -39,23 +39,23 @@ public class CuckooClockTileEntity extends KineticTileEntity {
super(type); super(type);
animationType = Animation.NONE; animationType = Animation.NONE;
} }
@Override @Override
public CompoundNBT writeToClient(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
if (sendAnimationUpdate) super.read(compound, clientPacket);
NBTHelper.writeEnum(compound, "Animation", animationType); if (clientPacket && compound.contains("Animation")) {
sendAnimationUpdate = false; animationType = NBTHelper.readEnum(compound, "Animation", Animation.class);
return super.writeToClient(compound);
}
@Override
public void readClientUpdate(CompoundNBT tag) {
if (tag.contains("Animation")) {
animationType = NBTHelper.readEnum(tag, "Animation", Animation.class);
animationProgress.lastValue = 0; animationProgress.lastValue = 0;
animationProgress.value = 0; animationProgress.value = 0;
} }
super.readClientUpdate(tag); }
@Override
public void write(CompoundNBT compound, boolean clientPacket) {
if (clientPacket && sendAnimationUpdate)
NBTHelper.writeEnum(compound, "Animation", animationType);
sendAnimationUpdate = false;
super.write(compound, clientPacket);
} }
@Override @Override

View file

@ -123,7 +123,7 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.put("Inventory", inventory.serializeNBT()); compound.put("Inventory", inventory.serializeNBT());
CompoundNBT inputNBT = new CompoundNBT(); CompoundNBT inputNBT = new CompoundNBT();
@ -138,43 +138,19 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
compound.putInt("CountDown", countDown); compound.putInt("CountDown", countDown);
compound.putBoolean("Cover", covered); compound.putBoolean("Cover", covered);
return super.write(compound); super.write(compound, clientPacket);
}
if (clientPacket && reRender) {
@Override compound.putBoolean("Redraw", true);
public CompoundNBT writeToClient(CompoundNBT tag) {
if (reRender) {
tag.putBoolean("Redraw", true);
reRender = false; reRender = false;
} }
return super.writeToClient(tag);
} }
@Override @Override
public void readClientUpdate(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
if (tag.contains("Redraw"))
world.notifyBlockUpdate(getPos(), getBlockState(), getBlockState(), 16);
Phase phaseBefore = phase; Phase phaseBefore = phase;
GroupedItems before = this.groupedItems; GroupedItems before = this.groupedItems;
super.readClientUpdate(tag);
if (phaseBefore != phase && phase == Phase.CRAFTING)
groupedItemsBeforeCraft = before;
if (phaseBefore == Phase.EXPORTING && phase == Phase.WAITING) {
Direction facing = getBlockState().get(MechanicalCrafterBlock.HORIZONTAL_FACING);
Vec3d vec = new Vec3d(facing.getDirectionVec()).scale(.75)
.add(VecHelper.getCenterOf(pos));
Direction targetDirection = MechanicalCrafterBlock.getTargetDirection(getBlockState());
vec = vec.add(new Vec3d(targetDirection.getDirectionVec()).scale(1));
world.addParticle(ParticleTypes.CRIT, vec.x, vec.y, vec.z, 0, 0, 0);
}
}
@Override
public void read(CompoundNBT compound) {
inventory.deserializeNBT(compound.getCompound("Inventory")); inventory.deserializeNBT(compound.getCompound("Inventory"));
input.read(compound.getCompound("ConnectedInput")); input.read(compound.getCompound("ConnectedInput"));
groupedItems = GroupedItems.read(compound.getCompound("GroupedItems")); groupedItems = GroupedItems.read(compound.getCompound("GroupedItems"));
@ -186,7 +162,22 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
this.phase = phase; this.phase = phase;
countDown = compound.getInt("CountDown"); countDown = compound.getInt("CountDown");
covered = compound.getBoolean("Cover"); covered = compound.getBoolean("Cover");
super.read(compound); super.read(compound, clientPacket);
if (!clientPacket)
return;
if (compound.contains("Redraw"))
world.notifyBlockUpdate(getPos(), getBlockState(), getBlockState(), 16);
if (phaseBefore != phase && phase == Phase.CRAFTING)
groupedItemsBeforeCraft = before;
if (phaseBefore == Phase.EXPORTING && phase == Phase.WAITING) {
Direction facing = getBlockState().get(MechanicalCrafterBlock.HORIZONTAL_FACING);
Vec3d vec = new Vec3d(facing.getDirectionVec()).scale(.75)
.add(VecHelper.getCenterOf(pos));
Direction targetDirection = MechanicalCrafterBlock.getTargetDirection(getBlockState());
vec = vec.add(new Vec3d(targetDirection.getDirectionVec()).scale(1));
world.addParticle(ParticleTypes.CRIT, vec.x, vec.y, vec.z, 0, 0, 0);
}
} }
@Override @Override
@ -293,7 +284,7 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
Vec3d vec = facingVec.scale(.65) Vec3d vec = facingVec.scale(.65)
.add(VecHelper.getCenterOf(pos)); .add(VecHelper.getCenterOf(pos));
Vec3d offset = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .125f) Vec3d offset = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .125f)
.mul(VecHelper.planeByNormal(facingVec)) .mul(VecHelper.axisAlingedPlaneOf(facingVec))
.normalize() .normalize()
.scale(progress * .5f) .scale(progress * .5f)
.add(vec); .add(vec);
@ -307,7 +298,7 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
Vec3d randVec = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .125f) Vec3d randVec = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .125f)
.mul(VecHelper.planeByNormal(facingVec)) .mul(VecHelper.axisAlingedPlaneOf(facingVec))
.normalize() .normalize()
.scale(.25f); .scale(.25f);
Vec3d offset2 = randVec.add(vec); Vec3d offset2 = randVec.add(vec);

View file

@ -38,15 +38,15 @@ public class HandCrankTileEntity extends GeneratingKineticTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putInt("InUse", inUse); compound.putInt("InUse", inUse);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
inUse = compound.getInt("InUse"); inUse = compound.getInt("InUse");
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override

View file

@ -215,19 +215,17 @@ public class CrushingWheelControllerTileEntity extends SmartTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
if (hasEntity()) if (hasEntity())
compound.put("Entity", NBTUtil.writeUniqueId(entityUUID)); compound.put("Entity", NBTUtil.writeUniqueId(entityUUID));
compound.put("Inventory", inventory.serializeNBT()); compound.put("Inventory", inventory.serializeNBT());
compound.putFloat("Speed", crushingspeed); compound.putFloat("Speed", crushingspeed);
super.write(compound, clientPacket);
return super.write(compound);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
super.read(compound); super.read(compound, clientPacket);
if (compound.contains("Entity") && !isFrozen() && !isOccupied()) { if (compound.contains("Entity") && !isFrozen() && !isOccupied()) {
entityUUID = NBTUtil.readUniqueId(compound.getCompound("Entity")); entityUUID = NBTUtil.readUniqueId(compound.getCompound("Entity"));
this.searchForEntity = true; this.searchForEntity = true;

View file

@ -51,11 +51,10 @@ import net.minecraftforge.items.ItemHandlerHelper;
public class DeployerTileEntity extends KineticTileEntity { public class DeployerTileEntity extends KineticTileEntity {
private static final List<Pair<BlockPos, Direction>> EXTRACTING_LOCATIONS = Arrays private static final List<Pair<BlockPos, Direction>> EXTRACTING_LOCATIONS = Arrays.asList(Direction.values())
.asList(Direction.values()) .stream()
.stream() .map(d -> Pair.of(BlockPos.ZERO.offset(d), d.getOpposite()))
.map(d -> Pair.of(BlockPos.ZERO.offset(d), d.getOpposite())) .collect(Collectors.toList());
.collect(Collectors.toList());
private FilteringBehaviour filtering; private FilteringBehaviour filtering;
private ExtractingBehaviour extracting; private ExtractingBehaviour extracting;
@ -167,7 +166,8 @@ public class DeployerTileEntity extends KineticTileEntity {
return; return;
} }
if (filtering.getFilter().isEmpty() && stack.isEmpty()) if (filtering.getFilter()
.isEmpty() && stack.isEmpty())
extracting.extract(1); extracting.extract(1);
Direction facing = getBlockState().get(FACING); Direction facing = getBlockState().get(FACING);
@ -182,12 +182,16 @@ public class DeployerTileEntity extends KineticTileEntity {
state = State.EXPANDING; state = State.EXPANDING;
Vec3d movementVector = getMovementVector(); Vec3d movementVector = getMovementVector();
Vec3d rayOrigin = VecHelper.getCenterOf(pos).add(movementVector.scale(3 / 2f)); Vec3d rayOrigin = VecHelper.getCenterOf(pos)
Vec3d rayTarget = VecHelper.getCenterOf(pos).add(movementVector.scale(5 / 2f)); .add(movementVector.scale(3 / 2f));
Vec3d rayTarget = VecHelper.getCenterOf(pos)
.add(movementVector.scale(5 / 2f));
RayTraceContext rayTraceContext = RayTraceContext rayTraceContext =
new RayTraceContext(rayOrigin, rayTarget, BlockMode.OUTLINE, FluidMode.NONE, player); new RayTraceContext(rayOrigin, rayTarget, BlockMode.OUTLINE, FluidMode.NONE, player);
BlockRayTraceResult result = world.rayTraceBlocks(rayTraceContext); BlockRayTraceResult result = world.rayTraceBlocks(rayTraceContext);
reach = (float) (.5f + Math.min(result.getHitVec().subtract(rayOrigin).length(), .75f)); reach = (float) (.5f + Math.min(result.getHitVec()
.subtract(rayOrigin)
.length(), .75f));
timer = 1000; timer = 1000;
sendData(); sendData();
@ -226,7 +230,9 @@ public class DeployerTileEntity extends KineticTileEntity {
if (!(otherTile instanceof DeployerTileEntity)) if (!(otherTile instanceof DeployerTileEntity))
return false; return false;
DeployerTileEntity deployerTile = (DeployerTileEntity) otherTile; DeployerTileEntity deployerTile = (DeployerTileEntity) otherTile;
if (world.getBlockState(otherDeployer).get(FACING).getOpposite() != facing || deployerTile.mode != Mode.PUNCH) if (world.getBlockState(otherDeployer)
.get(FACING)
.getOpposite() != facing || deployerTile.mode != Mode.PUNCH)
return false; return false;
boop = true; boop = true;
@ -295,13 +301,15 @@ public class DeployerTileEntity extends KineticTileEntity {
} }
protected void tryDisposeOfItems() { protected void tryDisposeOfItems() {
boolean noInv = extracting.getInventories().isEmpty(); boolean noInv = extracting.getInventories()
.isEmpty();
for (Iterator<ItemStack> iterator = overflowItems.iterator(); iterator.hasNext();) { for (Iterator<ItemStack> iterator = overflowItems.iterator(); iterator.hasNext();) {
ItemStack itemStack = iterator.next(); ItemStack itemStack = iterator.next();
if (noInv) { if (noInv) {
Vec3d offset = getMovementVector(); Vec3d offset = getMovementVector();
Vec3d outPos = VecHelper.getCenterOf(pos).add(offset.scale(-.65f)); Vec3d outPos = VecHelper.getCenterOf(pos)
.add(offset.scale(-.65f));
Vec3d motion = offset.scale(-.25f); Vec3d motion = offset.scale(-.25f);
ItemEntity e = new ItemEntity(world, outPos.x, outPos.y, outPos.z, itemStack.copy()); ItemEntity e = new ItemEntity(world, outPos.x, outPos.y, outPos.z, itemStack.copy());
e.setMotion(motion); e.setMotion(motion);
@ -328,11 +336,12 @@ public class DeployerTileEntity extends KineticTileEntity {
protected Vec3d getMovementVector() { protected Vec3d getMovementVector() {
if (!AllBlocks.DEPLOYER.has(getBlockState())) if (!AllBlocks.DEPLOYER.has(getBlockState()))
return Vec3d.ZERO; return Vec3d.ZERO;
return new Vec3d(getBlockState().get(FACING).getDirectionVec()); return new Vec3d(getBlockState().get(FACING)
.getDirectionVec());
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
state = NBTHelper.readEnum(compound, "State", State.class); state = NBTHelper.readEnum(compound, "State", State.class);
mode = NBTHelper.readEnum(compound, "Mode", Mode.class); mode = NBTHelper.readEnum(compound, "Mode", Mode.class);
timer = compound.getInt("Timer"); timer = compound.getInt("Timer");
@ -340,48 +349,45 @@ public class DeployerTileEntity extends KineticTileEntity {
overflowItems = NBTHelper.readItemList(compound.getList("Overflow", NBT.TAG_COMPOUND)); overflowItems = NBTHelper.readItemList(compound.getList("Overflow", NBT.TAG_COMPOUND));
if (compound.contains("HeldItem")) if (compound.contains("HeldItem"))
heldItem = ItemStack.read(compound.getCompound("HeldItem")); heldItem = ItemStack.read(compound.getCompound("HeldItem"));
super.read(compound); super.read(compound, clientPacket);
if (!clientPacket)
return;
reach = compound.getFloat("Reach");
if (compound.contains("Particle")) {
ItemStack particleStack = ItemStack.read(compound.getCompound("Particle"));
SandPaperItem.spawnParticles(VecHelper.getCenterOf(pos)
.add(getMovementVector().scale(2f)), particleStack, this.world);
}
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
NBTHelper.writeEnum(compound, "Mode", mode); NBTHelper.writeEnum(compound, "Mode", mode);
NBTHelper.writeEnum(compound, "State", state); NBTHelper.writeEnum(compound, "State", state);
compound.putInt("Timer", timer); compound.putInt("Timer", timer);
if (player != null) { if (player != null) {
compound.put("HeldItem", player.getHeldItemMainhand().serializeNBT()); compound.put("HeldItem", player.getHeldItemMainhand()
.serializeNBT());
ListNBT invNBT = new ListNBT(); ListNBT invNBT = new ListNBT();
player.inventory.write(invNBT); player.inventory.write(invNBT);
compound.put("Inventory", invNBT); compound.put("Inventory", invNBT);
compound.put("Overflow", NBTHelper.writeItemList(overflowItems)); compound.put("Overflow", NBTHelper.writeItemList(overflowItems));
} }
return super.write(compound);
} super.write(compound, clientPacket);
@Override if (!clientPacket)
public CompoundNBT writeToClient(CompoundNBT compound) { return;
compound.putFloat("Reach", reach); compound.putFloat("Reach", reach);
if (player != null) { if (player == null)
compound.put("HeldItem", player.getHeldItemMainhand().serializeNBT()); return;
if (player.spawnedItemEffects != null) { compound.put("HeldItem", player.getHeldItemMainhand()
compound.put("Particle", player.spawnedItemEffects.serializeNBT()); .serializeNBT());
player.spawnedItemEffects = null; if (player.spawnedItemEffects != null) {
} compound.put("Particle", player.spawnedItemEffects.serializeNBT());
player.spawnedItemEffects = null;
} }
return super.writeToClient(compound);
}
@Override
public void readClientUpdate(CompoundNBT tag) {
reach = tag.getFloat("Reach");
if (tag.contains("Particle")) {
ItemStack particleStack = ItemStack.read(tag.getCompound("Particle"));
SandPaperItem
.spawnParticles(VecHelper.getCenterOf(pos).add(getMovementVector().scale(2f)), particleStack,
this.world);
}
super.readClientUpdate(tag);
} }
private IItemHandlerModifiable createHandler() { private IItemHandlerModifiable createHandler() {
@ -395,7 +401,7 @@ public class DeployerTileEntity extends KineticTileEntity {
public AllBlockPartials getHandPose() { public AllBlockPartials getHandPose() {
return mode == Mode.PUNCH ? AllBlockPartials.DEPLOYER_HAND_PUNCHING return mode == Mode.PUNCH ? AllBlockPartials.DEPLOYER_HAND_PUNCHING
: heldItem.isEmpty() ? AllBlockPartials.DEPLOYER_HAND_POINTING : AllBlockPartials.DEPLOYER_HAND_HOLDING; : heldItem.isEmpty() ? AllBlockPartials.DEPLOYER_HAND_POINTING : AllBlockPartials.DEPLOYER_HAND_HOLDING;
} }
@Override @Override

View file

@ -165,7 +165,7 @@ public class AirCurrent {
.get(BlockStateProperties.FACING); .get(BlockStateProperties.FACING);
pushing = source.getAirFlowDirection() == direction; pushing = source.getAirFlowDirection() == direction;
Vec3d directionVec = new Vec3d(direction.getDirectionVec()); Vec3d directionVec = new Vec3d(direction.getDirectionVec());
Vec3d planeVec = VecHelper.planeByNormal(directionVec); Vec3d planeVec = VecHelper.axisAlingedPlaneOf(directionVec);
// 4 Rays test for holes in the shapes blocking the flow // 4 Rays test for holes in the shapes blocking the flow
float offsetDistance = .25f; float offsetDistance = .25f;

View file

@ -6,6 +6,7 @@ import com.simibubi.create.content.contraptions.processing.burner.BlazeBurnerBlo
import com.simibubi.create.content.logistics.block.chute.ChuteTileEntity; import com.simibubi.create.content.logistics.block.chute.ChuteTileEntity;
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.config.CKinetics; import com.simibubi.create.foundation.config.CKinetics;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.state.properties.BlockStateProperties;
@ -30,21 +31,17 @@ public class EncasedFanTileEntity extends GeneratingKineticTileEntity {
} }
@Override @Override
public void readClientUpdate(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
super.readClientUpdate(tag); super.read(compound, clientPacket);
airCurrent.rebuild();
}
@Override
public void read(CompoundNBT compound) {
super.read(compound);
isGenerator = compound.getBoolean("Generating"); isGenerator = compound.getBoolean("Generating");
if (clientPacket)
airCurrent.rebuild();
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putBoolean("Generating", isGenerator); compound.putBoolean("Generating", isGenerator);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
@ -77,10 +74,12 @@ public class EncasedFanTileEntity extends GeneratingKineticTileEntity {
return false; return false;
BlockState checkState = world.getBlockState(pos.down()); BlockState checkState = world.getBlockState(pos.down());
if (!checkState.getBlock().isIn(AllBlockTags.FAN_HEATERS.tag)) if (!checkState.getBlock()
.isIn(AllBlockTags.FAN_HEATERS.tag))
return false; return false;
if (checkState.has(BlazeBurnerBlock.HEAT_LEVEL) && !checkState.get(BlazeBurnerBlock.HEAT_LEVEL).isAtLeast(BlazeBurnerBlock.HeatLevel.FADING)) if (checkState.has(BlazeBurnerBlock.HEAT_LEVEL) && !checkState.get(BlazeBurnerBlock.HEAT_LEVEL)
.isAtLeast(BlazeBurnerBlock.HeatLevel.FADING))
return false; return false;
if (checkState.has(BlockStateProperties.LIT) && !checkState.get(BlockStateProperties.LIT)) if (checkState.has(BlockStateProperties.LIT) && !checkState.get(BlockStateProperties.LIT))

View file

@ -38,26 +38,30 @@ public class NozzleTileEntity extends SmartTileEntity {
} }
@Override @Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) { public void addBehaviours(List<TileEntityBehaviour> behaviours) {}
}
@Override @Override
public CompoundNBT writeToClient(CompoundNBT compound) { protected void write(CompoundNBT compound, boolean clientPacket) {
super.write(compound, clientPacket);
if (!clientPacket)
return;
compound.putFloat("Range", range); compound.putFloat("Range", range);
compound.putBoolean("Pushing", pushing); compound.putBoolean("Pushing", pushing);
return super.writeToClient(compound);
} }
@Override @Override
public void readClientUpdate(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
range = tag.getFloat("Range"); super.read(compound, clientPacket);
pushing = tag.getBoolean("Pushing"); if (!clientPacket)
super.readClientUpdate(tag); return;
range = compound.getFloat("Range");
pushing = compound.getBoolean("Pushing");
} }
@Override @Override
public void initialize() { public void initialize() {
fanPos = pos.offset(getBlockState().get(NozzleBlock.FACING).getOpposite()); fanPos = pos.offset(getBlockState().get(NozzleBlock.FACING)
.getOpposite());
super.initialize(); super.initialize();
} }
@ -72,24 +76,26 @@ public class NozzleTileEntity extends SmartTileEntity {
Vec3d center = VecHelper.getCenterOf(pos); Vec3d center = VecHelper.getCenterOf(pos);
if (world.isRemote && range != 0) { if (world.isRemote && range != 0) {
if (world.rand.nextInt( if (world.rand.nextInt(
MathHelper.clamp((AllConfigs.SERVER.kinetics.fanPushDistance.get() - (int) range), 1, 10)) == 0) { MathHelper.clamp((AllConfigs.SERVER.kinetics.fanPushDistance.get() - (int) range), 1, 10)) == 0) {
Vec3d start = VecHelper.offsetRandomly(center, world.rand, pushing ? 1 : range / 2); Vec3d start = VecHelper.offsetRandomly(center, world.rand, pushing ? 1 : range / 2);
Vec3d motion = center.subtract(start).normalize() Vec3d motion = center.subtract(start)
.scale(MathHelper.clamp(range * (pushing ? .025f : 1f), 0, .5f) * (pushing ? -1 : 1)); .normalize()
.scale(MathHelper.clamp(range * (pushing ? .025f : 1f), 0, .5f) * (pushing ? -1 : 1));
world.addParticle(ParticleTypes.POOF, start.x, start.y, start.z, motion.x, motion.y, motion.z); world.addParticle(ParticleTypes.POOF, start.x, start.y, start.z, motion.x, motion.y, motion.z);
} }
} }
for (Iterator<Entity> iterator = pushingEntities.iterator(); iterator.hasNext();) { for (Iterator<Entity> iterator = pushingEntities.iterator(); iterator.hasNext();) {
Entity entity = iterator.next(); Entity entity = iterator.next();
Vec3d diff = entity.getPositionVec().subtract(center); Vec3d diff = entity.getPositionVec()
.subtract(center);
if (!(entity instanceof PlayerEntity) && world.isRemote) if (!(entity instanceof PlayerEntity) && world.isRemote)
continue; continue;
double distance = diff.length(); double distance = diff.length();
if (distance > range || entity.isSneaking() if (distance > range || entity.isSneaking()
|| (entity instanceof PlayerEntity && ((PlayerEntity) entity).isCreative())) { || (entity instanceof PlayerEntity && ((PlayerEntity) entity).isCreative())) {
iterator.remove(); iterator.remove();
continue; continue;
} }
@ -98,8 +104,10 @@ public class NozzleTileEntity extends SmartTileEntity {
continue; continue;
float factor = (entity instanceof ItemEntity) ? 1 / 128f : 1 / 32f; float factor = (entity instanceof ItemEntity) ? 1 / 128f : 1 / 32f;
Vec3d pushVec = diff.normalize().scale((range - distance) * (pushing ? 1 : -1)); Vec3d pushVec = diff.normalize()
entity.setMotion(entity.getMotion().add(pushVec.scale(factor))); .scale((range - distance) * (pushing ? 1 : -1));
entity.setMotion(entity.getMotion()
.add(pushVec.scale(factor)));
entity.fallDistance = 0; entity.fallDistance = 0;
entity.velocityChanged = true; entity.velocityChanged = true;
} }
@ -125,7 +133,8 @@ public class NozzleTileEntity extends SmartTileEntity {
return 0; return 0;
if (fan.getSpeed() == 0) if (fan.getSpeed() == 0)
return 0; return 0;
pushing = fan.getAirFlowDirection() == fan.getBlockState().get(EncasedFanBlock.FACING); pushing = fan.getAirFlowDirection() == fan.getBlockState()
.get(EncasedFanBlock.FACING);
return fan.getMaxDistance(); return fan.getMaxDistance();
} }
@ -140,11 +149,12 @@ public class NozzleTileEntity extends SmartTileEntity {
AxisAlignedBB bb = new AxisAlignedBB(center, center).grow(range / 2f); AxisAlignedBB bb = new AxisAlignedBB(center, center).grow(range / 2f);
for (Entity entity : world.getEntitiesWithinAABB(Entity.class, bb)) { for (Entity entity : world.getEntitiesWithinAABB(Entity.class, bb)) {
Vec3d diff = entity.getPositionVec().subtract(center); Vec3d diff = entity.getPositionVec()
.subtract(center);
double distance = diff.length(); double distance = diff.length();
if (distance > range || entity.isSneaking() if (distance > range || entity.isSneaking()
|| (entity instanceof PlayerEntity && ((PlayerEntity) entity).isCreative())) { || (entity instanceof PlayerEntity && ((PlayerEntity) entity).isCreative())) {
continue; continue;
} }
@ -164,7 +174,7 @@ public class NozzleTileEntity extends SmartTileEntity {
continue; continue;
iterator.remove(); iterator.remove();
} }
if (!pushing && pushingEntities.size() > 256 && !world.isRemote) { if (!pushing && pushingEntities.size() > 256 && !world.isRemote) {
world.createExplosion(null, center.x, center.y, center.z, 2, Mode.NONE); world.createExplosion(null, center.x, center.y, center.z, 2, Mode.NONE);
for (Iterator<Entity> iterator = pushingEntities.iterator(); iterator.hasNext();) { for (Iterator<Entity> iterator = pushingEntities.iterator(); iterator.hasNext();) {
@ -178,8 +188,9 @@ public class NozzleTileEntity extends SmartTileEntity {
private boolean canSee(Entity entity) { private boolean canSee(Entity entity) {
RayTraceContext context = new RayTraceContext(entity.getPositionVec(), VecHelper.getCenterOf(pos), RayTraceContext context = new RayTraceContext(entity.getPositionVec(), VecHelper.getCenterOf(pos),
BlockMode.COLLIDER, FluidMode.NONE, entity); BlockMode.COLLIDER, FluidMode.NONE, entity);
return pos.equals(world.rayTraceBlocks(context).getPos()); return pos.equals(world.rayTraceBlocks(context)
.getPos());
} }
} }

View file

@ -53,30 +53,21 @@ public class FlywheelTileEntity extends GeneratingKineticTileEntity {
} }
@Override @Override
public CompoundNBT writeToClient(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
return super.writeToClient(compound);
}
@Override
public void readClientUpdate(CompoundNBT tag) {
super.readClientUpdate(tag);
visualSpeed.withSpeed(1 / 32f).target(getGeneratedSpeed());
}
@Override
public CompoundNBT write(CompoundNBT compound) {
compound.putFloat("GeneratedSpeed", generatedSpeed); compound.putFloat("GeneratedSpeed", generatedSpeed);
compound.putFloat("GeneratedCapacity", generatedCapacity); compound.putFloat("GeneratedCapacity", generatedCapacity);
compound.putInt("Cooldown", stoppingCooldown); compound.putInt("Cooldown", stoppingCooldown);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
generatedSpeed = compound.getFloat("GeneratedSpeed"); generatedSpeed = compound.getFloat("GeneratedSpeed");
generatedCapacity = compound.getFloat("GeneratedCapacity"); generatedCapacity = compound.getFloat("GeneratedCapacity");
stoppingCooldown = compound.getInt("Cooldown"); stoppingCooldown = compound.getInt("Cooldown");
super.read(compound); super.read(compound, clientPacket);
if (clientPacket)
visualSpeed.withSpeed(1 / 32f).target(getGeneratedSpeed());
} }
@Override @Override

View file

@ -116,19 +116,19 @@ public class MillstoneTileEntity extends KineticTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putInt("Timer", timer); compound.putInt("Timer", timer);
compound.put("InputInventory", inputInv.serializeNBT()); compound.put("InputInventory", inputInv.serializeNBT());
compound.put("OutputInventory", outputInv.serializeNBT()); compound.put("OutputInventory", outputInv.serializeNBT());
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
timer = compound.getInt("Timer"); timer = compound.getInt("Timer");
inputInv.deserializeNBT(compound.getCompound("InputInventory")); inputInv.deserializeNBT(compound.getCompound("InputInventory"));
outputInv.deserializeNBT(compound.getCompound("OutputInventory")); outputInv.deserializeNBT(compound.getCompound("OutputInventory"));
super.read(compound); super.read(compound, clientPacket);
} }
public int getProcessingSpeed() { public int getProcessingSpeed() {

View file

@ -109,17 +109,17 @@ public class MechanicalMixerTileEntity extends BasinOperatingTileEntity {
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
running = compound.getBoolean("Running"); running = compound.getBoolean("Running");
runningTicks = compound.getInt("Ticks"); runningTicks = compound.getInt("Ticks");
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putBoolean("Running", running); compound.putBoolean("Running", running);
compound.putInt("Ticks", runningTicks); compound.putInt("Ticks", runningTicks);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override

View file

@ -15,6 +15,7 @@ import com.simibubi.create.foundation.advancement.AllTriggers;
import com.simibubi.create.foundation.item.ItemHelper; import com.simibubi.create.foundation.item.ItemHelper;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.belt.BeltProcessingBehaviour; import com.simibubi.create.foundation.tileEntity.behaviour.belt.BeltProcessingBehaviour;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@ -25,7 +26,6 @@ import net.minecraft.item.crafting.ICraftingRecipe;
import net.minecraft.item.crafting.IRecipe; import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.Ingredient; import net.minecraft.item.crafting.Ingredient;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.particles.ItemParticleData; import net.minecraft.particles.ItemParticleData;
import net.minecraft.particles.ParticleTypes; import net.minecraft.particles.ParticleTypes;
import net.minecraft.tileentity.TileEntityType; import net.minecraft.tileentity.TileEntityType;
@ -85,37 +85,30 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
running = compound.getBoolean("Running"); running = compound.getBoolean("Running");
mode = Mode.values()[compound.getInt("Mode")]; mode = Mode.values()[compound.getInt("Mode")];
finished = compound.getBoolean("Finished"); finished = compound.getBoolean("Finished");
runningTicks = compound.getInt("Ticks"); runningTicks = compound.getInt("Ticks");
super.read(compound); super.read(compound, clientPacket);
if (clientPacket) {
NBTHelper.iterateCompoundList(compound.getList("ParticleItems", NBT.TAG_COMPOUND),
c -> pressedItems.add(ItemStack.read(c)));
spawnParticles();
}
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putBoolean("Running", running); compound.putBoolean("Running", running);
compound.putInt("Mode", mode.ordinal()); compound.putInt("Mode", mode.ordinal());
compound.putBoolean("Finished", finished); compound.putBoolean("Finished", finished);
compound.putInt("Ticks", runningTicks); compound.putInt("Ticks", runningTicks);
return super.write(compound); super.write(compound, clientPacket);
}
@Override if (clientPacket)
public CompoundNBT writeToClient(CompoundNBT tag) { compound.put("ParticleItems", NBTHelper.writeCompoundList(pressedItems, ItemStack::serializeNBT));
ListNBT particleItems = new ListNBT();
pressedItems.forEach(stack -> particleItems.add(stack.serializeNBT()));
tag.put("ParticleItems", particleItems);
return super.writeToClient(tag);
}
@Override
public void readClientUpdate(CompoundNBT tag) {
super.readClientUpdate(tag);
ListNBT particleItems = tag.getList("ParticleItems", NBT.TAG_COMPOUND);
particleItems.forEach(nbt -> pressedItems.add(ItemStack.read((CompoundNBT) nbt)));
spawnParticles();
} }
@Override @Override
@ -301,7 +294,8 @@ public class MechanicalPressTileEntity extends BasinOperatingTileEntity {
CombinedItemFluidList remaining = new CombinedItemFluidList(); CombinedItemFluidList remaining = new CombinedItemFluidList();
inputs.forEachItemStack(stack -> remaining.add(stack.copy())); inputs.forEachItemStack(stack -> remaining.add(stack.copy()));
basinFluidInv.ifPresent(fluidInv -> ((CombinedFluidHandler) fluidInv).forEachTank(fluidStack -> remaining.add(fluidStack.copy()))); basinFluidInv.ifPresent(
fluidInv -> ((CombinedFluidHandler) fluidInv).forEachTank(fluidStack -> remaining.add(fluidStack.copy())));
Ingredients: for (Ingredient ingredient : ingredients) { Ingredients: for (Ingredient ingredient : ingredients) {
for (ItemStack stack : remaining.getItemStacks()) { for (ItemStack stack : remaining.getItemStacks()) {

View file

@ -94,15 +94,15 @@ public class SawTileEntity extends BlockBreakingKineticTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.put("Inventory", inventory.serializeNBT()); compound.put("Inventory", inventory.serializeNBT());
compound.putInt("RecipeIndex", recipeIndex); compound.putInt("RecipeIndex", recipeIndex);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
super.read(compound); super.read(compound, clientPacket);
inventory.deserializeNBT(compound.getCompound("Inventory")); inventory.deserializeNBT(compound.getCompound("Inventory"));
recipeIndex = compound.getInt("RecipeIndex"); recipeIndex = compound.getInt("RecipeIndex");
} }

View file

@ -797,7 +797,7 @@ public abstract class Contraption {
Vec3d vec = new Vec3d(Direction.getFacingFromAxis(AxisDirection.POSITIVE, axis) Vec3d vec = new Vec3d(Direction.getFacingFromAxis(AxisDirection.POSITIVE, axis)
.getDirectionVec()); .getDirectionVec());
Vec3d planeByNormal = VecHelper.planeByNormal(vec); Vec3d planeByNormal = VecHelper.axisAlingedPlaneOf(vec);
Vec3d min = vec.mul(bb.minX, bb.minY, bb.minZ) Vec3d min = vec.mul(bb.minX, bb.minY, bb.minZ)
.add(planeByNormal.scale(-maxDiff)); .add(planeByNormal.scale(-maxDiff));
Vec3d max = vec.mul(bb.maxX, bb.maxY, bb.maxZ) Vec3d max = vec.mul(bb.maxX, bb.maxY, bb.maxZ)

View file

@ -476,7 +476,7 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
BearingContraption bc = (BearingContraption) getContraption(); BearingContraption bc = (BearingContraption) getContraption();
Direction facing = bc.getFacing(); Direction facing = bc.getFacing();
Vec3d activeAreaOffset = actor.getActiveAreaOffset(context); Vec3d activeAreaOffset = actor.getActiveAreaOffset(context);
if (activeAreaOffset.mul(VecHelper.planeByNormal(new Vec3d(facing.getDirectionVec()))) if (activeAreaOffset.mul(VecHelper.axisAlingedPlaneOf(new Vec3d(facing.getDirectionVec())))
.equals(Vec3d.ZERO)) { .equals(Vec3d.ZERO)) {
if (VecHelper.onSameAxis(blockInfo.pos, BlockPos.ZERO, facing.getAxis())) { if (VecHelper.onSameAxis(blockInfo.pos, BlockPos.ZERO, facing.getAxis())) {
context.motion = new Vec3d(facing.getDirectionVec()).scale(facing.getAxis() context.motion = new Vec3d(facing.getDirectionVec()).scale(facing.getAxis()

View file

@ -1,7 +1,6 @@
package com.simibubi.create.content.contraptions.components.structureMovement;
import org.apache.commons.lang3.mutable.MutableObject; import org.apache.commons.lang3.mutable.MutableObject;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionInteractionPacket; import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionInteractionPacket;
import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.RaycastHelper; import com.simibubi.create.foundation.utility.RaycastHelper;

View file

@ -227,26 +227,26 @@ public class ClockworkBearingTileEntity extends KineticTileEntity implements IBe
} }
@Override @Override
public CompoundNBT write(CompoundNBT tag) { public void write(CompoundNBT compound, boolean clientPacket) {
tag.putBoolean("Running", running); compound.putBoolean("Running", running);
tag.putFloat("HourAngle", hourAngle); compound.putFloat("HourAngle", hourAngle);
tag.putFloat("MinuteAngle", minuteAngle); compound.putFloat("MinuteAngle", minuteAngle);
return super.write(tag); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
running = tag.getBoolean("Running");
hourAngle = tag.getFloat("HourAngle");
minuteAngle = tag.getFloat("MinuteAngle");
super.read(tag);
}
@Override
public void readClientUpdate(CompoundNBT tag) {
float hourAngleBefore = hourAngle; float hourAngleBefore = hourAngle;
float minuteAngleBefore = minuteAngle; float minuteAngleBefore = minuteAngle;
super.readClientUpdate(tag);
running = compound.getBoolean("Running");
hourAngle = compound.getFloat("HourAngle");
minuteAngle = compound.getFloat("MinuteAngle");
super.read(compound, clientPacket);
if (!clientPacket)
return;
if (running) { if (running) {
clientHourAngleDiff = AngleHelper.getShortestAngleDiff(hourAngleBefore, hourAngle); clientHourAngleDiff = AngleHelper.getShortestAngleDiff(hourAngleBefore, hourAngle);
clientMinuteAngleDiff = AngleHelper.getShortestAngleDiff(minuteAngleBefore, minuteAngle); clientMinuteAngleDiff = AngleHelper.getShortestAngleDiff(minuteAngleBefore, minuteAngle);

View file

@ -102,27 +102,25 @@ public class MechanicalBearingTileEntity extends GeneratingKineticTileEntity imp
} }
@Override @Override
public CompoundNBT write(CompoundNBT tag) { public void write(CompoundNBT compound, boolean clientPacket) {
tag.putBoolean("Running", running); compound.putBoolean("Running", running);
tag.putBoolean("Windmill", isWindmill); compound.putBoolean("Windmill", isWindmill);
tag.putFloat("Angle", angle); compound.putFloat("Angle", angle);
tag.putFloat("LastGenerated", lastGeneratedSpeed); compound.putFloat("LastGenerated", lastGeneratedSpeed);
return super.write(tag); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
running = tag.getBoolean("Running");
isWindmill = tag.getBoolean("Windmill");
angle = tag.getFloat("Angle");
lastGeneratedSpeed = tag.getFloat("LastGenerated");
super.read(tag);
}
@Override
public void readClientUpdate(CompoundNBT tag) {
float angleBefore = angle; float angleBefore = angle;
super.readClientUpdate(tag); running = compound.getBoolean("Running");
isWindmill = compound.getBoolean("Windmill");
angle = compound.getFloat("Angle");
lastGeneratedSpeed = compound.getFloat("LastGenerated");
super.read(compound, clientPacket);
if (!clientPacket)
return;
if (running) { if (running) {
clientAngleDiff = AngleHelper.getShortestAngleDiff(angleBefore, angle); clientAngleDiff = AngleHelper.getShortestAngleDiff(angleBefore, angle);
angle = angleBefore; angle = angleBefore;

View file

@ -81,7 +81,7 @@ public class SuperGlueItem extends Item {
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public static void spawnParticles(World world, BlockPos pos, Direction direction, boolean fullBlock) { public static void spawnParticles(World world, BlockPos pos, Direction direction, boolean fullBlock) {
Vec3d vec = new Vec3d(direction.getDirectionVec()); Vec3d vec = new Vec3d(direction.getDirectionVec());
Vec3d plane = VecHelper.planeByNormal(vec); Vec3d plane = VecHelper.axisAlingedPlaneOf(vec);
Vec3d facePos = VecHelper.getCenterOf(pos) Vec3d facePos = VecHelper.getCenterOf(pos)
.add(vec.scale(.5f)); .add(vec.scale(.5f));

View file

@ -6,6 +6,7 @@ import com.mojang.blaze3d.vertex.IVertexBuilder;
import com.simibubi.create.AllItems; import com.simibubi.create.AllItems;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.foundation.utility.AngleHelper; import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.MatrixStacker;
import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -63,7 +64,9 @@ public class SuperGlueRenderer extends EntityRenderer<SuperGlueEntity> {
Direction face = entity.getFacingDirection(); Direction face = entity.getFacingDirection();
ms.push(); ms.push();
AngleHelper.applyRotation(face, ms); MatrixStacker.of(ms)
.rotateY(AngleHelper.horizontalAngle(face))
.rotateX(AngleHelper.verticalAngle(face));
Entry peek = ms.peek(); Entry peek = ms.peek();
Vec3d[][] quads = { quad1, quad2 }; Vec3d[][] quads = { quad1, quad2 };
@ -87,7 +90,7 @@ public class SuperGlueRenderer extends EntityRenderer<SuperGlueEntity> {
Vec3d diff = new Vec3d(Direction.SOUTH.getDirectionVec()); Vec3d diff = new Vec3d(Direction.SOUTH.getDirectionVec());
Vec3d extension = diff.normalize() Vec3d extension = diff.normalize()
.scale(1 / 32f - 1 / 128f); .scale(1 / 32f - 1 / 128f);
Vec3d plane = VecHelper.planeByNormal(diff); Vec3d plane = VecHelper.axisAlingedPlaneOf(diff);
Axis axis = Direction.getFacingFromVector(diff.x, diff.y, diff.z) Axis axis = Direction.getFacingFromVector(diff.x, diff.y, diff.z)
.getAxis(); .getAxis();

View file

@ -145,50 +145,38 @@ public abstract class LinearActuatorTileEntity extends KineticTileEntity impleme
} }
@Override @Override
public CompoundNBT write(CompoundNBT tag) { protected void write(CompoundNBT compound, boolean clientPacket) {
tag.putBoolean("Running", running); compound.putBoolean("Running", running);
tag.putBoolean("Waiting", waitingForSpeedChange); compound.putBoolean("Waiting", waitingForSpeedChange);
tag.putFloat("Offset", offset); compound.putFloat("Offset", offset);
return super.write(tag); super.write(compound, clientPacket);
}
if (clientPacket && forceMove) {
@Override
public CompoundNBT writeToClient(CompoundNBT compound) {
if (forceMove) {
compound.putBoolean("ForceMovement", forceMove); compound.putBoolean("ForceMovement", forceMove);
forceMove = false; forceMove = false;
} }
return super.writeToClient(compound);
} }
@Override @Override
public void read(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
running = tag.getBoolean("Running"); boolean forceMovement = compound.contains("ForceMovement");
waitingForSpeedChange = tag.getBoolean("Waiting");
offset = tag.getFloat("Offset");
super.read(tag);
}
@Override
public void readClientUpdate(CompoundNBT tag) {
boolean forceMovement = tag.contains("ForceMovement");
float offsetBefore = offset; float offsetBefore = offset;
super.readClientUpdate(tag);
if (forceMovement) { running = compound.getBoolean("Running");
if (movedContraption != null) { waitingForSpeedChange = compound.getBoolean("Waiting");
applyContraptionPosition(); offset = compound.getFloat("Offset");
} super.read(compound, clientPacket);
} else {
if (running) { if (!clientPacket)
clientOffsetDiff = offset - offsetBefore; return;
offset = offsetBefore; if (forceMovement)
} applyContraptionPosition();
else if (running) {
clientOffsetDiff = offset - offsetBefore;
offset = offsetBefore;
} }
if (!running) if (!running)
movedContraption = null; movedContraption = null;
} }
public abstract void disassemble(); public abstract void disassemble();

View file

@ -29,15 +29,15 @@ public class MechanicalPistonTileEntity extends LinearActuatorTileEntity {
} }
@Override @Override
public void read(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
extensionLength = tag.getInt("ExtensionLength"); extensionLength = compound.getInt("ExtensionLength");
super.read(tag); super.read(compound, clientPacket);
} }
@Override @Override
public CompoundNBT write(CompoundNBT tag) { protected void write(CompoundNBT tag, boolean clientPacket) {
tag.putInt("ExtensionLength", extensionLength); tag.putInt("ExtensionLength", extensionLength);
return super.write(tag); super.write(tag, clientPacket);
} }
@Override @Override

View file

@ -8,6 +8,7 @@ import com.simibubi.create.content.contraptions.components.structureMovement.pis
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.tileEntity.behaviour.CenteredSideValueBoxTransform; import com.simibubi.create.foundation.tileEntity.behaviour.CenteredSideValueBoxTransform;
import com.simibubi.create.foundation.tileEntity.behaviour.ValueBoxTransform; import com.simibubi.create.foundation.tileEntity.behaviour.ValueBoxTransform;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.block.IWaterLoggable; import net.minecraft.block.IWaterLoggable;
@ -168,15 +169,15 @@ public class PulleyTileEntity extends LinearActuatorTileEntity {
} }
@Override @Override
public void read(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
initialOffset = tag.getInt("InitialOffset"); initialOffset = compound.getInt("InitialOffset");
super.read(tag); super.read(compound, clientPacket);
} }
@Override @Override
public CompoundNBT write(CompoundNBT tag) { public void write(CompoundNBT compound, boolean clientPacket) {
tag.putInt("InitialOffset", initialOffset); compound.putInt("InitialOffset", initialOffset);
return super.write(tag); super.write(compound, clientPacket);
} }
@Override @Override

View file

@ -24,8 +24,8 @@ public class WaterWheelTileEntity extends GeneratingKineticTileEntity {
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
super.read(compound); super.read(compound, clientPacket);
if (compound.contains("Flows")) { if (compound.contains("Flows")) {
for (Direction d : Direction.values()) for (Direction d : Direction.values())
setFlow(d, compound.getCompound("Flows") setFlow(d, compound.getCompound("Flows")
@ -39,13 +39,13 @@ public class WaterWheelTileEntity extends GeneratingKineticTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
CompoundNBT flows = new CompoundNBT(); CompoundNBT flows = new CompoundNBT();
for (Direction d : Direction.values()) for (Direction d : Direction.values())
flows.putFloat(d.getName(), this.flows.get(d)); flows.putFloat(d.getName(), this.flows.get(d));
compound.put("Flows", flows); compound.put("Flows", flows);
return super.write(compound); super.write(compound, clientPacket);
} }
public void setFlow(Direction direction, float speed) { public void setFlow(Direction direction, float speed) {

View file

@ -87,17 +87,20 @@ public class CombinedFluidHandler implements IFluidHandler {
@Nonnull @Nonnull
@Override @Override
public FluidStack drain(int maxDrain, FluidAction action) { public FluidStack drain(int maxDrain, FluidAction action) {
FluidStack stack = new FluidStack(tanks[0].getFluid(), 0); FluidStack stack = new FluidStack(tanks[0].getFluid(), 0);
for (int i = 0; i < tanks.length; i++) { for (int i = 0; i < tanks.length; i++) {
if (tanks[i].isFluidEqual(stack)) { if (stack.isEmpty() || tanks[i].isFluidEqual(stack)) {
int newDrainAmount = MathHelper.clamp(stack.getAmount() + tanks[i].getAmount(), 0, maxDrain); int newDrainAmount = MathHelper.clamp(stack.getAmount() + tanks[i].getAmount(), 0, maxDrain);
if (action == FluidAction.EXECUTE) { if (action == FluidAction.EXECUTE) {
tanks[i].shrink(newDrainAmount - stack.getAmount()); tanks[i].shrink(newDrainAmount - stack.getAmount());
if (tanks[i].isEmpty()) if (tanks[i].isEmpty())
tanks[i] = FluidStack.EMPTY; tanks[i] = FluidStack.EMPTY;
} }
if (stack.isEmpty())
stack = tanks[i].copy();
if (stack.isEmpty())
continue;
stack.setAmount(newDrainAmount); stack.setAmount(newDrainAmount);
} }
} }

View file

@ -0,0 +1,355 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
public class FluidNetwork {
BlockFace pumpLocation;
Map<BlockPos, Pair<Integer, Map<Direction, Boolean>>> pipeGraph;
List<FluidNetworkFlow> flows;
Set<FluidNetworkEndpoint> targets;
Set<BlockFace> rangeEndpoints;
Map<BlockFace, FluidStack> previousFlow;
boolean connectToPumps;
int waitForUnloadedNetwork;
public FluidNetwork() {
pipeGraph = new HashMap<>();
flows = new ArrayList<>();
targets = new HashSet<>();
rangeEndpoints = new HashSet<>();
previousFlow = new HashMap<>();
}
public boolean hasEndpoints() {
for (FluidNetworkFlow pipeFlow : flows)
if (pipeFlow.hasValidTargets())
return true;
return false;
}
public Collection<FluidNetworkEndpoint> getEndpoints(boolean pulling) {
if (!pulling) {
for (FluidNetworkFlow pipeFlow : flows)
return pipeFlow.outputEndpoints;
return Collections.emptySet();
}
List<FluidNetworkEndpoint> list = new ArrayList<>();
for (FluidNetworkFlow pipeFlow : flows) {
if (!pipeFlow.hasValidTargets())
continue;
list.add(pipeFlow.source);
}
return list;
}
public void tick(IWorld world, PumpTileEntity pumpTE) {
if (connectToPumps) {
connectToOtherFNs(world, pumpTE);
connectToPumps = false;
}
}
public void tickFlows(IWorld world, PumpTileEntity pumpTE, boolean pulling, float speed) {
if (connectToPumps)
return;
initFlows(pumpTE, pulling);
previousFlow.clear();
flows.forEach(ep -> ep.tick(world, speed));
}
private void initFlows(PumpTileEntity pumpTE, boolean pulling) {
if (targets.isEmpty())
return;
if (!flows.isEmpty())
return;
World world = pumpTE.getWorld();
if (pulling) {
targets.forEach(ne -> flows.add(new FluidNetworkFlow(this, ne, world, pulling)));
} else {
PumpEndpoint pumpEndpoint = new PumpEndpoint(pumpLocation.getOpposite(), pumpTE);
flows.add(new FluidNetworkFlow(this, pumpEndpoint, world, pulling));
}
}
public void connectToOtherFNs(IWorld world, PumpTileEntity pump) {
List<Pair<Integer, BlockPos>> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
int maxDistance = FluidPropagator.getPumpRange() * 2;
frontier.add(Pair.of(-1, pumpLocation.getPos()));
while (!frontier.isEmpty()) {
Pair<Integer, BlockPos> entry = frontier.remove(0);
int distance = entry.getFirst();
BlockPos currentPos = entry.getSecond();
if (!world.isAreaLoaded(currentPos, 0))
continue;
if (visited.contains(currentPos))
continue;
visited.add(currentPos);
List<Direction> connections;
if (currentPos.equals(pumpLocation.getPos())) {
connections = ImmutableList.of(pumpLocation.getFace());
} else {
BlockState currentState = world.getBlockState(currentPos);
FluidPipeBehaviour pipe = FluidPropagator.getPipe(world, currentPos);
if (pipe == null)
continue;
connections = FluidPropagator.getPipeConnections(currentState, pipe);
}
for (Direction face : connections) {
BlockFace blockFace = new BlockFace(currentPos, face);
BlockPos connectedPos = blockFace.getConnectedPos();
BlockState connectedState = world.getBlockState(connectedPos);
if (connectedPos.equals(pumpLocation.getPos()))
continue;
if (!world.isAreaLoaded(connectedPos, 0))
continue;
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING)
.getAxis() == face.getAxis()) {
TileEntity tileEntity = world.getTileEntity(connectedPos);
if (tileEntity instanceof PumpTileEntity) {
PumpTileEntity otherPump = (PumpTileEntity) tileEntity;
if (otherPump.networks == null)
continue;
otherPump.networks.forEach(fn -> {
int nearest = Integer.MAX_VALUE;
BlockFace argNearest = null;
for (BlockFace pumpEndpoint : fn.rangeEndpoints) {
if (pumpEndpoint.isEquivalent(pumpLocation)) {
argNearest = pumpEndpoint;
break;
}
Pair<Integer, Map<Direction, Boolean>> pair =
pipeGraph.get(pumpEndpoint.getConnectedPos());
if (pair == null)
continue;
Integer distanceFromPump = pair.getFirst();
Map<Direction, Boolean> pipeConnections = pair.getSecond();
if (!pipeConnections.containsKey(pumpEndpoint.getOppositeFace()))
continue;
if (nearest <= distanceFromPump)
continue;
nearest = distanceFromPump;
argNearest = pumpEndpoint;
}
if (argNearest != null) {
InterPumpEndpoint endpoint = new InterPumpEndpoint(world, argNearest.getOpposite(),
pump, otherPump, pumpLocation, fn.pumpLocation);
targets.add(endpoint);
fn.targets.add(endpoint.opposite(world));
}
});
}
continue;
}
if (visited.contains(connectedPos))
continue;
if (distance > maxDistance)
continue;
FluidPipeBehaviour targetPipe = FluidPropagator.getPipe(world, connectedPos);
if (targetPipe == null)
continue;
if (targetPipe.isConnectedTo(connectedState, face.getOpposite()))
frontier.add(Pair.of(distance + 1, connectedPos));
}
}
}
public void assemble(IWorld world, PumpTileEntity pumpTE, BlockFace pumpLocation) {
Map<BlockFace, OpenEndedPipe> openEnds = pumpTE.getOpenEnds(pumpLocation.getFace());
openEnds.values()
.forEach(OpenEndedPipe::markStale);
this.pumpLocation = pumpLocation;
if (!collectEndpoint(world, pumpLocation, openEnds, 0)) {
List<Pair<Integer, BlockPos>> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
int maxDistance = FluidPropagator.getPumpRange();
frontier.add(Pair.of(0, pumpLocation.getConnectedPos()));
while (!frontier.isEmpty()) {
Pair<Integer, BlockPos> entry = frontier.remove(0);
int distance = entry.getFirst();
BlockPos currentPos = entry.getSecond();
if (!world.isAreaLoaded(currentPos, 0))
continue;
if (visited.contains(currentPos))
continue;
visited.add(currentPos);
BlockState currentState = world.getBlockState(currentPos);
FluidPipeBehaviour pipe = FluidPropagator.getPipe(world, currentPos);
if (pipe == null)
continue;
for (Direction face : FluidPropagator.getPipeConnections(currentState, pipe)) {
BlockFace blockFace = new BlockFace(currentPos, face);
BlockPos connectedPos = blockFace.getConnectedPos();
if (connectedPos.equals(pumpLocation.getPos())) {
addEntry(blockFace.getPos(), blockFace.getFace(), true, distance);
continue;
}
if (!world.isAreaLoaded(connectedPos, 0))
continue;
if (collectEndpoint(world, blockFace, openEnds, distance))
continue;
if (FluidPropagator.getPipe(world, connectedPos) == null)
continue;
if (visited.contains(connectedPos))
continue;
if (distance + 1 >= maxDistance) {
rangeEndpoints.add(blockFace);
addEntry(currentPos, face, false, distance);
FluidPropagator.showBlockFace(blockFace)
.lineWidth(1 / 8f)
.colored(0xff0000);
continue;
}
addConnection(connectedPos, currentPos, face.getOpposite(), distance);
frontier.add(Pair.of(distance + 1, connectedPos));
}
}
}
Set<BlockFace> staleEnds = new HashSet<>();
openEnds.entrySet()
.forEach(e -> {
if (e.getValue()
.isStale())
staleEnds.add(e.getKey());
});
staleEnds.forEach(openEnds::remove);
connectToPumps = true;
}
private FluidNetworkEndpoint reuseOrCreateOpenEnd(IWorld world, Map<BlockFace, OpenEndedPipe> openEnds,
BlockFace toCreate) {
OpenEndedPipe openEndedPipe = null;
if (openEnds.containsKey(toCreate)) {
openEndedPipe = openEnds.get(toCreate);
openEndedPipe.unmarkStale();
} else {
openEndedPipe = new OpenEndedPipe(toCreate);
openEnds.put(toCreate, openEndedPipe);
}
return new FluidNetworkEndpoint(world, toCreate, openEndedPipe.getCapability());
}
private boolean collectEndpoint(IWorld world, BlockFace blockFace, Map<BlockFace, OpenEndedPipe> openEnds,
int distance) {
BlockPos connectedPos = blockFace.getConnectedPos();
BlockState connectedState = world.getBlockState(connectedPos);
// other pipe, no endpoint
FluidPipeBehaviour pipe = FluidPropagator.getPipe(world, connectedPos);
if (pipe != null && pipe.isConnectedTo(connectedState, blockFace.getOppositeFace()))
return false;
TileEntity tileEntity = world.getTileEntity(connectedPos);
// fluid handler endpoint
Direction face = blockFace.getFace();
if (tileEntity != null) {
LazyOptional<IFluidHandler> capability =
tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face.getOpposite());
if (capability.isPresent()) {
targets.add(new FluidNetworkEndpoint(world, blockFace, capability));
addEntry(blockFace.getPos(), face, false, distance);
FluidPropagator.showBlockFace(blockFace)
.colored(0x00b7c2)
.lineWidth(1 / 8f);
return true;
}
}
// open endpoint
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING)
.getAxis() == face.getAxis()) {
rangeEndpoints.add(blockFace);
addEntry(blockFace.getPos(), face, false, distance);
return true;
}
if (!FluidPropagator.isOpenEnd(world, blockFace.getPos(), face))
return false;
targets.add(reuseOrCreateOpenEnd(world, openEnds, blockFace));
addEntry(blockFace.getPos(), face, false, distance);
FluidPropagator.showBlockFace(blockFace)
.colored(0xb700c2)
.lineWidth(1 / 8f);
return true;
}
private void addConnection(BlockPos from, BlockPos to, Direction direction, int distance) {
addEntry(from, direction, true, distance);
addEntry(to, direction.getOpposite(), false, distance + 1);
}
private void addEntry(BlockPos pos, Direction direction, boolean outbound, int distance) {
if (!pipeGraph.containsKey(pos))
pipeGraph.put(pos, Pair.of(distance, new HashMap<>()));
pipeGraph.get(pos)
.getSecond()
.put(direction, outbound);
}
public void reAssemble(IWorld world, PumpTileEntity pumpTE, BlockFace pumpLocation) {
rangeEndpoints.clear();
targets.clear();
pipeGraph.clear();
assemble(world, pumpTE, pumpLocation);
}
public void remove(IWorld world) {
clearFlows(world, false);
}
public void clearFlows(IWorld world, boolean saveState) {
for (FluidNetworkFlow networkFlow : flows) {
if (!networkFlow.getFluidStack()
.isEmpty())
networkFlow.addToSkippedConnections(world);
networkFlow.resetFlow(world);
}
flows.clear();
}
}

View file

@ -0,0 +1,168 @@
package com.simibubi.create.content.contraptions.fluids;
import java.lang.ref.WeakReference;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.IWorld;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
class FluidNetworkEndpoint {
BlockFace location;
protected LazyOptional<IFluidHandler> handler;
public FluidNetworkEndpoint(IWorld world, BlockFace location, LazyOptional<IFluidHandler> handler) {
this.location = location;
this.handler = handler;
this.handler.addListener($ -> onHandlerInvalidated(world));
}
protected void onHandlerInvalidated(IWorld world) {
IFluidHandler tank = handler.orElse(null);
if (tank != null)
return;
TileEntity tileEntity = world.getTileEntity(location.getConnectedPos());
if (tileEntity == null)
return;
LazyOptional<IFluidHandler> capability =
tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, location.getOppositeFace());
if (capability.isPresent()) {
handler = capability;
handler.addListener($ -> onHandlerInvalidated(world));
}
}
public FluidStack provideFluid() {
IFluidHandler tank = provideHandler().orElse(null);
if (tank == null)
return FluidStack.EMPTY;
return tank.drain(1, FluidAction.SIMULATE);
}
public LazyOptional<IFluidHandler> provideHandler() {
return handler;
}
}
class PumpEndpoint extends FluidNetworkEndpoint {
PumpTileEntity pumpTE;
public PumpEndpoint(BlockFace location, PumpTileEntity pumpTE) {
super(pumpTE.getWorld(), location, LazyOptional.empty());
this.pumpTE = pumpTE;
}
@Override
protected void onHandlerInvalidated(IWorld world) {}
@Override
public FluidStack provideFluid() {
return pumpTE.providedFluid;
}
}
class InterPumpEndpoint extends FluidNetworkEndpoint {
Couple<Pair<BlockFace, WeakReference<PumpTileEntity>>> pumps;
private InterPumpEndpoint(IWorld world, BlockFace location, LazyOptional<IFluidHandler> handler) {
super(world, location, handler);
}
public InterPumpEndpoint(IWorld world, BlockFace location, PumpTileEntity source, PumpTileEntity interfaced,
BlockFace sourcePos, BlockFace interfacedPos) {
this(world, location, LazyOptional.empty());
handler = LazyOptional.of(() -> new InterPumpFluidHandler(this));
pumps = Couple.create(Pair.of(sourcePos, new WeakReference<>(source)),
Pair.of(interfacedPos, new WeakReference<>(interfaced)));
}
public InterPumpEndpoint opposite(IWorld world) {
InterPumpEndpoint interPumpEndpoint = new InterPumpEndpoint(world, this.location.getOpposite(), handler);
interPumpEndpoint.pumps = pumps.copy();
return interPumpEndpoint;
}
public Couple<Pair<BlockFace, WeakReference<PumpTileEntity>>> getPumps() {
return pumps;
}
public boolean isPulling(boolean first) {
Pair<BlockFace, WeakReference<PumpTileEntity>> pair = getPumps().get(first);
PumpTileEntity pumpTileEntity = pair.getSecond()
.get();
if (pumpTileEntity == null || pumpTileEntity.isRemoved())
return false;
return pumpTileEntity.isPullingOnSide(pumpTileEntity.isFront(pair.getFirst()
.getFace()));
}
public int getTransferSpeed(boolean first) {
PumpTileEntity pumpTileEntity = getPumps().get(first)
.getSecond()
.get();
if (pumpTileEntity == null || pumpTileEntity.isRemoved())
return 0;
return pumpTileEntity.getFluidTransferSpeed();
}
@Override
public LazyOptional<IFluidHandler> provideHandler() {
if (isPulling(true) == isPulling(false))
return LazyOptional.empty();
if (getTransferSpeed(true) > getTransferSpeed(false))
return LazyOptional.empty();
return super.provideHandler();
}
@Override
public FluidStack provideFluid() {
if (!provideHandler().isPresent())
return FluidStack.EMPTY;
Couple<Pair<BlockFace, WeakReference<PumpTileEntity>>> pumps = getPumps();
for (boolean current : Iterate.trueAndFalse) {
if (isPulling(current))
continue;
Pair<BlockFace, WeakReference<PumpTileEntity>> pair = pumps.get(current);
BlockFace blockFace = pair.getFirst();
PumpTileEntity pumpTileEntity = pair.getSecond()
.get();
if (pumpTileEntity == null)
continue;
if (pumpTileEntity.networks == null)
continue;
FluidNetwork fluidNetwork = pumpTileEntity.networks.get(pumpTileEntity.isFront(blockFace.getFace()));
for (FluidNetworkFlow fluidNetworkFlow : fluidNetwork.flows) {
for (FluidNetworkEndpoint fne : fluidNetworkFlow.outputEndpoints) {
if (!(fne instanceof InterPumpEndpoint))
continue;
InterPumpEndpoint ipe = (InterPumpEndpoint) fne;
if (!ipe.location.isEquivalent(location))
continue;
FluidStack heldFluid = fluidNetworkFlow.fluidStack;
if (heldFluid.isEmpty())
return heldFluid;
FluidStack copy = heldFluid.copy();
copy.setAmount(1);
return heldFluid;
}
}
}
return FluidStack.EMPTY;
}
}

View file

@ -0,0 +1,304 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorld;
import net.minecraftforge.fluids.FluidStack;
class FluidNetworkFlow {
@FunctionalInterface
static interface PipeFlowConsumer {
void accept(FluidPipeBehaviour pipe, Direction face, boolean inbound);
}
/**
*
*/
private final FluidNetwork activePipeNetwork;
FluidNetworkEndpoint source;
FluidStack fluidStack;
Set<BlockFace> flowPointers;
Set<FluidNetworkEndpoint> outputEndpoints;
boolean pumpReached;
boolean pulling;
float speed;
public FluidNetworkFlow(FluidNetwork activePipeNetwork, FluidNetworkEndpoint source, IWorld world,
boolean pulling) {
this.activePipeNetwork = activePipeNetwork;
this.source = source;
this.pulling = pulling;
flowPointers = new HashSet<>();
outputEndpoints = new HashSet<>();
fluidStack = FluidStack.EMPTY;
tick(world, 0);
}
void resetFlow(IWorld world) {
fluidStack = FluidStack.EMPTY;
flowPointers.clear();
outputEndpoints.clear();
pumpReached = false;
forEachPipeFlow(world, (pipe, face, inbound) -> pipe.removeFlow(this, face, inbound));
}
void addToSkippedConnections(IWorld world) {
forEachPipeFlow(world, (pipe, face, inbound) -> {
if (!pipe.fluid.isFluidEqual(fluidStack))
return;
BlockFace blockFace = new BlockFace(pipe.getPos(), face);
this.activePipeNetwork.previousFlow.put(blockFace, pipe.fluid);
});
}
void forEachPipeFlow(IWorld world, FluidNetworkFlow.PipeFlowConsumer consumer) {
Set<BlockFace> flowPointers = new HashSet<>();
flowPointers.add(getSource());
// Update all branches of this flow, and create new ones if necessary
while (!flowPointers.isEmpty()) {
List<BlockFace> toAdd = new ArrayList<>();
for (Iterator<BlockFace> iterator = flowPointers.iterator(); iterator.hasNext();) {
BlockFace flowPointer = iterator.next();
BlockPos currentPos = flowPointer.getPos();
FluidPipeBehaviour pipe = getPipeInTree(world, currentPos);
if (pipe == null) {
iterator.remove();
continue;
}
Map<Direction, Boolean> directions = this.activePipeNetwork.pipeGraph.get(currentPos)
.getSecond();
for (Entry<Direction, Boolean> entry : directions.entrySet()) {
boolean inbound = entry.getValue() != pulling;
Direction face = entry.getKey();
if (inbound && face != flowPointer.getFace())
continue;
consumer.accept(pipe, face, inbound);
if (inbound)
continue;
toAdd.add(new BlockFace(currentPos.offset(face), face.getOpposite()));
}
iterator.remove();
}
flowPointers.addAll(toAdd);
}
}
void tick(IWorld world, float speed) {
boolean skipping = speed == 0;
Map<BlockFace, FluidStack> previousFlow = this.activePipeNetwork.previousFlow;
if (skipping && previousFlow.isEmpty())
return;
this.speed = speed;
FluidStack provideFluid = source.provideFluid();
if (!fluidStack.isEmpty() && !fluidStack.isFluidEqual(provideFluid)) {
resetFlow(world);
return;
}
fluidStack = provideFluid;
// There is currently no unfinished flow being followed
if (flowPointers.isEmpty()) {
// The fluid source has run out -> reset
if (fluidStack.isEmpty()) {
if (hasValidTargets())
resetFlow(world);
return;
}
// Keep the flows if all is well
if (hasValidTargets())
return;
// Start a new flow from or towards the pump
BlockFace source = getSource();
if (tryConnectTo(world, source.getOpposite()))
return;
flowPointers.add(source);
}
boolean skipped = false;
Set<BlockFace> pausedPointers = new HashSet<>();
do {
skipped = false;
List<BlockFace> toAdd = null;
// Update all branches of this flow, and create new ones if necessary
for (Iterator<BlockFace> iterator = flowPointers.iterator(); iterator.hasNext();) {
BlockFace flowPointer = iterator.next();
BlockPos currentPos = flowPointer.getPos();
if (pausedPointers.contains(flowPointer))
continue;
FluidPipeBehaviour pipe = getPipeInTree(world, currentPos);
if (pipe == null) {
iterator.remove();
continue;
}
Map<Direction, Boolean> directions = this.activePipeNetwork.pipeGraph.get(currentPos)
.getSecond();
boolean inboundComplete = false;
boolean allFlowsComplete = true;
BlockState state = world.getBlockState(currentPos);
// First loop only inbound flows of a pipe to see if they have reached the
// center
for (boolean inboundPass : Iterate.trueAndFalse) {
if (!inboundPass && !inboundComplete)
break;
// For all connections of the pipe tree of the pump
for (Entry<Direction, Boolean> entry : directions.entrySet()) {
Boolean awayFromPump = entry.getValue();
Direction direction = entry.getKey();
boolean inbound = awayFromPump != pulling;
if (inboundPass && direction != flowPointer.getFace())
continue;
if (!inboundPass && inbound)
continue;
if (!pipe.canTransferToward(fluidStack, state, direction, inbound))
continue;
BlockFace blockface = new BlockFace(currentPos, direction);
if (!pipe.hasStartedFlow(this, direction, inbound))
pipe.addFlow(this, direction, inbound);
if (skipping && canSkip(previousFlow, blockface)) {
pipe.skipFlow(direction, inbound);
FluidPropagator.showBlockFace(blockface)
.colored(0x0)
.lineWidth(1 / 8f);
skipped = true;
}
if (!pipe.hasCompletedFlow(direction, inbound)) {
allFlowsComplete = false;
continue;
}
if (inboundPass) {
inboundComplete = true;
continue;
}
// Outward pass, check if any target was reached
tryConnectTo(world, blockface);
}
}
if (!allFlowsComplete && !skipping)
continue;
// Create a new flow branch at each outward pipe connection
for (Entry<Direction, Boolean> entry : directions.entrySet()) {
if (entry.getValue() != pulling)
continue;
Direction face = entry.getKey();
BlockFace addedBlockFace = new BlockFace(currentPos.offset(face), face.getOpposite());
if (skipping && !canSkip(previousFlow, addedBlockFace)) {
allFlowsComplete = false;
continue;
}
if (toAdd == null)
toAdd = new ArrayList<>();
toAdd.add(addedBlockFace);
}
if (!allFlowsComplete && skipping) {
pausedPointers.add(flowPointer);
continue;
}
iterator.remove();
} // End of branch loop
if (toAdd != null)
flowPointers.addAll(toAdd);
} while (skipping && skipped);
}
private boolean canSkip(Map<BlockFace, FluidStack> previousFlow, BlockFace blockface) {
return previousFlow.containsKey(blockface) && previousFlow.get(blockface)
.isFluidEqual(fluidStack);
}
private boolean tryConnectTo(IWorld world, BlockFace blockface) {
// Pulling flow, target is the pump
if (pulling) {
if (!this.activePipeNetwork.pumpLocation.getOpposite()
.equals(blockface))
return false;
pumpReached = true;
TileEntity targetTE = world.getTileEntity(this.activePipeNetwork.pumpLocation.getPos());
if (targetTE instanceof PumpTileEntity)
((PumpTileEntity) targetTE).setProvidedFluid(fluidStack);
FluidPropagator.showBlockFace(this.activePipeNetwork.pumpLocation)
.colored(0x799351)
.lineWidth(1 / 8f);
return true;
}
// Pushing flow, targets are the endpoints
for (FluidNetworkEndpoint networkEndpoint : this.activePipeNetwork.targets) {
if (!networkEndpoint.location.isEquivalent(blockface))
continue;
outputEndpoints.add(networkEndpoint);
FluidPropagator.showBlockFace(blockface)
.colored(0x799351)
.lineWidth(1 / 8f);
return !(networkEndpoint instanceof InterPumpEndpoint);
}
return false;
}
private BlockFace getSource() {
return pulling ? source.location : this.activePipeNetwork.pumpLocation.getOpposite();
}
private FluidPipeBehaviour getPipeInTree(IWorld world, BlockPos currentPos) {
if (!world.isAreaLoaded(currentPos, 0))
return null;
if (!this.activePipeNetwork.pipeGraph.containsKey(currentPos))
return null;
return TileEntityBehaviour.get(world, currentPos, FluidPipeBehaviour.TYPE);
}
boolean hasValidTargets() {
return pumpReached || !outputEndpoints.isEmpty();
}
public float getSpeed() {
return speed;
}
public FluidStack getFluidStack() {
return fluidStack;
}
}

View file

@ -0,0 +1,478 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import javax.annotation.Nullable;
import com.simibubi.create.AllSpecialTextures;
import com.simibubi.create.CreateClient;
import com.simibubi.create.content.contraptions.KineticDebugger;
import com.simibubi.create.foundation.fluid.FluidHelper;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.LerpedFloat;
import com.simibubi.create.foundation.utility.LerpedFloat.Chaser;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.particles.BlockParticleData;
import net.minecraft.particles.IParticleData;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fml.DistExecutor;
public abstract class FluidPipeBehaviour extends TileEntityBehaviour {
public static BehaviourType<FluidPipeBehaviour> TYPE = new BehaviourType<>();
// Direction -> (inboundflows{}, outwardflows{})
Map<Direction, Couple<PipeFlows>> allFlows;
FluidStack fluid;
Couple<FluidStack> collision;
public FluidPipeBehaviour(SmartTileEntity te) {
super(te);
allFlows = new IdentityHashMap<>();
fluid = FluidStack.EMPTY;
}
@Override
public BehaviourType<?> getType() {
return TYPE;
}
public void notifyNetwork() {
FluidPropagator.propagateChangedPipe(this.getWorld(), tileEntity.getPos(), tileEntity.getBlockState());
}
public boolean canTransferToward(FluidStack fluid, BlockState state, Direction direction, boolean inbound) {
return isConnectedTo(state, direction);
}
public abstract boolean isConnectedTo(BlockState state, Direction direction);
public float getRimRadius(BlockState state, Direction direction) {
return 1 / 4f + 1 / 64f;
}
public boolean hasStartedFlow(FluidNetworkFlow flow, Direction face, boolean inbound) {
return allFlows.containsKey(face) && allFlows.get(face)
.get(inbound)
.hasFlow(flow);
}
public boolean hasCompletedFlow(Direction face, boolean inbound) {
return allFlows.containsKey(face) && allFlows.get(face)
.get(inbound)
.isCompleted();
}
@Override
public void write(CompoundNBT compound, boolean client) {
compound.put("Fluid", fluid.writeToNBT(new CompoundNBT()));
ListNBT flows = new ListNBT();
for (Direction face : Iterate.directions)
for (boolean inbound : Iterate.trueAndFalse) {
LerpedFloat flowProgress = getFlowProgress(face, inbound);
if (flowProgress == null)
continue;
CompoundNBT nbt = new CompoundNBT();
NBTHelper.writeEnum(nbt, "Face", face);
nbt.putBoolean("In", inbound);
nbt.put("Progress", flowProgress.writeNBT());
flows.add(nbt);
}
compound.put("Flows", flows);
}
@Override
public void read(CompoundNBT compound, boolean client) {
fluid = FluidStack.loadFluidStackFromNBT(compound.getCompound("Fluid"));
if (client) {
for (Direction face : Iterate.directions)
if (allFlows.containsKey(face))
allFlows.get(face)
.forEach(pf -> pf.progress = null);
}
NBTHelper.iterateCompoundList(compound.getList("Flows", NBT.TAG_COMPOUND), nbt -> {
Direction face = NBTHelper.readEnum(nbt, "Face", Direction.class);
boolean inbound = nbt.getBoolean("In");
LerpedFloat progress = createFlowProgress(0);
progress.readNBT(nbt.getCompound("Progress"), false);
addFlow(null, face, inbound);
setFlowProgress(face, inbound, progress);
});
if (!client)
return;
for (Direction face : Iterate.directions) {
if (!allFlows.containsKey(face))
return;
Couple<PipeFlows> couple = allFlows.get(face);
if (couple.get(true).progress == null && couple.get(false).progress == null)
allFlows.remove(face);
if (allFlows.isEmpty())
clear();
}
}
public void addFlow(@Nullable FluidNetworkFlow flow, Direction face, boolean inbound) {
if (flow != null) {
FluidStack fluid = flow.getFluidStack();
if (!this.fluid.isEmpty() && !fluid.isFluidEqual(this.fluid)) {
collision = Couple.create(this.fluid, fluid);
return;
}
this.fluid = fluid;
}
if (!allFlows.containsKey(face)) {
allFlows.put(face, Couple.create(PipeFlows::new));
if (inbound)
spawnSplashOnRim(face);
}
if (flow != null) {
PipeFlows flows = allFlows.get(face)
.get(inbound);
flows.addFlow(flow);
contentsChanged();
}
}
public void removeFlow(FluidNetworkFlow flow, Direction face, boolean inbound) {
if (!allFlows.containsKey(face))
return;
Couple<PipeFlows> couple = allFlows.get(face);
couple.get(inbound)
.removeFlow(flow);
if (!couple.get(true)
.isActive()
&& !couple.get(false)
.isActive())
allFlows.remove(face);
if (allFlows.isEmpty())
clear();
}
public void setFlowProgress(Direction face, boolean inbound, LerpedFloat progress) {
if (!allFlows.containsKey(face))
return;
allFlows.get(face)
.get(inbound).progress = progress;
}
public LerpedFloat getFlowProgress(Direction face, boolean inbound) {
if (!allFlows.containsKey(face))
return null;
return allFlows.get(face)
.get(inbound).progress;
}
public void skipFlow(Direction face, boolean inbound) {
if (!allFlows.containsKey(face))
return;
Couple<PipeFlows> couple = allFlows.get(face);
couple.get(inbound)
.skip();
}
public void clear() {
allFlows.clear();
fluid = FluidStack.EMPTY;
contentsChanged();
}
public void spawnParticles() {
DistExecutor.runWhenOn(Dist.CLIENT, () -> this::spawnParticlesInner);
}
public void spawnSplashOnRim(Direction face) {
DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> spawnSplashOnRimInner(face));
}
public static final int MAX_PARTICLE_RENDER_DISTANCE = 20;
public static final int SPLASH_PARTICLE_AMOUNT = 10;
public static final float IDLE_PARTICLE_SPAWN_CHANCE = 1 / 100f;
public static final Random r = new Random();
@OnlyIn(Dist.CLIENT)
private void spawnParticlesInner() {
if (!isRenderEntityWithinDistance())
return;
if (fluid.isEmpty())
return;
World world = Minecraft.getInstance().world;
BlockPos pos = tileEntity.getPos();
BlockState state = world.getBlockState(pos);
for (Direction face : Iterate.directions) {
boolean open = FluidPropagator.isOpenEnd(world, pos, face);
if (isConnectedTo(state, face)) {
if (open) {
spawnPouringLiquid(world, state, fluid, face, 1);
continue;
}
if (r.nextFloat() < IDLE_PARTICLE_SPAWN_CHANCE)
spawnRimParticles(world, state, fluid, face, 1);
}
}
}
@OnlyIn(Dist.CLIENT)
private void spawnSplashOnRimInner(Direction face) {
if (!isRenderEntityWithinDistance())
return;
if (fluid.isEmpty())
return;
World world = Minecraft.getInstance().world;
BlockPos pos = tileEntity.getPos();
BlockState state = world.getBlockState(pos);
spawnRimParticles(world, state, fluid, face, 20);
}
@OnlyIn(Dist.CLIENT)
private void spawnRimParticles(World world, BlockState state, FluidStack fluid, Direction side, int amount) {
BlockPos pos = tileEntity.getPos();
if (FluidPropagator.isOpenEnd(world, pos, side)) {
spawnPouringLiquid(world, state, fluid, side, amount);
return;
}
IParticleData particle = null;
if (FluidHelper.isWater(fluid.getFluid()))
particle = ParticleTypes.DRIPPING_WATER;
if (FluidHelper.isLava(fluid.getFluid()))
particle = ParticleTypes.DRIPPING_LAVA;
// TODO: Generic drip particle type for forge fluids
if (particle == null)
return;
float rimRadius = getRimRadius(state, side);
Vec3d directionVec = new Vec3d(side.getDirectionVec());
for (int i = 0; i < amount; i++) {
Vec3d vec = VecHelper.offsetRandomly(Vec3d.ZERO, r, 1)
.normalize();
vec = VecHelper.clampComponentWise(vec, rimRadius)
.mul(VecHelper.axisAlingedPlaneOf(directionVec))
.add(directionVec.scale(.45 + r.nextFloat() / 16f));
Vec3d m = vec;
vec = vec.add(VecHelper.getCenterOf(pos));
world.addOptionalParticle(particle, vec.x, vec.y - 1 / 16f, vec.z, m.x, m.y, m.z);
}
}
@OnlyIn(Dist.CLIENT)
private void spawnPouringLiquid(World world, BlockState state, FluidStack fluid, Direction side, int amount) {
IParticleData particle = new BlockParticleData(ParticleTypes.BLOCK, fluid.getFluid()
.getDefaultState()
.getBlockState());
float rimRadius = getRimRadius(state, side);
Vec3d directionVec = new Vec3d(side.getDirectionVec());
Couple<PipeFlows> couple = allFlows.get(side);
if (couple == null)
return;
couple.forEachWithContext((flow, inbound) -> {
if (flow.progress == null)
return;
for (int i = 0; i < amount; i++) {
Vec3d vec = VecHelper.offsetRandomly(Vec3d.ZERO, r, rimRadius);
vec = vec.mul(VecHelper.axisAlingedPlaneOf(directionVec))
.add(directionVec.scale(.5 + r.nextFloat() / 4f));
Vec3d m = vec;
Vec3d centerOf = VecHelper.getCenterOf(tileEntity.getPos());
vec = vec.add(centerOf);
if (inbound) {
vec = vec.add(m);
m = centerOf.add(directionVec.scale(.5)).subtract(vec).scale(3);
}
world.addOptionalParticle(particle, vec.x, vec.y - 1 / 16f, vec.z, m.x, m.y, m.z);
}
});
}
@OnlyIn(Dist.CLIENT)
private boolean isRenderEntityWithinDistance() {
Entity renderViewEntity = Minecraft.getInstance()
.getRenderViewEntity();
if (renderViewEntity == null)
return false;
Vec3d center = VecHelper.getCenterOf(tileEntity.getPos());
if (renderViewEntity.getPositionVec()
.distanceTo(center) > MAX_PARTICLE_RENDER_DISTANCE)
return false;
return true;
}
static AxisAlignedBB smallCenter = new AxisAlignedBB(BlockPos.ZERO).shrink(.25);
@Override
public void tick() {
super.tick();
boolean isRemote = getWorld().isRemote;
allFlows.values()
.forEach(c -> c.forEach(pf -> pf.tick(isRemote)));
if (isRemote) {
clientTick();
return;
}
if (collision != null) {
FluidReactions.handlePipeFlowCollision(getWorld(), tileEntity.getPos(), collision.getFirst(),
collision.getSecond());
collision = null;
return;
}
}
private void clientTick() {
spawnParticles();
if (!KineticDebugger.isActive())
return;
if (fluid.isEmpty())
return;
for (Entry<Direction, Couple<PipeFlows>> entry : allFlows.entrySet()) {
Direction face = entry.getKey();
Vec3d directionVec = new Vec3d(face.getDirectionVec());
float size = 1 / 4f;
boolean extended = !isConnectedTo(tileEntity.getBlockState(), face.getOpposite());
float length = extended ? .75f : .5f;
entry.getValue()
.forEachWithContext((flow, inbound) -> {
if (flow.progress == null)
return;
float value = flow.progress.getValue();
Vec3d start = directionVec.scale(inbound ? .5 : .5f - length);
Vec3d offset = directionVec.scale(length * (inbound ? -1 : 1))
.scale(value);
Vec3d scale = new Vec3d(1, 1, 1).subtract(directionVec.scale(face.getAxisDirection()
.getOffset()))
.scale(size);
AxisAlignedBB bb =
new AxisAlignedBB(start, start.add(offset)).offset(VecHelper.getCenterOf(tileEntity.getPos()))
.grow(scale.x, scale.y, scale.z);
int color = 0x7fdbda;
if (!fluid.isEmpty()) {
Fluid fluid2 = fluid.getFluid();
if (fluid2 == Fluids.WATER)
color = 0x1D4D9B;
if (fluid2 == Fluids.LAVA)
color = 0xFF773D;
}
CreateClient.outliner.chaseAABB(Pair.of(this, face), bb)
.withFaceTexture(AllSpecialTextures.CUTOUT_CHECKERED)
.colored(color)
.lineWidth(1 / 16f);
});
}
}
private void contentsChanged() {
tileEntity.markDirty();
tileEntity.sendData();
}
private LerpedFloat createFlowProgress(double speed) {
return LerpedFloat.linear()
.startWithValue(0)
.chase(1, speed, Chaser.LINEAR);
}
class PipeFlows {
LerpedFloat progress;
Set<FluidNetworkFlow> participants;
float bestFlowStrength;
void addFlow(FluidNetworkFlow flow) {
if (participants == null)
participants = new HashSet<>();
participants.add(flow);
if (progress == null) {
progress = createFlowProgress(flow.getSpeed());
}
}
boolean hasFlow(FluidNetworkFlow flow) {
return participants != null && participants.contains(flow);
}
void tick(boolean onClient) {
if (progress == null)
return;
if (!onClient) {
if (participants == null)
return;
bestFlowStrength = 0;
for (FluidNetworkFlow networkFlow : participants)
bestFlowStrength = Math.max(bestFlowStrength, networkFlow.getSpeed());
if (isCompleted())
return;
if (progress.updateChaseSpeed(bestFlowStrength))
contentsChanged();
}
progress.tickChaser();
}
void skip() {
progress = LerpedFloat.linear()
.startWithValue(1);
}
void removeFlow(FluidNetworkFlow flow) {
if (participants == null)
return;
participants.remove(flow);
}
boolean isActive() {
return participants != null && !participants.isEmpty();
}
boolean isCompleted() {
return progress != null && progress.getValue() == 1;
}
}
}

View file

@ -1,15 +1,24 @@
package com.simibubi.create.content.contraptions.fluids; package com.simibubi.create.content.contraptions.fluids;
import java.util.Random;
import javax.annotation.Nullable;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.block.IWaterLoggable; import net.minecraft.block.IWaterLoggable;
import net.minecraft.block.SixWayBlock; import net.minecraft.block.SixWayBlock;
import net.minecraft.fluid.Fluids; import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState; import net.minecraft.fluid.IFluidState;
import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.BlockItemUseContext;
import net.minecraft.network.DebugPacketSender;
import net.minecraft.state.StateContainer.Builder; import net.minecraft.state.StateContainer.Builder;
import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis; import net.minecraft.util.Direction.Axis;
import net.minecraft.util.Direction.AxisDirection; import net.minecraft.util.Direction.AxisDirection;
@ -17,141 +26,210 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader; import net.minecraft.world.IBlockReader;
import net.minecraft.world.ILightReader; import net.minecraft.world.ILightReader;
import net.minecraft.world.IWorld; import net.minecraft.world.IWorld;
import net.minecraft.world.TickPriority;
import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler; import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import javax.annotation.Nullable;
public class FluidPipeBlock extends SixWayBlock implements IWaterLoggable { public class FluidPipeBlock extends SixWayBlock implements IWaterLoggable {
public FluidPipeBlock(Properties properties) { public FluidPipeBlock(Properties properties) {
super(4 / 16f, properties); super(4 / 16f, properties);
this.setDefaultState(super.getDefaultState().with(BlockStateProperties.WATERLOGGED, false)); this.setDefaultState(super.getDefaultState().with(BlockStateProperties.WATERLOGGED, false));
} }
public static boolean isPipe(BlockState state) { @Override
return state.getBlock() instanceof FluidPipeBlock; public boolean hasTileEntity(BlockState state) {
} return true;
}
public static boolean isTank(BlockState state, IBlockReader world, BlockPos pos, Direction blockFace) { @Override
return state.hasTileEntity() && world.getTileEntity(pos).getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, blockFace.getOpposite()).isPresent(); public TileEntity createTileEntity(BlockState state, IBlockReader world) {
} return AllTileEntities.FLUID_PIPE.create();
}
// TODO: more generic pipe connection handling. Ideally without marker interface @Override
public static boolean canConnectTo(ILightReader world, BlockPos pos, BlockState neighbour, Direction blockFace) { public void onReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean isMoving) {
if (isPipe(neighbour) || isTank(neighbour, world, pos, blockFace)) boolean blockTypeChanged = state.getBlock() != newState.getBlock();
return true; if (blockTypeChanged && !world.isRemote)
return neighbour.getBlock() instanceof PumpBlock && blockFace.getAxis() == neighbour.get(PumpBlock.FACING) FluidPropagator.propagateChangedPipe(world, pos, state);
.getAxis(); if (state.hasTileEntity() && (blockTypeChanged || !newState.hasTileEntity()))
} world.removeTileEntity(pos);
}
public static boolean shouldDrawRim(ILightReader world, BlockPos pos, BlockState state, Direction direction) { @Override
if (!isPipe(state)) public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean isMoving) {
return false; if (world.isRemote)
if (!state.get(FACING_TO_PROPERTY_MAP.get(direction))) return;
return false; if (state != oldState)
BlockPos offsetPos = pos.offset(direction); world.getPendingBlockTicks()
BlockState facingState = world.getBlockState(offsetPos); .scheduleTick(pos, this, 1, TickPriority.HIGH);
if (facingState.getBlock() instanceof PumpBlock && facingState.get(PumpBlock.FACING) }
.getAxis() == direction.getAxis())
return false;
if (!isPipe(facingState))
return true;
if (!isCornerOrEndPipe(world, pos, state))
return false;
if (isStraightPipe(world, offsetPos, facingState))
return true;
if (!shouldDrawCasing(world, pos, state) && shouldDrawCasing(world, offsetPos, facingState))
return true;
if (isCornerOrEndPipe(world, offsetPos, facingState))
return direction.getAxisDirection() == AxisDirection.POSITIVE;
return false;
}
public static boolean isCornerOrEndPipe(ILightReader world, BlockPos pos, BlockState state) { @Override
return isPipe(state) && !isStraightPipe(world, pos, state) && !shouldDrawCasing(world, pos, state); public void neighborChanged(BlockState state, World world, BlockPos pos, Block otherBlock, BlockPos neighborPos,
} boolean isMoving) {
DebugPacketSender.func_218806_a(world, pos);
if (world.isRemote)
return;
if (otherBlock instanceof FluidPipeBlock)
return;
if (otherBlock instanceof PumpBlock)
return;
if (otherBlock instanceof FlowingFluidBlock)
return;
if (!isStraightPipe(state))
return;
for (Direction d : Iterate.directions) {
if (!pos.offset(d)
.equals(neighborPos))
continue;
if (!isOpenAt(state, d))
return;
world.getPendingBlockTicks()
.scheduleTick(pos, this, 1, TickPriority.HIGH);
}
}
public static boolean isStraightPipe(ILightReader world, BlockPos pos, BlockState state) { @Override
if (!isPipe(state)) public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random r) {
return false; FluidPropagator.propagateChangedPipe(world, pos, state);
boolean axisFound = false; }
for (Axis axis : Iterate.axes) {
Direction d1 = Direction.getFacingFromAxis(AxisDirection.NEGATIVE, axis);
Direction d2 = Direction.getFacingFromAxis(AxisDirection.POSITIVE, axis);
if (state.get(FACING_TO_PROPERTY_MAP.get(d1)) && state.get(FACING_TO_PROPERTY_MAP.get(d2)))
if (axisFound)
return false;
else
axisFound = true;
}
return axisFound;
}
public static boolean shouldDrawCasing(ILightReader world, BlockPos pos, BlockState state) { public static boolean isPipe(BlockState state) {
if (!isPipe(state)) return state.getBlock() instanceof FluidPipeBlock;
return false; }
for (Axis axis : Iterate.axes) {
int connections = 0;
for (Direction direction : Iterate.directions)
if (direction.getAxis() != axis && state.get(FACING_TO_PROPERTY_MAP.get(direction)))
connections++;
if (connections > 2)
return true;
}
return false;
}
@Override public static boolean hasFluidCapability(BlockState state, IBlockReader world, BlockPos pos, Direction blockFace) {
protected void fillStateContainer(Builder<Block, BlockState> builder) { return state.hasTileEntity() && world.getTileEntity(pos)
builder.add(NORTH, EAST, SOUTH, WEST, UP, DOWN, BlockStateProperties.WATERLOGGED); .getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, blockFace.getOpposite())
super.fillStateContainer(builder); .isPresent();
} }
@Override public static boolean canConnectTo(ILightReader world, BlockPos pos, BlockState neighbour, Direction blockFace) {
public BlockState getStateForPlacement(BlockItemUseContext context) { if (isPipe(neighbour) || hasFluidCapability(neighbour, world, pos, blockFace))
IFluidState ifluidstate = context.getWorld().getFluidState(context.getPos()); return true;
return updateBlockState(getDefaultState(), context.getNearestLookingDirection(), null, context.getWorld(), // TODO: more generic pipe connection handling.
context.getPos()).with(BlockStateProperties.WATERLOGGED, Boolean.valueOf(ifluidstate.getFluid() == Fluids.WATER)); return neighbour.getBlock() instanceof PumpBlock && blockFace.getAxis() == neighbour.get(PumpBlock.FACING)
} .getAxis();
}
@Override public static boolean shouldDrawRim(ILightReader world, BlockPos pos, BlockState state, Direction direction) {
public BlockState updatePostPlacement(BlockState state, Direction direction, BlockState neighbourState, if (!isPipe(state))
IWorld world, BlockPos pos, BlockPos neighbourPos) { return false;
if (state.get(BlockStateProperties.WATERLOGGED)) { if (!isOpenAt(state, direction))
world.getPendingFluidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world)); return false;
} BlockPos offsetPos = pos.offset(direction);
return updateBlockState(state, direction, direction.getOpposite(), world, pos); BlockState facingState = world.getBlockState(offsetPos);
} if (facingState.getBlock() instanceof PumpBlock && facingState.get(PumpBlock.FACING)
.getAxis() == direction.getAxis())
return false;
if (!isPipe(facingState))
return true;
if (!isCornerOrEndPipe(world, pos, state))
return false;
if (isStraightPipe(facingState))
return true;
if (!shouldDrawCasing(world, pos, state) && shouldDrawCasing(world, offsetPos, facingState))
return true;
if (isCornerOrEndPipe(world, offsetPos, facingState))
return direction.getAxisDirection() == AxisDirection.POSITIVE;
return false;
}
public BlockState updateBlockState(BlockState state, Direction preferredDirection, @Nullable Direction ignore, private static boolean isOpenAt(BlockState state, Direction direction) {
ILightReader world, BlockPos pos) { return state.get(FACING_TO_PROPERTY_MAP.get(direction));
// Update sides that are not ignored }
for (Direction d : Iterate.directions)
if (d != ignore)
state = state.with(FACING_TO_PROPERTY_MAP.get(d),
canConnectTo(world, pos.offset(d), world.getBlockState(pos.offset(d)), d.getOpposite()));
// See if it has enough connections public static boolean isCornerOrEndPipe(ILightReader world, BlockPos pos, BlockState state) {
Direction connectedDirection = null; return isPipe(state) && !isStraightPipe(state) && !shouldDrawCasing(world, pos, state);
for (Direction d : Iterate.directions) { }
if (state.get(FACING_TO_PROPERTY_MAP.get(d))) {
if (connectedDirection != null)
return state;
connectedDirection = d;
}
}
// Add opposite end if only one connection public static boolean isStraightPipe(BlockState state) {
if (connectedDirection != null) if (!isPipe(state))
return state.with(FACING_TO_PROPERTY_MAP.get(connectedDirection.getOpposite()), true); return false;
boolean axisFound = false;
for (Axis axis : Iterate.axes) {
Direction d1 = Direction.getFacingFromAxis(AxisDirection.NEGATIVE, axis);
Direction d2 = Direction.getFacingFromAxis(AxisDirection.POSITIVE, axis);
if (isOpenAt(state, d1) && isOpenAt(state, d2))
if (axisFound)
return false;
else
axisFound = true;
}
return axisFound;
}
// Use preferred public static boolean shouldDrawCasing(ILightReader world, BlockPos pos, BlockState state) {
return state.with(FACING_TO_PROPERTY_MAP.get(preferredDirection), true) if (!isPipe(state))
.with(FACING_TO_PROPERTY_MAP.get(preferredDirection.getOpposite()), true); return false;
} for (Axis axis : Iterate.axes) {
int connections = 0;
for (Direction direction : Iterate.directions)
if (direction.getAxis() != axis && isOpenAt(state, direction))
connections++;
if (connections > 2)
return true;
}
return false;
}
@Override @Override
public IFluidState getFluidState(BlockState state) { protected void fillStateContainer(Builder<Block, BlockState> builder) {
return state.get(BlockStateProperties.WATERLOGGED) ? Fluids.WATER.getStillFluidState(false) : Fluids.EMPTY.getDefaultState(); builder.add(NORTH, EAST, SOUTH, WEST, UP, DOWN, BlockStateProperties.WATERLOGGED);
} super.fillStateContainer(builder);
}
@Override
public BlockState getStateForPlacement(BlockItemUseContext context) {
IFluidState ifluidstate = context.getWorld()
.getFluidState(context.getPos());
return updateBlockState(getDefaultState(), context.getNearestLookingDirection(), null, context.getWorld(),
context.getPos()).with(BlockStateProperties.WATERLOGGED,
Boolean.valueOf(ifluidstate.getFluid() == Fluids.WATER));
}
@Override
public BlockState updatePostPlacement(BlockState state, Direction direction, BlockState neighbourState,
IWorld world, BlockPos pos, BlockPos neighbourPos) {
if (state.get(BlockStateProperties.WATERLOGGED)) {
world.getPendingFluidTicks()
.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world));
}
return updateBlockState(state, direction, direction.getOpposite(), world, pos);
}
public BlockState updateBlockState(BlockState state, Direction preferredDirection, @Nullable Direction ignore,
ILightReader world, BlockPos pos) {
// Update sides that are not ignored
for (Direction d : Iterate.directions)
if (d != ignore)
state = state.with(FACING_TO_PROPERTY_MAP.get(d),
canConnectTo(world, pos.offset(d), world.getBlockState(pos.offset(d)), d.getOpposite()));
// See if it has enough connections
Direction connectedDirection = null;
for (Direction d : Iterate.directions) {
if (isOpenAt(state, d)) {
if (connectedDirection != null)
return state;
connectedDirection = d;
}
}
// Add opposite end if only one connection
if (connectedDirection != null)
return state.with(FACING_TO_PROPERTY_MAP.get(connectedDirection.getOpposite()), true);
// Use preferred
return state.with(FACING_TO_PROPERTY_MAP.get(preferredDirection), true)
.with(FACING_TO_PROPERTY_MAP.get(preferredDirection.getOpposite()), true);
}
@Override
public IFluidState getFluidState(BlockState state) {
return state.get(BlockStateProperties.WATERLOGGED) ? Fluids.WATER.getStillFluidState(false)
: Fluids.EMPTY.getDefaultState();
}
} }

View file

@ -0,0 +1,39 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.List;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Direction;
public class FluidPipeTileEntity extends SmartTileEntity {
FluidPipeBehaviour behaviour;
public FluidPipeTileEntity(TileEntityType<?> tileEntityTypeIn) {
super(tileEntityTypeIn);
}
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
behaviour = new StandardPipeBehaviour(this);
behaviours.add(behaviour);
}
class StandardPipeBehaviour extends FluidPipeBehaviour {
public StandardPipeBehaviour(SmartTileEntity te) {
super(te);
}
@Override
public boolean isConnectedTo(BlockState state, Direction direction) {
return FluidPipeBlock.isPipe(state) && state.get(FluidPipeBlock.FACING_TO_PROPERTY_MAP.get(direction));
}
}
}

View file

@ -0,0 +1,128 @@
package com.simibubi.create.content.contraptions.fluids;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.mutable.MutableObject;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.CreateClient;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.outliner.Outline.OutlineParams;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
public class FluidPropagator {
public static FluidPipeBehaviour getPipe(IBlockReader reader, BlockPos pos) {
return TileEntityBehaviour.get(reader, pos, FluidPipeBehaviour.TYPE);
}
public static boolean isOpenEnd(IBlockReader reader, BlockPos pos, Direction side) {
BlockPos connectedPos = pos.offset(side);
BlockState connectedState = reader.getBlockState(connectedPos);
FluidPipeBehaviour pipe = FluidPropagator.getPipe(reader, connectedPos);
if (pipe != null && pipe.isConnectedTo(connectedState, side.getOpposite()))
return false;
if (PumpBlock.isPump(connectedState) && connectedState.get(PumpBlock.FACING)
.getAxis() == side.getAxis())
return false;
if (Block.hasSolidSide(connectedState, reader, connectedPos, side.getOpposite()))
return false;
if (!(connectedState.getMaterial()
.isReplaceable() && connectedState.getBlockHardness(reader, connectedPos) != -1)
&& !connectedState.has(BlockStateProperties.WATERLOGGED))
return false;
return true;
}
public static void propagateChangedPipe(IWorld world, BlockPos pipePos, BlockState pipeState) {
List<BlockPos> frontier = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>();
frontier.add(pipePos);
// Visit all connected pumps to update their network
while (!frontier.isEmpty()) {
BlockPos currentPos = frontier.remove(0);
if (visited.contains(currentPos))
continue;
visited.add(currentPos);
BlockState currentState = currentPos.equals(pipePos) ? pipeState : world.getBlockState(currentPos);
FluidPipeBehaviour pipe = getPipe(world, currentPos);
if (pipe == null)
continue;
for (Direction direction : getPipeConnections(currentState, pipe)) {
BlockPos target = currentPos.offset(direction);
if (!world.isAreaLoaded(target, 0))
continue;
TileEntity tileEntity = world.getTileEntity(target);
BlockState targetState = world.getBlockState(target);
if (tileEntity instanceof PumpTileEntity) {
if (!AllBlocks.MECHANICAL_PUMP.has(targetState) || targetState.get(PumpBlock.FACING)
.getAxis() != direction.getAxis())
continue;
PumpTileEntity pump = (PumpTileEntity) tileEntity;
pump.updatePipesOnSide(direction.getOpposite());
continue;
}
if (visited.contains(target))
continue;
FluidPipeBehaviour targetPipe = getPipe(world, target);
if (targetPipe == null)
continue;
if (targetPipe.isConnectedTo(targetState, direction.getOpposite()))
frontier.add(target);
}
}
}
public static List<Direction> getPipeConnections(BlockState state, FluidPipeBehaviour pipe) {
List<Direction> list = new ArrayList<>();
for (Direction d : Iterate.directions)
if (pipe.isConnectedTo(state, d))
list.add(d);
return list;
}
public static int getPumpRange() {
return AllConfigs.SERVER.fluids.mechanicalPumpRange.get();
}
public static OutlineParams showBlockFace(BlockFace face) {
MutableObject<OutlineParams> params = new MutableObject<>(new OutlineParams());
DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> {
Vec3d directionVec = new Vec3d(face.getFace()
.getDirectionVec());
Vec3d scaleVec = directionVec.scale(-.25f * face.getFace()
.getAxisDirection()
.getOffset());
directionVec = directionVec.scale(.5f);
params.setValue(CreateClient.outliner.showAABB(face,
FluidPropagator.smallCenter.offset(directionVec.add(new Vec3d(face.getPos())))
.grow(scaleVec.x, scaleVec.y, scaleVec.z)
.grow(1 / 16f)));
});
return params.getValue();
}
static AxisAlignedBB smallCenter = new AxisAlignedBB(BlockPos.ZERO).shrink(.25);
}

View file

@ -0,0 +1,37 @@
package com.simibubi.create.content.contraptions.fluids;
import com.simibubi.create.foundation.fluid.FluidHelper;
import com.simibubi.create.foundation.utility.BlockHelper;
import net.minecraft.block.Blocks;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fluids.FluidStack;
public class FluidReactions {
public static void handlePipeFlowCollision(World world, BlockPos pos, FluidStack fluid, FluidStack fluid2) {
Fluid f1 = fluid.getFluid();
Fluid f2 = fluid2.getFluid();
BlockHelper.destroyBlock(world, pos, 1);
if (f1 == Fluids.WATER && f2 == Fluids.LAVA || f2 == Fluids.WATER && f1 == Fluids.LAVA)
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState());
}
public static void handlePipeSpillCollision(World world, BlockPos pos, Fluid pipeFluid, IFluidState worldFluid) {
Fluid pf = FluidHelper.convertToStill(pipeFluid);
Fluid wf = worldFluid.getFluid();
if (pf == Fluids.WATER && wf == Fluids.LAVA)
world.setBlockState(pos, Blocks.OBSIDIAN.getDefaultState());
if (pf == Fluids.WATER && wf == Fluids.FLOWING_LAVA)
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState());
else if (pf == Fluids.LAVA && wf == Fluids.WATER)
world.setBlockState(pos, Blocks.STONE.getDefaultState());
else if (pf == Fluids.LAVA && wf == Fluids.FLOWING_WATER)
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState());
}
}

View file

@ -182,7 +182,7 @@ public class FluidTankBlock extends Block implements IWrenchable, ITE<FluidTankT
return ActionResultType.SUCCESS; return ActionResultType.SUCCESS;
} }
controllerTE.sendData(); controllerTE.sendDataImmediately();
controllerTE.markDirty(); controllerTE.markDirty();
} }
} }

View file

@ -276,6 +276,7 @@ public class FluidTankConnectivityHandler {
} }
} }
te.fluidCapability.invalidate();
if (tryReconnect) if (tryReconnect)
formTanks(world, cache == null ? new TankSearchCache() : cache, frontier); formTanks(world, cache == null ? new TankSearchCache() : cache, frontier);
} }

View file

@ -48,6 +48,10 @@ public class FluidTankTileEntity extends SmartTileEntity {
protected int width; protected int width;
protected int height; protected int height;
private static final int SYNC_RATE = 8;
protected int syncCooldown;
protected boolean queuedSync;
// For rendering purposes only // For rendering purposes only
InterpolatedChasingValue fluidLevel; InterpolatedChasingValue fluidLevel;
@ -60,6 +64,7 @@ public class FluidTankTileEntity extends SmartTileEntity {
window = true; window = true;
height = 1; height = 1;
width = 1; width = 1;
refreshCapability();
} }
protected void updateConnectivity() { protected void updateConnectivity() {
@ -74,6 +79,11 @@ public class FluidTankTileEntity extends SmartTileEntity {
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
if (syncCooldown > 0) {
syncCooldown--;
if (syncCooldown == 0 && queuedSync)
sendData();
}
if (updateConnectivity) if (updateConnectivity)
updateConnectivity(); updateConnectivity();
if (fluidLevel != null) if (fluidLevel != null)
@ -96,7 +106,7 @@ public class FluidTankTileEntity extends SmartTileEntity {
FluidAttributes attributes = newFluidStack.getFluid() FluidAttributes attributes = newFluidStack.getFluid()
.getAttributes(); .getAttributes();
int luminosity = attributes.getLuminosity(newFluidStack) / 2; int luminosity = (int) (attributes.getLuminosity(newFluidStack) / 1.2f);
boolean reversed = attributes.isLighterThanAir(); boolean reversed = attributes.isLighterThanAir();
int maxY = (int) ((getFillState() * height) + 1); int maxY = (int) ((getFillState() * height) + 1);
@ -116,6 +126,11 @@ public class FluidTankTileEntity extends SmartTileEntity {
} }
} }
} }
if (!world.isRemote) {
markDirty();
sendData();
}
} }
protected void setLuminosity(int luminosity) { protected void setLuminosity(int luminosity) {
@ -162,6 +177,7 @@ public class FluidTankTileEntity extends SmartTileEntity {
getWorld().setBlockState(pos, state, 22); getWorld().setBlockState(pos, state, 22);
} }
refreshCapability();
markDirty(); markDirty();
sendData(); sendData();
} }
@ -173,6 +189,23 @@ public class FluidTankTileEntity extends SmartTileEntity {
te.setWindows(!te.window); te.setWindows(!te.window);
} }
public void sendDataImmediately() {
syncCooldown = 0;
queuedSync = false;
sendData();
}
@Override
public void sendData() {
if (syncCooldown > 0) {
queuedSync = true;
return;
}
super.sendData();
queuedSync = false;
syncCooldown = SYNC_RATE;
}
public void setWindows(boolean window) { public void setWindows(boolean window) {
this.window = window; this.window = window;
for (int yOffset = 0; yOffset < height; yOffset++) { for (int yOffset = 0; yOffset < height; yOffset++) {
@ -213,10 +246,18 @@ public class FluidTankTileEntity extends SmartTileEntity {
if (controller.equals(this.controller)) if (controller.equals(this.controller))
return; return;
this.controller = controller; this.controller = controller;
refreshCapability();
markDirty(); markDirty();
sendData(); sendData();
} }
private void refreshCapability() {
LazyOptional<IFluidHandler> oldCap = fluidCapability;
fluidCapability = LazyOptional.of(() -> isController() ? tankInventory
: getControllerTE() != null ? getControllerTE().tankInventory : new FluidTank(0));
oldCap.invalidate();
}
public BlockPos getController() { public BlockPos getController() {
return isController() ? pos : controller; return isController() ? pos : controller;
} }
@ -236,39 +277,38 @@ public class FluidTankTileEntity extends SmartTileEntity {
} }
@Override @Override
public void read(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
super.read(tag); super.read(compound, clientPacket);
updateConnectivity = tag.contains("Uninitialized");
luminosity = tag.getInt("Luminosity");
controller = null;
if (tag.contains("Controller"))
controller = NBTUtil.readBlockPos(tag.getCompound("Controller"));
if (isController()) {
window = tag.getBoolean("Window");
width = tag.getInt("Size");
height = tag.getInt("Height");
tankInventory.setCapacity(getTotalTankSize() * getCapacityMultiplier());
tankInventory.readFromNBT(tag.getCompound("TankContent"));
if (tankInventory.getSpace() < 0)
tankInventory.drain(-tankInventory.getSpace(), FluidAction.EXECUTE);
}
if (tag.contains("ForceFluidLevel") || fluidLevel == null)
fluidLevel = new InterpolatedChasingValue().start(getFillState())
.withSpeed(1 / 2f);
}
@Override
public void readClientUpdate(CompoundNBT tag) {
BlockPos controllerBefore = controller; BlockPos controllerBefore = controller;
int prevSize = width; int prevSize = width;
int prevHeight = height; int prevHeight = height;
int prevLum = luminosity; int prevLum = luminosity;
updateConnectivity = compound.contains("Uninitialized");
luminosity = compound.getInt("Luminosity");
controller = null;
super.readClientUpdate(tag); if (compound.contains("Controller"))
controller = NBTUtil.readBlockPos(compound.getCompound("Controller"));
if (isController()) {
window = compound.getBoolean("Window");
width = compound.getInt("Size");
height = compound.getInt("Height");
tankInventory.setCapacity(getTotalTankSize() * getCapacityMultiplier());
tankInventory.readFromNBT(compound.getCompound("TankContent"));
if (tankInventory.getSpace() < 0)
tankInventory.drain(-tankInventory.getSpace(), FluidAction.EXECUTE);
}
if (compound.contains("ForceFluidLevel") || fluidLevel == null)
fluidLevel = new InterpolatedChasingValue().start(getFillState())
.withSpeed(1 / 2f);
if (!clientPacket)
return;
boolean changeOfController = boolean changeOfController =
controllerBefore == null ? controller != null : !controllerBefore.equals(controller); controllerBefore == null ? controller != null : !controllerBefore.equals(controller);
if (changeOfController || prevSize != width || prevHeight != height) { if (changeOfController || prevSize != width || prevHeight != height) {
@ -279,15 +319,17 @@ public class FluidTankTileEntity extends SmartTileEntity {
} }
if (isController()) { if (isController()) {
float fillState = getFillState(); float fillState = getFillState();
if (tag.contains("ForceFluidLevel") || fluidLevel == null) if (compound.contains("ForceFluidLevel") || fluidLevel == null)
fluidLevel = new InterpolatedChasingValue().start(fillState) fluidLevel = new InterpolatedChasingValue().start(fillState);
.withSpeed(1 / 2f);
fluidLevel.target(fillState); fluidLevel.target(fillState);
} }
if (luminosity != prevLum && hasWorld()) if (luminosity != prevLum && hasWorld())
world.getChunkProvider() world.getChunkProvider()
.getLightManager() .getLightManager()
.checkBlock(pos); .checkBlock(pos);
if (compound.contains("LazySync"))
fluidLevel.withSpeed(compound.contains("LazySync") ? 1 / 8f : 1 / 2f);
} }
protected float getFillState() { protected float getFillState() {
@ -295,44 +337,42 @@ public class FluidTankTileEntity extends SmartTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT tag) { public void write(CompoundNBT compound, boolean clientPacket) {
if (updateConnectivity) if (updateConnectivity)
tag.putBoolean("Uninitialized", true); compound.putBoolean("Uninitialized", true);
if (!isController()) if (!isController())
tag.put("Controller", NBTUtil.writeBlockPos(controller)); compound.put("Controller", NBTUtil.writeBlockPos(controller));
if (isController()) { if (isController()) {
tag.putBoolean("Window", window); compound.putBoolean("Window", window);
tag.put("TankContent", tankInventory.writeToNBT(new CompoundNBT())); compound.put("TankContent", tankInventory.writeToNBT(new CompoundNBT()));
tag.putInt("Size", width); compound.putInt("Size", width);
tag.putInt("Height", height); compound.putInt("Height", height);
} }
tag.putInt("Luminosity", luminosity); compound.putInt("Luminosity", luminosity);
return super.write(tag); super.write(compound, clientPacket);
}
if (!clientPacket)
@Override return;
public CompoundNBT writeToClient(CompoundNBT compound) {
if (forceFluidLevelUpdate) if (forceFluidLevelUpdate)
compound.putBoolean("ForceFluidLevel", true); compound.putBoolean("ForceFluidLevel", true);
if (queuedSync)
compound.putBoolean("LazySync", true);
forceFluidLevelUpdate = false; forceFluidLevelUpdate = false;
return super.writeToClient(compound);
} }
@Nonnull @Nonnull
@Override @Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) { public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
if (cap == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) { if (!fluidCapability.isPresent())
FluidTankTileEntity controller = getControllerTE(); refreshCapability();
if (controller != null) if (cap == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY)
return controller.fluidCapability.cast(); return fluidCapability.cast();
}
return super.getCapability(cap, side); return super.getCapability(cap, side);
} }
@Override @Override
public void remove() { public void remove() {
super.remove(); super.remove();
fluidCapability.invalidate();
} }
@Override @Override

View file

@ -0,0 +1,44 @@
package com.simibubi.create.content.contraptions.fluids;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.templates.FluidTank;
public class InterPumpFluidHandler extends FluidTank {
InterPumpEndpoint endpoint;
public InterPumpFluidHandler(InterPumpEndpoint endpoint) {
super(Integer.MAX_VALUE);
this.endpoint = endpoint;
}
@Override
public int fill(FluidStack resource, FluidAction action) {
if (resource.isEmpty())
return 0;
int maxInput = Math.min(resource.getAmount(), Math.max(getTransferCapacity() - getFluidAmount(), 0));
FluidStack toInsert = resource.copy();
toInsert.setAmount(maxInput);
FluidPropagator.showBlockFace(endpoint.location).colored(0x77d196).lineWidth(1/4f);
return super.fill(toInsert, action);
}
@Override
public FluidStack drain(int maxDrain, FluidAction action) {
return super.drain(maxDrain, action);
}
public FluidStack provide() {
FluidStack heldFluid = getFluid();
if (heldFluid.isEmpty())
return heldFluid;
FluidStack copy = heldFluid.copy();
copy.setAmount(1);
return copy;
}
private int getTransferCapacity() {
return Math.min(endpoint.getTransferSpeed(true), endpoint.getTransferSpeed(false));
}
}

View file

@ -0,0 +1,170 @@
package com.simibubi.create.content.contraptions.fluids;
import javax.annotation.Nullable;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.BlockState;
import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.templates.FluidTank;
public class OpenEndedPipe {
World world;
private OpenEndFluidHandler fluidHandler;
private BlockPos outputPos;
private boolean wasPulling;
private boolean stale;
public OpenEndedPipe(BlockFace face) {
fluidHandler = new OpenEndFluidHandler();
outputPos = face.getConnectedPos();
}
public void tick(World world, boolean pulling) {
this.world = world;
if (!world.isAreaLoaded(outputPos, 0))
return;
if (pulling != wasPulling) {
if (pulling)
fluidHandler.clear();
wasPulling = pulling;
}
BlockState state = world.getBlockState(outputPos);
IFluidState fluidState = state.getFluidState();
boolean waterlog = state.has(BlockStateProperties.WATERLOGGED);
if (!waterlog && !state.getMaterial()
.isReplaceable())
return;
// TODO different pipe end types
if (pulling) {
if (fluidState.isEmpty() || !fluidState.isSource())
return;
if (!fluidHandler.tryCollectFluid(fluidState.getFluid()))
return;
if (waterlog) {
world.setBlockState(outputPos, state.with(BlockStateProperties.WATERLOGGED, false), 3);
return;
}
world.setBlockState(outputPos, fluidState.getBlockState()
.with(FlowingFluidBlock.LEVEL, 14), 3);
return;
}
Fluid providedFluid = fluidHandler.tryProvidingFluid();
if (providedFluid == null)
return;
if (!fluidState.isEmpty() && fluidState.getFluid() != providedFluid) {
FluidReactions.handlePipeSpillCollision(world, outputPos, providedFluid, fluidState);
return;
}
if (fluidState.isSource())
return;
if (waterlog) {
if (providedFluid.getFluid() != Fluids.WATER)
return;
world.setBlockState(outputPos, state.with(BlockStateProperties.WATERLOGGED, true), 3);
return;
}
world.setBlockState(outputPos, providedFluid.getDefaultState()
.getBlockState(), 3);
}
public LazyOptional<IFluidHandler> getCapability() {
return LazyOptional.of(() -> fluidHandler);
}
public CompoundNBT writeToNBT(CompoundNBT compound) {
fluidHandler.writeToNBT(compound);
compound.putBoolean("Pulling", wasPulling);
return compound;
}
public void readNBT(CompoundNBT compound) {
fluidHandler.readFromNBT(compound);
wasPulling = compound.getBoolean("Pulling");
}
public void markStale() {
stale = true;
}
public void unmarkStale() {
stale = false;
}
public boolean isStale() {
return stale;
}
private class OpenEndFluidHandler extends FluidTank {
public OpenEndFluidHandler() {
super(1500);
}
@Override
public int fill(FluidStack resource, FluidAction action) {
// Never allow being filled when a source is attached
if (world == null)
return 0;
if (!world.isAreaLoaded(outputPos, 0))
return 0;
BlockState state = world.getBlockState(outputPos);
IFluidState fluidState = state.getFluidState();
if (!fluidState.isEmpty() && fluidState.getFluid() != resource.getFluid()) {
FluidReactions.handlePipeSpillCollision(world, outputPos, resource.getFluid(), fluidState);
return 0;
}
if (fluidState.isSource())
return 0;
if (!(state.has(BlockStateProperties.WATERLOGGED) && resource.getFluid() == Fluids.WATER)
&& !state.getMaterial()
.isReplaceable())
return 0;
// Never allow being filled above 1000
FluidStack insertable = resource.copy();
insertable.setAmount(Math.min(insertable.getAmount(), Math.max(1000 - getFluidAmount(), 0)));
return super.fill(insertable, action);
}
public boolean tryCollectFluid(Fluid fluid) {
for (boolean simulate : Iterate.trueAndFalse)
if (super.fill(new FluidStack(fluid, 1000),
simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE) != 1000)
return false;
return true;
}
@Nullable
public Fluid tryProvidingFluid() {
Fluid fluid = getFluid().getFluid();
for (boolean simulate : Iterate.trueAndFalse)
if (drain(1000, simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE).getAmount() != 1000)
return null;
return fluid;
}
public void clear() {
setFluid(FluidStack.EMPTY);
}
}
}

View file

@ -1,14 +1,23 @@
package com.simibubi.create.content.contraptions.fluids; package com.simibubi.create.content.contraptions.fluids;
import java.util.Map;
import org.apache.commons.lang3.mutable.MutableBoolean;
import com.simibubi.create.AllShapes; import com.simibubi.create.AllShapes;
import com.simibubi.create.AllTileEntities; import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.contraptions.base.DirectionalKineticBlock; import com.simibubi.create.content.contraptions.base.DirectionalKineticBlock;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.IWaterLoggable; import net.minecraft.block.IWaterLoggable;
import net.minecraft.fluid.Fluids; import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState; import net.minecraft.fluid.IFluidState;
import net.minecraft.item.BlockItemUseContext; import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemUseContext;
import net.minecraft.network.DebugPacketSender;
import net.minecraft.state.StateContainer.Builder; import net.minecraft.state.StateContainer.Builder;
import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
@ -20,71 +29,137 @@ import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader; import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld; import net.minecraft.world.IWorld;
import net.minecraft.world.IWorldReader; import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraftforge.fluids.FluidStack;
public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable { public class PumpBlock extends DirectionalKineticBlock implements IWaterLoggable {
public PumpBlock(Properties p_i48415_1_) { public PumpBlock(Properties p_i48415_1_) {
super(p_i48415_1_); super(p_i48415_1_);
setDefaultState(super.getDefaultState().with(BlockStateProperties.WATERLOGGED, false)); setDefaultState(super.getDefaultState().with(BlockStateProperties.WATERLOGGED, false));
} }
@Override @Override
public boolean hasTileEntity(BlockState state) { public boolean hasTileEntity(BlockState state) {
return true; return true;
} }
@Override @Override
public TileEntity createTileEntity(BlockState state, IBlockReader world) { public TileEntity createTileEntity(BlockState state, IBlockReader world) {
return AllTileEntities.MECHANICAL_PUMP.create(); return AllTileEntities.MECHANICAL_PUMP.create();
} }
@Override @Override
public BlockState getRotatedBlockState(BlockState originalState, Direction targetedFace) { public BlockState getRotatedBlockState(BlockState originalState, Direction targetedFace) {
return originalState.with(FACING, originalState.get(FACING) return originalState.with(FACING, originalState.get(FACING)
.getOpposite()); .getOpposite());
} }
@Override @Override
public Axis getRotationAxis(BlockState state) { public BlockState updateAfterWrenched(BlockState newState, ItemUseContext context) {
return state.get(FACING) BlockState state = super.updateAfterWrenched(newState, context);
.getAxis(); World world = context.getWorld();
} BlockPos pos = context.getPos();
if (world.isRemote)
return state;
TileEntity tileEntity = world.getTileEntity(pos);
if (!(tileEntity instanceof PumpTileEntity))
return state;
PumpTileEntity pump = (PumpTileEntity) tileEntity;
if (pump.networks == null)
return state;
@Override FluidNetwork apn1 = pump.networks.get(true);
public VoxelShape getShape(BlockState state, IBlockReader p_220053_2_, BlockPos p_220053_3_, FluidNetwork apn2 = pump.networks.get(false);
ISelectionContext p_220053_4_) {
return AllShapes.PUMP.get(state.get(FACING));
}
@Override // Collect pipes that can be skipped
public boolean hasIntegratedCogwheel(IWorldReader world, BlockPos pos, BlockState state) { apn1.clearFlows(world, true);
return true; apn2.clearFlows(world, true);
}
@Override // Swap skipsets as the networks change sides
public IFluidState getFluidState(BlockState state) { Map<BlockFace, FluidStack> skippedConnections = apn1.previousFlow;
return state.get(BlockStateProperties.WATERLOGGED) ? Fluids.WATER.getStillFluidState(false) : Fluids.EMPTY.getDefaultState(); apn1.previousFlow = apn2.previousFlow;
} apn2.previousFlow = skippedConnections;
@Override // Init networks next tick
protected void fillStateContainer(Builder<Block, BlockState> builder) { pump.networksToUpdate.forEach(MutableBoolean::setTrue);
builder.add(BlockStateProperties.WATERLOGGED); pump.networks.swap();
super.fillStateContainer(builder); pump.reversed = !pump.reversed;
}
@Override return state;
public BlockState updatePostPlacement(BlockState state, Direction direction, BlockState neighbourState, }
IWorld world, BlockPos pos, BlockPos neighbourPos) {
if (state.get(BlockStateProperties.WATERLOGGED)) {
world.getPendingFluidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world));
}
return state;
}
@Override @Override
public BlockState getStateForPlacement(BlockItemUseContext context) { public Axis getRotationAxis(BlockState state) {
IFluidState ifluidstate = context.getWorld().getFluidState(context.getPos()); return state.get(FACING)
return super.getStateForPlacement(context).with(BlockStateProperties.WATERLOGGED, Boolean.valueOf(ifluidstate.getFluid() == Fluids.WATER)); .getAxis();
} }
@Override
public VoxelShape getShape(BlockState state, IBlockReader p_220053_2_, BlockPos p_220053_3_,
ISelectionContext p_220053_4_) {
return AllShapes.PUMP.get(state.get(FACING));
}
@Override
public boolean hasIntegratedCogwheel(IWorldReader world, BlockPos pos, BlockState state) {
return true;
}
@Override
public void neighborChanged(BlockState state, World world, BlockPos pos, Block otherBlock, BlockPos neighborPos,
boolean isMoving) {
DebugPacketSender.func_218806_a(world, pos);
if (world.isRemote)
return;
if (otherBlock instanceof FluidPipeBlock)
return;
TileEntity tileEntity = world.getTileEntity(pos);
if (!(tileEntity instanceof PumpTileEntity))
return;
PumpTileEntity pump = (PumpTileEntity) tileEntity;
Direction facing = state.get(FACING);
for (boolean front : Iterate.trueAndFalse) {
Direction side = front ? facing : facing.getOpposite();
if (!pos.offset(side)
.equals(neighborPos))
continue;
pump.updatePipesOnSide(side);
}
}
@Override
public IFluidState getFluidState(BlockState state) {
return state.get(BlockStateProperties.WATERLOGGED) ? Fluids.WATER.getStillFluidState(false)
: Fluids.EMPTY.getDefaultState();
}
@Override
protected void fillStateContainer(Builder<Block, BlockState> builder) {
builder.add(BlockStateProperties.WATERLOGGED);
super.fillStateContainer(builder);
}
@Override
public BlockState updatePostPlacement(BlockState state, Direction direction, BlockState neighbourState,
IWorld world, BlockPos pos, BlockPos neighbourPos) {
if (state.get(BlockStateProperties.WATERLOGGED)) {
world.getPendingFluidTicks()
.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world));
}
return state;
}
@Override
public BlockState getStateForPlacement(BlockItemUseContext context) {
IFluidState ifluidstate = context.getWorld()
.getFluidState(context.getPos());
return super.getStateForPlacement(context).with(BlockStateProperties.WATERLOGGED,
Boolean.valueOf(ifluidstate.getFluid() == Fluids.WATER));
}
public static boolean isPump(BlockState state) {
return state.getBlock() instanceof PumpBlock;
}
} }

View file

@ -31,7 +31,7 @@ public class PumpRenderer extends KineticTileEntityRenderer {
PumpTileEntity pump = (PumpTileEntity) te; PumpTileEntity pump = (PumpTileEntity) te;
Vec3d rotationOffset = new Vec3d(.5, 14 / 16f, .5); Vec3d rotationOffset = new Vec3d(.5, 14 / 16f, .5);
BlockState blockState = te.getBlockState(); BlockState blockState = te.getBlockState();
float angle = MathHelper.lerp(pump.arrowDirection.get(partialTicks), 0, 90) - 90; float angle = MathHelper.lerp(pump.arrowDirection.getValue(partialTicks), 0, 90) - 90;
for (float yRot : new float[] { 0, 90 }) { for (float yRot : new float[] { 0, 90 }) {
ms.push(); ms.push();
SuperByteBuffer arrow = AllBlockPartials.MECHANICAL_PUMP_ARROW.renderOn(blockState); SuperByteBuffer arrow = AllBlockPartials.MECHANICAL_PUMP_ARROW.renderOn(blockState);

View file

@ -1,29 +1,349 @@
package com.simibubi.create.content.contraptions.fluids; package com.simibubi.create.content.contraptions.fluids;
import com.simibubi.create.content.contraptions.base.KineticTileEntity; import java.util.ArrayList;
import com.simibubi.create.foundation.gui.widgets.InterpolatedChasingValue; import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.mutable.MutableBoolean;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.LerpedFloat;
import com.simibubi.create.foundation.utility.LerpedFloat.Chaser;
import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.tileentity.TileEntityType; import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Direction;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
public class PumpTileEntity extends KineticTileEntity { public class PumpTileEntity extends KineticTileEntity {
InterpolatedChasingValue arrowDirection; LerpedFloat arrowDirection;
Couple<FluidNetwork> networks;
Couple<Map<BlockFace, OpenEndedPipe>> openEnds;
Couple<MutableBoolean> networksToUpdate;
boolean reversed;
FluidStack providedFluid;
public PumpTileEntity(TileEntityType<?> typeIn) { public PumpTileEntity(TileEntityType<?> typeIn) {
super(typeIn); super(typeIn);
arrowDirection = new InterpolatedChasingValue(); arrowDirection = LerpedFloat.linear()
arrowDirection.start(1); .startWithValue(1);
networksToUpdate = Couple.create(MutableBoolean::new);
openEnds = Couple.create(HashMap::new);
setProvidedFluid(FluidStack.EMPTY);
}
@Override
public void initialize() {
super.initialize();
reversed = getSpeed() < 0;
} }
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
float speed = getSpeed();
if (world.isRemote) { if (world.isRemote) {
float speed = getSpeed(); if (speed == 0)
if (speed != 0) return;
arrowDirection.target(Math.signum(speed)); arrowDirection.chase(speed >= 0 ? 1 : -1, .5f, Chaser.EXP);
arrowDirection.tick(); arrowDirection.tickChaser();
return;
}
BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof PumpBlock))
return;
Direction face = blockState.get(PumpBlock.FACING);
MutableBoolean networkUpdated = new MutableBoolean(false);
if (networks == null) {
networks = Couple.create(new FluidNetwork(), new FluidNetwork());
networks.forEachWithContext((fn, front) -> {
BlockFace blockFace = new BlockFace(pos, front ? face : face.getOpposite());
fn.assemble(world, this, blockFace);
FluidPropagator.showBlockFace(blockFace)
.lineWidth(1 / 8f);
});
networkUpdated.setTrue();
}
networksToUpdate.forEachWithContext((update, front) -> {
if (update.isFalse())
return;
FluidNetwork activePipeNetwork = networks.get(front);
if (activePipeNetwork == null)
return;
BlockFace blockFace = new BlockFace(pos, front ? face : face.getOpposite());
activePipeNetwork.reAssemble(world, this, blockFace);
FluidPropagator.showBlockFace(blockFace)
.lineWidth(1 / 8f);
update.setFalse();
networkUpdated.setTrue();
});
if (networkUpdated.isTrue())
return;
networks.forEach(fn -> fn.tick(world, this));
if (speed == 0)
return;
if (speed < 0 != reversed) {
networks.forEachWithContext((fn, current) -> fn.clearFlows(world, true));
reversed = speed < 0;
return;
}
boolean pullingSide = isPullingOnSide(true);
float flowSpeed = Math.abs(speed) / 256f;
networks.forEachWithContext((fn, front) -> {
boolean pulling = isPullingOnSide(front);
fn.tickFlows(world, this, pulling, flowSpeed);
openEnds.get(front)
.values()
.forEach(oep -> oep.tick(world, pulling));
});
if (!networks.get(pullingSide)
.hasEndpoints()) {
setProvidedFluid(FluidStack.EMPTY);
return;
}
if (networks.getFirst()
.hasEndpoints()
&& networks.getSecond()
.hasEndpoints()) {
performTransfer();
}
}
@Override
public void remove() {
super.remove();
if (networks != null)
networks.forEachWithContext((fn, current) -> fn.clearFlows(world, false));
}
private void performTransfer() {
boolean input = isPullingOnSide(true);
Collection<FluidNetworkEndpoint> inputs = networks.get(input)
.getEndpoints(true);
Collection<FluidNetworkEndpoint> outputs = networks.get(!input)
.getEndpoints(false);
int flowSpeed = getFluidTransferSpeed();
FluidStack transfer = FluidStack.EMPTY;
for (boolean simulate : Iterate.trueAndFalse) {
FluidAction action = simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE;
List<FluidNetworkEndpoint> availableInputs = new ArrayList<>(inputs);
while (!availableInputs.isEmpty() && transfer.getAmount() < flowSpeed) {
int diff = flowSpeed - transfer.getAmount();
int dividedTransfer = diff / availableInputs.size();
int remainder = diff % availableInputs.size();
for (Iterator<FluidNetworkEndpoint> iterator = availableInputs.iterator(); iterator.hasNext();) {
int toTransfer = dividedTransfer;
if (remainder > 0) {
toTransfer++;
remainder--;
}
FluidNetworkEndpoint ne = iterator.next();
IFluidHandler handler = ne.provideHandler()
.orElse(null);
if (handler == null) {
iterator.remove();
continue;
}
FluidStack drained = handler.drain(toTransfer, action);
if (drained.isEmpty()) {
iterator.remove();
continue;
}
if (transfer.isFluidEqual(drained) || transfer.isEmpty()) {
if (drained.getAmount() < toTransfer)
iterator.remove();
FluidStack copy = drained.copy();
copy.setAmount(drained.getAmount() + transfer.getAmount());
transfer = copy;
continue;
}
iterator.remove();
continue;
}
}
List<FluidNetworkEndpoint> availableOutputs = new ArrayList<>(outputs);
while (!availableOutputs.isEmpty() && transfer.getAmount() > 0) {
int dividedTransfer = transfer.getAmount() / availableOutputs.size();
int remainder = transfer.getAmount() % availableOutputs.size();
for (Iterator<FluidNetworkEndpoint> iterator = availableOutputs.iterator(); iterator.hasNext();) {
FluidNetworkEndpoint ne = iterator.next();
int toTransfer = dividedTransfer;
if (remainder > 0) {
toTransfer++;
remainder--;
}
if (transfer.isEmpty())
break;
IFluidHandler handler = ne.provideHandler()
.orElse(null);
if (handler == null) {
iterator.remove();
continue;
}
FluidStack divided = transfer.copy();
divided.setAmount(toTransfer);
int fill = handler.fill(divided, action);
transfer.setAmount(transfer.getAmount() - fill);
if (fill < toTransfer)
iterator.remove();
}
}
flowSpeed -= transfer.getAmount();
transfer = FluidStack.EMPTY;
} }
} }
public int getFluidTransferSpeed() {
float rotationSpeed = Math.abs(getSpeed());
int flowSpeed = (int) (rotationSpeed / 2f);
if (rotationSpeed != 0 && flowSpeed == 0)
flowSpeed = 1;
return flowSpeed;
}
@Override
public void write(CompoundNBT compound, boolean clientPacket) {
compound.putBoolean("Reversed", reversed);
serializeOpenEnds(compound);
super.write(compound, clientPacket);
}
@Override
protected void read(CompoundNBT compound, boolean clientPacket) {
reversed = compound.getBoolean("Reversed");
deserializeOpenEnds(compound);
super.read(compound, clientPacket);
}
public void updatePipesOnSide(Direction side) {
if (!isSideAccessible(side))
return;
updatePipeNetwork(isFront(side));
}
protected boolean isFront(Direction side) {
if (networks == null)
return false;
BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof PumpBlock))
return false;
Direction front = blockState.get(PumpBlock.FACING);
boolean isFront = side == front;
return isFront;
}
protected void updatePipeNetwork(boolean front) {
if (networks != null)
networks.get(front)
.clearFlows(world, true);
networksToUpdate.get(front)
.setTrue();
if (getSpeed() == 0 || (isPullingOnSide(front)) && networks != null)
setProvidedFluid(FluidStack.EMPTY);
}
public boolean isSideAccessible(Direction side) {
BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof PumpBlock))
return false;
return blockState.get(PumpBlock.FACING)
.getAxis() == side.getAxis();
}
public boolean isPullingOnSide(boolean front) {
return front == reversed;
}
public Map<BlockFace, OpenEndedPipe> getOpenEnds(Direction side) {
return openEnds.get(isFront(side));
}
private void serializeOpenEnds(CompoundNBT compound) {
compound.put("OpenEnds", openEnds.serializeEach(m -> {
CompoundNBT compoundNBT = new CompoundNBT();
ListNBT entries = new ListNBT();
m.entrySet()
.forEach(e -> {
CompoundNBT innerCompound = new CompoundNBT();
innerCompound.put("Pos", e.getKey()
.serializeNBT());
e.getValue()
.writeToNBT(innerCompound);
entries.add(innerCompound);
});
compoundNBT.put("Entries", entries);
return compoundNBT;
}));
}
private void deserializeOpenEnds(CompoundNBT compound) {
openEnds = Couple.deserializeEach(compound.getList("OpenEnds", NBT.TAG_COMPOUND), c -> {
Map<BlockFace, OpenEndedPipe> map = new HashMap<>();
NBTHelper.iterateCompoundList(c.getList("Entries", NBT.TAG_COMPOUND), innerCompound -> {
BlockFace key = BlockFace.fromNBT(innerCompound.getCompound("Pos"));
OpenEndedPipe value = new OpenEndedPipe(key);
value.readNBT(innerCompound);
map.put(key, value);
});
return map;
});
compound.put("OpenEnds", openEnds.serializeEach(m -> {
CompoundNBT compoundNBT = new CompoundNBT();
ListNBT entries = new ListNBT();
m.entrySet()
.forEach(e -> {
CompoundNBT innerCompound = new CompoundNBT();
innerCompound.put("Pos", e.getKey()
.serializeNBT());
e.getValue()
.writeToNBT(innerCompound);
entries.add(innerCompound);
});
compoundNBT.put("Entries", entries);
return compoundNBT;
}));
}
public void setProvidedFluid(FluidStack providedFluid) {
this.providedFluid = providedFluid;
}
} }

View file

@ -118,8 +118,8 @@ public class BasinTileEntity extends SmartTileEntity implements ITickableTileEnt
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
super.read(compound); super.read(compound, clientPacket);
inputItemInventory.deserializeNBT(compound.getCompound("InputItems")); inputItemInventory.deserializeNBT(compound.getCompound("InputItems"));
outputItemInventory.deserializeNBT(compound.getCompound("OutputItems")); outputItemInventory.deserializeNBT(compound.getCompound("OutputItems"));
if (compound.contains("fluids")) if (compound.contains("fluids"))
@ -128,15 +128,14 @@ public class BasinTileEntity extends SmartTileEntity implements ITickableTileEnt
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
super.write(compound); super.write(compound, clientPacket);
compound.put("InputItems", inputItemInventory.serializeNBT()); compound.put("InputItems", inputItemInventory.serializeNBT());
compound.put("OutputItems", outputItemInventory.serializeNBT()); compound.put("OutputItems", outputItemInventory.serializeNBT());
fluidInventory.ifPresent(combinedFuidHandler -> { fluidInventory.ifPresent(combinedFuidHandler -> {
ListNBT nbt = combinedFuidHandler.getListNBT(); ListNBT nbt = combinedFuidHandler.getListNBT();
compound.put("fluids", nbt); compound.put("fluids", nbt);
}); });
return compound;
} }
public void onEmptied() { public void onEmptied() {

View file

@ -155,17 +155,17 @@ public class BlazeBurnerTileEntity extends SmartTileEntity {
public void addBehaviours(List<TileEntityBehaviour> behaviours) {} public void addBehaviours(List<TileEntityBehaviour> behaviours) {}
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putInt("fuelLevel", activeFuel.ordinal()); compound.putInt("fuelLevel", activeFuel.ordinal());
compound.putInt("burnTimeRemaining", remainingBurnTime); compound.putInt("burnTimeRemaining", remainingBurnTime);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
activeFuel = FuelType.values()[compound.getInt("fuelLevel")]; activeFuel = FuelType.values()[compound.getInt("fuelLevel")];
remainingBurnTime = compound.getInt("burnTimeRemaining"); remainingBurnTime = compound.getInt("burnTimeRemaining");
super.read(compound); super.read(compound, clientPacket);
} }
/** /**

View file

@ -103,21 +103,21 @@ public class SequencedGearshiftTileEntity extends SplitShaftTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putInt("InstructionIndex", currentInstruction); compound.putInt("InstructionIndex", currentInstruction);
compound.putInt("InstructionDuration", currentInstructionDuration); compound.putInt("InstructionDuration", currentInstructionDuration);
compound.putInt("Timer", timer); compound.putInt("Timer", timer);
compound.put("Instructions", Instruction.serializeAll(instructions)); compound.put("Instructions", Instruction.serializeAll(instructions));
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
currentInstruction = compound.getInt("InstructionIndex"); currentInstruction = compound.getInt("InstructionIndex");
currentInstructionDuration = compound.getInt("InstructionDuration"); currentInstructionDuration = compound.getInt("InstructionDuration");
timer = compound.getInt("Timer"); timer = compound.getInt("Timer");
instructions = Instruction.deserializeAll(compound.getList("Instructions", NBT.TAG_COMPOUND)); instructions = Instruction.deserializeAll(compound.getList("Instructions", NBT.TAG_COMPOUND));
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override

View file

@ -79,8 +79,7 @@ public class BeltTileEntity extends KineticTileEntity {
@Override @Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) { public void addBehaviours(List<TileEntityBehaviour> behaviours) {
super.addBehaviours(behaviours); super.addBehaviours(behaviours);
behaviours.add(new DirectBeltInputBehaviour(this) behaviours.add(new DirectBeltInputBehaviour(this).setInsertionHandler(this::tryInsertingFromSide));
.setInsertionHandler(this::tryInsertingFromSide));
behaviours.add(new TransportedItemStackHandlerBehaviour(this, this::applyToAllItems) behaviours.add(new TransportedItemStackHandlerBehaviour(this, this::applyToAllItems)
.withStackPlacement(this::getWorldPositionOf)); .withStackPlacement(this::getWorldPositionOf));
} }
@ -175,7 +174,7 @@ public class BeltTileEntity extends KineticTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
if (controller != null) if (controller != null)
compound.put("Controller", NBTUtil.writeBlockPos(controller)); compound.put("Controller", NBTUtil.writeBlockPos(controller));
compound.putBoolean("IsController", isController()); compound.putBoolean("IsController", isController());
@ -186,23 +185,12 @@ public class BeltTileEntity extends KineticTileEntity {
if (isController()) if (isController())
compound.put("Inventory", getInventory().write()); compound.put("Inventory", getInventory().write());
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void readClientUpdate(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
CasingType casingBefore = casing; super.read(compound, clientPacket);
super.readClientUpdate(tag);
if (casingBefore != casing) {
requestModelDataUpdate();
if (hasWorld())
world.notifyBlockUpdate(getPos(), getBlockState(), getBlockState(), 16);
}
}
@Override
public void read(CompoundNBT compound) {
super.read(compound);
if (compound.getBoolean("IsController")) if (compound.getBoolean("IsController"))
controller = pos; controller = pos;
@ -219,7 +207,16 @@ public class BeltTileEntity extends KineticTileEntity {
if (isController()) if (isController())
getInventory().read(compound.getCompound("Inventory")); getInventory().read(compound.getCompound("Inventory"));
CasingType casingBefore = casing;
casing = NBTHelper.readEnum(compound, "Casing", CasingType.class); casing = NBTHelper.readEnum(compound, "Casing", CasingType.class);
if (!clientPacket)
return;
if (casingBefore == casing)
return;
requestModelDataUpdate();
if (hasWorld())
world.notifyBlockUpdate(getPos(), getBlockState(), getBlockState(), 16);
} }
@Override @Override
@ -397,16 +394,17 @@ public class BeltTileEntity extends KineticTileEntity {
BeltTileEntity nextBeltController = getControllerTE(); BeltTileEntity nextBeltController = getControllerTE();
ItemStack inserted = transportedStack.stack; ItemStack inserted = transportedStack.stack;
ItemStack empty = ItemStack.EMPTY; ItemStack empty = ItemStack.EMPTY;
if (nextBeltController == null) if (nextBeltController == null)
return inserted; return inserted;
BeltInventory nextInventory = nextBeltController.getInventory(); BeltInventory nextInventory = nextBeltController.getInventory();
TileEntity teAbove = world.getTileEntity(pos.up()); TileEntity teAbove = world.getTileEntity(pos.up());
if (teAbove instanceof BrassTunnelTileEntity) { if (teAbove instanceof BrassTunnelTileEntity) {
BrassTunnelTileEntity tunnelTE = (BrassTunnelTileEntity) teAbove; BrassTunnelTileEntity tunnelTE = (BrassTunnelTileEntity) teAbove;
if (tunnelTE.hasDistributionBehaviour()) { if (tunnelTE.hasDistributionBehaviour()) {
if (!tunnelTE.getStackToDistribute().isEmpty()) if (!tunnelTE.getStackToDistribute()
.isEmpty())
return inserted; return inserted;
if (!tunnelTE.testFlapFilter(side.getOpposite(), inserted)) if (!tunnelTE.testFlapFilter(side.getOpposite(), inserted))
return inserted; return inserted;

View file

@ -1,6 +1,9 @@
package com.simibubi.create.content.contraptions.relays.encased; package com.simibubi.create.content.contraptions.relays.encased;
import javax.annotation.Nullable;
import com.simibubi.create.content.contraptions.base.RotatedPillarKineticBlock; import com.simibubi.create.content.contraptions.base.RotatedPillarKineticBlock;
import mcp.MethodsReturnNonnullByDefault; import mcp.MethodsReturnNonnullByDefault;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -11,8 +14,6 @@ import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorldReader; import net.minecraft.world.IWorldReader;
import javax.annotation.Nullable;
@MethodsReturnNonnullByDefault @MethodsReturnNonnullByDefault
public abstract class AbstractEncasedShaftBlock extends RotatedPillarKineticBlock { public abstract class AbstractEncasedShaftBlock extends RotatedPillarKineticBlock {
public AbstractEncasedShaftBlock(Properties properties) { public AbstractEncasedShaftBlock(Properties properties) {
@ -30,7 +31,6 @@ public abstract class AbstractEncasedShaftBlock extends RotatedPillarKineticBloc
} }
@Override @Override
@SuppressWarnings("deprecation")
public PushReaction getPushReaction(@Nullable BlockState state) { public PushReaction getPushReaction(@Nullable BlockState state) {
return PushReaction.PUSH_ONLY; return PushReaction.PUSH_ONLY;
} }

View file

@ -17,15 +17,15 @@ public class AdjustablePulleyTileEntity extends KineticTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putInt("Signal", signal); compound.putInt("Signal", signal);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
signal = compound.getInt("Signal"); signal = compound.getInt("Signal");
super.read(compound); super.read(compound, clientPacket);
} }
public float getModifier() { public float getModifier() {

View file

@ -15,10 +15,8 @@ import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer.Builder; import net.minecraft.state.StateContainer.Builder;
import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader; import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.TickPriority; import net.minecraft.world.TickPriority;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld; import net.minecraft.world.server.ServerWorld;

View file

@ -21,17 +21,17 @@ public class GaugeTileEntity extends KineticTileEntity implements IHaveGoggleInf
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putFloat("Value", dialTarget); compound.putFloat("Value", dialTarget);
compound.putInt("Color", color); compound.putInt("Color", color);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
dialTarget = compound.getFloat("Value"); dialTarget = compound.getFloat("Value");
color = compound.getInt("Color"); color = compound.getInt("Color");
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override

View file

@ -73,15 +73,15 @@ public class BeltObserverTileEntity extends SmartTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putInt("TurnOff", turnOffTicks); compound.putInt("TurnOff", turnOffTicks);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
turnOffTicks = compound.getInt("TurnOff"); turnOffTicks = compound.getInt("TurnOff");
super.read(compound); super.read(compound, clientPacket);
} }
} }

View file

@ -52,38 +52,18 @@ public class BeltTunnelTileEntity extends SmartTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
ListNBT flapsNBT = new ListNBT(); ListNBT flapsNBT = new ListNBT();
for (Direction direction : flaps.keySet()) for (Direction direction : flaps.keySet())
flapsNBT.add(IntNBT.of(direction.getIndex())); flapsNBT.add(IntNBT.of(direction.getIndex()));
compound.put("Flaps", flapsNBT); compound.put("Flaps", flapsNBT);
return super.write(compound); super.write(compound, clientPacket);
}
@Override if (!clientPacket)
public void read(CompoundNBT compound) { return;
Set<Direction> newFlaps = new HashSet<>(6);
ListNBT flapsNBT = compound.getList("Flaps", NBT.TAG_INT);
for (INBT inbt : flapsNBT)
if (inbt instanceof IntNBT)
newFlaps.add(Direction.byIndex(((IntNBT) inbt).getInt()));
for (Direction d : Iterate.directions)
if (!newFlaps.contains(d))
flaps.remove(d);
else if (!flaps.containsKey(d))
flaps.put(d, new InterpolatedChasingValue().start(.25f)
.target(0)
.withSpeed(.05f));
super.read(compound); flapsNBT = new ListNBT();
}
@Override
public CompoundNBT writeToClient(CompoundNBT tag) {
CompoundNBT writeToClient = super.writeToClient(tag);
if (!flapsToSend.isEmpty()) { if (!flapsToSend.isEmpty()) {
ListNBT flapsNBT = new ListNBT();
for (Pair<Direction, Boolean> pair : flapsToSend) { for (Pair<Direction, Boolean> pair : flapsToSend) {
CompoundNBT flap = new CompoundNBT(); CompoundNBT flap = new CompoundNBT();
flap.putInt("Flap", pair.getKey() flap.putInt("Flap", pair.getKey()
@ -91,22 +71,38 @@ public class BeltTunnelTileEntity extends SmartTileEntity {
flap.putBoolean("FlapInward", pair.getValue()); flap.putBoolean("FlapInward", pair.getValue());
flapsNBT.add(flap); flapsNBT.add(flap);
} }
writeToClient.put("TriggerFlaps", flapsNBT); compound.put("TriggerFlaps", flapsNBT);
flapsToSend.clear(); flapsToSend.clear();
} }
return writeToClient;
} }
@Override @Override
public void readClientUpdate(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
super.readClientUpdate(tag); Set<Direction> newFlaps = new HashSet<>(6);
if (tag.contains("TriggerFlaps")) { ListNBT flapsNBT = compound.getList("Flaps", NBT.TAG_INT);
ListNBT flapsNBT = tag.getList("TriggerFlaps", NBT.TAG_COMPOUND); for (INBT inbt : flapsNBT)
for (INBT inbt : flapsNBT) { if (inbt instanceof IntNBT)
CompoundNBT flap = (CompoundNBT) inbt; newFlaps.add(Direction.byIndex(((IntNBT) inbt).getInt()));
Direction side = Direction.byIndex(flap.getInt("Flap"));
flap(side, flap.getBoolean("FlapInward")); for (Direction d : Iterate.directions)
} if (!newFlaps.contains(d))
flaps.remove(d);
else if (!flaps.containsKey(d))
flaps.put(d, new InterpolatedChasingValue().start(.25f)
.target(0)
.withSpeed(.05f));
super.read(compound, clientPacket);
if (!clientPacket)
return;
if (!compound.contains("TriggerFlaps"))
return;
flapsNBT = compound.getList("TriggerFlaps", NBT.TAG_COMPOUND);
for (INBT inbt : flapsNBT) {
CompoundNBT flap = (CompoundNBT) inbt;
Direction side = Direction.byIndex(flap.getInt("Flap"));
flap(side, flap.getBoolean("FlapInward"));
} }
} }
@ -131,12 +127,12 @@ public class BeltTunnelTileEntity extends SmartTileEntity {
if (!positive && shape == Shape.T_RIGHT) if (!positive && shape == Shape.T_RIGHT)
continue; continue;
} }
BlockState funnelState = world.getBlockState(getPos().offset(direction)); BlockState funnelState = world.getBlockState(getPos().offset(direction));
if (funnelState.getBlock() instanceof BeltFunnelBlock) if (funnelState.getBlock() instanceof BeltFunnelBlock)
if (funnelState.get(BeltFunnelBlock.HORIZONTAL_FACING) == direction.getOpposite()) if (funnelState.get(BeltFunnelBlock.HORIZONTAL_FACING) == direction.getOpposite())
continue; continue;
flaps.put(direction, new InterpolatedChasingValue().start(.25f) flaps.put(direction, new InterpolatedChasingValue().start(.25f)
.target(0) .target(0)
.withSpeed(.05f)); .withSpeed(.05f));

View file

@ -328,7 +328,7 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putBoolean("ConnectedLeft", connectedLeft); compound.putBoolean("ConnectedLeft", connectedLeft);
compound.putBoolean("ConnectedRight", connectedRight); compound.putBoolean("ConnectedRight", connectedRight);
@ -344,14 +344,16 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity {
return nbt; return nbt;
})); }));
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
boolean wasConnectedLeft = connectedLeft;
boolean wasConnectedRight = connectedRight;
connectedLeft = compound.getBoolean("ConnectedLeft"); connectedLeft = compound.getBoolean("ConnectedLeft");
connectedRight = compound.getBoolean("ConnectedRight"); connectedRight = compound.getBoolean("ConnectedRight");
stackToDistribute = ItemStack.read(compound.getCompound("StackToDistribute")); stackToDistribute = ItemStack.read(compound.getCompound("StackToDistribute"));
distributionProgress = compound.getFloat("DistributionProgress"); distributionProgress = compound.getFloat("DistributionProgress");
distributionDistanceLeft = compound.getInt("DistanceLeft"); distributionDistanceLeft = compound.getInt("DistanceLeft");
@ -362,14 +364,10 @@ public class BrassTunnelTileEntity extends BeltTunnelTileEntity {
return Pair.of(pos, face); return Pair.of(pos, face);
}); });
super.read(compound); super.read(compound, clientPacket);
}
if (!clientPacket)
@Override return;
public void readClientUpdate(CompoundNBT tag) {
boolean wasConnectedLeft = connectedLeft;
boolean wasConnectedRight = connectedRight;
super.readClientUpdate(tag);
if (wasConnectedLeft != connectedLeft || wasConnectedRight != connectedRight) { if (wasConnectedLeft != connectedLeft || wasConnectedRight != connectedRight) {
requestModelDataUpdate(); requestModelDataUpdate();
if (hasWorld()) if (hasWorld())

View file

@ -277,22 +277,22 @@ public class ChuteTileEntity extends SmartTileEntity implements IHaveGoggleInfor
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.put("Item", item.serializeNBT()); compound.put("Item", item.serializeNBT());
compound.putFloat("ItemPosition", itemPosition.value); compound.putFloat("ItemPosition", itemPosition.value);
compound.putFloat("Pull", pull); compound.putFloat("Pull", pull);
compound.putFloat("Push", push); compound.putFloat("Push", push);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
ItemStack previousItem = item; ItemStack previousItem = item;
item = ItemStack.read(compound.getCompound("Item")); item = ItemStack.read(compound.getCompound("Item"));
itemPosition.lastValue = itemPosition.value = compound.getFloat("ItemPosition"); itemPosition.lastValue = itemPosition.value = compound.getFloat("ItemPosition");
pull = compound.getFloat("Pull"); pull = compound.getFloat("Pull");
push = compound.getFloat("Push"); push = compound.getFloat("Push");
super.read(compound); super.read(compound, clientPacket);
if (hasWorld() && world.isRemote && !previousItem.equals(item, false) && !item.isEmpty()) { if (hasWorld() && world.isRemote && !previousItem.equals(item, false) && !item.isEmpty()) {
if (world.rand.nextInt(3) != 0) if (world.rand.nextInt(3) != 0)

View file

@ -93,20 +93,20 @@ public class DepotTileEntity extends SmartTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
if (heldItem != null) if (heldItem != null)
compound.put("HeldItem", heldItem.serializeNBT()); compound.put("HeldItem", heldItem.serializeNBT());
compound.put("OutputBuffer", processingOutputBuffer.serializeNBT()); compound.put("OutputBuffer", processingOutputBuffer.serializeNBT());
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
heldItem = null; heldItem = null;
if (compound.contains("HeldItem")) if (compound.contains("HeldItem"))
heldItem = TransportedItemStack.read(compound.getCompound("HeldItem")); heldItem = TransportedItemStack.read(compound.getCompound("HeldItem"));
processingOutputBuffer.deserializeNBT(compound.getCompound("OutputBuffer")); processingOutputBuffer.deserializeNBT(compound.getCompound("OutputBuffer"));
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override

View file

@ -43,17 +43,17 @@ public class AdjustableRepeaterTileEntity extends SmartTileEntity {
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
state = compound.getInt("State"); state = compound.getInt("State");
charging = compound.getBoolean("Charging"); charging = compound.getBoolean("Charging");
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putInt("State", state); compound.putInt("State", state);
compound.putBoolean("Charging", charging); compound.putBoolean("Charging", charging);
return super.write(compound); super.write(compound, clientPacket);
} }
private int step(StepContext context) { private int step(StepContext context) {

View file

@ -218,23 +218,23 @@ public class FunnelTileEntity extends SmartTileEntity {
return getBlockState().getBlock() instanceof BeltFunnelBlock return getBlockState().getBlock() instanceof BeltFunnelBlock
&& getBlockState().get(BeltFunnelBlock.SHAPE) == Shape.RETRACTED; && getBlockState().get(BeltFunnelBlock.SHAPE) == Shape.RETRACTED;
} }
@Override @Override
public CompoundNBT writeToClient(CompoundNBT compound) { protected void write(CompoundNBT compound, boolean clientPacket) {
if (sendFlap != 0) { super.write(compound, clientPacket);
if (clientPacket && sendFlap != 0) {
compound.putInt("Flap", sendFlap); compound.putInt("Flap", sendFlap);
sendFlap = 0; sendFlap = 0;
} }
return super.writeToClient(compound);
} }
@Override @Override
public void readClientUpdate(CompoundNBT tag) { protected void read(CompoundNBT compound, boolean clientPacket) {
if (tag.contains("Flap")) { super.read(compound, clientPacket);
int direction = tag.getInt("Flap"); if (clientPacket && compound.contains("Flap")) {
int direction = compound.getInt("Flap");
flap.set(direction); flap.set(direction);
} }
super.readClientUpdate(tag);
} }
@Override @Override

View file

@ -142,18 +142,18 @@ public class AdjustableCrateTileEntity extends CrateTileEntity implements INamed
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putBoolean("Main", true); compound.putBoolean("Main", true);
compound.putInt("AllowedAmount", allowedAmount); compound.putInt("AllowedAmount", allowedAmount);
compound.put("Inventory", inventory.serializeNBT()); compound.put("Inventory", inventory.serializeNBT());
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
allowedAmount = compound.getInt("AllowedAmount"); allowedAmount = compound.getInt("AllowedAmount");
inventory.deserializeNBT(compound.getCompound("Inventory")); inventory.deserializeNBT(compound.getCompound("Inventory"));
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override

View file

@ -272,8 +272,8 @@ public class ArmTileEntity extends KineticTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
super.write(compound); super.write(compound, clientPacket);
ListNBT pointsNBT = new ListNBT(); ListNBT pointsNBT = new ListNBT();
inputs.stream() inputs.stream()
@ -288,32 +288,24 @@ public class ArmTileEntity extends KineticTileEntity {
compound.put("HeldItem", heldItem.serializeNBT()); compound.put("HeldItem", heldItem.serializeNBT());
compound.putInt("TargetPointIndex", chasedPointIndex); compound.putInt("TargetPointIndex", chasedPointIndex);
compound.putFloat("MovementProgress", chasedPointProgress); compound.putFloat("MovementProgress", chasedPointProgress);
return compound;
} }
@Override @Override
public CompoundNBT writeToClient(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
super.writeToClient(compound); int previousIndex = chasedPointIndex;
return compound; Phase previousPhase = phase;
} ListNBT interactionPointTagBefore = interactionPointTag;
@Override super.read(compound, clientPacket);
public void read(CompoundNBT compound) {
super.read(compound);
heldItem = ItemStack.read(compound.getCompound("HeldItem")); heldItem = ItemStack.read(compound.getCompound("HeldItem"));
phase = NBTHelper.readEnum(compound, "Phase", Phase.class); phase = NBTHelper.readEnum(compound, "Phase", Phase.class);
chasedPointIndex = compound.getInt("TargetPointIndex"); chasedPointIndex = compound.getInt("TargetPointIndex");
chasedPointProgress = compound.getFloat("MovementProgress"); chasedPointProgress = compound.getFloat("MovementProgress");
interactionPointTag = compound.getList("InteractionPoints", NBT.TAG_COMPOUND); interactionPointTag = compound.getList("InteractionPoints", NBT.TAG_COMPOUND);
}
if (!clientPacket)
@Override return;
public void readClientUpdate(CompoundNBT tag) {
int previousIndex = chasedPointIndex;
Phase previousPhase = phase;
ListNBT interactionPointTagBefore = interactionPointTag;
super.readClientUpdate(tag);
boolean ceiling = isOnCeiling(); boolean ceiling = isOnCeiling();
if (interactionPointTagBefore == null || interactionPointTagBefore.size() != interactionPointTag.size()) if (interactionPointTagBefore == null || interactionPointTagBefore.size() != interactionPointTag.size())
updateInteractionPoints = true; updateInteractionPoints = true;

View file

@ -23,18 +23,18 @@ public class AnalogLeverTileEntity extends SmartTileEntity implements IHaveGoggl
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putInt("State", state); compound.putInt("State", state);
compound.putInt("ChangeTimer", lastChange); compound.putInt("ChangeTimer", lastChange);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
state = compound.getInt("State"); state = compound.getInt("State");
lastChange = compound.getInt("ChangeTimer"); lastChange = compound.getInt("ChangeTimer");
clientState.target(state); clientState.target(state);
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override

View file

@ -64,18 +64,18 @@ public class RedstoneLinkTileEntity extends SmartTileEntity {
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putBoolean("Transmitter", transmitter); compound.putBoolean("Transmitter", transmitter);
compound.putInt("Receive", getReceivedSignal()); compound.putInt("Receive", getReceivedSignal());
compound.putBoolean("ReceivedChanged", receivedSignalChanged); compound.putBoolean("ReceivedChanged", receivedSignalChanged);
compound.putInt("Transmit", transmittedSignal); compound.putInt("Transmit", transmittedSignal);
return super.write(compound); super.write(compound, clientPacket);
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
transmitter = compound.getBoolean("Transmitter"); transmitter = compound.getBoolean("Transmitter");
super.read(compound); super.read(compound, clientPacket);
receivedSignal = compound.getInt("Receive"); receivedSignal = compound.getInt("Receive");
receivedSignalChanged = compound.getBoolean("ReceivedChanged"); receivedSignalChanged = compound.getBoolean("ReceivedChanged");

View file

@ -36,25 +36,23 @@ public class StockpileSwitchTileEntity extends SmartTileEntity {
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
onWhenAbove = compound.getFloat("OnAbove"); onWhenAbove = compound.getFloat("OnAbove");
offWhenBelow = compound.getFloat("OffBelow"); offWhenBelow = compound.getFloat("OffBelow");
currentLevel = compound.getFloat("Current"); currentLevel = compound.getFloat("Current");
powered = compound.getBoolean("Powered"); powered = compound.getBoolean("Powered");
super.read(compound); super.read(compound, clientPacket);
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.putFloat("OnAbove", onWhenAbove); compound.putFloat("OnAbove", onWhenAbove);
compound.putFloat("OffBelow", offWhenBelow); compound.putFloat("OffBelow", offWhenBelow);
compound.putFloat("Current", currentLevel); compound.putFloat("Current", currentLevel);
compound.putBoolean("Powered", powered); compound.putBoolean("Powered", powered);
return super.write(compound); super.write(compound, clientPacket);
} }
public float getLevel() { public float getLevel() {

View file

@ -6,7 +6,6 @@ import static net.minecraft.util.text.TextFormatting.GRAY;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.mojang.blaze3d.systems.RenderSystem;
import com.simibubi.create.content.logistics.item.filter.FilterScreenPacket.Option; import com.simibubi.create.content.logistics.item.filter.FilterScreenPacket.Option;
import com.simibubi.create.foundation.gui.AbstractSimiContainerScreen; import com.simibubi.create.foundation.gui.AbstractSimiContainerScreen;
import com.simibubi.create.foundation.gui.AllGuiTextures; import com.simibubi.create.foundation.gui.AllGuiTextures;
@ -20,7 +19,6 @@ import com.simibubi.create.foundation.item.TooltipHelper;
import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.networking.AllPackets;
import net.minecraft.client.gui.widget.Widget; import net.minecraft.client.gui.widget.Widget;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.I18n;
import net.minecraft.entity.player.PlayerInventory; import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextComponent;

View file

@ -7,9 +7,9 @@ import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllItems; import com.simibubi.create.AllItems;
import com.simibubi.create.AllSoundEvents; import com.simibubi.create.AllSoundEvents;
import com.simibubi.create.content.contraptions.relays.belt.BeltBlock; import com.simibubi.create.content.contraptions.relays.belt.BeltBlock;
import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
import com.simibubi.create.content.contraptions.relays.belt.BeltPart; import com.simibubi.create.content.contraptions.relays.belt.BeltPart;
import com.simibubi.create.content.contraptions.relays.belt.BeltSlope; import com.simibubi.create.content.contraptions.relays.belt.BeltSlope;
import com.simibubi.create.content.contraptions.relays.belt.BeltTileEntity;
import com.simibubi.create.content.contraptions.relays.elementary.ShaftBlock; import com.simibubi.create.content.contraptions.relays.elementary.ShaftBlock;
import com.simibubi.create.content.schematics.ItemRequirement; import com.simibubi.create.content.schematics.ItemRequirement;
import com.simibubi.create.content.schematics.ItemRequirement.ItemUseType; import com.simibubi.create.content.schematics.ItemRequirement.ItemUseType;
@ -162,19 +162,13 @@ public class SchematicannonTileEntity extends SmartTileEntity implements INamedC
} }
@Override @Override
public void read(CompoundNBT compound) { protected void read(CompoundNBT compound, boolean clientPacket) {
inventory.deserializeNBT(compound.getCompound("Inventory")); if (!clientPacket) {
inventory.deserializeNBT(compound.getCompound("Inventory"));
if (compound.contains("CurrentPos")) if (compound.contains("CurrentPos"))
currentPos = NBTUtil.readBlockPos(compound.getCompound("CurrentPos")); currentPos = NBTUtil.readBlockPos(compound.getCompound("CurrentPos"));
}
readClientUpdate(compound);
super.read(compound);
}
@Override
public void readClientUpdate(CompoundNBT compound) {
// Gui information // Gui information
statusMsg = compound.getString("Status"); statusMsg = compound.getString("Status");
schematicProgress = compound.getFloat("Progress"); schematicProgress = compound.getFloat("Progress");
@ -184,23 +178,24 @@ public class SchematicannonTileEntity extends SmartTileEntity implements INamedC
blocksPlaced = compound.getInt("AmountPlaced"); blocksPlaced = compound.getInt("AmountPlaced");
blocksToPlace = compound.getInt("AmountToPlace"); blocksToPlace = compound.getInt("AmountToPlace");
printingEntityIndex = compound.getInt("EntityProgress"); printingEntityIndex = compound.getInt("EntityProgress");
missingItem = null; missingItem = null;
if (compound.contains("MissingItem")) if (compound.contains("MissingItem"))
missingItem = ItemStack.read(compound.getCompound("MissingItem")); missingItem = ItemStack.read(compound.getCompound("MissingItem"));
// Settings // Settings
CompoundNBT options = compound.getCompound("Options"); CompoundNBT options = compound.getCompound("Options");
replaceMode = options.getInt("ReplaceMode"); replaceMode = options.getInt("ReplaceMode");
skipMissing = options.getBoolean("SkipMissing"); skipMissing = options.getBoolean("SkipMissing");
replaceTileEntities = options.getBoolean("ReplaceTileEntities"); replaceTileEntities = options.getBoolean("ReplaceTileEntities");
// Printer & Flying Blocks // Printer & Flying Blocks
if (compound.contains("Target")) if (compound.contains("Target"))
target = NBTUtil.readBlockPos(compound.getCompound("Target")); target = NBTUtil.readBlockPos(compound.getCompound("Target"));
if (compound.contains("FlyingBlocks")) if (compound.contains("FlyingBlocks"))
readFlyingBlocks(compound); readFlyingBlocks(compound);
super.read(compound, clientPacket);
} }
protected void readFlyingBlocks(CompoundNBT compound) { protected void readFlyingBlocks(CompoundNBT compound) {
@ -239,22 +234,16 @@ public class SchematicannonTileEntity extends SmartTileEntity implements INamedC
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public void write(CompoundNBT compound, boolean clientPacket) {
compound.put("Inventory", inventory.serializeNBT()); if (!clientPacket) {
compound.put("Inventory", inventory.serializeNBT());
if (state == State.RUNNING) { if (state == State.RUNNING) {
compound.putBoolean("Running", true); compound.putBoolean("Running", true);
if (currentPos != null) if (currentPos != null)
compound.put("CurrentPos", NBTUtil.writeBlockPos(currentPos)); compound.put("CurrentPos", NBTUtil.writeBlockPos(currentPos));
}
} }
writeToClient(compound);
return super.write(compound);
}
@Override
public CompoundNBT writeToClient(CompoundNBT compound) {
// Gui information // Gui information
compound.putFloat("Progress", schematicProgress); compound.putFloat("Progress", schematicProgress);
compound.putFloat("PaperProgress", bookPrintingProgress); compound.putFloat("PaperProgress", bookPrintingProgress);
@ -264,17 +253,17 @@ public class SchematicannonTileEntity extends SmartTileEntity implements INamedC
compound.putInt("AmountPlaced", blocksPlaced); compound.putInt("AmountPlaced", blocksPlaced);
compound.putInt("AmountToPlace", blocksToPlace); compound.putInt("AmountToPlace", blocksToPlace);
compound.putInt("EntityProgress", printingEntityIndex); compound.putInt("EntityProgress", printingEntityIndex);
if (missingItem != null) if (missingItem != null)
compound.put("MissingItem", missingItem.serializeNBT()); compound.put("MissingItem", missingItem.serializeNBT());
// Settings // Settings
CompoundNBT options = new CompoundNBT(); CompoundNBT options = new CompoundNBT();
options.putInt("ReplaceMode", replaceMode); options.putInt("ReplaceMode", replaceMode);
options.putBoolean("SkipMissing", skipMissing); options.putBoolean("SkipMissing", skipMissing);
options.putBoolean("ReplaceTileEntities", replaceTileEntities); options.putBoolean("ReplaceTileEntities", replaceTileEntities);
compound.put("Options", options); compound.put("Options", options);
// Printer & Flying Blocks // Printer & Flying Blocks
if (target != null) if (target != null)
compound.put("Target", NBTUtil.writeBlockPos(target)); compound.put("Target", NBTUtil.writeBlockPos(target));
@ -283,7 +272,7 @@ public class SchematicannonTileEntity extends SmartTileEntity implements INamedC
tagBlocks.add(b.serializeNBT()); tagBlocks.add(b.serializeNBT());
compound.put("FlyingBlocks", tagBlocks); compound.put("FlyingBlocks", tagBlocks);
return compound; super.write(compound, clientPacket);
} }
@Override @Override

View file

@ -4,6 +4,7 @@ public class CFluids extends ConfigBase {
public ConfigInt fluidTankCapacity = i(8, 1, "fluidTankCapacity", Comments.buckets, Comments.fluidTankCapacity); public ConfigInt fluidTankCapacity = i(8, 1, "fluidTankCapacity", Comments.buckets, Comments.fluidTankCapacity);
public ConfigInt fluidTankMaxHeight = i(32, 1, "fluidTankMaxHeight", Comments.blocks, Comments.fluidTankMaxHeight); public ConfigInt fluidTankMaxHeight = i(32, 1, "fluidTankMaxHeight", Comments.blocks, Comments.fluidTankMaxHeight);
public ConfigInt mechanicalPumpRange = i(16, 1, "mechanicalPumpRange", Comments.blocks, Comments.mechanicalPumpRange);
@Override @Override
public String getName() { public String getName() {
@ -15,6 +16,7 @@ public class CFluids extends ConfigBase {
static String buckets = "[in Buckets]"; static String buckets = "[in Buckets]";
static String fluidTankCapacity = "The amount of liquid a tank can hold per block."; static String fluidTankCapacity = "The amount of liquid a tank can hold per block.";
static String fluidTankMaxHeight = "The maximum height a fluid tank can reach."; static String fluidTankMaxHeight = "The maximum height a fluid tank can reach.";
static String mechanicalPumpRange = "The maximum distance a mechanical pump can push or pull liquids on either side.";
} }
} }

View file

@ -2,7 +2,10 @@ package com.simibubi.create.foundation.fluid;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.ForgeFlowingFluid;
import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.IFluidHandlerItem; import net.minecraftforge.fluids.capability.IFluidHandlerItem;
@ -13,6 +16,34 @@ public class FluidHelper {
ITEM_TO_TANK, TANK_TO_ITEM; ITEM_TO_TANK, TANK_TO_ITEM;
} }
public static boolean isWater(Fluid fluid) {
return convertToStill(fluid) == Fluids.WATER;
}
public static boolean isLava(Fluid fluid) {
return convertToStill(fluid) == Fluids.LAVA;
}
public static Fluid convertToFlowing(Fluid fluid) {
if (fluid == Fluids.WATER)
return Fluids.FLOWING_WATER;
if (fluid == Fluids.LAVA)
return Fluids.FLOWING_LAVA;
if (fluid instanceof ForgeFlowingFluid)
return ((ForgeFlowingFluid) fluid).getFlowingFluid();
return fluid;
}
public static Fluid convertToStill(Fluid fluid) {
if (fluid == Fluids.FLOWING_WATER)
return Fluids.WATER;
if (fluid == Fluids.FLOWING_LAVA)
return Fluids.LAVA;
if (fluid instanceof ForgeFlowingFluid)
return ((ForgeFlowingFluid) fluid).getStillFluid();
return fluid;
}
@Nullable @Nullable
public static FluidExchange exchange(IFluidHandler fluidTank, IFluidHandlerItem fluidItem, FluidExchange preferred, public static FluidExchange exchange(IFluidHandler fluidTank, IFluidHandlerItem fluidItem, FluidExchange preferred,
int maxAmount) { int maxAmount) {

View file

@ -3,6 +3,7 @@ package com.simibubi.create.foundation.fluid;
import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.matrix.MatrixStack.Entry; import com.mojang.blaze3d.matrix.MatrixStack.Entry;
import com.mojang.blaze3d.vertex.IVertexBuilder; import com.mojang.blaze3d.vertex.IVertexBuilder;
import com.simibubi.create.foundation.utility.ColorHelper;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.MatrixStacker; import com.simibubi.create.foundation.utility.MatrixStacker;
@ -62,8 +63,9 @@ public class FluidRenderer {
.translateBack(center); .translateBack(center);
boolean X = side.getAxis() == Axis.X; boolean X = side.getAxis() == Axis.X;
int darkColor = ColorHelper.mixColors(color, 0xff000011, 1/4f);
renderTiledHorizontalFace(X ? xMax : zMax, side, X ? zMin : xMin, yMin, X ? zMax : xMax, yMax, builder, renderTiledHorizontalFace(X ? xMax : zMax, side, X ? zMin : xMin, yMin, X ? zMax : xMax, yMax, builder,
ms, light, color, fluidTexture); ms, light, darkColor, fluidTexture);
ms.pop(); ms.pop();
continue; continue;

View file

@ -1,59 +0,0 @@
package com.simibubi.create.foundation.tileEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.math.BlockPos;
public abstract class PosBoundSmartTileEntity extends SmartTileEntity {
private boolean newPositionVisited;
public PosBoundSmartTileEntity(TileEntityType<?> tileEntityTypeIn) {
super(tileEntityTypeIn);
newPositionVisited = true;
}
@Override
public void initialize() {
if (!world.isRemote && newPositionVisited) {
newPositionVisited = false;
initInNewPosition();
}
super.initialize();
}
@Override
public void read(CompoundNBT compound) {
long positionInTag = new BlockPos(compound.getInt("x"), compound.getInt("y"), compound.getInt("z")).toLong();
long positionKey = compound.getLong("BoundPosition");
newPositionVisited = false;
if (!hasWorld() || !world.isRemote) {
if (positionInTag != positionKey) {
removePositionDependentData(compound);
newPositionVisited = true;
}
}
super.read(compound);
}
/**
* Server-only. When this TE realizes, that it's reading its tag in a different
* position, it should remove all position dependent information here.
*
* @param nbt
*/
protected void removePositionDependentData(CompoundNBT nbt) {
}
/**
* Server-only. When a TE has been created or moved, it will call this before the
* regular init.
*/
protected void initInNewPosition() {
}
}

View file

@ -38,8 +38,7 @@ public abstract class SmartTileEntity extends SyncedTileEntity implements ITicka
* Gets called just before reading tile data for behaviours. Register anything * Gets called just before reading tile data for behaviours. Register anything
* here that depends on your custom te data. * here that depends on your custom te data.
*/ */
public void addBehavioursDeferred(List<TileEntityBehaviour> behaviours) { public void addBehavioursDeferred(List<TileEntityBehaviour> behaviours) {}
}
@Override @Override
public void tick() { public void tick() {
@ -53,44 +52,61 @@ public abstract class SmartTileEntity extends SyncedTileEntity implements ITicka
lazyTick(); lazyTick();
} }
behaviours.values().forEach(TileEntityBehaviour::tick); behaviours.values()
.forEach(TileEntityBehaviour::tick);
} }
public void initialize() { public void initialize() {
behaviours.values().forEach(TileEntityBehaviour::initialize); behaviours.values()
.forEach(TileEntityBehaviour::initialize);
lazyTick(); lazyTick();
} }
public void updateClient(CompoundNBT compound) { @Override
behaviours.values().forEach(tb -> tb.updateClient(compound)); public final CompoundNBT write(CompoundNBT compound) {
write(compound, false);
return compound;
} }
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public final CompoundNBT writeToClient(CompoundNBT compound) {
behaviours.values().forEach(tb -> tb.writeNBT(compound)); write(compound, true);
return super.write(compound); return compound;
}
@Override
public final void readClientUpdate(CompoundNBT tag) {
read(tag, true);
} }
@Override @Override
public CompoundNBT writeToClient(CompoundNBT compound) { public final void read(CompoundNBT tag) {
behaviours.values().forEach(tb -> tb.writeToClient(compound)); read(tag, false);
return super.writeToClient(compound);
} }
@Override /**
public void read(CompoundNBT compound) { * Hook only these in future subclasses of STE
*/
protected void read(CompoundNBT compound, boolean clientPacket) {
if (firstNbtRead) { if (firstNbtRead) {
firstNbtRead = false; firstNbtRead = false;
ArrayList<TileEntityBehaviour> list = new ArrayList<>(); ArrayList<TileEntityBehaviour> list = new ArrayList<>();
addBehavioursDeferred(list); addBehavioursDeferred(list);
list.forEach(b -> behaviours.put(b.getType(), b)); list.forEach(b -> behaviours.put(b.getType(), b));
} }
super.read(compound); super.read(compound);
forEachBehaviour(tb -> tb.readNBT(compound)); behaviours.values()
.forEach(tb -> tb.read(compound, clientPacket));
if (world != null && world.isRemote) }
updateClient(compound);
/**
* Hook only these in future subclasses of STE
*/
protected void write(CompoundNBT compound, boolean clientPacket) {
super.write(compound);
behaviours.values()
.forEach(tb -> tb.write(compound, clientPacket));
} }
@Override @Override
@ -107,12 +123,13 @@ public abstract class SmartTileEntity extends SyncedTileEntity implements ITicka
public void lazyTick() { public void lazyTick() {
} }
protected void forEachBehaviour(Consumer<TileEntityBehaviour> action) { protected void forEachBehaviour(Consumer<TileEntityBehaviour> action) {
behaviours.values().forEach(tb -> { behaviours.values()
if (!tb.isPaused()) .forEach(tb -> {
action.accept(tb); if (!tb.isPaused())
}); action.accept(tb);
});
} }
protected void putBehaviour(TileEntityBehaviour behaviour) { protected void putBehaviour(TileEntityBehaviour behaviour) {

View file

@ -37,15 +37,11 @@ public abstract class TileEntityBehaviour {
} }
public void readNBT(CompoundNBT nbt) { public void read(CompoundNBT nbt, boolean clientPacket) {
} }
public void updateClient(CompoundNBT nbt) { public void write(CompoundNBT nbt, boolean clientPacket) {
}
public void writeNBT(CompoundNBT nbt) {
} }
@ -111,8 +107,4 @@ public abstract class TileEntityBehaviour {
return ste.getBehaviour(type); return ste.getBehaviour(type);
} }
public CompoundNBT writeToClient(CompoundNBT compound) {
return compound;
}
} }

View file

@ -52,30 +52,26 @@ public class FilteringBehaviour extends TileEntityBehaviour {
} }
@Override @Override
public void writeNBT(CompoundNBT nbt) { public void write(CompoundNBT nbt, boolean clientPacket) {
nbt.put("Filter", getFilter().serializeNBT()); nbt.put("Filter", getFilter().serializeNBT());
nbt.putInt("FilterAmount", count); nbt.putInt("FilterAmount", count);
super.writeNBT(nbt);
if (clientPacket && forceClientState) {
nbt.putBoolean("ForceScrollable", true);
forceClientState = false;
}
super.write(nbt, clientPacket);
} }
@Override @Override
public void readNBT(CompoundNBT nbt) { public void read(CompoundNBT nbt, boolean clientPacket) {
filter = ItemStack.read(nbt.getCompound("Filter")); filter = ItemStack.read(nbt.getCompound("Filter"));
count = nbt.getInt("FilterAmount"); count = nbt.getInt("FilterAmount");
if (nbt.contains("ForceScrollable")) { if (nbt.contains("ForceScrollable")) {
scrollableValue = count; scrollableValue = count;
ticksUntilScrollPacket = -1; ticksUntilScrollPacket = -1;
} }
super.readNBT(nbt); super.read(nbt, clientPacket);
}
@Override
public CompoundNBT writeToClient(CompoundNBT compound) {
if (forceClientState) {
compound.putBoolean("ForceScrollable", true);
forceClientState = false;
}
return super.writeToClient(compound);
} }
@Override @Override

View file

@ -35,12 +35,12 @@ public class SidedFilteringBehaviour extends FilteringBehaviour {
sidedFilters = new IdentityHashMap<>(); sidedFilters = new IdentityHashMap<>();
updateFilterPresence(); updateFilterPresence();
} }
@Override @Override
public void initialize() { public void initialize() {
super.initialize(); super.initialize();
} }
public FilteringBehaviour get(Direction side) { public FilteringBehaviour get(Direction side) {
return sidedFilters.get(side); return sidedFilters.get(side);
} }
@ -59,40 +59,27 @@ public class SidedFilteringBehaviour extends FilteringBehaviour {
} }
@Override @Override
public void writeNBT(CompoundNBT nbt) { public void write(CompoundNBT nbt, boolean clientPacket) {
nbt.put("Filters", NBTHelper.writeCompoundList(sidedFilters.entrySet(), entry -> { nbt.put("Filters", NBTHelper.writeCompoundList(sidedFilters.entrySet(), entry -> {
CompoundNBT compound = new CompoundNBT(); CompoundNBT compound = new CompoundNBT();
compound.putInt("Side", entry.getKey() compound.putInt("Side", entry.getKey()
.getIndex()); .getIndex());
entry.getValue() entry.getValue()
.writeNBT(compound); .write(compound, clientPacket);
return compound; return compound;
})); }));
super.writeNBT(nbt); super.write(nbt, clientPacket);
} }
@Override @Override
public void readNBT(CompoundNBT nbt) { public void read(CompoundNBT nbt, boolean clientPacket) {
NBTHelper.iterateCompoundList(nbt.getList("Filters", NBT.TAG_COMPOUND), compound -> { NBTHelper.iterateCompoundList(nbt.getList("Filters", NBT.TAG_COMPOUND), compound -> {
Direction face = Direction.byIndex(compound.getInt("Side")); Direction face = Direction.byIndex(compound.getInt("Side"));
if (sidedFilters.containsKey(face)) if (sidedFilters.containsKey(face))
sidedFilters.get(face) sidedFilters.get(face)
.readNBT(compound); .read(compound, clientPacket);
}); });
super.readNBT(nbt); super.read(nbt, clientPacket);
}
@Override
public CompoundNBT writeToClient(CompoundNBT nbt) {
nbt.put("Filters", NBTHelper.writeCompoundList(sidedFilters.entrySet(), entry -> {
CompoundNBT compound = new CompoundNBT();
compound.putInt("Side", entry.getKey()
.getIndex());
entry.getValue()
.writeToClient(compound);
return compound;
}));
return super.writeToClient(nbt);
} }
@Override @Override

View file

@ -33,15 +33,15 @@ public class SingleTargetAutoExtractingBehaviour extends AutoExtractingBehaviour
} }
@Override @Override
public void writeNBT(CompoundNBT nbt) { public void write(CompoundNBT nbt, boolean clientPacket) {
nbt.putBoolean("Advantage", advantageOnNextSync); nbt.putBoolean("Advantage", advantageOnNextSync);
super.writeNBT(nbt); super.write(nbt, clientPacket);
} }
@Override @Override
public void readNBT(CompoundNBT nbt) { public void read(CompoundNBT nbt, boolean clientPacket) {
advantageOnNextSync = nbt.getBoolean("Advantage"); advantageOnNextSync = nbt.getBoolean("Advantage");
super.readNBT(nbt); super.read(nbt, clientPacket);
} }
@Override @Override

View file

@ -116,26 +116,26 @@ public class LinkBehaviour extends TileEntityBehaviour {
} }
@Override @Override
public void writeNBT(CompoundNBT compound) { public void write(CompoundNBT nbt, boolean clientPacket) {
super.writeNBT(compound); super.write(nbt, clientPacket);
compound.put("FrequencyFirst", frequencyFirst.getStack() nbt.put("FrequencyFirst", frequencyFirst.getStack()
.write(new CompoundNBT())); .write(new CompoundNBT()));
compound.put("FrequencyLast", frequencyLast.getStack() nbt.put("FrequencyLast", frequencyLast.getStack()
.write(new CompoundNBT())); .write(new CompoundNBT()));
compound.putLong("LastKnownPosition", tileEntity.getPos() nbt.putLong("LastKnownPosition", tileEntity.getPos()
.toLong()); .toLong());
} }
@Override @Override
public void readNBT(CompoundNBT compound) { public void read(CompoundNBT nbt, boolean clientPacket) {
long positionInTag = tileEntity.getPos() long positionInTag = tileEntity.getPos()
.toLong(); .toLong();
long positionKey = compound.getLong("LastKnownPosition"); long positionKey = nbt.getLong("LastKnownPosition");
newPosition = positionInTag != positionKey; newPosition = positionInTag != positionKey;
super.readNBT(compound); super.read(nbt, clientPacket);
frequencyFirst = new Frequency(ItemStack.read(compound.getCompound("FrequencyFirst"))); frequencyFirst = new Frequency(ItemStack.read(nbt.getCompound("FrequencyFirst")));
frequencyLast = new Frequency(ItemStack.read(compound.getCompound("FrequencyLast"))); frequencyLast = new Frequency(ItemStack.read(nbt.getCompound("FrequencyLast")));
} }
public void setFrequency(boolean first, ItemStack stack) { public void setFrequency(boolean first, ItemStack stack) {

View file

@ -51,28 +51,23 @@ public class ScrollValueBehaviour extends TileEntityBehaviour {
} }
@Override @Override
public void writeNBT(CompoundNBT nbt) { public void write(CompoundNBT nbt, boolean clientPacket) {
nbt.putInt("ScrollValue", value); nbt.putInt("ScrollValue", value);
super.writeNBT(nbt); if (clientPacket && forceClientState) {
nbt.putBoolean("ForceScrollable", true);
forceClientState = false;
}
super.write(nbt, clientPacket);
} }
@Override @Override
public void readNBT(CompoundNBT nbt) { public void read(CompoundNBT nbt, boolean clientPacket) {
value = nbt.getInt("ScrollValue"); value = nbt.getInt("ScrollValue");
if (nbt.contains("ForceScrollable")) { if (nbt.contains("ForceScrollable")) {
ticksUntilScrollPacket = -1; ticksUntilScrollPacket = -1;
scrollableValue = value; scrollableValue = value;
} }
super.readNBT(nbt); super.read(nbt, clientPacket);
}
@Override
public CompoundNBT writeToClient(CompoundNBT compound) {
if (forceClientState) {
compound.putBoolean("ForceScrollable", true);
forceClientState = false;
}
return super.writeToClient(compound);
} }
@Override @Override

View file

@ -21,15 +21,15 @@ public class DeferralBehaviour extends TileEntityBehaviour {
} }
@Override @Override
public void writeNBT(CompoundNBT nbt) { public void write(CompoundNBT nbt, boolean clientPacket) {
nbt.putBoolean("NeedsUpdate", needsUpdate); nbt.putBoolean("NeedsUpdate", needsUpdate);
super.writeNBT(nbt); super.write(nbt, clientPacket);
} }
@Override @Override
public void readNBT(CompoundNBT nbt) { public void read(CompoundNBT nbt, boolean clientPacket) {
needsUpdate = nbt.getBoolean("NeedsUpdate"); needsUpdate = nbt.getBoolean("NeedsUpdate");
super.readNBT(nbt); super.read(nbt, clientPacket);
} }
@Override @Override

View file

@ -1,20 +1,9 @@
package com.simibubi.create.foundation.utility; package com.simibubi.create.foundation.utility;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.renderer.Vector3f;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis; import net.minecraft.util.Direction.Axis;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
public class AngleHelper { public class AngleHelper {
@OnlyIn(Dist.CLIENT)
public static void applyRotation(Direction direction, MatrixStack ms) {
ms.multiply(Vector3f.POSITIVE_Y.getDegreesQuaternion(AngleHelper.horizontalAngle(direction)));
ms.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(AngleHelper.verticalAngle(direction)));
}
public static float horizontalAngle(Direction facing) { public static float horizontalAngle(Direction facing) {
float angle = facing.getHorizontalAngle(); float angle = facing.getHorizontalAngle();
@ -39,8 +28,8 @@ public class AngleHelper {
return (float) (angle * 180 / Math.PI); return (float) (angle * 180 / Math.PI);
} }
public static float angleLerp(float pct, float current, float target) { public static float angleLerp(double pct, double current, double target) {
return current + getShortestAngleDiff(current, target) * pct; return (float) (current + getShortestAngleDiff(current, target) * pct);
} }
public static float getShortestAngleDiff(double current, double target) { public static float getShortestAngleDiff(double current, double target) {

View file

@ -0,0 +1,52 @@
package com.simibubi.create.foundation.utility;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
public class BlockFace extends Pair<BlockPos, Direction> {
public BlockFace(BlockPos first, Direction second) {
super(first, second);
}
public boolean isEquivalent(BlockFace other) {
if (equals(other))
return true;
return getConnectedPos().equals(other.getPos()) && getPos().equals(other.getConnectedPos());
}
public BlockPos getPos() {
return getFirst();
}
public Direction getFace() {
return getSecond();
}
public Direction getOppositeFace() {
return getSecond().getOpposite();
}
public BlockFace getOpposite() {
return new BlockFace(getConnectedPos(), getOppositeFace());
}
public BlockPos getConnectedPos() {
return getPos().offset(getFace());
}
public CompoundNBT serializeNBT() {
CompoundNBT compoundNBT = new CompoundNBT();
compoundNBT.put("Pos", NBTUtil.writeBlockPos(getPos()));
NBTHelper.writeEnum(compoundNBT, "Face", getFace());
return compoundNBT;
}
public static BlockFace fromNBT(CompoundNBT compound) {
return new BlockFace(NBTUtil.readBlockPos(compound.getCompound("Pos")),
NBTHelper.readEnum(compound, "Face", Direction.class));
}
}

View file

@ -1,38 +1,49 @@
package com.simibubi.create.foundation.utility; package com.simibubi.create.foundation.utility;
import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
public class Couple<T> extends Pair<T, T> { public class Couple<T> extends Pair<T, T> {
private static Couple<Boolean> TRUE_AND_FALSE = Couple.create(true, false); private static Couple<Boolean> TRUE_AND_FALSE = Couple.create(true, false);
protected Couple(T first, T second) { protected Couple(T first, T second) {
super(first, second); super(first, second);
} }
public static <T> Couple<T> create(T first, T second) { public static <T> Couple<T> create(T first, T second) {
return new Couple<>(first, second); return new Couple<>(first, second);
} }
public static <T> Couple<T> create(Supplier<T> factory) {
return new Couple<>(factory.get(), factory.get());
}
public T get(boolean first) { public T get(boolean first) {
return first ? getFirst() : getSecond(); return first ? getFirst() : getSecond();
} }
public void set(boolean first, T value) { public void set(boolean first, T value) {
if (first) if (first)
setFirst(value); setFirst(value);
else else
setSecond(value); setSecond(value);
} }
@Override @Override
public Couple<T> copy() { public Couple<T> copy() {
return create(first, second); return create(first, second);
} }
public <S> Couple<S> map(Function<T, S> function) { public <S> Couple<S> map(Function<T, S> function) {
return Couple.create(function.apply(first), function.apply(second)); return Couple.create(function.apply(first), function.apply(second));
} }
@ -41,11 +52,11 @@ public class Couple<T> extends Pair<T, T> {
setFirst(function.apply(getFirst())); setFirst(function.apply(getFirst()));
setSecond(function.apply(getSecond())); setSecond(function.apply(getSecond()));
} }
public void replaceWithContext(BiFunction<T, Boolean, T> function) { public void replaceWithContext(BiFunction<T, Boolean, T> function) {
replaceWithParams(function, TRUE_AND_FALSE); replaceWithParams(function, TRUE_AND_FALSE);
} }
public <S> void replaceWithParams(BiFunction<T, S, T> function, Couple<S> values) { public <S> void replaceWithParams(BiFunction<T, S, T> function, Couple<S> values) {
setFirst(function.apply(getFirst(), values.getFirst())); setFirst(function.apply(getFirst(), values.getFirst()));
setSecond(function.apply(getSecond(), values.getSecond())); setSecond(function.apply(getSecond(), values.getSecond()));
@ -55,18 +66,27 @@ public class Couple<T> extends Pair<T, T> {
consumer.accept(getFirst()); consumer.accept(getFirst());
consumer.accept(getSecond()); consumer.accept(getSecond());
} }
public void forEachWithContext(BiConsumer<T, Boolean> consumer) { public void forEachWithContext(BiConsumer<T, Boolean> consumer) {
forEachWithParams(consumer, TRUE_AND_FALSE); forEachWithParams(consumer, TRUE_AND_FALSE);
} }
public <S> void forEachWithParams(BiConsumer<T, S> function, Couple<S> values) { public <S> void forEachWithParams(BiConsumer<T, S> function, Couple<S> values) {
function.accept(getFirst(), values.getFirst()); function.accept(getFirst(), values.getFirst());
function.accept(getSecond(), values.getSecond()); function.accept(getSecond(), values.getSecond());
} }
public Couple<T> swap() { public Couple<T> swap() {
return Couple.create(second, first); return Couple.create(second, first);
} }
public ListNBT serializeEach(Function<T, CompoundNBT> serializer) {
return NBTHelper.writeCompoundList(ImmutableList.of(first, second), serializer);
}
public static <S> Couple<S> deserializeEach(ListNBT list, Function<CompoundNBT, S> deserializer) {
List<S> readCompoundList = NBTHelper.readCompoundList(list, deserializer);
return new Couple<>(readCompoundList.get(0), readCompoundList.get(1));
}
} }

View file

@ -0,0 +1,128 @@
package com.simibubi.create.foundation.utility;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.math.MathHelper;
// Can replace all Interpolated value classes
// InterpolatedChasingValue, InterpolatedValue, InterpolatedChasingAngle, InterpolatedAngle
public class LerpedFloat {
Interpolater interpolater;
float previousValue;
float value;
Chaser chaseFunction;
float chaseTarget;
float chaseSpeed;
boolean forcedSync;
public LerpedFloat(Interpolater interpolater) {
this.interpolater = interpolater;
startWithValue(0);
forcedSync = true;
}
public static LerpedFloat linear() {
return new LerpedFloat((p, c, t) -> (float) MathHelper.lerp(p, c, t));
}
public static LerpedFloat angular() {
return new LerpedFloat(AngleHelper::angleLerp);
}
public LerpedFloat startWithValue(double value) {
float f = (float) value;
this.previousValue = f;
this.chaseTarget = f;
this.value = f;
return this;
}
public LerpedFloat chase(double value, double speed, Chaser chaseFunction) {
this.chaseTarget = (float) value;
this.chaseSpeed = (float) speed;
this.chaseFunction = chaseFunction;
return this;
}
public boolean updateChaseSpeed(double speed) {
float prevSpeed = this.chaseSpeed;
this.chaseSpeed = (float) speed;
return !MathHelper.epsilonEquals(prevSpeed, speed);
}
public void tickChaser() {
previousValue = value;
if (chaseFunction == null)
return;
if (MathHelper.epsilonEquals((double) value, chaseTarget)) {
value = chaseTarget;
return;
}
value = chaseFunction.chase(value, chaseSpeed, chaseTarget);
}
public void setValue(double value) {
this.previousValue = this.value;
this.value = (float) value;
}
public float getValue() {
return getValue(1);
}
public float getValue(float partialTicks) {
return MathHelper.lerp(partialTicks, previousValue, value);
}
public float getChaseTarget() {
return chaseTarget;
}
public void forceNextSync() {
forcedSync = true;
}
public CompoundNBT writeNBT() {
CompoundNBT compoundNBT = new CompoundNBT();
compoundNBT.putFloat("Speed", chaseSpeed);
compoundNBT.putFloat("Target", chaseTarget);
compoundNBT.putFloat("Value", value);
if (forcedSync)
compoundNBT.putBoolean("Force", true);
forcedSync = false;
return compoundNBT;
}
public void readNBT(CompoundNBT compoundNBT, boolean clientPacket) {
if (!clientPacket || compoundNBT.contains("Force"))
startWithValue(compoundNBT.getFloat("Value"));
readChaser(compoundNBT);
}
private void readChaser(CompoundNBT compoundNBT) {
chaseSpeed = compoundNBT.getFloat("Speed");
chaseTarget = compoundNBT.getFloat("Target");
}
@FunctionalInterface
public interface Interpolater {
float interpolate(double progress, double current, double target);
}
@FunctionalInterface
public interface Chaser {
public static final Chaser IDLE = (c, s, t) -> (float) c;
public static final Chaser EXP = exp(Double.MAX_VALUE);
public static final Chaser LINEAR = (c, s, t) -> (float) (c + MathHelper.clamp(t - c, -s, s));
public static Chaser exp(double maxEffectiveSpeed) {
return (c, s, t) -> (float) (c + MathHelper.clamp((t - c) * s, -maxEffectiveSpeed, maxEffectiveSpeed));
}
float chase(double current, double speed, double target);
}
}

View file

@ -68,10 +68,14 @@ public class VecHelper {
vec.z + (r.nextFloat() - .5f) * 2 * radius); vec.z + (r.nextFloat() - .5f) * 2 * radius);
} }
public static Vec3d planeByNormal(Vec3d vec) { public static Vec3d axisAlingedPlaneOf(Vec3d vec) {
vec = vec.normalize(); vec = vec.normalize();
return new Vec3d(1, 1, 1).subtract(Math.abs(vec.x), Math.abs(vec.y), Math.abs(vec.z)); return new Vec3d(1, 1, 1).subtract(Math.abs(vec.x), Math.abs(vec.y), Math.abs(vec.z));
} }
public static Vec3d axisAlingedPlaneOf(Direction face) {
return axisAlingedPlaneOf(new Vec3d(face.getDirectionVec()));
}
public static ListNBT writeNBT(Vec3d vec) { public static ListNBT writeNBT(Vec3d vec) {
ListNBT listnbt = new ListNBT(); ListNBT listnbt = new ListNBT();
@ -114,12 +118,17 @@ public class VecHelper {
.scale(maxLength) : vec; .scale(maxLength) : vec;
} }
public static Vec3d clampComponentWise(Vec3d vec, float maxLength) {
return new Vec3d(MathHelper.clamp(vec.x, -maxLength, maxLength), MathHelper.clamp(vec.y, -maxLength, maxLength),
MathHelper.clamp(vec.z, -maxLength, maxLength));
}
public static Vec3d project(Vec3d vec, Vec3d ontoVec) { public static Vec3d project(Vec3d vec, Vec3d ontoVec) {
if (ontoVec.equals(Vec3d.ZERO)) if (ontoVec.equals(Vec3d.ZERO))
return Vec3d.ZERO; return Vec3d.ZERO;
return ontoVec.scale(vec.dotProduct(ontoVec) / ontoVec.lengthSquared()); return ontoVec.scale(vec.dotProduct(ontoVec) / ontoVec.lengthSquared());
} }
@Nullable @Nullable
public static Vec3d intersectSphere(Vec3d origin, Vec3d lineDirection, Vec3d sphereCenter, double radius) { public static Vec3d intersectSphere(Vec3d origin, Vec3d lineDirection, Vec3d sphereCenter, double radius) {
if (lineDirection.equals(Vec3d.ZERO)) if (lineDirection.equals(Vec3d.ZERO))

View file

@ -58,7 +58,7 @@ public class BlockClusterOutline extends Outline {
Vec3d center = VecHelper.getCenterOf(pos); Vec3d center = VecHelper.getCenterOf(pos);
Vec3d offset = new Vec3d(face.getDirectionVec()); Vec3d offset = new Vec3d(face.getDirectionVec());
Vec3d plane = VecHelper.planeByNormal(offset); Vec3d plane = VecHelper.axisAlingedPlaneOf(offset);
Axis axis = face.getAxis(); Axis axis = face.getAxis();
offset = offset.scale(1 / 2f + 1 / 64d); offset = offset.scale(1 / 2f + 1 / 64d);

View file

@ -61,7 +61,7 @@ public abstract class Outline {
float lineWidth = params.getLineWidth(); float lineWidth = params.getLineWidth();
Vec3d extension = diff.normalize() Vec3d extension = diff.normalize()
.scale(lineWidth / 2); .scale(lineWidth / 2);
Vec3d plane = VecHelper.planeByNormal(diff); Vec3d plane = VecHelper.axisAlingedPlaneOf(diff);
Direction face = Direction.getFacingFromVector(diff.x, diff.y, diff.z); Direction face = Direction.getFacingFromVector(diff.x, diff.y, diff.z);
Axis axis = face.getAxis(); Axis axis = face.getAxis();

View file

@ -1,5 +1,6 @@
package com.simibubi.create.foundation.utility.outliner; package com.simibubi.create.foundation.utility.outliner;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -110,7 +111,7 @@ public class Outliner {
// Maintenance // Maintenance
public Outliner() { public Outliner() {
outlines = new HashMap<>(); outlines = Collections.synchronizedMap(new HashMap<>());
} }
public void tickOutlines() { public void tickOutlines() {

View file

@ -39,7 +39,7 @@
"advancement.create.electron_tube": "Beep boop", "advancement.create.electron_tube": "Beep boop",
"advancement.create.electron_tube.desc": "Make some Electron Tubes, useful in crafting less primitive machinery.", "advancement.create.electron_tube.desc": "Make some Electron Tubes, useful in crafting less primitive machinery.",
"advancement.create.mechanical_saw": "Stationary Chopping", "advancement.create.mechanical_saw": "Stationary Chopping",
"advancement.create.mechanical_saw.desc": "Place and power a Mechanical mechanical_saw", "advancement.create.mechanical_saw.desc": "Place and power a Mechanical Saw",
"advancement.create.basin": "Basin Operation", "advancement.create.basin": "Basin Operation",
"advancement.create.basin.desc": "Place a basin and try throwing items into it.", "advancement.create.basin.desc": "Place a basin and try throwing items into it.",
"advancement.create.mixer": "Mixin' it Up", "advancement.create.mixer": "Mixin' it Up",

View file

@ -7,7 +7,7 @@
"death.attack.create.fan_fire": "%1$s was burned to death by hot air", "death.attack.create.fan_fire": "%1$s was burned to death by hot air",
"death.attack.create.fan_lava": "%1$s was burned to death by lava fan", "death.attack.create.fan_lava": "%1$s was burned to death by lava fan",
"death.attack.create.mechanical_drill": "%1$s was impaled by Mechanical mechanical_drill", "death.attack.create.mechanical_drill": "%1$s was impaled by Mechanical mechanical_drill",
"death.attack.create.mechanical_saw": "%1$s got cut in half by Mechanical mechanical_saw", "death.attack.create.mechanical_saw": "%1$s got cut in half by Mechanical Saw",
"death.attack.create.cuckoo_clock_explosion": "%1$s was blown up by tampered cuckoo clock", "death.attack.create.cuckoo_clock_explosion": "%1$s was blown up by tampered cuckoo clock",
"create.block.deployer.damage_source_name": "a rogue Deployer", "create.block.deployer.damage_source_name": "a rogue Deployer",
"create.block.cart_assembler.invalid": "Place your Cart Assembler on a rail block", "create.block.cart_assembler.invalid": "Place your Cart Assembler on a rail block",
@ -23,7 +23,7 @@
"create.recipe.pressing": "Pressing", "create.recipe.pressing": "Pressing",
"create.recipe.mixing": "Mixing", "create.recipe.mixing": "Mixing",
"create.recipe.packing": "Compacting", "create.recipe.packing": "Compacting",
"create.recipe.mechanical_sawing": "mechanical_sawing", "create.recipe.mechanical_sawing": "Sawing",
"create.recipe.mechanical_crafting": "Mechanical Crafting", "create.recipe.mechanical_crafting": "Mechanical Crafting",
"create.recipe.block_cutting": "Block Cutting", "create.recipe.block_cutting": "Block Cutting",
"create.recipe.blockzapper_upgrade": "Handheld Blockzapper", "create.recipe.blockzapper_upgrade": "Handheld Blockzapper",

View file

@ -13,10 +13,10 @@
"to": [12, 12, 12], "to": [12, 12, 12],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 7, 8]}, "rotation": {"angle": 0, "axis": "y", "origin": [8, 7, 8]},
"faces": { "faces": {
"north": {"uv": [0, 8, 4, 12], "texture": "#3"}, "north": {"uv": [0, 6, 4, 10], "texture": "#3"},
"east": {"uv": [0, 8, 4, 12], "texture": "#3"}, "east": {"uv": [0, 6, 4, 10], "texture": "#3"},
"south": {"uv": [0, 8, 4, 12], "texture": "#3"}, "south": {"uv": [0, 6, 4, 10], "texture": "#3"},
"west": {"uv": [0, 8, 4, 12], "texture": "#3"} "west": {"uv": [0, 6, 4, 10], "texture": "#3"}
} }
}, },
{ {

View file

@ -76,20 +76,6 @@
"up": {"uv": [0, 6, 6, 8.5], "rotation": 180, "texture": "#5"}, "up": {"uv": [0, 6, 6, 8.5], "rotation": 180, "texture": "#5"},
"down": {"uv": [0, 6, 6, 8.5], "texture": "#5"} "down": {"uv": [0, 6, 6, 8.5], "texture": "#5"}
} }
},
{
"name": "GearCaseOuter",
"from": [4, 4, 4.5],
"to": [12, 12, 11.5],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 8, 6.5]},
"faces": {
"north": {"uv": [1, 1, 5, 5], "rotation": 180, "texture": "#5"},
"east": {"uv": [6, 4.5, 10, 8], "rotation": 270, "texture": "#5"},
"south": {"uv": [1, 1, 5, 5], "texture": "#5"},
"west": {"uv": [6, 4.5, 10, 8], "rotation": 90, "texture": "#5"},
"up": {"uv": [6, 4.5, 10, 8], "rotation": 180, "texture": "#5"},
"down": {"uv": [6, 4.5, 10, 8], "texture": "#5"}
}
} }
], ],
"display": { "display": {
@ -130,7 +116,7 @@
{ {
"name": "cogwheel", "name": "cogwheel",
"origin": [8, 8, 8], "origin": [8, 8, 8],
"children": [0, 1, 2, 3, 4, 5] "children": [0, 1, 2, 3, 4]
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B