Send nodes

- Fixed incomplete track graph network packets at >1000 nodes
- Fixed Metal girders having incorrect block properties
- Trains getting abnormally high coupling stress now derail and stop moving
- Fixed trains not always migrating graphs properly when track connections are severed
- Motion applied to players run over by trains now caps at 60m/s
- Fixed train relocation able to cause run-over damage
- Fixed minecart contraption relocation sending nearby players to the moon
- Fixed non-player entities not getting run over by trains
- Trains can now reverse out of being stalled
- Fixed train relocation not deactivating mounted actors
- Fixed drill trains occasionally getting stuck breaking blocks at very low speeds
This commit is contained in:
simibubi 2022-05-31 02:01:32 +02:00
parent 224c5d0426
commit 64bc8a3499
21 changed files with 215 additions and 54 deletions

View file

@ -740,6 +740,7 @@ public class AllBlocks {
.register(); .register();
public static final BlockEntry<GirderBlock> METAL_GIRDER = REGISTRATE.block("metal_girder", GirderBlock::new) public static final BlockEntry<GirderBlock> METAL_GIRDER = REGISTRATE.block("metal_girder", GirderBlock::new)
.initialProperties(SharedProperties::softMetal)
.blockstate(GirderBlockStateGenerator::blockState) .blockstate(GirderBlockStateGenerator::blockState)
.properties(p -> p.color(MaterialColor.COLOR_GRAY)) .properties(p -> p.color(MaterialColor.COLOR_GRAY))
.properties(p -> p.sound(SoundType.NETHERITE_BLOCK)) .properties(p -> p.sound(SoundType.NETHERITE_BLOCK))
@ -751,6 +752,7 @@ public class AllBlocks {
public static final BlockEntry<GirderEncasedShaftBlock> METAL_GIRDER_ENCASED_SHAFT = public static final BlockEntry<GirderEncasedShaftBlock> METAL_GIRDER_ENCASED_SHAFT =
REGISTRATE.block("metal_girder_encased_shaft", GirderEncasedShaftBlock::new) REGISTRATE.block("metal_girder_encased_shaft", GirderEncasedShaftBlock::new)
.initialProperties(SharedProperties::softMetal)
.blockstate(GirderBlockStateGenerator::blockStateWithShaft) .blockstate(GirderBlockStateGenerator::blockStateWithShaft)
.properties(p -> p.color(MaterialColor.COLOR_GRAY)) .properties(p -> p.color(MaterialColor.COLOR_GRAY))
.properties(p -> p.sound(SoundType.NETHERITE_BLOCK)) .properties(p -> p.sound(SoundType.NETHERITE_BLOCK))

View file

@ -3,7 +3,9 @@ package com.simibubi.create.content.contraptions.components.actors;
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity; import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour; import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext; import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import com.simibubi.create.content.contraptions.components.structureMovement.OrientedContraptionEntity;
import com.simibubi.create.foundation.utility.BlockHelper; import com.simibubi.create.foundation.utility.BlockHelper;
import com.simibubi.create.foundation.utility.Debug;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@ -47,6 +49,8 @@ public class BlockBreakingMovementBehaviour implements MovementBehaviour {
} }
public void damageEntities(MovementContext context, BlockPos pos, Level world) { public void damageEntities(MovementContext context, BlockPos pos, Level world) {
if (context.contraption.entity instanceof OrientedContraptionEntity oce && oce.nonDamageTicks > 0)
return;
DamageSource damageSource = getDamageSource(); DamageSource damageSource = getDamageSource();
if (damageSource == null && !throwsEntities()) if (damageSource == null && !throwsEntities())
return; return;
@ -58,7 +62,7 @@ public class BlockBreakingMovementBehaviour implements MovementBehaviour {
if (entity instanceof AbstractMinecart) if (entity instanceof AbstractMinecart)
for (Entity passenger : entity.getIndirectPassengers()) for (Entity passenger : entity.getIndirectPassengers())
if (passenger instanceof AbstractContraptionEntity if (passenger instanceof AbstractContraptionEntity
&& ((AbstractContraptionEntity) passenger).getContraption() == context.contraption) && ((AbstractContraptionEntity) passenger).getContraption() == context.contraption)
continue Entities; continue Entities;
if (damageSource != null && !world.isClientSide) { if (damageSource != null && !world.isClientSide) {
@ -69,9 +73,11 @@ public class BlockBreakingMovementBehaviour implements MovementBehaviour {
Vec3 motionBoost = context.motion.add(0, context.motion.length() / 4f, 0); Vec3 motionBoost = context.motion.add(0, context.motion.length() / 4f, 0);
int maxBoost = 4; int maxBoost = 4;
if (motionBoost.length() > maxBoost) { if (motionBoost.length() > maxBoost) {
motionBoost = motionBoost.subtract(motionBoost.normalize().scale(motionBoost.length() - maxBoost)); motionBoost = motionBoost.subtract(motionBoost.normalize()
.scale(motionBoost.length() - maxBoost));
} }
entity.setDeltaMovement(entity.getDeltaMovement().add(motionBoost)); entity.setDeltaMovement(entity.getDeltaMovement()
.add(motionBoost));
entity.hurtMarked = true; entity.hurtMarked = true;
} }
} }
@ -86,7 +92,7 @@ public class BlockBreakingMovementBehaviour implements MovementBehaviour {
} }
@Override @Override
public void stopMoving(MovementContext context) { public void cancelStall(MovementContext context) {
CompoundTag data = context.data; CompoundTag data = context.data;
if (context.world.isClientSide) if (context.world.isClientSide)
return; return;
@ -101,10 +107,15 @@ public class BlockBreakingMovementBehaviour implements MovementBehaviour {
data.remove("TicksUntilNextProgress"); data.remove("TicksUntilNextProgress");
data.remove("BreakingPos"); data.remove("BreakingPos");
context.stall = false; MovementBehaviour.super.cancelStall(context);
world.destroyBlockProgress(id, breakingPos, -1); world.destroyBlockProgress(id, breakingPos, -1);
} }
@Override
public void stopMoving(MovementContext context) {
cancelStall(context);
}
@Override @Override
public void tick(MovementContext context) { public void tick(MovementContext context) {
tickBreaker(context); tickBreaker(context);
@ -165,7 +176,8 @@ public class BlockBreakingMovementBehaviour implements MovementBehaviour {
float breakSpeed = Mth.clamp(Math.abs(context.getAnimationSpeed()) / 500f, 1 / 128f, 16f); float breakSpeed = Mth.clamp(Math.abs(context.getAnimationSpeed()) / 500f, 1 / 128f, 16f);
destroyProgress += Mth.clamp((int) (breakSpeed / blockHardness), 1, 10 - destroyProgress); destroyProgress += Mth.clamp((int) (breakSpeed / blockHardness), 1, 10 - destroyProgress);
world.playSound(null, breakingPos, stateToBreak.getSoundType().getHitSound(), SoundSource.NEUTRAL, .25f, 1); world.playSound(null, breakingPos, stateToBreak.getSoundType()
.getHitSound(), SoundSource.NEUTRAL, .25f, 1);
if (destroyProgress >= 10) { if (destroyProgress >= 10) {
world.destroyBlockProgress(id, breakingPos, -1); world.destroyBlockProgress(id, breakingPos, -1);
@ -181,7 +193,7 @@ public class BlockBreakingMovementBehaviour implements MovementBehaviour {
context.stall = false; context.stall = false;
if (shouldDestroyStartBlock(stateToBreak)) if (shouldDestroyStartBlock(stateToBreak))
BlockHelper.destroyBlock(context.world, breakingPos, 1f, stack -> this.dropItem(context, stack)); BlockHelper.destroyBlock(context.world, breakingPos, 1f, stack -> this.dropItem(context, stack));
onBlockBroken(context, ogPos, stateToBreak); onBlockBroken(context, ogPos, stateToBreak);
ticksUntilNextProgress = -1; ticksUntilNextProgress = -1;
data.remove("Progress"); data.remove("Progress");

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.actors.PloughBlock.PloughFakePlayer;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext; import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import com.simibubi.create.content.contraptions.components.structureMovement.OrientedContraptionEntity;
import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;

View file

@ -152,6 +152,11 @@ public class PortableStorageInterfaceMovement implements MovementBehaviour {
public void stopMoving(MovementContext context) { public void stopMoving(MovementContext context) {
// reset(context); // reset(context);
} }
@Override
public void cancelStall(MovementContext context) {
reset(context);
}
public void reset(MovementContext context) { public void reset(MovementContext context) {
context.data.remove(_clientPrevPos_); context.data.remove(_clientPrevPos_);

View file

@ -186,6 +186,21 @@ public class DeployerMovementBehaviour implements MovementBehaviour {
context.stall = player.blockBreakingProgress != null; context.stall = player.blockBreakingProgress != null;
} }
@Override
public void cancelStall(MovementContext context) {
if (context.world.isClientSide)
return;
MovementBehaviour.super.cancelStall(context);
DeployerFakePlayer player = getPlayer(context);
if (player == null)
return;
if (player.blockBreakingProgress == null)
return;
context.world.destroyBlockProgress(player.getId(), player.blockBreakingProgress.getKey(), -1);
player.blockBreakingProgress = null;
}
@Override @Override
public void stopMoving(MovementContext context) { public void stopMoving(MovementContext context) {
if (context.world.isClientSide) if (context.world.isClientSide)
@ -195,6 +210,7 @@ public class DeployerMovementBehaviour implements MovementBehaviour {
if (player == null) if (player == null)
return; return;
cancelStall(context);
context.tileData.put("Inventory", player.getInventory() context.tileData.put("Inventory", player.getInventory()
.save(new ListTag())); .save(new ListTag()));
player.discard(); player.discard();

View file

@ -29,6 +29,8 @@ import com.simibubi.create.content.contraptions.components.structureMovement.int
import com.simibubi.create.content.contraptions.components.structureMovement.mounted.MountedContraption; import com.simibubi.create.content.contraptions.components.structureMovement.mounted.MountedContraption;
import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionSeatMappingPacket; import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionSeatMappingPacket;
import com.simibubi.create.content.logistics.trains.entity.CarriageContraption; import com.simibubi.create.content.logistics.trains.entity.CarriageContraption;
import com.simibubi.create.content.logistics.trains.entity.CarriageContraptionEntity;
import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.foundation.collision.Matrix3d; import com.simibubi.create.foundation.collision.Matrix3d;
import com.simibubi.create.foundation.mixin.accessor.ServerLevelAccessor; import com.simibubi.create.foundation.mixin.accessor.ServerLevelAccessor;
import com.simibubi.create.foundation.networking.AllPackets; import com.simibubi.create.foundation.networking.AllPackets;
@ -440,6 +442,14 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
return false; return false;
context.motion = actorPosition.subtract(previousPosition); context.motion = actorPosition.subtract(previousPosition);
if (!level.isClientSide() && context.contraption.entity instanceof CarriageContraptionEntity cce
&& cce.getCarriage() != null) {
Train train = cce.getCarriage().train;
double actualSpeed = train.speedBeforeStall != null ? train.speedBeforeStall : train.speed;
context.motion = context.motion.normalize()
.scale(actualSpeed);
}
Vec3 relativeMotion = context.motion; Vec3 relativeMotion = context.motion;
relativeMotion = reverseRotation(relativeMotion, 1); relativeMotion = reverseRotation(relativeMotion, 1);
context.relativeMotion = relativeMotion; context.relativeMotion = relativeMotion;

View file

@ -78,6 +78,8 @@ public class ContraptionCollider {
List<Entity> entitiesWithinAABB = world.getEntitiesOfClass(Entity.class, bounds.inflate(2) List<Entity> entitiesWithinAABB = world.getEntitiesOfClass(Entity.class, bounds.inflate(2)
.expandTowards(0, 32, 0), contraptionEntity::canCollideWith); .expandTowards(0, 32, 0), contraptionEntity::canCollideWith);
for (Entity entity : entitiesWithinAABB) { for (Entity entity : entitiesWithinAABB) {
if (!entity.isAlive())
continue;
PlayerType playerType = getPlayerType(entity); PlayerType playerType = getPlayerType(entity);
if (playerType == PlayerType.REMOTE) if (playerType == PlayerType.REMOTE)
@ -116,17 +118,18 @@ public class ContraptionCollider {
// Use simplified bbs when present // Use simplified bbs when present
final Vec3 motionCopy = motion; final Vec3 motionCopy = motion;
List<AABB> collidableBBs = contraption.getSimplifiedEntityColliders().orElseGet(() -> { List<AABB> collidableBBs = contraption.getSimplifiedEntityColliders()
.orElseGet(() -> {
// Else find 'nearby' individual block shapes to collide with // Else find 'nearby' individual block shapes to collide with
List<AABB> bbs = new ArrayList<>(); List<AABB> bbs = new ArrayList<>();
List<VoxelShape> potentialHits = List<VoxelShape> potentialHits =
getPotentiallyCollidedShapes(world, contraption, localBB.expandTowards(motionCopy)); getPotentiallyCollidedShapes(world, contraption, localBB.expandTowards(motionCopy));
potentialHits.forEach(shape -> shape.toAabbs() potentialHits.forEach(shape -> shape.toAabbs()
.forEach(bbs::add)); .forEach(bbs::add));
return bbs; return bbs;
}); });
MutableObject<Vec3> collisionResponse = new MutableObject<>(Vec3.ZERO); MutableObject<Vec3> collisionResponse = new MutableObject<>(Vec3.ZERO);
MutableObject<Vec3> normal = new MutableObject<>(Vec3.ZERO); MutableObject<Vec3> normal = new MutableObject<>(Vec3.ZERO);
@ -308,25 +311,36 @@ public class ContraptionCollider {
entityPosition.z + allowedMovement.z); entityPosition.z + allowedMovement.z);
entityPosition = entity.position(); entityPosition = entity.position();
if (contraptionEntity instanceof CarriageContraptionEntity cce && entity.isOnGround()) { if (contraptionEntity instanceof CarriageContraptionEntity cce && entity.isOnGround()
if (AllConfigs.SERVER.trains.trainsCauseDamage.get()) { && !(entity instanceof ItemEntity) && cce.nonDamageTicks == 0
Vec3 diffMotion = contraptionMotion.subtract(entity.getDeltaMovement()); && AllConfigs.SERVER.trains.trainsCauseDamage.get()) {
if (diffMotion.length() > 0.35f && contraptionMotion.length() > 0.35f) {
EntityDamageSource pSource = new EntityDamageSource("create.run_over", contraptionEntity); Vec3 diffMotion = contraptionMotion.subtract(entity.getDeltaMovement());
double damage = diffMotion.length(); if (diffMotion.length() > 0.35f && contraptionMotion.length() > 0.35f) {
if (playerType == PlayerType.CLIENT)
EntityDamageSource pSource = new EntityDamageSource("create.run_over", contraptionEntity);
double damage = diffMotion.length();
if (!(entity instanceof Player p) || !p.isCreative() && !p.isSpectator()) {
if (playerType == PlayerType.CLIENT) {
AllPackets.channel AllPackets.channel
.sendToServer(new TrainCollisionPacket((int) (damage * 16), contraptionEntity.getId())); .sendToServer(new TrainCollisionPacket((int) (damage * 16), contraptionEntity.getId()));
else world.playSound((Player) entity, entity.blockPosition(), SoundEvents.PLAYER_ATTACK_CRIT,
SoundSource.NEUTRAL, 1, .75f);
} else {
entity.hurt(pSource, (int) (damage * 16)); entity.hurt(pSource, (int) (damage * 16));
if (!(entity instanceof Player p) || !p.isCreative() && !p.isSpectator()) world.playSound(null, entity.blockPosition(), SoundEvents.PLAYER_ATTACK_CRIT,
entityMotion = entityMotion.add(entity.position() SoundSource.NEUTRAL, 1, .75f);
.subtract(contraptionPosition) }
.multiply(1, 0, 1)
.normalize() Vec3 added = entityMotion.add(entity.position()
.add(0, .25, 0) .subtract(contraptionPosition)
.scale(damage * 4)) .multiply(1, 0, 1)
.add(diffMotion); .normalize()
.add(0, .25, 0)
.scale(damage * 4))
.add(diffMotion);
entityMotion = VecHelper.clamp(added, 3);
} }
} }
} }

View file

@ -52,6 +52,10 @@ public interface MovementBehaviour {
default void onSpeedChanged(MovementContext context, Vec3 oldMotion, Vec3 motion) {} default void onSpeedChanged(MovementContext context, Vec3 oldMotion, Vec3 motion) {}
default void stopMoving(MovementContext context) {} default void stopMoving(MovementContext context) {}
default void cancelStall(MovementContext context) {
context.stall = false;
}
default void writeExtraData(MovementContext context) {} default void writeExtraData(MovementContext context) {}

View file

@ -74,12 +74,15 @@ public class OrientedContraptionEntity extends AbstractContraptionEntity {
public float prevPitch; public float prevPitch;
public float pitch; public float pitch;
public int nonDamageTicks;
public OrientedContraptionEntity(EntityType<?> type, Level world) { public OrientedContraptionEntity(EntityType<?> type, Level world) {
super(type, world); super(type, world);
motionBeforeStall = Vec3.ZERO; motionBeforeStall = Vec3.ZERO;
attachedExtraInventories = false; attachedExtraInventories = false;
isSerializingFurnaceCart = false; isSerializingFurnaceCart = false;
nonDamageTicks = 10;
} }
public static OrientedContraptionEntity create(Level world, Contraption contraption, Direction initialOrientation) { public static OrientedContraptionEntity create(Level world, Contraption contraption, Direction initialOrientation) {
@ -243,6 +246,8 @@ public class OrientedContraptionEntity extends AbstractContraptionEntity {
@Override @Override
protected void tickContraption() { protected void tickContraption() {
if (nonDamageTicks > 0)
nonDamageTicks--;
Entity e = getVehicle(); Entity e = getVehicle();
if (e == null) if (e == null)
return; return;

View file

@ -7,6 +7,8 @@ import com.simibubi.create.foundation.networking.SimplePacketBase;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.damagesource.EntityDamageSource; import net.minecraft.world.damagesource.EntityDamageSource;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
@ -43,8 +45,10 @@ public class TrainCollisionPacket extends SimplePacketBase {
Entity entity = level.getEntity(contraptionEntityId); Entity entity = level.getEntity(contraptionEntityId);
if (!(entity instanceof CarriageContraptionEntity cce)) if (!(entity instanceof CarriageContraptionEntity cce))
return; return;
player.hurt(new EntityDamageSource("create.run_over", cce), (int) damage); player.hurt(new EntityDamageSource("create.run_over", cce), (int) damage);
player.level.playSound(player, entity.blockPosition(), SoundEvents.PLAYER_ATTACK_CRIT, SoundSource.NEUTRAL,
1, .75f);
}); });
ctx.setPacketHandled(true); ctx.setPacketHandled(true);
} }

View file

@ -49,6 +49,12 @@ public class ContactMovementBehaviour implements MovementBehaviour {
public void stopMoving(MovementContext context) { public void stopMoving(MovementContext context) {
deactivateLastVisitedContact(context); deactivateLastVisitedContact(context);
} }
@Override
public void cancelStall(MovementContext context) {
MovementBehaviour.super.cancelStall(context);
deactivateLastVisitedContact(context);
}
public void deactivateLastVisitedContact(MovementContext context) { public void deactivateLastVisitedContact(MovementContext context) {
if (context.data.contains("lastContact")) { if (context.data.contains("lastContact")) {

View file

@ -294,9 +294,9 @@ public class TrackGraph {
frontier.add(connected.getLocation()); frontier.add(connected.getLocation());
if (target != null) { if (target != null) {
transfer(level, currentNode, target);
if (preAssignedIds != null && preAssignedIds.containsKey(currentNode.getNetId())) if (preAssignedIds != null && preAssignedIds.containsKey(currentNode.getNetId()))
target.setId(preAssignedIds.get(currentNode.getNetId())); target.setId(preAssignedIds.get(currentNode.getNetId()));
transfer(level, currentNode, target);
} }
} }
@ -331,16 +331,18 @@ public class TrackGraph {
} }
} }
for (Iterator<UUID> iterator = trains.keySet() if (level != null)
.iterator(); iterator.hasNext();) { for (Iterator<UUID> iterator = trains.keySet()
UUID uuid = iterator.next(); .iterator(); iterator.hasNext();) {
Train train = trains.get(uuid); UUID uuid = iterator.next();
if (train.graph != this) Train train = trains.get(uuid);
continue; if (train.graph != this)
if (!train.isTravellingOn(node)) continue;
continue; if (!train.isTravellingOn(node))
train.graph = target; continue;
} train.graph = target;
train.syncTrackGraphChanges();
}
nodes.remove(nodeLoc); nodes.remove(nodeLoc);
nodesById.remove(node.getNetId()); nodesById.remove(node.getNetId());

View file

@ -123,6 +123,15 @@ public class TrackGraphSync {
for (TrackNode node : graph.nodes.values()) { for (TrackNode node : graph.nodes.values()) {
TrackGraphSyncPacket currentPacket = packet; TrackGraphSyncPacket currentPacket = packet;
currentPacket.addedNodes.put(node.getNetId(), Pair.of(node.getLocation(), node.getNormal())); currentPacket.addedNodes.put(node.getNetId(), Pair.of(node.getLocation(), node.getNormal()));
if (sent++ < 1000)
continue;
sent = 0;
packet = flushAndCreateNew(graph, player, packet);
}
for (TrackNode node : graph.nodes.values()) {
TrackGraphSyncPacket currentPacket = packet;
if (!graph.connectionsByNode.containsKey(node)) if (!graph.connectionsByNode.containsKey(node))
continue; continue;
graph.connectionsByNode.get(node) graph.connectionsByNode.get(node)
@ -130,7 +139,7 @@ public class TrackGraphSync {
Couple<Integer> key = Couple.create(node.getNetId(), node2.getNetId()); Couple<Integer> key = Couple.create(node.getNetId(), node2.getNetId());
currentPacket.addedEdges.add(Pair.of(key, edge.getTurn())); currentPacket.addedEdges.add(Pair.of(key, edge.getTurn()));
currentPacket.syncEdgeData(node, node2, edge); currentPacket.syncEdgeData(node, node2, edge);
});//FIXME these edges will have missing nodes });
if (sent++ < 1000) if (sent++ < 1000)
continue; continue;

View file

@ -122,7 +122,7 @@ public class Carriage {
Function<TravellingPoint, ITrackSelector> forwardControl, Function<TravellingPoint, ITrackSelector> forwardControl,
Function<TravellingPoint, ITrackSelector> backwardControl, int type) { Function<TravellingPoint, ITrackSelector> backwardControl, int type) {
boolean onTwoBogeys = isOnTwoBogeys(); boolean onTwoBogeys = isOnTwoBogeys();
double stress = onTwoBogeys ? bogeySpacing - getAnchorDiff() : 0; double stress = train.derailed ? 0 : onTwoBogeys ? bogeySpacing - getAnchorDiff() : 0;
blocked = false; blocked = false;
MutableDouble distanceMoved = new MutableDouble(distance); MutableDouble distanceMoved = new MutableDouble(distance);
@ -190,7 +190,7 @@ public class Carriage {
return distanceMoved.getValue(); return distanceMoved.getValue();
} }
private double getAnchorDiff() { public double getAnchorDiff() {
double diff = 0; double diff = 0;
int entries = 0; int entries = 0;
@ -753,7 +753,7 @@ public class Carriage {
return; return;
cc.portalCutoffMin = minAllowedLocalCoord(); cc.portalCutoffMin = minAllowedLocalCoord();
cc.portalCutoffMax = maxAllowedLocalCoord(); cc.portalCutoffMax = maxAllowedLocalCoord();
if (!entity.level.isClientSide()) if (!entity.level.isClientSide())
return; return;
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> invalidate(cce)); DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> invalidate(cce));
} }
@ -825,6 +825,9 @@ public class Carriage {
double diffY = positionVec.y - coupledVec.y; double diffY = positionVec.y - coupledVec.y;
double diffZ = positionVec.z - coupledVec.z; double diffZ = positionVec.z - coupledVec.z;
if (!entity.level.isClientSide())
entity.setServerSidePrevPosition();
entity.setPos(positionAnchor); entity.setPos(positionAnchor);
entity.prevYaw = entity.yaw; entity.prevYaw = entity.yaw;
entity.prevPitch = entity.pitch; entity.prevPitch = entity.pitch;

View file

@ -109,6 +109,8 @@ public class CarriageBogey {
public double getStress() { public double getStress() {
if (getDimension() == null) if (getDimension() == null)
return 0; return 0;
if (carriage.train.derailed)
return 0;
return type.getWheelPointSpacing() - leading().getPosition() return type.getWheelPointSpacing() - leading().getPosition()
.distanceTo(trailing().getPosition()); .distanceTo(trailing().getPosition());
} }

View file

@ -76,6 +76,8 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
private boolean arrivalSoundReversed; private boolean arrivalSoundReversed;
private int arrivalSoundTicks; private int arrivalSoundTicks;
private Vec3 serverPrevPos;
public CarriageContraptionEntity(EntityType<?> type, Level world) { public CarriageContraptionEntity(EntityType<?> type, Level world) {
super(type, world); super(type, world);
validForRender = false; validForRender = false;
@ -133,6 +135,17 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
return entityData.get(SCHEDULED); return entityData.get(SCHEDULED);
} }
public void setServerSidePrevPosition() {
serverPrevPos = position();
}
@Override
public Vec3 getPrevPositionVec() {
if (!level.isClientSide() && serverPrevPos != null)
return serverPrevPos;
return super.getPrevPositionVec();
}
public boolean isLocalCoordWithin(BlockPos localPos, int min, int max) { public boolean isLocalCoordWithin(BlockPos localPos, int min, int max) {
if (!(getContraption() instanceof CarriageContraption cc)) if (!(getContraption() instanceof CarriageContraption cc))
return false; return false;
@ -173,6 +186,8 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
@Override @Override
protected void tickContraption() { protected void tickContraption() {
if (nonDamageTicks > 0)
nonDamageTicks--;
if (!(contraption instanceof CarriageContraption cc)) if (!(contraption instanceof CarriageContraption cc))
return; return;
if (carriage == null) { if (carriage == null) {
@ -511,6 +526,11 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
return true; return true;
} }
if (carriage.train.speedBeforeStall != null && targetSpeed != 0
&& Math.signum(carriage.train.speedBeforeStall) != Math.signum(targetSpeed)) {
carriage.train.cancelStall();
}
if (currentStation != null && targetSpeed != 0) { if (currentStation != null && targetSpeed != 0) {
stationMessage = false; stationMessage = false;
player.displayClientMessage(new TextComponent("<i> Departing from ").withStyle(ChatFormatting.YELLOW) player.displayClientMessage(new TextComponent("<i> Departing from ").withStyle(ChatFormatting.YELLOW)

View file

@ -19,7 +19,9 @@ import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.lang3.mutable.MutableObject; import org.apache.commons.lang3.mutable.MutableObject;
import com.simibubi.create.AllMovementBehaviours;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
import com.simibubi.create.content.logistics.trains.DimensionPalette; import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.GraphLocation; import com.simibubi.create.content.logistics.trains.GraphLocation;
import com.simibubi.create.content.logistics.trains.TrackEdge; import com.simibubi.create.content.logistics.trains.TrackEdge;
@ -91,7 +93,7 @@ public class Train {
// considered for removal // considered for removal
@Deprecated @Deprecated
public boolean heldForAssembly; public boolean heldForAssembly;
public boolean doubleEnded; public boolean doubleEnded;
public List<Carriage> carriages; public List<Carriage> carriages;
public List<Integer> carriageSpacing; public List<Integer> carriageSpacing;
@ -173,7 +175,7 @@ public class Train {
updateConductors(); updateConductors();
return; return;
} }
updateConductors(); updateConductors();
runtime.tick(level); runtime.tick(level);
navigation.tick(level); navigation.tick(level);
@ -186,6 +188,7 @@ public class Train {
Carriage previousCarriage = null; Carriage previousCarriage = null;
int carriageCount = carriages.size(); int carriageCount = carriages.size();
boolean stalled = false; boolean stalled = false;
double maxStress = 0;
for (int i = 0; i < carriageCount; i++) { for (int i = 0; i < carriageCount; i++) {
Carriage carriage = carriages.get(i); Carriage carriage = carriages.get(i);
@ -226,6 +229,7 @@ public class Train {
actual = total / entries; actual = total / entries;
stress[i - 1] = target - actual; stress[i - 1] = target - actual;
maxStress = Math.max(maxStress, Math.abs(target - actual));
} }
previousCarriage = carriage; previousCarriage = carriage;
@ -275,7 +279,7 @@ public class Train {
Function<TravellingPoint, ITrackSelector> backwardControl = Function<TravellingPoint, ITrackSelector> backwardControl =
toFollowBackward == null ? navigation::control : mp -> mp.follow(toFollowBackward); toFollowBackward == null ? navigation::control : mp -> mp.follow(toFollowBackward);
double totalStress = leadingStress + trailingStress; double totalStress = derailed ? 0 : leadingStress + trailingStress;
boolean first = i == 0; boolean first = i == 0;
boolean last = i == carriageCount - 1; boolean last = i == carriageCount - 1;
int carriageType = first ? last ? Carriage.BOTH : Carriage.FIRST : last ? Carriage.LAST : Carriage.MIDDLE; int carriageType = first ? last ? Carriage.BOTH : Carriage.FIRST : last ? Carriage.LAST : Carriage.MIDDLE;
@ -283,6 +287,14 @@ public class Train {
carriage.travel(level, graph, distance + totalStress, forwardControl, backwardControl, carriageType); carriage.travel(level, graph, distance + totalStress, forwardControl, backwardControl, carriageType);
blocked |= carriage.blocked; blocked |= carriage.blocked;
boolean onTwoBogeys = carriage.isOnTwoBogeys();
maxStress = Math.max(maxStress, onTwoBogeys ? carriage.bogeySpacing - carriage.getAnchorDiff() : 0);
maxStress = Math.max(maxStress, carriage.leadingBogey()
.getStress());
if (onTwoBogeys)
maxStress = Math.max(maxStress, carriage.trailingBogey()
.getStress());
if (index == 0) { if (index == 0) {
distance = actualDistance; distance = actualDistance;
collideWithOtherTrains(level, carriage); collideWithOtherTrains(level, carriage);
@ -296,6 +308,15 @@ public class Train {
navigation.cancelNavigation(); navigation.cancelNavigation();
runtime.tick(level); runtime.tick(level);
status.endOfTrack(); status.endOfTrack();
} else if (maxStress > 2) {
speed = 0;
navigation.cancelNavigation();
runtime.tick(level);
derailed = true;
syncTrackGraphChanges();
status.highStress();
} else if (speed != 0) } else if (speed != 0)
status.trackOK(); status.trackOK();
@ -338,6 +359,20 @@ public class Train {
}; };
} }
public void cancelStall() {
speedBeforeStall = null;
carriages.forEach(c -> {
c.stalled = false;
c.forEachPresentEntity(cce -> cce.getContraption()
.getActors()
.forEach(pair -> {
MovementBehaviour behaviour = AllMovementBehaviours.of(pair.getKey().state);
if (behaviour != null)
behaviour.cancelStall(pair.getValue());
}));
});
}
private boolean occupy(UUID groupId, @Nullable UUID boundaryId) { private boolean occupy(UUID groupId, @Nullable UUID boundaryId) {
reservedSignalBlocks.remove(groupId); reservedSignalBlocks.remove(groupId);
if (boundaryId != null && occupiedSignalBlocks.containsKey(groupId)) if (boundaryId != null && occupiedSignalBlocks.containsKey(groupId))

View file

@ -96,6 +96,7 @@ public class TrainRelocationPacket extends SimplePacketBase {
sender.displayClientMessage(Lang.translate("train.relocate.success") sender.displayClientMessage(Lang.translate("train.relocate.success")
.withStyle(ChatFormatting.GREEN), true); .withStyle(ChatFormatting.GREEN), true);
train.syncTrackGraphChanges(); train.syncTrackGraphChanges();
train.carriages.forEach(c -> c.forEachPresentEntity(e -> e.nonDamageTicks = 10));
return; return;
} }

View file

@ -147,9 +147,11 @@ public class TrainRelocator {
Vec3 lookAngle = mc.player.getLookAngle(); Vec3 lookAngle = mc.player.getLookAngle();
boolean direction = bezierSelection != null && lookAngle.dot(bezierSelection.direction()) < 0; boolean direction = bezierSelection != null && lookAngle.dot(bezierSelection.direction()) < 0;
boolean result = relocate(relocating, mc.level, blockPos, hoveredBezier, direction, lookAngle, true); boolean result = relocate(relocating, mc.level, blockPos, hoveredBezier, direction, lookAngle, true);
if (!simulate && result) if (!simulate && result) {
relocating.carriages.forEach(c -> c.forEachPresentEntity(e -> e.nonDamageTicks = 10));
AllPackets.channel.sendToServer(new TrainRelocationPacket(relocatingTrain, blockPos, hoveredBezier, AllPackets.channel.sendToServer(new TrainRelocationPacket(relocatingTrain, blockPos, hoveredBezier,
direction, lookAngle, relocatingEntityId)); direction, lookAngle, relocatingEntityId));
}
return lastHoveredResult = result; return lastHoveredResult = result;
} }
@ -238,6 +240,7 @@ public class TrainRelocator {
train.graph = graph; train.graph = graph;
train.speed = 0; train.speed = 0;
train.migratingPoints.clear(); train.migratingPoints.clear();
train.cancelStall();
if (train.navigation.destination != null) if (train.navigation.destination != null)
train.navigation.cancelNavigation(); train.navigation.cancelNavigation();

View file

@ -69,6 +69,13 @@ public class TrainStatus {
displayInformation("Tracks are missing beneath the Train", false); displayInformation("Tracks are missing beneath the Train", false);
track = true; track = true;
} }
public void highStress() {
if (track)
return;
displayInformation("Forced stop due to Stress on Couplings", false);
track = true;
}
public void doublePortal() { public void doublePortal() {
if (track) if (track)

View file

@ -39,7 +39,7 @@ public class CurvedTrackInteraction {
Minecraft mc = Minecraft.getInstance(); Minecraft mc = Minecraft.getInstance();
LocalPlayer player = mc.player; LocalPlayer player = mc.player;
ClientLevel level = mc.level; ClientLevel level = mc.level;
if (!player.getAbilities().mayBuild) if (!player.getAbilities().mayBuild)
return; return;
@ -64,7 +64,7 @@ public class CurvedTrackInteraction {
breakTicks++; breakTicks++;
breakTimeout = 2; breakTimeout = 2;
breakProgress += creative ? 0.25f : blockState.getDestroyProgress(player, level, breakPos); breakProgress += creative ? 0.125f : blockState.getDestroyProgress(player, level, breakPos) / 8f;
Vec3 vec = VecHelper.offsetRandomly(result.vec(), level.random, 0.25f); Vec3 vec = VecHelper.offsetRandomly(result.vec(), level.random, 0.25f);
level.addParticle(new BlockParticleOption(ParticleTypes.BLOCK, blockState), vec.x, vec.y, vec.z, 0, 0, 0); level.addParticle(new BlockParticleOption(ParticleTypes.BLOCK, blockState), vec.x, vec.y, vec.z, 0, 0, 0);