Catching up

- Fixed client-side trains not animating their wheels properly
- Client-side stations now receive information about a trains' orientation and whether it can be disassembled
- Server-safe train relocation
- Fixed client trains not catching up with received location when graph id changes
- Coupling renderer now entirely client-side
- Fixed Coupling render artefacts whenever a carriage drives out of the view frustum
- Fixed train information interpolated too quickly when a train has multiple carriage entities
This commit is contained in:
simibubi 2022-03-08 18:03:57 +01:00
parent 9629cb84eb
commit fa47939428
12 changed files with 184 additions and 37 deletions

View file

@ -38,7 +38,7 @@ public class Carriage {
CompoundTag serialisedEntity;
WeakReference<CarriageContraptionEntity> entity;
// client
public boolean pointsInitialised;
@ -63,8 +63,6 @@ public class Carriage {
public void setContraption(Level level, CarriageContraption contraption) {
CarriageContraptionEntity entity = CarriageContraptionEntity.create(level, contraption);
entity.setInitialOrientation(contraption.getAssemblyDirection()
.getClockWise());
entity.setCarriage(this);
contraption.startMoving(level);
serialisedEntity = entity.serializeNBT();

View file

@ -51,7 +51,7 @@ public class CarriageBogey {
if (carriage.train.derailed)
yRot += derailAngle;
wheelAngle.setValue((wheelAngle.getValue() - angleDiff * Math.signum(carriage.train.speed)) % 360);
wheelAngle.setValue((wheelAngle.getValue() - angleDiff) % 360);
pitch.setValue(xRot);
yaw.setValue(-yRot);
}

View file

@ -1,5 +1,6 @@
package com.simibubi.create.content.logistics.trains.entity;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
@ -29,6 +30,7 @@ import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
@ -114,7 +116,8 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
CarriageContraptionEntity entity =
new CarriageContraptionEntity(AllEntityTypes.CARRIAGE_CONTRAPTION.get(), world);
entity.setContraption(contraption);
entity.setInitialOrientation(contraption.getAssemblyDirection());
entity.setInitialOrientation(contraption.getAssemblyDirection()
.getClockWise());
entity.startAtInitialYaw();
return entity;
}
@ -129,6 +132,8 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
if (train == null || train.carriages.size() <= carriageIndex)
return;
carriage = train.carriages.get(carriageIndex);
if (carriage != null)
carriage.entity = new WeakReference<>(this);
updateTrackGraph();
} else
discard();
@ -170,7 +175,9 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
carriage.alignEntity(this);
double distanceTo = position().distanceTo(new Vec3(xo, yo, zo));
Vec3 diff = position().subtract(xo, yo, zo);
Vec3 relativeDiff = VecHelper.rotate(diff, yaw, Axis.Y);
double distanceTo = diff.length() * Math.signum(-relativeDiff.x);
carriage.bogeys.getFirst()
.updateAngles(distanceTo);
@ -410,6 +417,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
public void setGraph(@Nullable UUID graphId) {
entityData.set(TRACK_GRAPH, Optional.ofNullable(graphId));
prevPosInvalid = true;
}
}

View file

