The Eternal Offset

- Fixed Drills not leaving water sources when breaking ice blocks
- Fixed Ploughs not able to break track blocks
- Fixed Harvester replanting crops in invalid locations
- Fixed Train Relocation not always triggering when targeting long bends
- Display boards no longer predict trains behind wait conditions other than "timed delay"
- Fixed scoreboard and deathcount display sources cropping entries before sorting
- /create dumpRailways -> /create trains
- Pipe flows are no longer stopped if the blocking state is considered #fan_transparent (-> fixes 1.19 mangrove root)
- Fixed contraption interaction not firing when it happens far away from the contraptions origin
- Fixed inaccurate contact point motion for collisions with oriented contraptions
This commit is contained in:
simibubi 2022-09-17 20:52:15 +02:00
parent aeee9f8793
commit bf06e59938
13 changed files with 137 additions and 73 deletions

View file

@ -3,23 +3,19 @@ package com.simibubi.create.content.contraptions.components.actors;
import java.util.concurrent.atomic.AtomicInteger;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.foundation.utility.BlockHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.block.AirBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.Vec3;
public abstract class BlockBreakingKineticTileEntity extends KineticTileEntity {
@ -114,7 +110,8 @@ public abstract class BlockBreakingKineticTileEntity extends KineticTileEntity {
float breakSpeed = getBreakSpeed();
destroyProgress += Mth.clamp((int) (breakSpeed / blockHardness), 1, 10 - destroyProgress);
level.playSound(null, worldPosition, stateToBreak.getSoundType().getHitSound(), SoundSource.NEUTRAL, .25f, 1);
level.playSound(null, worldPosition, stateToBreak.getSoundType()
.getHitSound(), SoundSource.NEUTRAL, .25f, 1);
if (destroyProgress >= 10) {
onBlockBroken(stateToBreak);
@ -133,28 +130,26 @@ public abstract class BlockBreakingKineticTileEntity extends KineticTileEntity {
}
public static boolean isBreakable(BlockState stateToBreak, float blockHardness) {
return !(stateToBreak.getMaterial().isLiquid() || stateToBreak.getBlock() instanceof AirBlock
|| blockHardness == -1);
return !(stateToBreak.getMaterial()
.isLiquid() || stateToBreak.getBlock() instanceof AirBlock || blockHardness == -1);
}
public void onBlockBroken(BlockState stateToBreak) {
FluidState FluidState = level.getFluidState(breakingPos);
level.levelEvent(2001, breakingPos, Block.getId(stateToBreak));
BlockEntity tileentity = stateToBreak.hasBlockEntity() ? level.getBlockEntity(breakingPos) : null;
Vec3 vec = VecHelper.offsetRandomly(VecHelper.getCenterOf(breakingPos), level.random, .125f);
BlockHelper.destroyBlock(level, breakingPos, 1f, (stack) -> {
if (stack.isEmpty())
return;
if (!level.getGameRules()
.getBoolean(GameRules.RULE_DOBLOCKDROPS))
return;
if (level.restoringBlockSnapshots)
return;
Block.getDrops(stateToBreak, (ServerLevel) level, breakingPos, tileentity).forEach((stack) -> {
if (!stack.isEmpty() && level.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)
&& !level.restoringBlockSnapshots) {
ItemEntity itementity = new ItemEntity(level, vec.x, vec.y, vec.z, stack);
itementity.setDefaultPickUpDelay();
itementity.setDeltaMovement(Vec3.ZERO);
level.addFreshEntity(itementity);
}
});
if (level instanceof ServerLevel)
stateToBreak.spawnAfterBreak((ServerLevel) level, breakingPos, ItemStack.EMPTY);
level.setBlock(breakingPos, FluidState.createLegacyBlock(), 3);
}
protected float getBreakSpeed() {

View file

@ -104,7 +104,8 @@ public class HarvesterMovementBehaviour implements MovementBehaviour {
dropItem(context, stack);
});
world.setBlockAndUpdate(pos, cutCrop(world, pos, stateVisited));
BlockState cutCrop = cutCrop(world, pos, stateVisited);
world.setBlockAndUpdate(pos, cutCrop.canSurvive(world, pos) ? cutCrop : Blocks.AIR.defaultBlockState());
}
public boolean isValidCrop(Level world, BlockPos pos, BlockState state) {

View file

@ -2,6 +2,7 @@ package com.simibubi.create.content.contraptions.components.actors;
import com.simibubi.create.content.contraptions.components.actors.PloughBlock.PloughFakePlayer;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import com.simibubi.create.content.logistics.trains.ITrackBlock;
import com.simibubi.create.content.logistics.trains.track.FakeTrackBlock;
import com.simibubi.create.foundation.advancement.AllAdvancements;
import com.simibubi.create.foundation.utility.VecHelper;
@ -105,6 +106,8 @@ public class PloughMovementBehaviour extends BlockBreakingMovementBehaviour {
return false;
if (state.getBlock() instanceof NetherPortalBlock)
return false;
if (state.getBlock() instanceof ITrackBlock)
return true;
if (state.getBlock() instanceof FakeTrackBlock)
return false;
return state.getCollisionShape(world, breakingPos)

View file

@ -304,13 +304,13 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
localVec = localVec.subtract(rotationOffset);
localVec = applyRotation(localVec, partialTicks);
localVec = localVec.add(rotationOffset)
.add(getAnchorVec());
.add(getPrevAnchorVec());
return localVec;
}
public Vec3 toLocalVector(Vec3 globalVec, float partialTicks) {
Vec3 rotationOffset = VecHelper.getCenterOf(BlockPos.ZERO);
globalVec = globalVec.subtract(getAnchorVec())
globalVec = globalVec.subtract(getPrevAnchorVec())
.subtract(rotationOffset);
globalVec = reverseRotation(globalVec, partialTicks);
globalVec = globalVec.add(rotationOffset);
@ -520,6 +520,10 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
return position();
}
public Vec3 getPrevAnchorVec() {
return getPrevPositionVec();
}
public float getYawOffset() {
return 0;
}
@ -792,9 +796,11 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
public Vec3 getContactPointMotion(Vec3 globalContactPoint) {
if (prevPosInvalid)
return Vec3.ZERO;
Vec3 contactPoint = toGlobalVector(toLocalVector(globalContactPoint, 0), 1);
return contactPoint.subtract(globalContactPoint)
.add(position().subtract(getPrevPositionVec()));
Vec3 contraptionLocalMovement = contactPoint.subtract(globalContactPoint);
Vec3 contraptionAnchorMovement = position().subtract(getPrevPositionVec());
return contraptionLocalMovement.add(contraptionAnchorMovement);
}
public boolean canCollideWith(Entity e) {

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.contraptions.components.structureMovement;
import java.util.List;
import java.lang.ref.WeakReference;
import java.util.Collection;
import javax.annotation.Nullable;
@ -86,10 +87,19 @@ public class ContraptionHandlerClient {
Vec3 origin = rayInputs.getFirst();
Vec3 target = rayInputs.getSecond();
AABB aabb = new AABB(origin, target).inflate(16);
List<AbstractContraptionEntity> intersectingContraptions =
mc.level.getEntitiesOfClass(AbstractContraptionEntity.class, aabb);
for (AbstractContraptionEntity contraptionEntity : intersectingContraptions) {
Collection<WeakReference<AbstractContraptionEntity>> contraptions =
ContraptionHandler.loadedContraptions.get(mc.level)
.values();
for (WeakReference<AbstractContraptionEntity> ref : contraptions) {
AbstractContraptionEntity contraptionEntity = ref.get();
if (contraptionEntity == null)
continue;
if (!contraptionEntity.getBoundingBox()
.intersects(aabb))
continue;
BlockHitResult rayTraceResult = rayTraceContraption(origin, target, contraptionEntity);
if (rayTraceResult == null)
continue;

View file

@ -493,7 +493,14 @@ public class OrientedContraptionEntity extends AbstractContraptionEntity {
@Override
public Vec3 getAnchorVec() {
return new Vec3(getX() - .5, getY(), getZ() - .5);
Vec3 anchorVec = super.getAnchorVec();
return anchorVec.subtract(.5, 0, .5);
}
@Override
public Vec3 getPrevAnchorVec() {
Vec3 prevAnchorVec = super.getPrevAnchorVec();
return prevAnchorVec.subtract(.5, 0, .5);
}
@Override

View file

@ -11,6 +11,7 @@ import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.common.ForgeMod;
import net.minecraftforge.network.NetworkEvent.Context;
@ -54,7 +55,9 @@ public class ContraptionInteractionPacket extends SimplePacketBase {
if (!(entityByID instanceof AbstractContraptionEntity))
return;
AbstractContraptionEntity contraptionEntity = (AbstractContraptionEntity) entityByID;
double d = sender.getAttribute(ForgeMod.REACH_DISTANCE.get()).getValue() + 10;
AABB bb = contraptionEntity.getBoundingBox();
double boundsExtra = Math.max(bb.getXsize(), bb.getYsize());
double d = sender.getAttribute(ForgeMod.REACH_DISTANCE.get()).getValue() + 10 + boundsExtra;
if (!sender.hasLineOfSight(entityByID))
d -= 3;
d *= d;

View file

@ -8,6 +8,7 @@ import java.util.Set;
import javax.annotation.Nullable;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllTags.AllBlockTags;
import com.simibubi.create.content.contraptions.fluids.PipeConnection.Flow;
import com.simibubi.create.content.contraptions.fluids.pipes.AxisPipeBlock;
import com.simibubi.create.content.contraptions.fluids.pipes.FluidPipeBlock;
@ -173,7 +174,8 @@ public class FluidPropagator {
return false;
if (VanillaFluidTargets.shouldPipesConnectTo(connectedState))
return true;
if (BlockHelper.hasBlockSolidSide(connectedState, reader, connectedPos, side.getOpposite()))
if (BlockHelper.hasBlockSolidSide(connectedState, reader, connectedPos, side.getOpposite())
&& !AllBlockTags.FAN_TRANSPARENT.matches(connectedState))
return false;
if (hasFluidCapability(reader, connectedPos, side.getOpposite()))
return false;

View file

@ -40,9 +40,10 @@ public class ScoreboardDisplaySource extends ValueListDisplaySource {
return sLevel.getScoreboard()
.getPlayerScores(objective)
.stream()
.limit(maxRows)
.map(score -> IntAttached.with(score.getScore(), Components.literal(score.getOwner()).copy()))
.sorted(IntAttached.comparator());
.map(score -> IntAttached.with(score.getScore(), Components.literal(score.getOwner())
.copy()))
.sorted(IntAttached.comparator())
.limit(maxRows);
}
private ImmutableList<IntAttached<MutableComponent>> notFound(String objective) {

View file

@ -84,12 +84,12 @@ public class TrainRelocationPacket extends SimplePacketBase {
return;
if (!sender.position()
.closerThan(Vec3.atCenterOf(pos), 26)) {
.closerThan(Vec3.atCenterOf(pos), 64)) {
Create.LOGGER.warn(messagePrefix + train.name.getString() + ": player too far from clicked pos");
return;
}
if (!sender.position()
.closerThan(cce.position(), 26 + cce.getBoundingBox()
.closerThan(cce.position(), 64 + cce.getBoundingBox()
.getXsize() / 2)) {
Create.LOGGER.warn(messagePrefix + train.name.getString() + ": player too far from carriage entity");
return;

View file

@ -29,6 +29,9 @@ import net.minecraft.world.level.Level;
public class ScheduleRuntime {
private static final int TBD = -1;
private static final int INVALID = -2;
public enum State {
PRE_TRANSIT, IN_TRANSIT, POST_TRANSIT
}
@ -129,7 +132,7 @@ public class ScheduleRuntime {
destinationReached();
return;
}
if (train.navigation.startNavigation(nextStation, Double.MAX_VALUE, false) != -1) {
if (train.navigation.startNavigation(nextStation, Double.MAX_VALUE, false) != TBD) {
state = State.IN_TRANSIT;
ticksInTransit = 0;
}
@ -231,7 +234,7 @@ public class ScheduleRuntime {
isAutoSchedule = auto;
train.status.newSchedule();
predictionTicks = new ArrayList<>();
schedule.entries.forEach($ -> predictionTicks.add(-1));
schedule.entries.forEach($ -> predictionTicks.add(TBD));
displayLinkUpdateRequested = true;
}
@ -269,8 +272,10 @@ public class ScheduleRuntime {
if (currentStation != null)
predictions.add(createPrediction(current, currentStation.name, currentTitle, 0));
int departureTime = estimateStayDuration(current);
if (departureTime == -1)
accumulatedTime = -1;
if (departureTime == INVALID)
accumulatedTime = INVALID;
else
accumulatedTime += departureTime;
} else {
GlobalStation destination = train.navigation.destination;
@ -292,10 +297,10 @@ public class ScheduleRuntime {
predictions.add(createPrediction(current, destination.name, currentTitle, accumulatedTime));
int departureTime = estimateStayDuration(current);
if (departureTime != -1)
if (departureTime != INVALID)
accumulatedTime += departureTime;
if (departureTime == -1)
accumulatedTime = -1;
else
accumulatedTime = INVALID;
} else
predictForEntry(current, currentTitle, accumulatedTime, predictions);
@ -327,26 +332,27 @@ public class ScheduleRuntime {
return accumulatedTime;
if (predictionTicks.size() <= currentEntry)
return accumulatedTime;
if (accumulatedTime == -1) {
int departureTime = estimateStayDuration(index);
if (accumulatedTime < 0) {
predictions.add(createPrediction(index, filter.getFilter(), currentTitle, accumulatedTime));
return -1;
return Math.min(accumulatedTime, departureTime);
}
int predictedTime = predictionTicks.get(index);
int departureTime = estimateStayDuration(index);
if (predictedTime == -1)
accumulatedTime = -1;
else {
accumulatedTime += predictedTime;
if (departureTime != -1)
accumulatedTime += departureTime;
}
if (predictedTime == TBD)
accumulatedTime = TBD;
predictions.add(createPrediction(index, filter.getFilter(), currentTitle, accumulatedTime));
if (departureTime == -1)
return -1;
if (accumulatedTime != TBD)
accumulatedTime += departureTime;
if (departureTime == INVALID)
accumulatedTime = INVALID;
return accumulatedTime;
}
@ -354,20 +360,28 @@ public class ScheduleRuntime {
private int estimateStayDuration(int index) {
if (index >= schedule.entries.size()) {
if (!schedule.cyclic)
return 100000;
return INVALID;
index = 0;
}
ScheduleEntry scheduleEntry = schedule.entries.get(index);
for (List<ScheduleWaitCondition> list : scheduleEntry.conditions)
for (ScheduleWaitCondition condition : list)
if (condition instanceof ScheduledDelay wait)
return wait.totalWaitTicks();
Columns: for (List<ScheduleWaitCondition> list : scheduleEntry.conditions) {
int total = 0;
for (ScheduleWaitCondition condition : list) {
if (!(condition instanceof ScheduledDelay wait))
continue Columns;
total += wait.totalWaitTicks();
}
return total;
}
return 5; // TODO properly ask conditions for time prediction
return INVALID;
}
private TrainDeparturePrediction createPrediction(int index, String destination, String currentTitle, int time) {
if (time == INVALID)
return null;
int size = schedule.entries.size();
if (index >= size) {
if (!schedule.cyclic)
@ -422,7 +436,7 @@ public class ScheduleRuntime {
int[] readTransits = tag.getIntArray("TransitTimes");
if (schedule != null) {
schedule.entries.forEach($ -> predictionTicks.add(-1));
schedule.entries.forEach($ -> predictionTicks.add(TBD));
if (readTransits.length == schedule.entries.size())
for (int i = 0; i < readTransits.length; i++)
predictionTicks.set(i, readTransits[i]);

View file

@ -31,7 +31,7 @@ import net.minecraft.world.phys.Vec3;
public class DumpRailwaysCommand {
static ArgumentBuilder<CommandSourceStack, ?> register() {
return Commands.literal("dumpRailways")
return Commands.literal("trains")
.requires(cs -> cs.hasPermission(2))
.executes(ctx -> {
CommandSourceStack source = ctx.getSource();
@ -52,7 +52,7 @@ public class DumpRailwaysCommand {
int orange = 0xFFAD60;
chat.accept("", white);
chat.accept("-+------<< Railways Summary: >>------+-", white);
chat.accept("-+------<< Train Summary: >>------+-", white);
int graphCount = railways.trackNetworks.size();
chat.accept("Track Networks: " + graphCount, blue);
chat.accept("Signal Groups: " + railways.signalEdgeGroups.size(), blue);

View file

@ -24,6 +24,8 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
@ -31,6 +33,7 @@ import net.minecraft.world.level.block.BaseRailBlock;
import net.minecraft.world.level.block.BedBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.IceBlock;
import net.minecraft.world.level.block.SlimeBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
@ -40,6 +43,7 @@ import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Material;
import net.minecraftforge.common.IPlantable;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.BlockEvent;
@ -155,9 +159,11 @@ public class BlockHelper {
float effectChance, Consumer<ItemStack> droppedItemCallback) {
FluidState fluidState = world.getFluidState(pos);
BlockState state = world.getBlockState(pos);
if (world.random.nextFloat() < effectChance)
world.levelEvent(2001, pos, Block.getId(state));
BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
if (player != null) {
BlockEvent.BreakEvent event = new BlockEvent.BreakEvent(world, pos, state, player);
MinecraftForge.EVENT_BUS.post(event);
@ -177,6 +183,22 @@ public class BlockHelper {
&& (player == null || !player.isCreative())) {
for (ItemStack itemStack : Block.getDrops(state, (ServerLevel) world, pos, tileentity, player, usedTool))
droppedItemCallback.accept(itemStack);
// Simulating IceBlock#playerDestroy. Not calling method directly as it would drop item
// entities as a side-effect
if (state.getBlock() instanceof IceBlock
&& EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, usedTool) == 0) {
if (world.dimensionType()
.ultraWarm())
return;
Material material = world.getBlockState(pos.below())
.getMaterial();
if (material.blocksMotion() || material.isLiquid())
world.setBlockAndUpdate(pos, Blocks.WATER.defaultBlockState());
return;
}
state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY);
}