TCP Handbrake

- Manual Train Controls now use network packets
- Carriages now properly re-introduce their passengers when entering ticking chunks
- Fixed approach station prompt no longer appearing
- Fixed players shunted to 0,0 when seated while train assembles
- Fixed relocator not using client-side graph when testing validity
- Fixed entity data not synched properly from dedicated servers
- Fixed controls storing state in behaviour class
- Fixed carriages not serialising conductor seat data correctly
This commit is contained in:
simibubi 2022-03-09 02:21:20 +01:00
parent fa47939428
commit ed6712fd0b
20 changed files with 417 additions and 139 deletions

View file

@ -6,8 +6,11 @@ import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.tuple.MutablePair;
@ -21,6 +24,7 @@ import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.components.actors.SeatBlock;
import com.simibubi.create.content.contraptions.components.actors.SeatEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.glue.SuperGlueEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsStopControllingPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.mounted.MountedContraption;
import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionSeatMappingPacket;
import com.simibubi.create.foundation.collision.Matrix3d;
@ -39,6 +43,7 @@ import net.minecraft.network.protocol.Packet;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
@ -63,6 +68,8 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
private static final EntityDataAccessor<Boolean> STALLED =
SynchedEntityData.defineId(AbstractContraptionEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Optional<UUID>> CONTROLLED_BY =
SynchedEntityData.defineId(AbstractContraptionEntity.class, EntityDataSerializers.OPTIONAL_UUID);
public final Map<Entity, MutableInt> collidingEntities;
@ -178,6 +185,14 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
return getName();
}
public Optional<UUID> getControllingPlayer() {
return entityData.get(CONTROLLED_BY);
}
public void setControllingPlayer(@Nullable UUID playerId) {
entityData.set(CONTROLLED_BY, Optional.ofNullable(playerId));
}
public boolean startControlling(BlockPos controlsLocalPos, Player player) {
return false;
}
@ -186,7 +201,13 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
return true;
}
public void stopControlling(BlockPos controlsLocalPos) {}
public void stopControlling(BlockPos controlsLocalPos) {
getControllingPlayer().map(level::getPlayerByUUID)
.map(p -> (p instanceof ServerPlayer) ? ((ServerPlayer) p) : null)
.ifPresent(p -> AllPackets.channel.send(PacketDistributor.PLAYER.with(() -> p),
new ControlsStopControllingPacket()));
setControllingPlayer(null);
}
public boolean handlePlayerInteraction(Player player, BlockPos localPos, Direction side,
InteractionHand interactionHand) {
@ -403,6 +424,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
@Override
protected void defineSynchedData() {
this.entityData.define(STALLED, false);
this.entityData.define(CONTROLLED_BY, Optional.empty());
}
@Override

View file

@ -113,8 +113,7 @@ public class OrientedContraptionEntity extends AbstractContraptionEntity {
}
public float getInitialYaw() {
return (isInitialOrientationPresent() ? entityData.get(INITIAL_ORIENTATION) : Direction.SOUTH)
.toYRot();
return (isInitialOrientationPresent() ? entityData.get(INITIAL_ORIENTATION) : Direction.SOUTH).toYRot();
}
@Override
@ -177,8 +176,7 @@ public class OrientedContraptionEntity extends AbstractContraptionEntity {
super.writeAdditional(compound, spawnPacket);
if (motionBeforeStall != null)
compound.put("CachedMotion",
newDoubleList(motionBeforeStall.x, motionBeforeStall.y, motionBeforeStall.z));
compound.put("CachedMotion", newDoubleList(motionBeforeStall.x, motionBeforeStall.y, motionBeforeStall.z));
Direction optional = entityData.get(INITIAL_ORIENTATION);
if (optional.getAxis()
@ -200,7 +198,7 @@ public class OrientedContraptionEntity extends AbstractContraptionEntity {
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> key) {
super.onSyncedDataUpdated(key);
if (key == INITIAL_ORIENTATION && isInitialOrientationPresent() && !manuallyPlaced)
if (INITIAL_ORIENTATION.equals(key) && isInitialOrientationPresent() && !manuallyPlaced)
startAtInitialYaw();
}

View file

@ -5,19 +5,14 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Vector;
import org.lwjgl.glfw.GLFW;
import com.mojang.blaze3d.platform.InputConstants;
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.ControlsUtil;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player;
public class ControlsHandler {
@ -29,33 +24,28 @@ public class ControlsHandler {
static WeakReference<AbstractContraptionEntity> entityRef = new WeakReference<>(null);
static BlockPos controlsPos;
public static void controllerClicked(AbstractContraptionEntity entity, BlockPos controllerLocalPos, Player player) {
AbstractContraptionEntity prevEntity = entityRef.get();
if (prevEntity != null) {
stopControlling();
if (prevEntity == entity)
return;
}
if (!entity.startControlling(controllerLocalPos, player))
return;
public static void startControlling(AbstractContraptionEntity entity, BlockPos controllerLocalPos) {
entityRef = new WeakReference<AbstractContraptionEntity>(entity);
controlsPos = controllerLocalPos;
Minecraft.getInstance().player.displayClientMessage(
Lang.translate("contraption.controls.start_controlling", entity.getContraptionName()), true);
}
public static void stopControlling() {
AbstractContraptionEntity abstractContraptionEntity = entityRef.get();
if (abstractContraptionEntity != null)
abstractContraptionEntity.stopControlling(controlsPos);
ControlsUtil.getControls()
.forEach(kb -> kb.setDown(ControlsUtil.isActuallyPressed(kb)));
AbstractContraptionEntity abstractContraptionEntity = entityRef.get();
if (!currentlyPressed.isEmpty() && abstractContraptionEntity != null)
AllPackets.channel.sendToServer(
new ControlsInputPacket(currentlyPressed, false, abstractContraptionEntity.getId(), controlsPos));
packetCooldown = 0;
entityRef = new WeakReference<>(null);
controlsPos = null;
// if (!currentlyPressed.isEmpty())
// AllPackets.channel.sendToServer(new LinkedControllerInputPacket(currentlyPressed, false));
currentlyPressed.clear();
Minecraft.getInstance().player.displayClientMessage(Lang.translate("contraption.controls.stop_controlling"),
true);
}
@ -67,24 +57,6 @@ public class ControlsHandler {
if (packetCooldown > 0)
packetCooldown--;
Minecraft mc = Minecraft.getInstance();
LocalPlayer player = mc.player;
if (player.isSpectator()) {
stopControlling();
return;
}
if (InputConstants.isKeyDown(mc.getWindow()
.getWindow(), GLFW.GLFW_KEY_ESCAPE)) {
stopControlling();
return;
}
if (!entity.toGlobalVector(VecHelper.getCenterOf(controlsPos), 1)
.closerThan(player.position(), 10)) {
stopControlling();
return;
}
Vector<KeyMapping> controls = ControlsUtil.getControls();
Collection<Integer> pressedKeys = new HashSet<>();
for (int i = 0; i < controls.size(); i++) {
@ -99,34 +71,24 @@ public class ControlsHandler {
// Released Keys
if (!releasedKeys.isEmpty()) {
// AllPackets.channel.sendToServer(new LinkedControllerInputPacket(releasedKeys, false, lecternPos));
AllPackets.channel.sendToServer(new ControlsInputPacket(releasedKeys, false, entity.getId(), controlsPos));
// AllSoundEvents.CONTROLLER_CLICK.playAt(player.level, player.blockPosition(), 1f, .5f, true);
}
// Newly Pressed Keys
if (!newKeys.isEmpty()) {
if (newKeys.contains(Integer.valueOf(5))) {
stopControlling();
return;
}
// AllPackets.channel.sendToServer(new LinkedControllerInputPacket(newKeys, true, lecternPos));
// packetCooldown = PACKET_RATE;
AllPackets.channel.sendToServer(new ControlsInputPacket(newKeys, true, entity.getId(), controlsPos));
packetCooldown = PACKET_RATE;
// AllSoundEvents.CONTROLLER_CLICK.playAt(player.level, player.blockPosition(), 1f, .75f, true);
}
// Keepalive Pressed Keys
if (packetCooldown == 0) {
// if (!pressedKeys.isEmpty()) {
// AllPackets.channel.sendToServer(new LinkedControllerInputPacket(pressedKeys, true, lecternPos));
// packetCooldown = PACKET_RATE;
// }
if (!pressedKeys.isEmpty()) {
AllPackets.channel
.sendToServer(new ControlsInputPacket(pressedKeys, true, entity.getId(), controlsPos));
packetCooldown = PACKET_RATE;
}
// TODO do this server side
if (!entity.control(controlsPos, pressedKeys, player)) {
stopControlling();
return;
}
currentlyPressed = pressedKeys;

View file

@ -0,0 +1,76 @@
package com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import java.util.function.Supplier;
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
import com.simibubi.create.foundation.networking.SimplePacketBase;
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.level.Level;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.NetworkEvent.Context;
public class ControlsInputPacket extends SimplePacketBase {
private Collection<Integer> activatedButtons;
private boolean press;
private int contraptionEntityId;
private BlockPos controlsPos;
public ControlsInputPacket(Collection<Integer> activatedButtons, boolean press, int contraptionEntityId,
BlockPos controlsPos) {
this.contraptionEntityId = contraptionEntityId;
this.activatedButtons = activatedButtons;
this.press = press;
this.controlsPos = controlsPos;
}
public ControlsInputPacket(FriendlyByteBuf buffer) {
contraptionEntityId = buffer.readInt();
activatedButtons = new ArrayList<>();
press = buffer.readBoolean();
int size = buffer.readVarInt();
for (int i = 0; i < size; i++)
activatedButtons.add(buffer.readVarInt());
controlsPos = buffer.readBlockPos();
}
@Override
public void write(FriendlyByteBuf buffer) {
buffer.writeInt(contraptionEntityId);
buffer.writeBoolean(press);
buffer.writeVarInt(activatedButtons.size());
activatedButtons.forEach(buffer::writeVarInt);
buffer.writeBlockPos(controlsPos);
}
@Override
public void handle(Supplier<Context> context) {
Context ctx = context.get();
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
Level world = player.getCommandSenderWorld();
UUID uniqueID = player.getUUID();
if (player.isSpectator() && press)
return;
Entity entity = world.getEntity(contraptionEntityId);
if (!(entity instanceof AbstractContraptionEntity ace))
return;
if (ace.toGlobalVector(Vec3.atCenterOf(controlsPos), 0)
.distanceTo(player.position()) > 10)
return;
ControlsServerHandler.receivePressed(world, ace, controlsPos, uniqueID, activatedButtons, press);
});
ctx.setPacketHandled(true);
}
}

View file

@ -1,5 +1,8 @@
package com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls;
import java.util.UUID;
import com.google.common.base.Objects;
import com.simibubi.create.AllItems;
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.MovingInteractionBehaviour;
@ -7,6 +10,8 @@ import com.simibubi.create.content.contraptions.components.structureMovement.Mov
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
public class ControlsInteractionBehaviour extends MovingInteractionBehaviour {
@ -15,8 +20,23 @@ public class ControlsInteractionBehaviour extends MovingInteractionBehaviour {
AbstractContraptionEntity contraptionEntity) {
if (AllItems.WRENCH.isIn(player.getItemInHand(activeHand)))
return false;
UUID currentlyControlling = contraptionEntity.getControllingPlayer()
.orElse(null);
if (currentlyControlling != null) {
contraptionEntity.stopControlling(localPos);
if (Objects.equal(currentlyControlling, player.getUUID()))
return true;
}
if (!contraptionEntity.startControlling(localPos, player))
return false;
contraptionEntity.setControllingPlayer(player.getUUID());
if (player.level.isClientSide)
ControlsHandler.controllerClicked(contraptionEntity, localPos, player);
DistExecutor.unsafeRunWhenOn(Dist.CLIENT,
() -> () -> ControlsHandler.startControlling(contraptionEntity, localPos));
return true;
}

View file

@ -3,50 +3,80 @@ package com.simibubi.create.content.contraptions.components.structureMovement.in
import java.util.Collection;
import com.jozufozu.flywheel.core.virtual.VirtualRenderWorld;
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.MovementContext;
import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionMatrices;
import com.simibubi.create.content.logistics.trains.entity.Carriage;
import com.simibubi.create.content.logistics.trains.entity.CarriageContraptionEntity;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.animation.LerpedFloat;
import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.core.Direction;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
public class ControlsMovementBehaviour extends MovementBehaviour {
// TODO: this is specific to Carriage Contraptions - need to move this behaviour
// there
// TODO: rendering the levers should be specific to Carriage Contraptions -
static class LeverAngles {
LerpedFloat steering = LerpedFloat.linear();
LerpedFloat speed = LerpedFloat.linear();
LerpedFloat equipAnimation = LerpedFloat.linear();
}
@Override
public void tick(MovementContext context) {
steering.tickChaser();
speed.tickChaser();
equipAnimation.tickChaser();
super.tick(context);
if (!context.world.isClientSide)
return;
if (!(context.temporaryData instanceof LeverAngles))
context.temporaryData = new LeverAngles();
LeverAngles angles = (LeverAngles) context.temporaryData;
angles.steering.tickChaser();
angles.speed.tickChaser();
angles.equipAnimation.tickChaser();
}
@Override
@OnlyIn(Dist.CLIENT)
public void renderInContraption(MovementContext context, VirtualRenderWorld renderWorld,
ContraptionMatrices matrices, MultiBufferSource buffer) {
if (ControlsHandler.entityRef.get() == context.contraption.entity && ControlsHandler.controlsPos != null
if (!(context.temporaryData instanceof LeverAngles angles))
return;
AbstractContraptionEntity entity = context.contraption.entity;
if (!(entity instanceof CarriageContraptionEntity cce))
return;
StructureBlockInfo info = context.contraption.getBlocks()
.get(context.localPos);
Direction initialOrientation = cce.getInitialOrientation()
.getCounterClockWise();
boolean inverted = false;
if (info != null && info.state.hasProperty(ControlsBlock.FACING))
inverted = !info.state.getValue(ControlsBlock.FACING)
.equals(initialOrientation);
if (ControlsHandler.entityRef.get() == entity && ControlsHandler.controlsPos != null
&& ControlsHandler.controlsPos.equals(context.localPos)) {
Collection<Integer> pressed = ControlsHandler.currentlyPressed;
equipAnimation.chase(1, .2f, Chaser.EXP);
steering.chase((pressed.contains(3) ? 1 : 0) + (pressed.contains(2) ? -1 : 0), 0.2f, Chaser.EXP);
speed.chase(0, 0.2f, Chaser.EXP); // TODO
} else
equipAnimation.chase(0, .2f, Chaser.EXP);
angles.equipAnimation.chase(1, .2f, Chaser.EXP);
angles.steering.chase((pressed.contains(3) ? 1 : 0) + (pressed.contains(2) ? -1 : 0), 0.2f, Chaser.EXP);
float f = cce.movingBackwards ^ inverted ? -1 : 1;
angles.speed.chase(Math.min(context.motion.length(), 0.5f) * f, 0.2f, Chaser.EXP);
} else {
angles.equipAnimation.chase(0, .2f, Chaser.EXP);
angles.steering.chase(0, 0, Chaser.EXP);
angles.speed.chase(0, 0, Chaser.EXP);
}
float pt = AnimationTickHolder.getPartialTicks(context.world);
ControlsRenderer.render(context, renderWorld, matrices, buffer, equipAnimation.getValue(pt), speed.getValue(pt),
steering.getValue(pt));
ControlsRenderer.render(context, renderWorld, matrices, buffer, angles.equipAnimation.getValue(pt),
angles.speed.getValue(pt), angles.steering.getValue(pt));
}
}

View file

@ -0,0 +1,110 @@
package com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
import com.simibubi.create.foundation.utility.IntAttached;
import com.simibubi.create.foundation.utility.WorldAttached;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.LevelAccessor;
public class ControlsServerHandler {
public static WorldAttached<Map<UUID, ControlsContext>> receivedInputs = new WorldAttached<>($ -> new HashMap<>());
static final int TIMEOUT = 30;
public static void tick(LevelAccessor world) {
Map<UUID, ControlsContext> map = receivedInputs.get(world);
for (Iterator<Entry<UUID, ControlsContext>> iterator = map.entrySet()
.iterator(); iterator.hasNext();) {
Entry<UUID, ControlsContext> entry = iterator.next();
ControlsContext ctx = entry.getValue();
Collection<ManuallyPressedKey> list = ctx.keys;
for (Iterator<ManuallyPressedKey> entryIterator = list.iterator(); entryIterator.hasNext();) {
ManuallyPressedKey pressedKey = entryIterator.next();
pressedKey.decrement();
if (!pressedKey.isAlive())
entryIterator.remove(); // key released
}
if (!ctx.entity.control(ctx.controlsLocalPos, list.stream()
.map(ManuallyPressedKey::getSecond)
.toList(), world.getPlayerByUUID(entry.getKey()))) {
ctx.entity.stopControlling(ctx.controlsLocalPos);
}
if (list.isEmpty())
iterator.remove();
}
}
public static void receivePressed(LevelAccessor world, AbstractContraptionEntity entity, BlockPos controlsPos,
UUID uniqueID, Collection<Integer> collect, boolean pressed) {
Map<UUID, ControlsContext> map = receivedInputs.get(world);
if (map.containsKey(uniqueID) && map.get(uniqueID).entity != entity)
map.remove(uniqueID);
ControlsContext ctx = map.computeIfAbsent(uniqueID, $ -> new ControlsContext(entity, controlsPos));
Collection<ManuallyPressedKey> list = ctx.keys;
WithNext: for (Integer activated : collect) {
for (Iterator<ManuallyPressedKey> iterator = list.iterator(); iterator.hasNext();) {
ManuallyPressedKey entry = iterator.next();
Integer inputType = entry.getSecond();
if (inputType.equals(activated)) {
if (!pressed)
entry.setFirst(0);
else
entry.keepAlive();
continue WithNext;
}
}
if (!pressed)
continue;
list.add(new ManuallyPressedKey(activated)); // key newly pressed
}
}
static class ControlsContext {
Collection<ManuallyPressedKey> keys;
AbstractContraptionEntity entity;
BlockPos controlsLocalPos;
public ControlsContext(AbstractContraptionEntity entity, BlockPos controlsPos) {
this.entity = entity;
controlsLocalPos = controlsPos;
keys = new ArrayList<>();
}
}
static class ManuallyPressedKey extends IntAttached<Integer> {
public ManuallyPressedKey(Integer second) {
super(TIMEOUT, second);
}
public void keepAlive() {
setFirst(TIMEOUT);
}
public boolean isAlive() {
return getFirst() > 0;
}
}
}

View file

@ -0,0 +1,27 @@
package com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls;
import java.util.function.Supplier;
import com.simibubi.create.foundation.networking.SimplePacketBase;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent.Context;
public class ControlsStopControllingPacket extends SimplePacketBase {
public ControlsStopControllingPacket() {}
public ControlsStopControllingPacket(FriendlyByteBuf buffer) {}
@Override
public void write(FriendlyByteBuf buffer) {}
@Override
public void handle(Supplier<Context> context) {
context.get()
.enqueueWork(ControlsHandler::stopControlling);
context.get()
.setPacketHandled(true);
}
}

View file

@ -57,7 +57,7 @@ public class DoubleFaceAttachedBlock extends HorizontalDirectionalBlock {
face = DoubleAttachFace.WALL_REVERSED;
}
blockstate = this.defaultBlockState()
.setValue(FACE, face) // TODO wall reversed
.setValue(FACE, face)
.setValue(FACING, direction.getOpposite());
}

View file

@ -157,12 +157,12 @@ public class GlobalRailwayManager {
for (Train train : trains.values())
train.tick(level);
if (AllKeys.isKeyDown(GLFW.GLFW_KEY_H) && AllKeys.altDown())
trackNetworks.values()
.forEach(TrackGraph::debugViewSignalData);
if (AllKeys.isKeyDown(GLFW.GLFW_KEY_J) && AllKeys.altDown())
trackNetworks.values()
.forEach(TrackGraph::debugViewNodes);
// if (AllKeys.isKeyDown(GLFW.GLFW_KEY_H) && AllKeys.altDown())
// trackNetworks.values()
// .forEach(TrackGraph::debugViewSignalData);
// if (AllKeys.isKeyDown(GLFW.GLFW_KEY_J) && AllKeys.altDown())
// trackNetworks.values()
// .forEach(TrackGraph::debugViewNodes);
}
public void clientTick() {

View file

@ -451,7 +451,7 @@ public class TrackGraph {
EdgeData signalData = edge.getEdgeData();
UUID singleGroup = signalData.singleSignalGroup;
SignalEdgeGroup signalEdgeGroup =
singleGroup == null ? null : Create.RAILWAYS.signalEdgeGroups.get(singleGroup);
singleGroup == null ? null : Create.RAILWAYS.sided(null).signalEdgeGroups.get(singleGroup);
if (!edge.isTurn()) {
Vec3 p1 = edge.getPosition(node, other, 0);
@ -479,7 +479,7 @@ public class TrackGraph {
continue;
prevBoundary = boundary;
group = Create.RAILWAYS.signalEdgeGroups.get(boundary.getGroup(node));
group = Create.RAILWAYS.sided(null).signalEdgeGroups.get(boundary.getGroup(node));
if (group != null)
CreateClient.OUTLINER
@ -496,7 +496,7 @@ public class TrackGraph {
}
if (prevBoundary != null) {
group = Create.RAILWAYS.signalEdgeGroups.get(prevBoundary.getGroup(other));
group = Create.RAILWAYS.sided(null).signalEdgeGroups.get(prevBoundary.getGroup(other));
if (group != null)
CreateClient.OUTLINER
.showLine(edge, edge.getPosition(node, other, prev + 1 / 16f / length)

View file

@ -30,7 +30,8 @@ public class TrackGraphHelper {
TrackNodeLocation location = new TrackNodeLocation(Vec3.atBottomCenterOf(pos)
.add(0, track.getElevationAtCenter(level, pos, trackBlockState), 0));
graph = Create.RAILWAYS.getGraph(level, location);
graph = Create.RAILWAYS.sided(level)
.getGraph(level, location);
if (graph != null) {
TrackNode node = graph.locateNode(location);
if (node != null) {
@ -79,7 +80,8 @@ public class TrackGraphHelper {
for (int i = 0; i < 100 && distance < 32; i++) {
DiscoveredLocation loc = current;
if (graph == null)
graph = Create.RAILWAYS.getGraph(level, loc);
graph = Create.RAILWAYS.sided(level)
.getGraph(level, loc);
if (graph == null || graph.locateNode(loc) == null) {
Collection<DiscoveredLocation> list = ITrackBlock.walkConnectedTracks(level, loc, true);

View file

@ -1,7 +1,8 @@
package com.simibubi.create.content.logistics.trains.entity;
import java.lang.ref.WeakReference;
import java.util.Optional;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
@ -19,7 +20,9 @@ import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Entity.RemovalReason;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
@ -65,6 +68,21 @@ public class Carriage {
CarriageContraptionEntity entity = CarriageContraptionEntity.create(level, contraption);
entity.setCarriage(this);
contraption.startMoving(level);
contraption.onEntityInitialize(level, entity);
for (CarriageBogey carriageBogey : bogeys)
if (carriageBogey != null)
carriageBogey.updateAnchorPosition();
alignEntity(entity);
List<Entity> players = new ArrayList<>();
for (Entity passenger : entity.getPassengers())
if (!(passenger instanceof Player))
passenger.remove(RemovalReason.UNLOADED_WITH_PLAYER);
else
players.add(passenger);
for (Entity player : players)
player.stopRiding();
serialisedEntity = entity.serializeNBT();
}
@ -137,16 +155,18 @@ public class Carriage {
}
public void createEntity(Level level) {
Optional<Entity> entityFromData = EntityType.create(serialisedEntity, level);
Entity entity = entityFromData.orElse(null);
Entity entity = EntityType.loadEntityRecursive(serialisedEntity, level, e -> {
level.addFreshEntity(e);
return e;
});
if (!(entity instanceof CarriageContraptionEntity cce))
return;
Vec3 pos = leadingBogey().anchorPosition;
cce.setPos(pos);
cce.setCarriage(this);
cce.setGraph(train.graph == null ? null : train.graph.id);
cce.syncCarriage();
level.addFreshEntity(cce);
this.entity = new WeakReference<>(cce);
}
@ -161,8 +181,12 @@ public class Carriage {
createEntity(level);
} else {
CarriageEntityHandler.validateCarriageEntity(entity);
if (!entity.isAlive()) {
if (!entity.isAlive() || entity.leftTickingChunks) {
for (Entity passenger : entity.getPassengers())
if (!(passenger instanceof Player))
passenger.remove(RemovalReason.UNLOADED_WITH_PLAYER);
serialisedEntity = entity.serializeNBT();
entity.discard();
this.entity.clear();
return;
}
@ -195,13 +219,6 @@ public class Carriage {
entity.pitch = (float) (Math.atan2(diffY, Math.sqrt(diffX * diffX + diffZ * diffZ)) * 180 / Math.PI) * -1;
}
public void discardEntity() {
CarriageContraptionEntity entity = this.entity.get();
if (entity == null)
return;
entity.discard();
}
public TravellingPoint getLeadingPoint() {
return leadingBogey().leading();
}

View file

@ -25,6 +25,7 @@ import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.Level;
@ -138,7 +139,7 @@ public class CarriageContraption extends Contraption {
tag.putBoolean("BackControls", backwardControls);
tag.putBoolean("FrontBlazeConductor", blazeBurnerConductors.getFirst());
tag.putBoolean("BackBlazeConductor", blazeBurnerConductors.getSecond());
NBTHelper.writeCompoundList(conductorSeats.entrySet(), e -> {
ListTag list = NBTHelper.writeCompoundList(conductorSeats.entrySet(), e -> {
CompoundTag compoundTag = new CompoundTag();
compoundTag.put("Pos", NbtUtils.writeBlockPos(e.getKey()));
compoundTag.putBoolean("Forward", e.getValue()
@ -147,6 +148,7 @@ public class CarriageContraption extends Contraption {
.getSecond());
return compoundTag;
});
tag.put("ConductorSeats", list);
return tag;
}
@ -160,7 +162,7 @@ public class CarriageContraption extends Contraption {
conductorSeats.clear();
NBTHelper.iterateCompoundList(nbt.getList("ConductorSeats", Tag.TAG_COMPOUND),
c -> conductorSeats.put(NbtUtils.readBlockPos(c.getCompound("Pos")),
Couple.create(nbt.getBoolean("Forward"), nbt.getBoolean("Backward"))));
Couple.create(c.getBoolean("Forward"), c.getBoolean("Backward"))));
super.readNBT(world, nbt, spawnData);
}

View file

@ -27,13 +27,13 @@ import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.VecHelper;
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;
import net.minecraft.network.chat.KeybindComponent;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
@ -45,9 +45,6 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.DistExecutor;
public class CarriageContraptionEntity extends OrientedContraptionEntity {
@ -61,6 +58,9 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
private Carriage carriage;
public boolean validForRender;
public boolean movingBackwards;
public boolean leftTickingChunks;
public CarriageContraptionEntity(EntityType<?> type, Level world) {
super(type, world);
@ -95,10 +95,10 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
if (!level.isClientSide)
return;
if (key == TRACK_GRAPH)
if (TRACK_GRAPH.equals(key))
updateTrackGraph();
if (key == CARRIAGE_DATA) {
if (CARRIAGE_DATA.equals(key)) {
CarriageSyncData carriageData = getCarriageData();
if (carriageData == null)
return;
@ -177,8 +177,10 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
Vec3 diff = position().subtract(xo, yo, zo);
Vec3 relativeDiff = VecHelper.rotate(diff, yaw, Axis.Y);
double distanceTo = diff.length() * Math.signum(-relativeDiff.x);
double signum = Math.signum(-relativeDiff.x);
double distanceTo = diff.length() * signum;
movingBackwards = signum < 0;
carriage.bogeys.getFirst()
.updateAngles(distanceTo);
if (carriage.isOnTwoBogeys())
@ -272,6 +274,14 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
return false;
if (carriage.train.derailed)
return false;
if (level.isClientSide)
return true;
if (player.isSpectator())
return false;
if (!toGlobalVector(VecHelper.getCenterOf(controlsLocalPos), 1).closerThan(player.position(), 10))
return false;
if (heldControls.contains(5))
return false;
StructureBlockInfo info = contraption.getBlocks()
.get(controlsLocalPos);
@ -326,8 +336,8 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
new TextComponent(Strings.repeat("|", progress) + (arrived ? " ->" : " <-"));
TextComponent greenComponent =
new TextComponent((arrived ? "<- " : "-> ") + Strings.repeat("|", 30 - progress));
int mixedColor = Color.mixColors(0xff_91EA44, 0xff_FFC244, progress / 30f);
int targetColor = arrived ? 0xff_91EA44 : 0xff_ffffff;
int mixedColor = Color.mixColors(0x00_91EA44, 0x00_FFC244, progress / 30f);
int targetColor = arrived ? 0x00_91EA44 : 0x00_ffffff;
player.displayClientMessage(greenComponent.withStyle(st -> st.withColor(mixedColor))
.append(whiteComponent.withStyle(st -> st.withColor(targetColor))), true);
return true;
@ -344,12 +354,9 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
navDistanceTotal = nav.distanceToDestination;
return true;
}
if (level.isClientSide)
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> displayApproachStationMessage(lookAhead));
} else {
if (level.isClientSide)
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> this::cleanUpApproachStationMessage);
}
displayApproachStationMessage(player, lookAhead);
} else
cleanUpApproachStationMessage(player);
}
carriage.train.manualSteer =
@ -366,20 +373,17 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
boolean stationMessage = false;
@OnlyIn(Dist.CLIENT)
private void displayApproachStationMessage(GlobalStation station) {
Minecraft instance = Minecraft.getInstance();
instance.player.displayClientMessage(Lang.translate("contraption.controls.approach_station",
instance.options.keyJump.getTranslatedKeyMessage(), station.name), true);
private void displayApproachStationMessage(Player player, GlobalStation station) {
player.displayClientMessage(
Lang.translate("contraption.controls.approach_station", new KeybindComponent("key.jump"), station.name),
true);
stationMessage = true;
}
@OnlyIn(Dist.CLIENT)
private void cleanUpApproachStationMessage() {
private void cleanUpApproachStationMessage(Player player) {
if (!stationMessage)
return;
Minecraft instance = Minecraft.getInstance();
instance.player.displayClientMessage(new TextComponent(""), true);
player.displayClientMessage(new TextComponent(""), true);
stationMessage = false;
}

View file

@ -16,14 +16,14 @@ public class CarriageEntityHandler {
if (!event.didChunkChange())
return;
Entity entity = event.getEntity();
if (!(entity instanceof CarriageContraptionEntity))
if (!(entity instanceof CarriageContraptionEntity cce))
return;
SectionPos newPos = event.getNewPos();
Level level = entity.getLevel();
if (level.isClientSide)
return;
if (!isActiveChunk(level, newPos.chunk()))
entity.discard();
cce.leftTickingChunks = true;
}
public static void validateCarriageEntity(CarriageContraptionEntity entity) {
@ -33,7 +33,7 @@ public class CarriageEntityHandler {
if (level.isClientSide)
return;
if (!isActiveChunk(level, entity.chunkPosition()))
entity.discard();
entity.leftTickingChunks = true;
}
public static boolean isActiveChunk(Level level, ChunkPos chunk) {

View file

@ -155,12 +155,14 @@ public class StationTileEntity extends SmartTileEntity {
Train imminentTrain = station.getImminentTrain();
boolean trainPresent = imminentTrain != null && imminentTrain.getCurrentStation() == station;
boolean canDisassemble = trainPresent && imminentTrain.canDisassemble();
UUID imminentID = imminentTrain != null ? imminentTrain.id : null;
if (this.trainPresent != trainPresent || !Objects.equals(imminentID, this.imminentTrain)) {
if (this.trainPresent != trainPresent || this.trainCanDisassemble != canDisassemble
|| !Objects.equals(imminentID, this.imminentTrain)) {
this.imminentTrain = imminentID;
this.trainPresent = trainPresent;
this.trainCanDisassemble = trainPresent && imminentTrain.canDisassemble();
this.trainCanDisassemble = canDisassemble;
this.trainBackwards = imminentTrain != null && imminentTrain.currentlyBackwards;
sendData();
}

View file

@ -3,6 +3,7 @@ package com.simibubi.create.events;
import com.simibubi.create.AllFluids;
import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionHandler;
import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsServerHandler;
import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingPhysics;
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.CapabilityMinecartController;
import com.simibubi.create.content.contraptions.fluids.recipe.FluidTransferRecipes;
@ -114,6 +115,7 @@ public class CommonEvents {
CapabilityMinecartController.tick(world);
CouplingPhysics.tick(world);
LinkedControllerServerHandler.tick(world);
ControlsServerHandler.tick(world);
Create.RAILWAYS.tick(world);
}

View file

@ -6,7 +6,7 @@ import java.util.UUID;
import java.util.function.BiConsumer;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.simibubi.create.CreateClient;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.GlobalRailwayManager;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.entity.Train;
@ -40,7 +40,7 @@ public class DumpRailwaysCommand {
}
static void fillReport(ServerLevel level, BiConsumer<String, Integer> chat) {
GlobalRailwayManager railways = CreateClient.RAILWAYS;
GlobalRailwayManager railways = Create.RAILWAYS;
int white = ChatFormatting.WHITE.getColor();
int blue = 0xD3DEDC;
int darkBlue = 0x92A9BD;

View file

@ -12,6 +12,8 @@ import com.simibubi.create.content.contraptions.components.structureMovement.Con
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionStallPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.gantry.GantryContraptionUpdatePacket;
import com.simibubi.create.content.contraptions.components.structureMovement.glue.GlueEffectPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsInputPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsStopControllingPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.sync.ClientMotionPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionFluidPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionInteractionPacket;
@ -115,6 +117,7 @@ public enum AllPackets {
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),
CONTROLS_INPUT(ControlsInputPacket.class, ControlsInputPacket::new, PLAY_TO_SERVER),
// Server to Client
SYMMETRY_EFFECT(SymmetryEffectPacket.class, SymmetryEffectPacket::new, PLAY_TO_CLIENT),
@ -142,6 +145,7 @@ public enum AllPackets {
SYNC_TRAIN(TrainPacket.class, TrainPacket::new, PLAY_TO_CLIENT),
REMOVE_TE(RemoveTileEntityPacket.class, RemoveTileEntityPacket::new, PLAY_TO_CLIENT),
S_CONFIGURE_TRAIN(TrainEditReturnPacket.class, TrainEditReturnPacket::new, PLAY_TO_CLIENT),
CONTROLS_ABORT(ControlsStopControllingPacket.class, ControlsStopControllingPacket::new, PLAY_TO_CLIENT),
;