@ -18,6 +18,11 @@ public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer
@Override
public boolean shouldRender(CarriageContraptionEntity entity, Frustum clippingHelper, double cameraX,
double cameraY, double cameraZ) {
Carriage carriage = entity.getCarriage();
if (carriage != null)
for (CarriageBogey bogey : carriage.bogeys)
if (bogey != null)
bogey.leadingCouplingAnchor = bogey.trailingCouplingAnchor = null;
if (!super.shouldRender(entity, clippingHelper, cameraX, cameraY, cameraZ))
return false;
return entity.validForRender;

View file

@ -6,7 +6,7 @@ import java.util.List;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.simibubi.create.AllBlockPartials;
import com.simibubi.create.Create;
import com.simibubi.create.CreateClient;
import com.simibubi.create.foundation.render.CachedBufferer;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
@ -26,7 +26,7 @@ import net.minecraft.world.phys.Vec3;
public class CarriageCouplingRenderer {
public static void renderAll(PoseStack ms, MultiBufferSource buffer) {
Collection<Train> trains = Create.RAILWAYS.trains.values(); // TODO: thread breach
Collection<Train> trains = CreateClient.RAILWAYS.trains.values();
VertexConsumer vb = buffer.getBuffer(RenderType.solid());
BlockState air = Blocks.AIR.defaultBlockState();
float partialTicks = AnimationTickHolder.getPartialTicks();
@ -51,7 +51,7 @@ public class CarriageCouplingRenderer {
CarriageBogey bogey2 = carriage2.leadingBogey();
Vec3 anchor = bogey1.trailingCouplingAnchor;
Vec3 anchor2 = bogey2.leadingCouplingAnchor;
if (anchor == null || anchor2 == null)
continue;
if (!anchor.closerThan(camera, 64))

View file

@ -28,6 +28,7 @@ public class CarriageSyncData {
public Vector<Pair<Couple<Integer>, Float>> wheelLocations;
public float distanceToDestination;
public boolean leadingCarriage;
// For Client interpolation
private TravellingPoint[] pointsToApproach;
@ -39,6 +40,7 @@ public class CarriageSyncData {
pointDistanceSnapshot = new float[4];
pointsToApproach = new TravellingPoint[4];
destinationDistanceSnapshot = 0;
leadingCarriage = false;
for (int i = 0; i < 4; i++) {
wheelLocations.add(null);
pointsToApproach[i] = new TravellingPoint();
@ -50,6 +52,7 @@ public class CarriageSyncData {
for (int i = 0; i < 4; i++)
data.wheelLocations.set(i, wheelLocations.get(i));
data.distanceToDestination = distanceToDestination;
data.leadingCarriage = leadingCarriage;
return data;
}
@ -63,6 +66,7 @@ public class CarriageSyncData {
buffer.writeFloat(pair.getSecond());
}
buffer.writeFloat(distanceToDestination);
buffer.writeBoolean(leadingCarriage);
}
public void read(FriendlyByteBuf buffer) {
@ -72,6 +76,7 @@ public class CarriageSyncData {
wheelLocations.set(i, Pair.of(Couple.create(buffer::readInt), buffer.readFloat()));
}
distanceToDestination = buffer.readFloat();
leadingCarriage = buffer.readBoolean();
}
public void update(CarriageContraptionEntity entity, Carriage carriage) {
@ -79,6 +84,8 @@ public class CarriageSyncData {
if (graph == null)
return;
leadingCarriage = entity.carriageIndex == (carriage.train.speed >= 0 ? 0 : carriage.train.carriages.size() - 1);
for (boolean first : Iterate.trueAndFalse) {
if (!first && !carriage.isOnTwoBogeys())
break;
@ -154,6 +161,9 @@ public class CarriageSyncData {
return;
}
if (!leadingCarriage)
return;
destinationDistanceSnapshot = (float) (distanceToDestination - carriage.train.navigation.distanceToDestination);
}
@ -209,6 +219,9 @@ public class CarriageSyncData {
TrackEdge initialEdge = graph.getConnectionsFrom(initialNode1)
.get(initialNode2);
if (initialEdge == null)
return -1; // graph changed
TrackNode targetNode1 = forward ? target.node1 : target.node2;
TrackNode targetNode2 = forward ? target.node2 : target.node1;
TrackEdge targetEdge = graph.getConnectionsFrom(targetNode1)

View file

@ -374,15 +374,8 @@ public class Train {
}
public boolean disassemble(Direction assemblyDirection, BlockPos pos) {
for (Carriage carriage : carriages) {
CarriageContraptionEntity entity = carriage.entity.get();
if (entity == null)
return false;
if (!Mth.equal(entity.pitch, 0))
return false;
if (!Mth.equal(((entity.yaw % 90) + 360) % 90, 0))
return false;
}
if (!canDisassemble())
return false;
int offset = 1;
boolean backwards = currentlyBackwards;
@ -412,6 +405,19 @@ public class Train {
return true;
}
public boolean canDisassemble() {
for (Carriage carriage : carriages) {
CarriageContraptionEntity entity = carriage.entity.get();
if (entity == null)
return false;
if (!Mth.equal(entity.pitch, 0))
return false;
if (!Mth.equal(((entity.yaw % 90) + 360) % 90, 0))
return false;
}
return true;
}
public boolean isTravellingOn(TrackNode node) {
MutableBoolean affected = new MutableBoolean(false);
forEachTravellingPoint(tp -> {

View file

@ -0,0 +1,93 @@
package com.simibubi.create.content.logistics.trains.entity;
import java.util.UUID;
import java.util.function.Supplier;
import com.simibubi.create.Create;
import com.simibubi.create.foundation.networking.SimplePacketBase;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.NetworkEvent.Context;
public class TrainRelocationPacket extends SimplePacketBase {
UUID trainId;
BlockPos pos;
Vec3 lookAngle;
int entityId;
public TrainRelocationPacket(FriendlyByteBuf buffer) {
trainId = buffer.readUUID();
pos = buffer.readBlockPos();
lookAngle = VecHelper.read(buffer);
entityId = buffer.readInt();
}
public TrainRelocationPacket(UUID trainId, BlockPos pos, Vec3 lookAngle, int entityId) {
this.trainId = trainId;
this.pos = pos;
this.lookAngle = lookAngle;
this.entityId = entityId;
}
@Override
public void write(FriendlyByteBuf buffer) {
buffer.writeUUID(trainId);
buffer.writeBlockPos(pos);
VecHelper.write(lookAngle, buffer);
buffer.writeInt(entityId);
}
@Override
public void handle(Supplier<Context> context) {
Context ctx = context.get();
ctx.enqueueWork(() -> {
ServerPlayer sender = ctx.getSender();
Train train = Create.RAILWAYS.trains.get(trainId);
Entity entity = sender.level.getEntity(entityId);
String messagePrefix = sender.getName()
.getString() + " could not relocate Train ";
if (train == null || !(entity instanceof CarriageContraptionEntity cce)) {
Create.LOGGER.warn(messagePrefix + train.id.toString()
.substring(0, 5) + ": not present on server");
return;
}
if (!train.id.equals(cce.trainId))
return;
if (!sender.position()
.closerThan(Vec3.atCenterOf(pos), 26)) {
Create.LOGGER.warn(messagePrefix + train.name.getString() + ": player too far from clicked pos");
return;
}
if (!sender.position()
.closerThan(cce.position(), 26 + cce.getBoundingBox()
.getXsize() / 2)) {
Create.LOGGER.warn(messagePrefix + train.name.getString() + ": player too far from carriage entity");
return;
}
if (TrainRelocator.relocate(train, sender.level, pos, lookAngle, false)) {
sender.displayClientMessage(Lang.translate("train.relocate.success")
.withStyle(ChatFormatting.GREEN), true);
train.syncTrackGraphChanges();
return;
}
Create.LOGGER.warn(messagePrefix + train.name.getString() + ": relocation failed server-side");
});
ctx.setPacketHandled(true);
}
}

View file

@ -12,6 +12,7 @@ import org.apache.commons.lang3.mutable.MutableBoolean;
import com.simibubi.create.AllItems;
import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionHandlerClient;
import com.simibubi.create.content.logistics.trains.GraphLocation;
import com.simibubi.create.content.logistics.trains.ITrackBlock;
@ -23,6 +24,7 @@ import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ISign
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.SteerDirection;
import com.simibubi.create.foundation.item.TooltipHelper;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.Pair;
@ -33,7 +35,7 @@ import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction.AxisDirection;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
@ -41,6 +43,8 @@ import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.InputEvent.ClickInputEvent;
public class TrainRelocator {
@ -48,10 +52,12 @@ public class TrainRelocator {
static WeakReference<CarriageContraptionEntity> hoveredEntity = new WeakReference<>(null);
static UUID relocatingTrain;
static Vec3 relocatingOrigin;
static int relocatingEntityId;
static BlockPos lastHoveredPos;
static Boolean lastHoveredResult;
@OnlyIn(Dist.CLIENT)
public static void onClicked(ClickInputEvent event) {
if (relocatingTrain == null)
return;
@ -75,18 +81,16 @@ public class TrainRelocator {
return;
Train relocating = getRelocating(mc.level);
if (relocating != null) {
Boolean relocate = relocateClient(relocating, false); // TODO send packet
if (relocate != null && relocate.booleanValue()) {
Boolean relocate = relocateClient(relocating, false);
if (relocate != null && relocate.booleanValue())
relocatingTrain = null;
player.displayClientMessage(Lang.translate("train.relocate.success")
.withStyle(ChatFormatting.GREEN), true);
}
if (relocate != null)
event.setCanceled(true);
}
}
@Nullable
@OnlyIn(Dist.CLIENT)
public static Boolean relocateClient(Train relocating, boolean simulate) {
Minecraft mc = Minecraft.getInstance();
HitResult hitResult = mc.hitResult;
@ -103,12 +107,16 @@ public class TrainRelocator {
BlockState blockState = mc.level.getBlockState(blockPos);
if (!(blockState.getBlock()instanceof ITrackBlock track))
return lastHoveredResult = null;
return lastHoveredResult = relocate(relocating, mc.player, blockPos, simulate);
Vec3 lookAngle = mc.player.getLookAngle();
boolean result = relocate(relocating, mc.level, blockPos, lookAngle, true);
if (!simulate && result)
AllPackets.channel
.sendToServer(new TrainRelocationPacket(relocatingTrain, blockPos, lookAngle, relocatingEntityId));
return lastHoveredResult = result;
}
public static boolean relocate(Train train, Player player, BlockPos pos, boolean simulate) {
Vec3 lookAngle = player.getLookAngle();
Level level = player.getLevel();
public static boolean relocate(Train train, Level level, BlockPos pos, Vec3 lookAngle, boolean simulate) {
BlockState blockState = level.getBlockState(pos);
if (!(blockState.getBlock()instanceof ITrackBlock track))
return false;
@ -179,6 +187,7 @@ public class TrainRelocator {
return true;
}
@OnlyIn(Dist.CLIENT)
public static void clientTick() {
Minecraft mc = Minecraft.getInstance();
LocalPlayer player = mc.player;
@ -197,7 +206,10 @@ public class TrainRelocator {
return;
}
if (Math.abs(relocating.speed) > 1 / 1024d) {
Entity entity = mc.level.getEntity(relocatingEntityId);
if (entity instanceof AbstractContraptionEntity ce && Math.abs(ce.getPosition(0)
.subtract(ce.getPosition(1))
.lengthSqr()) > 1 / 1024d) {
player.displayClientMessage(Lang.translate("train.cannot_relocate_moving")
.withStyle(ChatFormatting.RED), true);
relocatingTrain = null;
@ -252,16 +264,19 @@ public class TrainRelocator {
}
}
@OnlyIn(Dist.CLIENT)
public static boolean carriageWrenched(Vec3 vec3, CarriageContraptionEntity entity) {
Train train = getTrainFromEntity(entity);
if (train != null && !train.heldForAssembly) {
relocatingOrigin = vec3;
relocatingTrain = train.id;
relocatingEntityId = entity.getId();
return true;
}
return false;
}
@OnlyIn(Dist.CLIENT)
public static boolean addToTooltip(List<Component> tooltip, boolean shiftKeyDown) {
Train train = getTrainFromEntity(hoveredEntity.get());
if (train != null && train.derailed) {
@ -271,6 +286,7 @@ public class TrainRelocator {
return false;
}
@OnlyIn(Dist.CLIENT)
private static Train getRelocating(LevelAccessor level) {
return relocatingTrain == null ? null : Create.RAILWAYS.sided(level).trains.get(relocatingTrain);
}

View file

@ -175,7 +175,7 @@ public class StationScreen extends AbstractStationScreen {
}
boolean trainAtStation = trainPresent();
disassembleTrainButton.active = trainAtStation; // TODO te.canAssemble
disassembleTrainButton.active = trainAtStation && te.trainCanDisassemble;
openScheduleButton.active = train.runtime.getSchedule() != null;
float f = trainAtStation ? 0 : (float) (train.navigation.distanceToDestination / 30f);
@ -221,7 +221,7 @@ public class StationScreen extends AbstractStationScreen {
offset += icon.render(TrainIconType.FLIPPED_ENGINE, ms, x + offset, y + 20) + 1;
continue;
}
Carriage carriage = carriages.get(train.currentlyBackwards ? carriages.size() - i - 1 : i);
Carriage carriage = carriages.get(te.trainBackwards ? carriages.size() - i - 1 : i);
offset += icon.render(carriage.bogeySpacing, ms, x + offset, y + 20) + 1;
}

View file

@ -62,6 +62,8 @@ public class StationTileEntity extends SmartTileEntity {
// for display
UUID imminentTrain;
boolean trainPresent;
boolean trainBackwards;
boolean trainCanDisassemble;
public StationTileEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
@ -88,11 +90,15 @@ public class StationTileEntity extends SmartTileEntity {
if (!tag.contains("ImminentTrain")) {
imminentTrain = null;
trainPresent = false;
trainCanDisassemble = false;
trainBackwards = false;
return;
}
imminentTrain = tag.getUUID("ImminentTrain");
trainPresent = tag.getBoolean("TrainPresent");
trainCanDisassemble = tag.getBoolean("TrainCanDisassemble");
trainBackwards = tag.getBoolean("TrainBackwards");
}
@Override
@ -103,15 +109,13 @@ public class StationTileEntity extends SmartTileEntity {
if (!clientPacket)
return;
GlobalStation station = getStation();
if (station == null)
return;
Train imminentTrain = station.getImminentTrain();
if (imminentTrain == null)
return;
tag.putUUID("ImminentTrain", imminentTrain.id);
tag.putBoolean("TrainPresent", imminentTrain.getCurrentStation() == station);
tag.putUUID("ImminentTrain", imminentTrain);
tag.putBoolean("TrainPresent", trainPresent);
tag.putBoolean("TrainCanDisassemble", trainCanDisassemble);
tag.putBoolean("TrainBackwards", trainBackwards);
}
@Nullable
@ -156,6 +160,8 @@ public class StationTileEntity extends SmartTileEntity {
if (this.trainPresent != trainPresent || !Objects.equals(imminentID, this.imminentTrain)) {
this.imminentTrain = imminentID;
this.trainPresent = trainPresent;
this.trainCanDisassemble = trainPresent && imminentTrain.canDisassemble();
this.trainBackwards = imminentTrain != null && imminentTrain.currentlyBackwards;
sendData();
}
}

View file

@ -45,6 +45,7 @@ import com.simibubi.create.content.logistics.packet.FunnelFlapPacket;
import com.simibubi.create.content.logistics.packet.TunnelFlapPacket;
import com.simibubi.create.content.logistics.trains.TrackGraphSyncPacket;
import com.simibubi.create.content.logistics.trains.entity.TrainPacket;
import com.simibubi.create.content.logistics.trains.entity.TrainRelocationPacket;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalEdgeGroupPacket;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationEditPacket;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.TrainEditPacket;
@ -113,6 +114,7 @@ public enum AllPackets {
CONFIGURE_SCHEDULE(ScheduleEditPacket.class, ScheduleEditPacket::new, PLAY_TO_SERVER),
CONFIGURE_STATION(StationEditPacket.class, StationEditPacket::new, PLAY_TO_SERVER),
C_CONFIGURE_TRAIN(TrainEditPacket.class, TrainEditPacket::new, PLAY_TO_SERVER),
RELOCATE_TRAIN(TrainRelocationPacket.class, TrainRelocationPacket::new, PLAY_TO_SERVER),
// Server to Client
SYMMETRY_EFFECT(SymmetryEffectPacket.class, SymmetryEffectPacket::new, PLAY_TO_CLIENT),