Interdimensional Railways

- Trackgraphs and trains now properly differentiate between the dimensions they are located in
- Tracks placed next to a nether portal will attempt to connect to a second track piece in the nether
This commit is contained in:
simibubi 2022-05-15 21:19:02 +02:00
parent e281512787
commit 32da097c26
68 changed files with 2182 additions and 524 deletions

View file

@ -486,7 +486,7 @@ f385988cb6fa9c48b5d59a6942ec50ed2b60c8bf assets/create/blockstates/stockpile_swi
e815bfd854c2653f10828bb11950f7fb991d7efc assets/create/blockstates/stressometer.json
8b0c2c7ac72529565b3339aa8df7565858100afa assets/create/blockstates/tiled_glass.json
a2454400b1cf9889f70aebdc89c52a1be25f543c assets/create/blockstates/tiled_glass_pane.json
85b57776edf426c2f8df6698b2482ea925914a5c assets/create/blockstates/track.json
a64fcf2bee9b49f1448d1ff691bd92d7590a59b0 assets/create/blockstates/track.json
408ae1009ee8bb2f2b83753d5909c53744f7865f assets/create/blockstates/track_signal.json
60609cfbcc9be6f7e41fb493ef3147beb9750b60 assets/create/blockstates/track_station.json
29af21c8d82891139d48d69f0393f612f2b6f8f1 assets/create/blockstates/tuff_pillar.json

View file

@ -30,6 +30,21 @@
"model": "create:block/track/ascending",
"y": 90
},
"shape=tn,turn=false": {
"model": "create:block/track/teleport",
"y": 180
},
"shape=ts,turn=false": {
"model": "create:block/track/teleport"
},
"shape=te,turn=false": {
"model": "create:block/track/teleport",
"y": 270
},
"shape=tw,turn=false": {
"model": "create:block/track/teleport",
"y": 90
},
"shape=cr_o,turn=false": {
"model": "create:block/track/cross_ortho"
},
@ -78,6 +93,21 @@
"model": "create:block/track/ascending",
"y": 90
},
"shape=tn,turn=true": {
"model": "create:block/track/teleport",
"y": 180
},
"shape=ts,turn=true": {
"model": "create:block/track/teleport"
},
"shape=te,turn=true": {
"model": "create:block/track/teleport",
"y": 270
},
"shape=tw,turn=true": {
"model": "create:block/track/teleport",
"y": 90
},
"shape=cr_o,turn=true": {
"model": "create:block/track/cross_ortho"
},

View file

@ -148,6 +148,7 @@ public class AllShapes {
TRACK_ORTHO = shape(TrackVoxelShapes.orthogonal()).forHorizontal(NORTH),
TRACK_ASC = shape(TrackVoxelShapes.ascending()).forHorizontal(SOUTH),
TRACK_DIAG = shape(TrackVoxelShapes.diagonal()).forHorizontal(SOUTH),
TRACK_ORTHO_LONG = shape(TrackVoxelShapes.longOrthogonalZOffset()).forHorizontal(SOUTH),
WHISTLE_BASE = shape(1, 0, 1, 15, 3, 15).add(5, 0, 5, 11, 8, 11)
.forDirectional(UP)

View file

@ -18,6 +18,7 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BubbleColumnBlock;
import net.minecraft.world.level.block.FarmBlock;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.NetherPortalBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
@ -80,6 +81,8 @@ public class PloughMovementBehaviour extends BlockBreakingMovementBehaviour {
return false;
if (state.getBlock() instanceof BubbleColumnBlock)
return false;
if (state.getBlock() instanceof NetherPortalBlock)
return false;
return state.getCollisionShape(world, breakingPos)
.isEmpty();
}

View file

@ -297,7 +297,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
contraption.onEntityTick(level);
tickContraption();
super.tick();
if (!(level instanceof ServerLevelAccessor sl))
return;
@ -362,7 +362,7 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
context.rotation = v -> applyRotation(v, 1);
context.position = actorPosition;
if (!actor.isActive(context))
if (!isActorActive(context, actor))
continue;
if (newPosVisited && !context.stall) {
actor.visitNewPosition(context, gridPosition);
@ -408,6 +408,10 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
contraption.stalled = isStalled();
}
protected boolean isActorActive(MovementContext context, MovementBehaviour actor) {
return actor.isActive(context);
}
protected void onContraptionStalled() {
AllPackets.channel.send(PacketDistributor.TRACKING_ENTITY.with(() -> this),
new ContraptionStallPacket(getId(), getX(), getY(), getZ(), getStalledAngle()));
@ -790,4 +794,8 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit
// Contraptions no longer catch fire
}
public boolean isReadyForRender() {
return initialized;
}
}

View file

@ -5,6 +5,7 @@ import static com.simibubi.create.content.contraptions.components.structureMovem
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -1372,6 +1373,22 @@ public abstract class Contraption {
// return pos.equals(te.getBlockPos());
// }
// }
public Collection<StructureBlockInfo> getRenderedBlocks() {
return blocks.values();
}
public Collection<BlockEntity> getSpecialRenderedTEs() {
return specialRenderedTileEntities;
}
public boolean isHiddenInPortal(BlockPos localPos) {
return false;
}
public Optional<List<AABB>> getSimplifiedEntityColliders() {
return simplifiedEntityColliders;
}
public static class ContraptionInvWrapper extends CombinedInvWrapper {
protected final boolean isExternal;
@ -1392,4 +1409,5 @@ public abstract class Contraption {
return handler instanceof ContraptionInvWrapper && ((ContraptionInvWrapper) handler).isSlotExternal(slot);
}
}
}

View file

@ -116,7 +116,7 @@ public class ContraptionCollider {
// Use simplified bbs when present
final Vec3 motionCopy = motion;
List<AABB> collidableBBs = contraption.simplifiedEntityColliders.orElseGet(() -> {
List<AABB> collidableBBs = contraption.getSimplifiedEntityColliders().orElseGet(() -> {
// Else find 'nearby' individual block shapes to collide with
List<AABB> bbs = new ArrayList<>();
@ -490,6 +490,7 @@ public class ContraptionCollider {
List<VoxelShape> potentialHits = BlockPos.betweenClosedStream(min, max)
.filter(contraption.getBlocks()::containsKey)
.filter(Predicates.not(contraption::isHiddenInPortal))
.map(p -> {
BlockState blockState = contraption.getBlocks()
.get(p).state;

View file

@ -82,7 +82,7 @@ public class ContraptionHandlerClient {
Couple<Vec3> rayInputs = getRayInputs(player);
Vec3 origin = rayInputs.getFirst();
Vec3 target = rayInputs.getSecond();
AABB aabb = new AABB(origin, target).inflate(4);
AABB aabb = new AABB(origin, target).inflate(16);
List<AbstractContraptionEntity> intersectingContraptions =
mc.level.getEntitiesOfClass(AbstractContraptionEntity.class, aabb);
@ -143,6 +143,8 @@ public class ContraptionHandlerClient {
VoxelShape raytraceShape = state.getShape(Minecraft.getInstance().level, BlockPos.ZERO.below());
if (raytraceShape.isEmpty())
return false;
if (contraption.isHiddenInPortal(p))
return false;
BlockHitResult rayTrace = raytraceShape.clip(localOrigin, localTarget, p);
if (rayTrace != null) {
mutableResult.setValue(rayTrace);

View file

@ -47,6 +47,7 @@ import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.portal.PortalInfo;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.entity.IEntityAdditionalSpawnData;
@ -287,6 +288,12 @@ public class SuperGlueEntity extends Entity implements IEntityAdditionalSpawnDat
return getBoundingBox().contains(Vec3.atCenterOf(pos));
}
@Override
public PortalInfo findDimensionEntryPoint(ServerLevel pDestination) {
portalEntrancePos = blockPosition();
return super.findDimensionEntryPoint(pDestination);
}
public void spawnParticles() {
AABB bb = getBoundingBox();
Vec3 origin = new Vec3(bb.minX, bb.minY, bb.minZ);

View file

@ -68,7 +68,7 @@ public class ControlsHandler {
if (packetCooldown > 0)
packetCooldown--;
if (InputConstants.isKeyDown(Minecraft.getInstance()
if (entity.isRemoved() || InputConstants.isKeyDown(Minecraft.getInstance()
.getWindow()
.getWindow(), GLFW.GLFW_KEY_ESCAPE)) {
BlockPos pos = controlsPos;

View file

@ -27,6 +27,12 @@ public class ControlsMovementBehaviour implements MovementBehaviour {
LerpedFloat equipAnimation = LerpedFloat.linear();
}
@Override
public void stopMoving(MovementContext context) {
context.contraption.entity.stopControlling(context.localPos);
MovementBehaviour.super.stopMoving(context);
}
@Override
public void tick(MovementContext context) {
MovementBehaviour.super.tick(context);

View file

@ -13,6 +13,7 @@ import com.simibubi.create.foundation.utility.IntAttached;
import com.simibubi.create.foundation.utility.WorldAttached;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.LevelAccessor;
public class ControlsServerHandler {
@ -29,6 +30,11 @@ public class ControlsServerHandler {
ControlsContext ctx = entry.getValue();
Collection<ManuallyPressedKey> list = ctx.keys;
if (ctx.entity.isRemoved()) {
iterator.remove();
continue;
}
for (Iterator<ManuallyPressedKey> entryIterator = list.iterator(); entryIterator.hasNext();) {
ManuallyPressedKey pressedKey = entryIterator.next();
pressedKey.decrement();
@ -36,9 +42,16 @@ public class ControlsServerHandler {
entryIterator.remove(); // key released
}
Player player = world.getPlayerByUUID(entry.getKey());
if (player == null) {
ctx.entity.stopControlling(ctx.controlsLocalPos);
iterator.remove();
continue;
}
if (!ctx.entity.control(ctx.controlsLocalPos, list.stream()
.map(ManuallyPressedKey::getSecond)
.toList(), world.getPlayerByUUID(entry.getKey()))) {
.toList(), player)) {
ctx.entity.stopControlling(ctx.controlsLocalPos);
}

View file

@ -17,7 +17,7 @@ public abstract class ActorInstance {
this.context = context;
}
public void tick() { }
public void tick() { }
public void beginFrame() { }

View file

@ -28,6 +28,8 @@ public class ContraptionEntityRenderer<C extends AbstractContraptionEntity> exte
return false;
if (!entity.isAlive())
return false;
if (!entity.isReadyForRender())
return false;
return super.shouldRender(entity, clippingHelper, cameraX, cameraY, cameraZ);
}

View file

@ -12,10 +12,12 @@ import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
import com.jozufozu.flywheel.core.virtual.VirtualRenderWorld;
import com.simibubi.create.AllMovementBehaviours;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import net.minecraft.client.Camera;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
public class ContraptionInstanceManager extends BlockEntityInstanceManager {
@ -24,14 +26,22 @@ public class ContraptionInstanceManager extends BlockEntityInstanceManager {
private final VirtualRenderWorld renderWorld;
ContraptionInstanceManager(MaterialManager materialManager, VirtualRenderWorld contraption) {
private Contraption contraption;
ContraptionInstanceManager(MaterialManager materialManager, VirtualRenderWorld renderWorld, Contraption contraption) {
super(materialManager);
this.renderWorld = contraption;
this.renderWorld = renderWorld;
this.contraption = contraption;
}
public void tick() {
actors.forEach(ActorInstance::tick);
}
@Override
protected boolean canCreateInstance(BlockEntity blockEntity) {
return !contraption.isHiddenInPortal(blockEntity.getBlockPos());
}
@Override
public void beginFrame(TaskEngine taskEngine, Camera info) {
@ -49,6 +59,9 @@ public class ContraptionInstanceManager extends BlockEntityInstanceManager {
public ActorInstance createActor(Pair<StructureBlockInfo, MovementContext> actor) {
StructureBlockInfo blockInfo = actor.getLeft();
MovementContext context = actor.getRight();
if (contraption.isHiddenInPortal(context.localPos))
return null;
MovementBehaviour movementBehaviour = AllMovementBehaviours.of(blockInfo.state);

View file

@ -48,29 +48,36 @@ public class ContraptionRenderDispatcher {
/**
* Reset a contraption's renderer.
*
* @param contraption The contraption to invalidate.
* @return true if there was a renderer associated with the given contraption.
*/
public static boolean invalidate(Contraption contraption) {
Level level = contraption.entity.level;
return WORLDS.get(level).invalidate(contraption);
return WORLDS.get(level)
.invalidate(contraption);
}
public static void tick(Level world) {
if (Minecraft.getInstance().isPaused()) return;
if (Minecraft.getInstance()
.isPaused())
return;
WORLDS.get(world).tick();
WORLDS.get(world)
.tick();
}
@SubscribeEvent
public static void beginFrame(BeginFrameEvent event) {
WORLDS.get(event.getWorld()).beginFrame(event);
WORLDS.get(event.getWorld())
.beginFrame(event);
}
@SubscribeEvent
public static void renderLayer(RenderLayerEvent event) {
WORLDS.get(event.getWorld()).renderLayer(event);
WORLDS.get(event.getWorld())
.renderLayer(event);
GlError.pollAndThrow(() -> "contraption layer: " + event.getLayer());
}
@ -84,15 +91,17 @@ public class ContraptionRenderDispatcher {
reset();
}
public static void renderFromEntity(AbstractContraptionEntity entity, Contraption contraption, MultiBufferSource buffers) {
public static void renderFromEntity(AbstractContraptionEntity entity, Contraption contraption,
MultiBufferSource buffers) {
Level world = entity.level;
ContraptionRenderInfo renderInfo = WORLDS.get(world)
.getRenderInfo(contraption);
.getRenderInfo(contraption);
ContraptionMatrices matrices = renderInfo.getMatrices();
// something went wrong with the other rendering
if (!matrices.isReady()) return;
if (!matrices.isReady())
return;
VirtualRenderWorld renderWorld = renderInfo.renderWorld;
@ -106,28 +115,30 @@ public class ContraptionRenderDispatcher {
public static VirtualRenderWorld setupRenderWorld(Level world, Contraption c) {
ContraptionWorld contraptionWorld = c.getContraptionWorld();
VirtualRenderWorld renderWorld = new VirtualRenderWorld(world, c.anchor, contraptionWorld.getHeight(), contraptionWorld.getMinBuildHeight());
BlockPos origin = c.anchor;
int height = contraptionWorld.getHeight();
int minBuildHeight = contraptionWorld.getMinBuildHeight();
VirtualRenderWorld renderWorld = new VirtualRenderWorld(world, origin, height, minBuildHeight);
renderWorld.setBlockEntities(c.presentTileEntities.values());
for (StructureTemplate.StructureBlockInfo info : c.getBlocks()
.values())
.values())
// Skip individual lighting updates to prevent lag with large contraptions
renderWorld.setBlock(info.pos, info.state, Block.UPDATE_SUPPRESS_LIGHT);
renderWorld.runLightingEngine();
return renderWorld;
}
public static void renderTileEntities(Level world, VirtualRenderWorld renderWorld, Contraption c,
ContraptionMatrices matrices, MultiBufferSource buffer) {
TileEntityRenderHelper.renderTileEntities(world, renderWorld, c.specialRenderedTileEntities,
matrices.getModelViewProjection(), matrices.getLight(), buffer);
ContraptionMatrices matrices, MultiBufferSource buffer) {
TileEntityRenderHelper.renderTileEntities(world, renderWorld, c.getSpecialRenderedTEs(),
matrices.getModelViewProjection(), matrices.getLight(), buffer);
}
protected static void renderActors(Level world, VirtualRenderWorld renderWorld, Contraption c,
ContraptionMatrices matrices, MultiBufferSource buffer) {
ContraptionMatrices matrices, MultiBufferSource buffer) {
PoseStack m = matrices.getModel();
for (Pair<StructureTemplate.StructureBlockInfo, MovementContext> actor : c.getActors()) {
@ -140,18 +151,20 @@ public class ContraptionRenderDispatcher {
MovementBehaviour movementBehaviour = AllMovementBehaviours.of(blockInfo.state);
if (movementBehaviour != null) {
if (c.isHiddenInPortal(blockInfo.pos))
continue;
m.pushPose();
TransformStack.cast(m)
.translate(blockInfo.pos);
.translate(blockInfo.pos);
movementBehaviour.renderInContraption(context, renderWorld, matrices, buffer);
m.popPose();
}
}
}
public static SuperByteBuffer buildStructureBuffer(VirtualRenderWorld renderWorld, Contraption c, RenderType layer) {
Collection<StructureTemplate.StructureBlockInfo> values = c.getBlocks()
.values();
public static SuperByteBuffer buildStructureBuffer(VirtualRenderWorld renderWorld, Contraption c,
RenderType layer) {
Collection<StructureTemplate.StructureBlockInfo> values = c.getRenderedBlocks();
BufferBuilder builder = ModelUtil.getBufferBuilderFromTemplate(renderWorld, layer, values);
return new SuperByteBuffer(builder);
}

View file

@ -38,7 +38,7 @@ public class ContraptionRenderInfo {
}
public boolean isVisible() {
return visible && contraption.entity.isAlive();
return visible && contraption.entity.isAlive() && contraption.entity.isReadyForRender();
}
/**

View file

@ -29,6 +29,7 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@ -137,10 +138,10 @@ public class FlwContraption extends ContraptionRenderInfo {
renderLayers.clear();
List<RenderType> blockLayers = RenderType.chunkBufferLayers();
Collection<StructureBlockInfo> renderedBlocks = contraption.getRenderedBlocks();
for (RenderType layer : blockLayers) {
Model layerModel = new WorldModel(renderWorld, layer, contraption.getBlocks().values(), layer + "_" + contraption.entity.getId());
Model layerModel = new WorldModel(renderWorld, layer, renderedBlocks, layer + "_" + contraption.entity.getId());
renderLayers.put(layer, new ArrayModelRenderer(layerModel));
}
}
@ -187,14 +188,14 @@ public class FlwContraption extends ContraptionRenderInfo {
.setGroupFactory(ContraptionGroup.forContraption(parent))
.setIgnoreOriginCoordinate(true)
.build();
tileInstanceManager = new ContraptionInstanceManager(engine, parent.renderWorld);
tileInstanceManager = new ContraptionInstanceManager(engine, parent.renderWorld, parent.contraption);
engine.addListener(tileInstanceManager);
this.engine = engine;
}
case BATCHING -> {
engine = new BatchingEngine();
tileInstanceManager = new ContraptionInstanceManager(engine, parent.renderWorld);
tileInstanceManager = new ContraptionInstanceManager(engine, parent.renderWorld, parent.contraption);
}
default -> throw new IllegalArgumentException("Unknown engine type");
}

View file

@ -0,0 +1,68 @@
package com.simibubi.create.content.logistics.trains;
import java.util.ArrayList;
import java.util.List;
import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
public class DimensionPalette {
List<ResourceKey<Level>> gatheredDims;
public DimensionPalette() {
gatheredDims = new ArrayList<>();
}
public int encode(ResourceKey<Level> dimension) {
int indexOf = gatheredDims.indexOf(dimension);
if (indexOf == -1) {
indexOf = gatheredDims.size();
gatheredDims.add(dimension);
}
return indexOf;
}
public ResourceKey<Level> decode(int index) {
if (gatheredDims.size() <= index || index < 0)
return Level.OVERWORLD;
return gatheredDims.get(index);
}
public void send(FriendlyByteBuf buffer) {
buffer.writeInt(gatheredDims.size());
gatheredDims.forEach(rk -> buffer.writeResourceLocation(rk.location()));
}
public static DimensionPalette receive(FriendlyByteBuf buffer) {
DimensionPalette palette = new DimensionPalette();
int length = buffer.readInt();
for (int i = 0; i < length; i++)
palette.gatheredDims.add(ResourceKey.create(Registry.DIMENSION_REGISTRY, buffer.readResourceLocation()));
return palette;
}
public void write(CompoundTag tag) {
tag.put("DimensionPalette", NBTHelper.writeCompoundList(gatheredDims, rk -> {
CompoundTag c = new CompoundTag();
c.putString("Id", rk.location()
.toString());
return c;
}));
}
public static DimensionPalette read(CompoundTag tag) {
DimensionPalette palette = new DimensionPalette();
NBTHelper.iterateCompoundList(tag.getList("DimensionPalette", Tag.TAG_COMPOUND), c -> palette.gatheredDims
.add(ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(c.getString("Id")))));
return palette;
}
}

View file

@ -146,8 +146,8 @@ public class GlobalRailwayManager {
markTracksDirty();
}
public void updateSplitGraph(TrackGraph graph) {
Set<TrackGraph> disconnected = graph.findDisconnectedGraphs(null);
public void updateSplitGraph(LevelAccessor level, TrackGraph graph) {
Set<TrackGraph> disconnected = graph.findDisconnectedGraphs(level, null);
disconnected.forEach(this::putGraphWithDefaultGroup);
if (!disconnected.isEmpty()) {
sync.graphSplit(graph, disconnected);
@ -219,6 +219,13 @@ public class GlobalRailwayManager {
for (Iterator<Train> iterator = waitingTrains.iterator(); iterator.hasNext();) {
Train train = iterator.next();
if (train.invalid) {
iterator.remove();
trains.remove(train.id);
continue;
}
if (train.navigation.waitingForSignal != null)
continue;
movingTrains.add(train);
@ -227,11 +234,20 @@ public class GlobalRailwayManager {
for (Iterator<Train> iterator = movingTrains.iterator(); iterator.hasNext();) {
Train train = iterator.next();
if (train.invalid) {
iterator.remove();
trains.remove(train.id);
continue;
}
if (train.navigation.waitingForSignal == null)
continue;
waitingTrains.add(train);
iterator.remove();
}
}
public void tickSignalOverlay() {

View file

@ -21,7 +21,10 @@ import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.AxisDirection;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
@ -47,8 +50,10 @@ public interface ITrackBlock {
return isSlope(world, pos, state) ? .5 : 0;
}
public static Collection<DiscoveredLocation> walkConnectedTracks(BlockGetter world, TrackNodeLocation location,
public static Collection<DiscoveredLocation> walkConnectedTracks(BlockGetter worldIn, TrackNodeLocation location,
boolean linear) {
BlockGetter world = location != null && worldIn instanceof ServerLevel sl ? sl.getServer()
.getLevel(location.dimension) : worldIn;
List<DiscoveredLocation> list = new ArrayList<>();
for (BlockPos blockPos : location.allAdjacent()) {
BlockState blockState = world.getBlockState(blockPos);
@ -58,15 +63,18 @@ public interface ITrackBlock {
return list;
}
public default Collection<DiscoveredLocation> getConnected(BlockGetter world, BlockPos pos, BlockState state,
public default Collection<DiscoveredLocation> getConnected(BlockGetter worldIn, BlockPos pos, BlockState state,
boolean linear, @Nullable TrackNodeLocation connectedTo) {
BlockGetter world = connectedTo != null && worldIn instanceof ServerLevel sl ? sl.getServer()
.getLevel(connectedTo.dimension) : worldIn;
Vec3 center = Vec3.atBottomCenterOf(pos)
.add(0, getElevationAtCenter(world, pos, state), 0);
List<DiscoveredLocation> list = new ArrayList<>();
TrackShape shape = state.getValue(TrackBlock.SHAPE);
getTrackAxes(world, pos, state).forEach(axis -> {
addToListIfConnected(connectedTo, list, (d, b) -> axis.scale(b ? d : -d)
.add(center), b -> shape.getNormal(), axis, null);
.add(center), b -> shape.getNormal(), b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD,
axis, null);
});
return list;
@ -74,15 +82,22 @@ public interface ITrackBlock {
public static void addToListIfConnected(@Nullable TrackNodeLocation fromEnd, Collection<DiscoveredLocation> list,
BiFunction<Double, Boolean, Vec3> offsetFactory, Function<Boolean, Vec3> normalFactory,
Vec3 axis, BezierConnection viaTurn) {
Function<Boolean, ResourceKey<Level>> dimensionFactory, Vec3 axis, BezierConnection viaTurn) {
DiscoveredLocation firstLocation = new DiscoveredLocation(offsetFactory.apply(0.5d, true)).viaTurn(viaTurn)
.withNormal(normalFactory.apply(true))
.withDirection(axis);
DiscoveredLocation secondLocation = new DiscoveredLocation(offsetFactory.apply(0.5d, false)).viaTurn(viaTurn)
.withNormal(normalFactory.apply(false))
.withDirection(axis);
DiscoveredLocation firstLocation =
new DiscoveredLocation(dimensionFactory.apply(true), offsetFactory.apply(0.5d, true)).viaTurn(viaTurn)
.withNormal(normalFactory.apply(true))
.withDirection(axis);
DiscoveredLocation secondLocation =
new DiscoveredLocation(dimensionFactory.apply(false), offsetFactory.apply(0.5d, false)).viaTurn(viaTurn)
.withNormal(normalFactory.apply(false))
.withDirection(axis);
if (!firstLocation.dimension.equals(secondLocation.dimension)) {
firstLocation.forceNode();
secondLocation.forceNode();
}
boolean skipFirst = false;
boolean skipSecond = false;

View file

@ -26,14 +26,15 @@ public class RailwaySavedData extends SavedData {
public CompoundTag save(CompoundTag nbt) {
GlobalRailwayManager railways = Create.RAILWAYS;
Create.LOGGER.info("Saving Railway Information...");
nbt.put("RailGraphs", NBTHelper.writeCompoundList(railways.trackNetworks.values(), TrackGraph::write));
nbt.put("SignalBlocks",
NBTHelper.writeCompoundList(railways.signalEdgeGroups.values(), seg -> {
if (seg.fallbackGroup && !railways.trackNetworks.containsKey(seg.id))
return null;
return seg.write();
}));
nbt.put("Trains", NBTHelper.writeCompoundList(railways.trains.values(), Train::write));
DimensionPalette dimensions = new DimensionPalette();
nbt.put("RailGraphs", NBTHelper.writeCompoundList(railways.trackNetworks.values(), tg -> tg.write(dimensions)));
nbt.put("SignalBlocks", NBTHelper.writeCompoundList(railways.signalEdgeGroups.values(), seg -> {
if (seg.fallbackGroup && !railways.trackNetworks.containsKey(seg.id))
return null;
return seg.write();
}));
nbt.put("Trains", NBTHelper.writeCompoundList(railways.trains.values(), t -> t.write(dimensions)));
dimensions.write(nbt);
return nbt;
}
@ -44,18 +45,19 @@ public class RailwaySavedData extends SavedData {
sd.trains = new HashMap<>();
Create.LOGGER.info("Loading Railway Information...");
DimensionPalette dimensions = DimensionPalette.read(nbt);
NBTHelper.iterateCompoundList(nbt.getList("RailGraphs", Tag.TAG_COMPOUND), c -> {
TrackGraph graph = TrackGraph.read(c);
TrackGraph graph = TrackGraph.read(c, dimensions);
sd.trackNetworks.put(graph.id, graph);
});
NBTHelper.iterateCompoundList(nbt.getList("Trains", Tag.TAG_COMPOUND), c -> {
Train train = Train.read(c, sd.trackNetworks);
sd.trains.put(train.id, train);
});
NBTHelper.iterateCompoundList(nbt.getList("SignalBlocks", Tag.TAG_COMPOUND), c -> {
SignalEdgeGroup group = SignalEdgeGroup.read(c);
sd.signalEdgeGroups.put(group.id, group);
});
NBTHelper.iterateCompoundList(nbt.getList("Trains", Tag.TAG_COMPOUND), c -> {
Train train = Train.read(c, sd.trackNetworks, dimensions);
sd.trains.put(train.id, train);
});
for (TrackGraph graph : sd.trackNetworks.values()) {
for (SignalBoundary signal : graph.getPoints(EdgePointType.SIGNAL)) {

View file

@ -11,7 +11,6 @@ import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction.Axis;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@ -22,8 +21,10 @@ public class TrackEdge {
public TrackNode node2;
BezierConnection turn;
EdgeData edgeData;
boolean interDimensional;
public TrackEdge(TrackNode node1, TrackNode node2, BezierConnection turn) {
this.interDimensional = !node1.location.dimension.equals(node2.location.dimension);
this.edgeData = new EdgeData(this);
this.node1 = node1;
this.node2 = node2;
@ -34,6 +35,10 @@ public class TrackEdge {
return turn != null;
}
public boolean isInterDimensional() {
return interDimensional;
}
public EdgeData getEdgeData() {
return edgeData;
}
@ -47,15 +52,24 @@ public class TrackEdge {
.normalize();
}
public boolean canTravelTo(TrackEdge other) {
if (isInterDimensional() || other.isInterDimensional())
return true;
Vec3 newDirection = other.getDirection(true);
return getDirection(false).dot(newDirection) > 7 / 8f;
}
public double getLength() {
return isTurn() ? turn.getLength()
: node1.location.getLocation()
.distanceTo(node2.location.getLocation());
return isInterDimensional() ? 0
: isTurn() ? turn.getLength()
: node1.location.getLocation()
.distanceTo(node2.location.getLocation());
}
public double incrementT(double currentT, double distance) {
boolean tooFar = Math.abs(distance) > 5;
distance = distance / getLength();
double length = getLength();
distance = distance / (length == 0 ? 1 : length);
return !tooFar && isTurn() ? turn.incrementT(currentT, distance) : currentT + distance;
}
@ -71,6 +85,8 @@ public class TrackEdge {
Vec3 w1 = other1.location.getLocation();
Vec3 w2 = other2.location.getLocation();
if (isInterDimensional() || other.isInterDimensional())
return Collections.emptyList();
if (v1.y != v2.y || v1.y != w1.y || v1.y != w2.y)
return Collections.emptyList();
@ -152,26 +168,17 @@ public class TrackEdge {
return isTurn() ? turn.getNormal(Mth.clamp(t, 0, 1)) : node1.getNormal();
}
public void write(FriendlyByteBuf buffer) {
buffer.writeBoolean(isTurn());
if (isTurn())
turn.write(buffer);
}
public static TrackEdge read(FriendlyByteBuf buffer) {
return new TrackEdge(null, null, buffer.readBoolean() ? new BezierConnection(buffer) : null);
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag baseCompound = isTurn() ? turn.write(BlockPos.ZERO) : new CompoundTag();
baseCompound.put("Signals", edgeData.write());
baseCompound.put("Signals", edgeData.write(dimensions));
return baseCompound;
}
public static TrackEdge read(CompoundTag tag, TrackGraph graph) {
public static TrackEdge read(TrackNode node1, TrackNode node2, CompoundTag tag, TrackGraph graph,
DimensionPalette dimensions) {
TrackEdge trackEdge =
new TrackEdge(null, null, tag.contains("Positions") ? new BezierConnection(tag, BlockPos.ZERO) : null);
trackEdge.edgeData = EdgeData.read(tag.getCompound("Signals"), trackEdge, graph);
new TrackEdge(node1, node2, tag.contains("Positions") ? new BezierConnection(tag, BlockPos.ZERO) : null);
trackEdge.edgeData = EdgeData.read(tag.getCompound("Signals"), trackEdge, graph, dimensions);
return trackEdge;
}

View file

@ -31,10 +31,8 @@ import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
@ -117,8 +115,8 @@ public class TrackGraph {
return nodes.keySet();
}
public TrackNode locateNode(Vec3 position) {
return locateNode(new TrackNodeLocation(position));
public TrackNode locateNode(Level level, Vec3 position) {
return locateNode(new TrackNodeLocation(position).in(level));
}
public TrackNode locateNode(TrackNodeLocation position) {
@ -269,7 +267,8 @@ public class TrackGraph {
}
}
public Set<TrackGraph> findDisconnectedGraphs(@Nullable Map<Integer, UUID> preAssignedIds) {
public Set<TrackGraph> findDisconnectedGraphs(@Nullable LevelAccessor level,
@Nullable Map<Integer, UUID> preAssignedIds) {
Set<TrackGraph> dicovered = new HashSet<>();
Set<TrackNodeLocation> vertices = new HashSet<>(nodes.keySet());
List<TrackNodeLocation> frontier = new ArrayList<>();
@ -295,7 +294,7 @@ public class TrackGraph {
frontier.add(connected.getLocation());
if (target != null) {
transfer(currentNode, target);
transfer(level, currentNode, target);
if (preAssignedIds != null && preAssignedIds.containsKey(currentNode.getNetId()))
target.setId(preAssignedIds.get(currentNode.getNetId()));
}
@ -313,13 +312,13 @@ public class TrackGraph {
color = Color.rainbowColor(new Random(id.getLeastSignificantBits()).nextInt());
}
public void transfer(TrackNode node, TrackGraph target) {
public void transfer(LevelAccessor level, TrackNode node, TrackGraph target) {
target.addNode(node);
target.invalidateBounds();
TrackNodeLocation nodeLoc = node.getLocation();
Map<TrackNode, TrackEdge> connections = getConnectionsFrom(node);
Map<UUID, Train> trains = Create.RAILWAYS.trains;
Map<UUID, Train> trains = Create.RAILWAYS.sided(level).trains;
if (!connections.isEmpty()) {
target.connectionsByNode.put(node, connections);
@ -487,7 +486,7 @@ public class TrackGraph {
Create.RAILWAYS.markTracksDirty();
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag tag = new CompoundTag();
tag.putUUID("Id", id);
tag.putInt("Color", color.getRGB());
@ -499,7 +498,8 @@ public class TrackGraph {
for (TrackNode railNode : nodes.values()) {
indexTracker.put(railNode, i);
CompoundTag nodeTag = new CompoundTag();
nodeTag.put("Location", NbtUtils.writeBlockPos(new BlockPos(railNode.getLocation())));
nodeTag.put("Location", railNode.getLocation()
.write(dimensions));
nodeTag.put("Normal", VecHelper.writeNBT(railNode.getNormal()));
nodesList.add(nodeTag);
i++;
@ -517,21 +517,21 @@ public class TrackGraph {
if (index2 == null)
return;
connectionTag.putInt("To", index2);
connectionTag.put("EdgeData", edge.write());
connectionTag.put("EdgeData", edge.write(dimensions));
connectionsList.add(connectionTag);
});
nodeTag.put("Connections", connectionsList);
});
tag.put("Nodes", nodesList);
tag.put("Points", edgePoints.write());
tag.put("Points", edgePoints.write(dimensions));
return tag;
}
public static TrackGraph read(CompoundTag tag) {
public static TrackGraph read(CompoundTag tag, DimensionPalette dimensions) {
TrackGraph graph = new TrackGraph(tag.getUUID("Id"));
graph.color = new Color(tag.getInt("Color"));
graph.edgePoints.read(tag.getCompound("Points"));
graph.edgePoints.read(tag.getCompound("Points"), dimensions);
Map<Integer, TrackNode> indexTracker = new HashMap<>();
ListTag nodesList = tag.getList("Nodes", Tag.TAG_COMPOUND);
@ -539,8 +539,7 @@ public class TrackGraph {
int i = 0;
for (Tag t : nodesList) {
CompoundTag nodeTag = (CompoundTag) t;
TrackNodeLocation location =
TrackNodeLocation.fromPackedPos(NbtUtils.readBlockPos(nodeTag.getCompound("Location")));
TrackNodeLocation location = TrackNodeLocation.read(nodeTag.getCompound("Location"), dimensions);
Vec3 normal = VecHelper.readNBT(nodeTag.getList("Normal", Tag.TAG_DOUBLE));
graph.loadNode(location, nextNodeId(), normal);
indexTracker.put(i, graph.locateNode(location));
@ -557,9 +556,7 @@ public class TrackGraph {
continue;
NBTHelper.iterateCompoundList(nodeTag.getList("Connections", Tag.TAG_COMPOUND), c -> {
TrackNode node2 = indexTracker.get(c.getInt("To"));
TrackEdge edge = TrackEdge.read(c.getCompound("EdgeData"), graph);
edge.node1 = node1;
edge.node2 = node2;
TrackEdge edge = TrackEdge.read(node1, node2, c.getCompound("EdgeData"), graph, dimensions);
graph.putConnection(node1, node2, edge);
});
}

View file

@ -34,7 +34,7 @@ public class TrackGraphHelper {
// Case 1: Centre of block lies on a node
TrackNodeLocation location = new TrackNodeLocation(Vec3.atBottomCenterOf(pos)
.add(0, track.getElevationAtCenter(level, pos, trackBlockState), 0));
.add(0, track.getElevationAtCenter(level, pos, trackBlockState), 0)).in(level);
graph = Create.RAILWAYS.sided(level)
.getGraph(level, location);
if (graph != null) {
@ -142,7 +142,7 @@ public class TrackGraphHelper {
if (bc == null || !bc.isPrimary())
return null;
TrackNodeLocation targetLoc = new TrackNodeLocation(bc.starts.getSecond());
TrackNodeLocation targetLoc = new TrackNodeLocation(bc.starts.getSecond()).in(level);
for (DiscoveredLocation location : track.getConnected(level, pos, state, true, null)) {
TrackGraph graph = Create.RAILWAYS.sided(level)
.getGraph(level, location);

View file

@ -46,7 +46,8 @@ public class TrackGraphSync {
public void edgeAdded(TrackGraph graph, TrackNode node1, TrackNode node2, TrackEdge edge) {
flushGraphPacket(graph.id);
currentGraphSyncPacket.addedEdges.add(Pair.of(Couple.create(node1.getNetId(), node2.getNetId()), edge));
currentGraphSyncPacket.addedEdges
.add(Pair.of(Couple.create(node1.getNetId(), node2.getNetId()), edge.getTurn()));
}
public void pointAdded(TrackGraph graph, TrackEdgePoint point) {
@ -127,7 +128,7 @@ public class TrackGraphSync {
graph.connectionsByNode.get(node)
.forEach((node2, edge) -> {
Couple<Integer> key = Couple.create(node.getNetId(), node2.getNetId());
currentPacket.addedEdges.add(Pair.of(key, edge));
currentPacket.addedEdges.add(Pair.of(key, edge.getTurn()));
currentPacket.syncEdgeData(node, node2, edge);
});

View file

@ -15,14 +15,13 @@ import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.phys.Vec3;
public class TrackGraphSyncPacket extends TrackGraphPacket {
Map<Integer, Pair<TrackNodeLocation, Vec3>> addedNodes;
List<Pair<Couple<Integer>, TrackEdge>> addedEdges;
List<Pair<Couple<Integer>, BezierConnection>> addedEdges;
List<Integer> removedNodes;
List<TrackEdgePoint> addedEdgePoints;
List<UUID> removedEdgePoints;
@ -52,6 +51,8 @@ public class TrackGraphSyncPacket extends TrackGraphPacket {
if (packetDeletesGraph)
return;
DimensionPalette dimensions = DimensionPalette.receive(buffer);
addedNodes = new HashMap<>();
addedEdges = new ArrayList<>();
addedEdgePoints = new ArrayList<>();
@ -67,15 +68,16 @@ public class TrackGraphSyncPacket extends TrackGraphPacket {
size = buffer.readVarInt();
for (int i = 0; i < size; i++)
addedNodes.put(buffer.readVarInt(),
Pair.of(TrackNodeLocation.fromPackedPos(buffer.readBlockPos()), VecHelper.read(buffer)));
Pair.of(TrackNodeLocation.receive(buffer, dimensions), VecHelper.read(buffer)));
size = buffer.readVarInt();
for (int i = 0; i < size; i++)
addedEdges.add(Pair.of(Couple.create(buffer::readVarInt), TrackEdge.read(buffer)));
addedEdges.add(
Pair.of(Couple.create(buffer::readVarInt), buffer.readBoolean() ? new BezierConnection(buffer) : null));
size = buffer.readVarInt();
for (int i = 0; i < size; i++)
addedEdgePoints.add(EdgePointType.read(buffer));
addedEdgePoints.add(EdgePointType.read(buffer, dimensions));
size = buffer.readVarInt();
for (int i = 0; i < size; i++)
@ -99,19 +101,26 @@ public class TrackGraphSyncPacket extends TrackGraphPacket {
@Override
public void write(FriendlyByteBuf buffer) {
buffer.writeUUID(graphId);
buffer.writeBoolean(packetDeletesGraph);
if (packetDeletesGraph)
return;
// Populate and send palette ahead of time
DimensionPalette dimensions = new DimensionPalette();
addedNodes.forEach((node, loc) -> dimensions.encode(loc.getFirst().dimension));
addedEdgePoints.forEach(ep -> ep.edgeLocation.forEach(loc -> dimensions.encode(loc.dimension)));
dimensions.send(buffer);
buffer.writeVarInt(removedNodes.size());
removedNodes.forEach(buffer::writeVarInt);
buffer.writeVarInt(addedNodes.size());
addedNodes.forEach((node, loc) -> {
buffer.writeVarInt(node);
buffer.writeBlockPos(new BlockPos(loc.getFirst()));
loc.getFirst()
.send(buffer, dimensions);
VecHelper.write(loc.getSecond(), buffer);
});
@ -119,12 +128,14 @@ public class TrackGraphSyncPacket extends TrackGraphPacket {
addedEdges.forEach(pair -> {
pair.getFirst()
.forEach(buffer::writeVarInt);
pair.getSecond()
.write(buffer);
BezierConnection turn = pair.getSecond();
buffer.writeBoolean(turn != null);
if (turn != null)
turn.write(buffer);
});
buffer.writeVarInt(addedEdgePoints.size());
addedEdgePoints.forEach(ep -> ep.write(buffer));
addedEdgePoints.forEach(ep -> ep.write(buffer, dimensions));
buffer.writeVarInt(removedEdgePoints.size());
removedEdgePoints.forEach(buffer::writeUUID);
@ -166,17 +177,13 @@ public class TrackGraphSyncPacket extends TrackGraphPacket {
graph.loadNode(nodeLocation.getFirst(), nodeId, nodeLocation.getSecond());
}
for (Pair<Couple<Integer>, TrackEdge> pair : addedEdges) {
for (Pair<Couple<Integer>, BezierConnection> pair : addedEdges) {
Couple<TrackNode> nodes = pair.getFirst()
.map(graph::getNode);
TrackNode node1 = nodes.getFirst();
TrackNode node2 = nodes.getSecond();
if (node1 != null && node2 != null) {
TrackEdge edge = pair.getSecond();
edge.node1 = node1;
edge.node2 = node2;
graph.putConnection(node1, node2, edge);
}
if (node1 != null && node2 != null)
graph.putConnection(node1, node2, new TrackEdge(node1, node2, pair.getSecond()));
}
for (TrackEdgePoint edgePoint : addedEdgePoints)
@ -189,7 +196,7 @@ public class TrackGraphSyncPacket extends TrackGraphPacket {
handleEdgeData(manager, graph);
if (!splitSubGraphs.isEmpty())
graph.findDisconnectedGraphs(splitSubGraphs)
graph.findDisconnectedGraphs(null, splitSubGraphs)
.forEach(manager::putGraph);
}

View file

@ -11,7 +11,6 @@ import com.simibubi.create.AllKeys;
import com.simibubi.create.Create;
import com.simibubi.create.CreateClient;
import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgeData;
import com.simibubi.create.content.logistics.trains.management.edgePoint.TrackEdgeIntersection;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalBoundary;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalEdgeGroup;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.TrackEdgePoint;
@ -51,6 +50,9 @@ public class TrackGraphVisualizer {
Vec3 location = nodeLocation.getLocation();
if (location.distanceTo(camera) > 50)
continue;
if (!mc.level.dimension()
.equals(nodeLocation.dimension))
continue;
Map<TrackNode, TrackEdge> map = graph.connectionsByNode.get(node);
if (map == null)
@ -62,16 +64,8 @@ public class TrackGraphVisualizer {
TrackEdge edge = entry.getValue();
EdgeData signalData = edge.getEdgeData();
// temporary
if (other.hashCode() > hashCode == ctrl)
for (TrackEdgeIntersection intersection : signalData.getIntersections()) {
Vec3 v1 = edge.getPosition(intersection.location / edge.getLength());
Vec3 v2 = v1.add(node.normal.scale(8 / 16f));
outliner.showLine(intersection, v1, v2)
.colored(Color.mixColors(Color.WHITE, graph.color, 1))
.lineWidth(width);
} //
if (!edge.node1.location.dimension.equals(edge.node2.location.dimension))
continue;
if (other.hashCode() > hashCode && !ctrl)
continue;
@ -234,6 +228,9 @@ public class TrackGraphVisualizer {
Vec3 location = nodeLocation.getLocation();
if (location.distanceTo(camera) > 50)
continue;
if (!mc.level.dimension()
.equals(nodeLocation.dimension))
continue;
Vec3 yOffset = new Vec3(0, 3 / 16f, 0);
Vec3 v1 = location.add(yOffset);
@ -249,12 +246,20 @@ public class TrackGraphVisualizer {
int hashCode = node.hashCode();
for (Entry<TrackNode, TrackEdge> entry : map.entrySet()) {
TrackNode other = entry.getKey();
TrackEdge edge = entry.getValue();
if (!edge.node1.location.dimension.equals(edge.node2.location.dimension)) {
v1 = location.add(yOffset);
v2 = v1.add(node.normal.scale(3 / 16f));
CreateClient.OUTLINER.showLine(Integer.valueOf(node.netId), v1, v2)
.colored(Color.mixColors(Color.WHITE, graph.color, 1))
.lineWidth(1 / 4f);
continue;
}
if (other.hashCode() > hashCode && !AllKeys.isKeyDown(GLFW.GLFW_KEY_LEFT_CONTROL))
continue;
yOffset = new Vec3(0, (other.hashCode() > hashCode ? 6 : 4) / 16f, 0);
TrackEdge edge = entry.getValue();
yOffset = new Vec3(0, (other.hashCode() > hashCode ? 6 : 4) / 16f, 0);
if (!edge.isTurn()) {
CreateClient.OUTLINER.showLine(edge, edge.getPosition(0)
.add(yOffset),

View file

@ -2,16 +2,24 @@ package com.simibubi.create.content.logistics.trains;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
public class TrackNodeLocation extends Vec3i {
public ResourceKey<Level> dimension;
public TrackNodeLocation(Vec3 vec) {
this(vec.x, vec.y, vec.z);
}
@ -20,7 +28,16 @@ public class TrackNodeLocation extends Vec3i {
super(Math.round(p_121865_ * 2), Math.floor(p_121866_ * 2), Math.round(p_121867_ * 2));
}
public static TrackNodeLocation fromPackedPos(BlockPos bufferPos) {
public TrackNodeLocation in(Level level) {
return in(level.dimension());
}
public TrackNodeLocation in(ResourceKey<Level> dimension) {
this.dimension = dimension;
return this;
}
private static TrackNodeLocation fromPackedPos(BlockPos bufferPos) {
return new TrackNodeLocation(bufferPos);
}
@ -32,14 +49,44 @@ public class TrackNodeLocation extends Vec3i {
return new Vec3(getX() / 2f, getY() / 2f, getZ() / 2f);
}
public ResourceKey<Level> getDimension() {
return dimension;
}
@Override
public boolean equals(Object pOther) {
return super.equals(pOther);
return super.equals(pOther) && pOther instanceof TrackNodeLocation tnl
&& Objects.equals(tnl.dimension, dimension);
}
@Override
public int hashCode() {
return (this.getY() + this.getZ() * 31) * 31 + this.getX();
return (this.getY() + (this.getZ() * 31 + dimension.hashCode()) * 31) * 31 + this.getX();
}
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag c = NbtUtils.writeBlockPos(new BlockPos(this));
if (dimensions != null)
c.putInt("D", dimensions.encode(dimension));
return c;
}
public static TrackNodeLocation read(CompoundTag tag, DimensionPalette dimensions) {
TrackNodeLocation location = fromPackedPos(NbtUtils.readBlockPos(tag));
if (dimensions != null)
location.dimension = dimensions.decode(tag.getInt("D"));
return location;
}
public void send(FriendlyByteBuf buffer, DimensionPalette dimensions) {
buffer.writeBlockPos(new BlockPos(this));
buffer.writeVarInt(dimensions.encode(dimension));
}
public static TrackNodeLocation receive(FriendlyByteBuf buffer, DimensionPalette dimensions) {
TrackNodeLocation location = fromPackedPos(buffer.readBlockPos());
location.dimension = dimensions.decode(buffer.readVarInt());
return location;
}
public Collection<BlockPos> allAdjacent() {
@ -60,12 +107,18 @@ public class TrackNodeLocation extends Vec3i {
Vec3 direction;
Vec3 normal;
public DiscoveredLocation(double p_121865_, double p_121866_, double p_121867_) {
public DiscoveredLocation(Level level, double p_121865_, double p_121866_, double p_121867_) {
super(p_121865_, p_121866_, p_121867_);
in(level);
}
public DiscoveredLocation(Vec3 vec) {
public DiscoveredLocation(ResourceKey<Level> dimension, Vec3 vec) {
super(vec);
in(dimension);
}
public DiscoveredLocation(Level level, Vec3 vec) {
this(level.dimension(), vec);
}
public DiscoveredLocation viaTurn(BezierConnection turn) {

View file

@ -73,7 +73,7 @@ public class TrackPropagator {
// 3. Ensure any affected graph gets checked for segmentation
for (TrackGraph railGraph : toUpdate)
manager.updateSplitGraph(railGraph);
manager.updateSplitGraph(reader, railGraph);
manager.markTracksDirty();
}
@ -235,9 +235,9 @@ public class TrackPropagator {
if (location.shouldForceNode())
return true;
if (next.stream()
.anyMatch(DiscoveredLocation::connectedViaTurn))
.anyMatch(DiscoveredLocation::shouldForceNode))
return true;
Vec3 direction = location.direction;
if (direction != null && next.stream()
.anyMatch(dl -> dl.notInLineWith(direction)))

View file

@ -29,10 +29,10 @@ public sealed class BogeyInstance {
shafts = new ModelData[2];
materialManager.defaultSolid()
.material(Materials.TRANSFORMED)
.getModel(AllBlocks.SHAFT.getDefaultState()
.setValue(ShaftBlock.AXIS, Direction.Axis.Z))
.createInstances(shafts);
.material(Materials.TRANSFORMED)
.getModel(AllBlocks.SHAFT.getDefaultState()
.setValue(ShaftBlock.AXIS, Direction.Axis.Z))
.createInstances(shafts);
}
@ -41,14 +41,23 @@ public sealed class BogeyInstance {
shaft.delete();
}
public void hiddenFrame() {
beginFrame(0, null);
}
public void beginFrame(float wheelAngle, PoseStack ms) {
if (ms == null) {
for (int i : Iterate.zeroAndOne)
shafts[i].setEmptyTransform();
return;
}
for (int i : Iterate.zeroAndOne)
shafts[i].setTransform(ms)
.translate(-.5f, .25f, i * -1)
.centre()
.rotateZ(wheelAngle)
.unCentre();
.translate(-.5f, .25f, i * -1)
.centre()
.rotateZ(wheelAngle)
.unCentre();
}
public void updateLight(BlockAndTintGetter world, CarriageContraptionEntity entity) {
@ -67,7 +76,8 @@ public sealed class BogeyInstance {
public void updateLight(int blockLight, int skyLight) {
for (ModelData shaft : shafts) {
shaft.setBlockLight(blockLight).setSkyLight(skyLight);
shaft.setBlockLight(blockLight)
.setSkyLight(skyLight);
}
}
@ -80,37 +90,46 @@ public sealed class BogeyInstance {
super(bogey, materialManager);
frame = materialManager.defaultSolid()
.material(Materials.TRANSFORMED)
.getModel(AllBlockPartials.BOGEY_FRAME)
.createInstance();
.material(Materials.TRANSFORMED)
.getModel(AllBlockPartials.BOGEY_FRAME)
.createInstance();
wheels = new ModelData[2];
materialManager.defaultSolid()
.material(Materials.TRANSFORMED)
.getModel(AllBlockPartials.SMALL_BOGEY_WHEELS)
.createInstances(wheels);
.material(Materials.TRANSFORMED)
.getModel(AllBlockPartials.SMALL_BOGEY_WHEELS)
.createInstances(wheels);
}
@Override
public void beginFrame(float wheelAngle, PoseStack ms) {
super.beginFrame(wheelAngle, ms);
if (ms == null) {
frame.setEmptyTransform();
for (int side : Iterate.positiveAndNegative)
wheels[(side + 1) / 2].setEmptyTransform();
return;
}
frame.setTransform(ms);
for (int side : Iterate.positiveAndNegative) {
wheels[(side + 1) / 2].setTransform(ms)
.translate(0, 12 / 16f, side)
.rotateX(wheelAngle);
.translate(0, 12 / 16f, side)
.rotateX(wheelAngle);
}
}
@Override
public void updateLight(int blockLight, int skyLight) {
super.updateLight(blockLight, skyLight);
frame.setBlockLight(blockLight).setSkyLight(skyLight);
frame.setBlockLight(blockLight)
.setSkyLight(skyLight);
for (ModelData wheel : wheels)
wheel.setBlockLight(blockLight).setSkyLight(skyLight);
wheel.setBlockLight(blockLight)
.setSkyLight(skyLight);
}
@Override
@ -133,58 +152,73 @@ public sealed class BogeyInstance {
public Drive(CarriageBogey bogey, MaterialManager materialManager) {
super(bogey, materialManager);
Material<ModelData> mat = materialManager.defaultSolid()
.material(Materials.TRANSFORMED);
.material(Materials.TRANSFORMED);
secondShaft = new ModelData[2];
mat.getModel(AllBlocks.SHAFT.getDefaultState()
.setValue(ShaftBlock.AXIS, Direction.Axis.X))
.createInstances(secondShaft);
.setValue(ShaftBlock.AXIS, Direction.Axis.X))
.createInstances(secondShaft);
drive = mat.getModel(AllBlockPartials.BOGEY_DRIVE)
.createInstance();
.createInstance();
piston = mat.getModel(AllBlockPartials.BOGEY_PISTON)
.createInstance();
.createInstance();
wheels = mat.getModel(AllBlockPartials.LARGE_BOGEY_WHEELS)
.createInstance();
.createInstance();
pin = mat.getModel(AllBlockPartials.BOGEY_PIN)
.createInstance();
.createInstance();
}
@Override
public void beginFrame(float wheelAngle, PoseStack ms) {
super.beginFrame(wheelAngle, ms);
if (ms == null) {
for (int i : Iterate.zeroAndOne)
secondShaft[i].setEmptyTransform();
drive.setEmptyTransform();
piston.setEmptyTransform();
wheels.setEmptyTransform();
pin.setEmptyTransform();
return;
}
for (int i : Iterate.zeroAndOne)
secondShaft[i].setTransform(ms)
.translate(-.5f, .25f, .5f + i * -2)
.centre()
.rotateX(wheelAngle)
.unCentre();
.translate(-.5f, .25f, .5f + i * -2)
.centre()
.rotateX(wheelAngle)
.unCentre();
drive.setTransform(ms);
piston.setTransform(ms)
.translate(0, 0, 1 / 4f * Math.sin(AngleHelper.rad(wheelAngle)));
.translate(0, 0, 1 / 4f * Math.sin(AngleHelper.rad(wheelAngle)));
wheels.setTransform(ms)
.translate(0, 1, 0)
.rotateX(wheelAngle);
.translate(0, 1, 0)
.rotateX(wheelAngle);
pin.setTransform(ms)
.translate(0, 1, 0)
.rotateX(wheelAngle)
.translate(0, 1 / 4f, 0)
.rotateX(-wheelAngle);
.translate(0, 1, 0)
.rotateX(wheelAngle)
.translate(0, 1 / 4f, 0)
.rotateX(-wheelAngle);
}
@Override
public void updateLight(int blockLight, int skyLight) {
super.updateLight(blockLight, skyLight);
for (ModelData shaft : secondShaft)
shaft.setBlockLight(blockLight).setSkyLight(skyLight);
drive.setBlockLight(blockLight).setSkyLight(skyLight);
piston.setBlockLight(blockLight).setSkyLight(skyLight);
wheels.setBlockLight(blockLight).setSkyLight(skyLight);
pin.setBlockLight(blockLight).setSkyLight(skyLight);
shaft.setBlockLight(blockLight)
.setSkyLight(skyLight);
drive.setBlockLight(blockLight)
.setSkyLight(skyLight);
piston.setBlockLight(blockLight)
.setSkyLight(skyLight);
wheels.setBlockLight(blockLight)
.setSkyLight(skyLight);
pin.setBlockLight(blockLight)
.setSkyLight(skyLight);
}
@Override

View file

@ -2,25 +2,41 @@ package com.simibubi.create.content.logistics.trains.entity;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableDouble;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.IEdgePointListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.Entity.RemovalReason;
@ -28,6 +44,9 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
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 Carriage {
@ -42,29 +61,21 @@ public class Carriage {
public int bogeySpacing;
public Couple<CarriageBogey> bogeys;
public Vec3 positionAnchor;
public Couple<Vec3> rotationAnchors;
CompoundTag serialisedEntity;
WeakReference<CarriageContraptionEntity> entity;
// client
public boolean pointsInitialised;
Map<Integer, CompoundTag> serialisedPassengers;
private Map<ResourceKey<Level>, DimensionalCarriageEntity> entities;
static final int FIRST = 0, MIDDLE = 1, LAST = 2, BOTH = 3;
public Carriage(CarriageBogey bogey1, @Nullable CarriageBogey bogey2, int bogeySpacing) {
this.bogeySpacing = bogeySpacing;
this.bogeys = Couple.create(bogey1, bogey2);
this.entity = new WeakReference<>(null);
this.id = netIdGenerator.incrementAndGet();
this.serialisedEntity = new CompoundTag();
this.pointsInitialised = false;
this.rotationAnchors = Couple.create(null, null);
this.presentConductors = Couple.create(false, false);
updateContraptionAnchors();
this.serialisedPassengers = new HashMap<>();
this.entities = new HashMap<>();
bogey1.setLeading();
bogey1.carriage = this;
@ -76,13 +87,18 @@ public class Carriage {
this.train = train;
}
public boolean presentInMultipleDimensions() {
return entities.size() > 1;
}
public void setContraption(Level level, CarriageContraption contraption) {
CarriageContraptionEntity entity = CarriageContraptionEntity.create(level, contraption);
entity.setCarriage(this);
contraption.startMoving(level);
contraption.onEntityInitialize(level, entity);
updateContraptionAnchors();
alignEntity(entity);
getDimensional(level).alignEntity(entity);
List<Entity> players = new ArrayList<>();
for (Entity passenger : entity.getPassengers())
@ -96,11 +112,24 @@ public class Carriage {
serialisedEntity = entity.serializeNBT();
}
public DimensionalCarriageEntity getDimensional(Level level) {
return getDimensional(level.dimension());
}
public DimensionalCarriageEntity getDimensional(ResourceKey<Level> dimension) {
return entities.computeIfAbsent(dimension, $ -> new DimensionalCarriageEntity());
}
@Nullable
public DimensionalCarriageEntity getDimensionalIfPresent(ResourceKey<Level> dimension) {
return entities.get(dimension);
}
public double travel(Level level, TrackGraph graph, double distance,
Function<TravellingPoint, ITrackSelector> forwardControl,
Function<TravellingPoint, ITrackSelector> backwardControl, int type) {
boolean onTwoBogeys = isOnTwoBogeys();
double stress = onTwoBogeys ? bogeySpacing - leadingAnchor().distanceTo(trailingAnchor()) : 0;
double stress = onTwoBogeys ? bogeySpacing - getAnchorDiff() : 0;
blocked = false;
MutableDouble distanceMoved = new MutableDouble(distance);
@ -140,12 +169,23 @@ public class Carriage {
IEdgePointListener passiveListener = point.ignoreEdgePoints();
toMove += correction + bogeyCorrection;
double moved =
point
.travel(graph, toMove, toMove > 0 ? frontTrackSelector : backTrackSelector,
toMove > 0 ? atFront ? frontListener : atBack ? backListener : passiveListener
: atFront ? backListener : atBack ? frontListener : passiveListener,
point.ignoreTurns());
ITrackSelector trackSelector = toMove > 0 ? frontTrackSelector : backTrackSelector;
IEdgePointListener signalListener =
toMove > 0 ? atFront ? frontListener : atBack ? backListener : passiveListener
: atFront ? backListener : atBack ? frontListener : passiveListener;
double moved = point.travel(graph, toMove, trackSelector, signalListener, point.ignoreTurns(), c -> {
for (DimensionalCarriageEntity dce : entities.values())
if (c.either(tnl -> tnl.equals(dce.pivot)))
return false;
if (entities.size() > 1) {
train.status.doublePortal();
return true;
}
return false;
});
blocked |= point.blocked;
distanceMoved.setValue(moved);
@ -153,98 +193,185 @@ public class Carriage {
}
updateContraptionAnchors();
manageEntity(level);
manageEntities(level);
return distanceMoved.getValue();
}
public void updateConductors() {
CarriageContraptionEntity entity = this.entity.get();
if (entity != null && entity.isAlive())
presentConductors = entity.checkConductors();
private double getAnchorDiff() {
double diff = 0;
int entries = 0;
TravellingPoint leadingPoint = getLeadingPoint();
TravellingPoint trailingPoint = getTrailingPoint();
if (leadingPoint.node1 != null && trailingPoint.node1 != null)
if (!leadingPoint.node1.getLocation().dimension.equals(trailingPoint.node1.getLocation().dimension))
return bogeySpacing;
for (DimensionalCarriageEntity dce : entities.values())
if (dce.leadingAnchor() != null && dce.trailingAnchor() != null) {
entries++;
diff += dce.leadingAnchor()
.distanceTo(dce.trailingAnchor());
}
if (entries == 0)
return bogeySpacing;
return diff / entries;
}
public void createEntity(Level level) {
Entity entity = EntityType.loadEntityRecursive(serialisedEntity, level, e -> {
e.moveTo(positionAnchor);
return e;
public void updateConductors() {
if (anyAvailableEntity() == null || entities.size() > 1)
return;
presentConductors.replace($ -> false);
for (DimensionalCarriageEntity dimensionalCarriageEntity : entities.values()) {
CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get();
if (entity != null && entity.isAlive())
presentConductors.replaceWithParams((current, checked) -> current || checked, entity.checkConductors());
}
}
private Set<ResourceKey<Level>> currentlyTraversedDimensions = new HashSet<>();
public void manageEntities(Level level) {
currentlyTraversedDimensions.clear();
bogeys.forEach(cb -> {
if (cb == null)
return;
cb.points.forEach(tp -> {
if (tp.node1 == null)
return;
currentlyTraversedDimensions.add(tp.node1.getLocation().dimension);
});
});
if (!(entity instanceof CarriageContraptionEntity cce))
return;
for (Iterator<Entry<ResourceKey<Level>, DimensionalCarriageEntity>> iterator = entities.entrySet()
.iterator(); iterator.hasNext();) {
Entry<ResourceKey<Level>, DimensionalCarriageEntity> entry = iterator.next();
cce.setGraph(train.graph == null ? null : train.graph.id);
cce.setCarriage(this);
cce.syncCarriage();
boolean discard =
!currentlyTraversedDimensions.isEmpty() && !currentlyTraversedDimensions.contains(entry.getKey());
ServerLevel currentLevel = level.getServer()
.getLevel(entry.getKey());
if (currentLevel == null)
continue;
this.entity = new WeakReference<>(cce);
DimensionalCarriageEntity dimensionalCarriageEntity = entry.getValue();
CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get();
if (level instanceof ServerLevel sl)
sl.tryAddFreshEntityWithPassengers(entity);
}
if (entity == null) {
if (discard)
iterator.remove();
else if (dimensionalCarriageEntity.positionAnchor != null && CarriageEntityHandler
.isActiveChunk(currentLevel, new BlockPos(dimensionalCarriageEntity.positionAnchor)))
dimensionalCarriageEntity.createEntity(currentLevel, anyAvailableEntity() == null);
public void manageEntity(Level level) {
CarriageContraptionEntity entity = this.entity.get();
if (entity == null) {
if (CarriageEntityHandler.isActiveChunk(level, new BlockPos(positionAnchor)))
createEntity(level);
} else {
CarriageEntityHandler.validateCarriageEntity(entity);
if (!entity.isAlive() || entity.leftTickingChunks) {
removeAndSaveEntity(entity);
return;
} else {
if (discard) {
discard = dimensionalCarriageEntity.discardTicks > 3;
dimensionalCarriageEntity.discardTicks++;
} else
dimensionalCarriageEntity.discardTicks = 0;
CarriageEntityHandler.validateCarriageEntity(entity);
if (!entity.isAlive() || entity.leftTickingChunks || discard) {
dimensionalCarriageEntity.removeAndSaveEntity(entity, discard);
if (discard)
iterator.remove();
continue;
}
}
entity = dimensionalCarriageEntity.entity.get();
if (entity != null && dimensionalCarriageEntity.positionAnchor != null) {
dimensionalCarriageEntity.alignEntity(entity);
entity.syncCarriage();
}
}
entity = this.entity.get();
if (entity == null)
return;
alignEntity(entity);
entity.syncCarriage();
}
private void removeAndSaveEntity(CarriageContraptionEntity entity) {
serialisedEntity = entity.serializeNBT();
for (Entity passenger : entity.getPassengers())
if (!(passenger instanceof Player))
passenger.discard();
entity.discard();
this.entity.clear();
}
public void updateContraptionAnchors() {
CarriageBogey leadingBogey = leadingBogey();
if (leadingBogey.points.either(t -> t.edge == null))
return;
positionAnchor = leadingBogey.getAnchorPosition();
rotationAnchors = bogeys.mapWithContext((b, first) -> isOnTwoBogeys() ? b.getAnchorPosition()
: leadingBogey.points.get(first)
.getPosition());
CarriageBogey trailingBogey = trailingBogey();
if (trailingBogey.points.either(t -> t.edge == null))
return;
ResourceKey<Level> leadingBogeyDim = leadingBogey.getDimension();
ResourceKey<Level> trailingBogeyDim = trailingBogey.getDimension();
double leadingWheelSpacing = leadingBogey.type.getWheelPointSpacing();
double trailingWheelSpacing = trailingBogey.type.getWheelPointSpacing();
for (boolean leading : Iterate.trueAndFalse) {
TravellingPoint point = leading ? getLeadingPoint() : getTrailingPoint();
TravellingPoint otherPoint = !leading ? getLeadingPoint() : getTrailingPoint();
ResourceKey<Level> dimension = point.node1.getLocation().dimension;
ResourceKey<Level> otherDimension = otherPoint.node1.getLocation().dimension;
if (dimension.equals(otherDimension) && leading) {
getDimensional(dimension).discardPivot();
continue;
}
DimensionalCarriageEntity dce = getDimensional(dimension);
dce.positionAnchor = dimension.equals(leadingBogeyDim) ? leadingBogey.getAnchorPosition()
: pivoted(dce, dimension, point,
leading ? leadingWheelSpacing / 2 : bogeySpacing + trailingWheelSpacing / 2);
int prevmin = dce.minAllowedLocalCoord();
int prevmax = dce.maxAllowedLocalCoord();
dce.updateCutoff(leading);
if (prevmin != dce.minAllowedLocalCoord() || prevmax != dce.maxAllowedLocalCoord()) {
dce.updateRenderedCutoff();
dce.updatePassengerLoadout();
}
if (isOnTwoBogeys()) {
dce.rotationAnchors.setFirst(dimension.equals(leadingBogeyDim) ? leadingBogey.getAnchorPosition()
: pivoted(dce, dimension, point,
leading ? leadingWheelSpacing / 2 : bogeySpacing + trailingWheelSpacing / 2));
dce.rotationAnchors.setSecond(dimension.equals(trailingBogeyDim) ? trailingBogey.getAnchorPosition()
: pivoted(dce, dimension, point,
leading ? leadingWheelSpacing / 2 + bogeySpacing : trailingWheelSpacing / 2));
continue;
}
if (dimension.equals(otherDimension)) {
dce.rotationAnchors = leadingBogey.points.map(TravellingPoint::getPosition);
continue;
}
dce.rotationAnchors.setFirst(leadingBogey.points.getFirst() == point ? point.getPosition()
: pivoted(dce, dimension, point, leadingWheelSpacing));
dce.rotationAnchors.setSecond(leadingBogey.points.getSecond() == point ? point.getPosition()
: pivoted(dce, dimension, point, leadingWheelSpacing));
}
}
public void alignEntity(CarriageContraptionEntity entity) {
Vec3 positionVec = rotationAnchors.getFirst();
Vec3 coupledVec = rotationAnchors.getSecond();
private Vec3 pivoted(DimensionalCarriageEntity dce, ResourceKey<Level> dimension, TravellingPoint start,
double offset) {
if (train.graph == null)
return dce.pivot == null ? null : dce.pivot.getLocation();
TrackNodeLocation pivot = dce.findPivot(dimension, start == getLeadingPoint());
if (pivot == null)
return null;
Vec3 startVec = start.getPosition();
Vec3 portalVec = pivot.getLocation()
.add(0, 1, 0);
return VecHelper.lerp((float) (offset / startVec.distanceTo(portalVec)), startVec, portalVec);
}
double diffX = positionVec.x - coupledVec.x;
double diffY = positionVec.y - coupledVec.y;
double diffZ = positionVec.z - coupledVec.z;
entity.setPos(positionAnchor);
entity.prevYaw = entity.yaw;
entity.prevPitch = entity.pitch;
entity.yaw = (float) (Mth.atan2(diffZ, diffX) * 180 / Math.PI) + 180;
entity.pitch = (float) (Math.atan2(diffY, Math.sqrt(diffX * diffX + diffZ * diffZ)) * 180 / Math.PI) * -1;
if (entity.firstPositionUpdate) {
entity.xo = entity.getX();
entity.yo = entity.getY();
entity.zo = entity.getZ();
entity.prevYaw = entity.yaw;
entity.prevPitch = entity.pitch;
public void alignEntity(Level level) {
DimensionalCarriageEntity dimensionalCarriageEntity = entities.get(level.dimension());
if (dimensionalCarriageEntity != null) {
CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get();
if (entity != null)
dimensionalCarriageEntity.alignEntity(entity);
}
}
@ -268,41 +395,79 @@ public class Carriage {
return bogeys.getSecond() != null;
}
public Vec3 leadingAnchor() {
return isOnTwoBogeys() ? rotationAnchors.getFirst() : positionAnchor;
public CarriageContraptionEntity anyAvailableEntity() {
for (DimensionalCarriageEntity dimensionalCarriageEntity : entities.values()) {
CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get();
if (entity != null)
return entity;
}
return null;
}
public Vec3 trailingAnchor() {
return isOnTwoBogeys() ? rotationAnchors.getSecond() : positionAnchor;
public void forEachPresentEntity(Consumer<CarriageContraptionEntity> callback) {
for (DimensionalCarriageEntity dimensionalCarriageEntity : entities.values()) {
CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get();
if (entity != null)
callback.accept(entity);
}
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag tag = new CompoundTag();
tag.put("FirstBogey", bogeys.getFirst()
.write());
.write(dimensions));
if (isOnTwoBogeys())
tag.put("SecondBogey", bogeys.getSecond()
.write());
.write(dimensions));
tag.putInt("Spacing", bogeySpacing);
tag.putBoolean("FrontConductor", presentConductors.getFirst());
tag.putBoolean("BackConductor", presentConductors.getSecond());
tag.putBoolean("Stalled", stalled);
CarriageContraptionEntity entity = this.entity.get();
if (entity != null)
serialisedEntity = entity.serializeNBT();
Map<Integer, CompoundTag> passengerMap = new HashMap<>();
for (DimensionalCarriageEntity dimensionalCarriageEntity : entities.values()) {
CarriageContraptionEntity entity = dimensionalCarriageEntity.entity.get();
if (entity == null)
continue;
serialize(entity);
Contraption contraption = entity.getContraption();
if (contraption == null)
continue;
Map<UUID, Integer> mapping = contraption.getSeatMapping();
for (Entity passenger : entity.getPassengers())
if (mapping.containsKey(passenger.getUUID()))
passengerMap.put(mapping.get(passenger.getUUID()), passenger.serializeNBT());
}
tag.put("Entity", serialisedEntity.copy());
tag.put("PositionAnchor", VecHelper.writeNBT(positionAnchor));
tag.put("RotationAnchors", rotationAnchors.serializeEach(VecHelper::writeNBTCompound));
CompoundTag passengerTag = new CompoundTag();
passengerMap.putAll(serialisedPassengers);
passengerMap.forEach((seat, nbt) -> passengerTag.put("Seat" + seat, nbt.copy()));
tag.put("Passengers", passengerTag);
tag.put("EntityPositioning", NBTHelper.writeCompoundList(entities.entrySet(), e -> {
CompoundTag c = e.getValue()
.write();
c.putInt("Dim", dimensions.encode(e.getKey()));
return c;
}));
return tag;
}
public static Carriage read(CompoundTag tag, TrackGraph graph) {
CarriageBogey bogey1 = CarriageBogey.read(tag.getCompound("FirstBogey"), graph);
private void serialize(Entity entity) {
serialisedEntity = entity.serializeNBT();
serialisedEntity.remove("Passengers");
serialisedEntity.getCompound("Contraption")
.remove("Passengers");
}
public static Carriage read(CompoundTag tag, TrackGraph graph, DimensionPalette dimensions) {
CarriageBogey bogey1 = CarriageBogey.read(tag.getCompound("FirstBogey"), graph, dimensions);
CarriageBogey bogey2 =
tag.contains("SecondBogey") ? CarriageBogey.read(tag.getCompound("SecondBogey"), graph) : null;
tag.contains("SecondBogey") ? CarriageBogey.read(tag.getCompound("SecondBogey"), graph, dimensions) : null;
Carriage carriage = new Carriage(bogey1, bogey2, tag.getInt("Spacing"));
@ -311,13 +476,373 @@ public class Carriage {
carriage.serialisedEntity = tag.getCompound("Entity")
.copy();
if (carriage.positionAnchor == null) {
carriage.positionAnchor = VecHelper.readNBT(tag.getList("PositionAnchor", Tag.TAG_DOUBLE));
carriage.rotationAnchors =
Couple.deserializeEach(tag.getList("RotationAnchors", Tag.TAG_COMPOUND), VecHelper::readNBTCompound);
}
NBTHelper.iterateCompoundList(tag.getList("EntityPositioning", Tag.TAG_COMPOUND),
c -> carriage.getDimensional(dimensions.decode(c.getInt("Dim")))
.read(c));
CompoundTag passengersTag = tag.getCompound("Passengers");
passengersTag.getAllKeys()
.forEach(key -> carriage.serialisedPassengers.put(Integer.valueOf(key.substring(4)),
passengersTag.getCompound(key)));
return carriage;
}
private TravellingPoint portalScout = new TravellingPoint();
public class DimensionalCarriageEntity {
public Vec3 positionAnchor;
public Couple<Vec3> rotationAnchors;
public WeakReference<CarriageContraptionEntity> entity;
public TrackNodeLocation pivot;
int discardTicks;
// 0 == whole, 0..1 = fading out, -1..0 = fading in
public float cutoff;
// client
public boolean pointsInitialised;
public DimensionalCarriageEntity() {
this.entity = new WeakReference<>(null);
this.rotationAnchors = Couple.create(null, null);
this.pointsInitialised = false;
}
public void discardPivot() {
float prevCutoff = cutoff;
cutoff = 0;
pivot = null;
if (!serialisedPassengers.isEmpty() || !Mth.equal(prevCutoff, cutoff)) {
updatePassengerLoadout();
updateRenderedCutoff();
}
}
public void updateCutoff(boolean leadingIsCurrent) {
Vec3 leadingAnchor = rotationAnchors.getFirst();
Vec3 trailingAnchor = rotationAnchors.getSecond();
if (leadingAnchor == null || trailingAnchor == null)
return;
if (pivot == null) {
cutoff = 0;
return;
}
Vec3 pivotLoc = pivot.getLocation()
.add(0, 1, 0);
double leadingSpacing = leadingBogey().type.getWheelPointSpacing() / 2;
double trailingSpacing = trailingBogey().type.getWheelPointSpacing() / 2;
double anchorSpacing = leadingSpacing + bogeySpacing + trailingSpacing;
if (isOnTwoBogeys()) {
Vec3 diff = trailingAnchor.subtract(leadingAnchor)
.normalize();
trailingAnchor = trailingAnchor.add(diff.scale(trailingSpacing));
leadingAnchor = leadingAnchor.add(diff.scale(-leadingSpacing));
}
double leadingDiff = leadingAnchor.distanceTo(pivotLoc);
double trailingDiff = trailingAnchor.distanceTo(pivotLoc);
leadingDiff /= anchorSpacing;
trailingDiff /= anchorSpacing;
if (leadingIsCurrent && leadingDiff > trailingDiff && leadingDiff > 1)
cutoff = 0;
else if (leadingIsCurrent && leadingDiff < trailingDiff && trailingDiff > 1)
cutoff = 1;
else if (!leadingIsCurrent && leadingDiff > trailingDiff && leadingDiff > 1)
cutoff = -1;
else if (!leadingIsCurrent && leadingDiff < trailingDiff && trailingDiff > 1)
cutoff = 0;
else
cutoff = (float) Mth.clamp(1 - (leadingIsCurrent ? leadingDiff : trailingDiff), 0, 1)
* (leadingIsCurrent ? 1 : -1);
}
public TrackNodeLocation findPivot(ResourceKey<Level> dimension, boolean leading) {
if (pivot != null)
return pivot;
TravellingPoint start = leading ? getLeadingPoint() : getTrailingPoint();
TravellingPoint end = !leading ? getLeadingPoint() : getTrailingPoint();
portalScout.node1 = start.node1;
portalScout.node2 = start.node2;
portalScout.edge = start.edge;
portalScout.position = start.position;
ITrackSelector trackSelector = portalScout.follow(end);
int distance = bogeySpacing + 10;
int direction = leading ? -1 : 1;
portalScout.travel(train.graph, direction * distance, trackSelector, portalScout.ignoreEdgePoints(),
portalScout.ignoreTurns(), nodes -> {
for (boolean b : Iterate.trueAndFalse)
if (nodes.get(b).dimension.equals(dimension))
pivot = nodes.get(b);
return true;
});
return pivot;
}
public CompoundTag write() {
CompoundTag tag = new CompoundTag();
tag.putFloat("Cutoff", cutoff);
tag.putInt("DiscardTicks", discardTicks);
if (pivot != null)
tag.put("Pivot", pivot.write(null));
if (positionAnchor != null)
tag.put("PositionAnchor", VecHelper.writeNBT(positionAnchor));
if (rotationAnchors.both(Objects::nonNull))
tag.put("RotationAnchors", rotationAnchors.serializeEach(VecHelper::writeNBTCompound));
return tag;
}
public void read(CompoundTag tag) {
cutoff = tag.getFloat("Cutoff");
discardTicks = tag.getInt("DiscardTicks");
if (tag.contains("Pivot"))
pivot = TrackNodeLocation.read(tag.getCompound("Pivot"), null);
if (positionAnchor != null)
return;
if (tag.contains("PositionAnchor"))
positionAnchor = VecHelper.readNBT(tag.getList("PositionAnchor", Tag.TAG_DOUBLE));
if (tag.contains("RotationAnchors"))
rotationAnchors = Couple.deserializeEach(tag.getList("RotationAnchors", Tag.TAG_COMPOUND),
VecHelper::readNBTCompound);
}
public Vec3 leadingAnchor() {
return isOnTwoBogeys() ? rotationAnchors.getFirst() : positionAnchor;
}
public Vec3 trailingAnchor() {
return isOnTwoBogeys() ? rotationAnchors.getSecond() : positionAnchor;
}
public int minAllowedLocalCoord() {
if (cutoff <= 0)
return Integer.MIN_VALUE;
if (cutoff >= 1)
return Integer.MAX_VALUE;
return Mth.floor(-bogeySpacing + -1 + (2 + bogeySpacing) * cutoff);
}
public int maxAllowedLocalCoord() {
if (cutoff >= 0)
return Integer.MAX_VALUE;
if (cutoff <= -1)
return Integer.MIN_VALUE;
return Mth.ceil(-bogeySpacing + -1 + (2 + bogeySpacing) * (cutoff + 1));
}
public void updatePassengerLoadout() {
Entity entity = this.entity.get();
if (!(entity instanceof CarriageContraptionEntity cce))
return;
if (!(entity.level instanceof ServerLevel sLevel))
return;
Set<Integer> loadedPassengers = new HashSet<>();
int min = minAllowedLocalCoord();
int max = maxAllowedLocalCoord();
for (Entry<Integer, CompoundTag> entry : serialisedPassengers.entrySet()) {
Integer seatId = entry.getKey();
List<BlockPos> seats = cce.getContraption()
.getSeats();
if (seatId >= seats.size())
continue;
BlockPos localPos = seats.get(seatId);
if (!cce.isLocalCoordWithin(localPos, min, max))
continue;
CompoundTag tag = entry.getValue();
Entity passenger = null;
if (tag.contains("PlayerPassenger")) {
passenger = sLevel.getServer()
.getPlayerList()
.getPlayer(tag.getUUID("PlayerPassenger"));
} else {
passenger = EntityType.loadEntityRecursive(tag, entity.level, e -> {
e.moveTo(positionAnchor);
return e;
});
if (passenger != null)
sLevel.tryAddFreshEntityWithPassengers(passenger);
}
if (passenger != null) {
ResourceKey<Level> passengerDimension = passenger.level.dimension();
if (!passengerDimension.equals(sLevel.dimension()) && passenger instanceof ServerPlayer sp)
continue;
cce.addSittingPassenger(passenger, seatId);
}
loadedPassengers.add(seatId);
}
loadedPassengers.forEach(serialisedPassengers::remove);
Map<UUID, Integer> mapping = cce.getContraption()
.getSeatMapping();
for (Entity passenger : entity.getPassengers()) {
BlockPos localPos = cce.getContraption()
.getSeatOf(passenger.getUUID());
if (cce.isLocalCoordWithin(localPos, min, max))
continue;
if (!mapping.containsKey(passenger.getUUID()))
continue;
Integer seat = mapping.get(passenger.getUUID());
if ((passenger instanceof ServerPlayer sp)) {
dismountPlayer(sLevel, sp, seat, true);
continue;
}
serialisedPassengers.put(seat, passenger.serializeNBT());
passenger.discard();
}
}
private void dismountPlayer(ServerLevel sLevel, ServerPlayer sp, Integer seat, boolean portal) {
if (!portal) {
sp.stopRiding();
return;
}
CompoundTag tag = new CompoundTag();
tag.putUUID("PlayerPassenger", sp.getUUID());
serialisedPassengers.put(seat, tag);
sp.stopRiding();
sp.getPersistentData()
.remove("ContraptionDismountLocation");
for (Entry<ResourceKey<Level>, DimensionalCarriageEntity> other : entities.entrySet()) {
DimensionalCarriageEntity otherDce = other.getValue();
if (otherDce == this)
continue;
if (sp.level.dimension()
.equals(other.getKey()))
continue;
if (otherDce.pivot == null)
continue;
Vec3 loc = otherDce.pivot.getLocation();
ServerLevel level = sLevel.getServer()
.getLevel(other.getKey());
sp.teleportTo(level, loc.x, loc.y, loc.z, sp.getYRot(), sp.getXRot());
sp.setPortalCooldown();
}
}
public void updateRenderedCutoff() {
Entity entity = this.entity.get();
if (!(entity instanceof CarriageContraptionEntity cce))
return;
if (!entity.level.isClientSide())
return;
Contraption contraption = cce.getContraption();
if (!(contraption instanceof CarriageContraption cc))
return;
cc.portalCutoffMin = minAllowedLocalCoord();
cc.portalCutoffMax = maxAllowedLocalCoord();
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> invalidate(cce));
}
@OnlyIn(Dist.CLIENT)
private void invalidate(CarriageContraptionEntity entity) {
ContraptionRenderDispatcher.invalidate(entity.getContraption());
entity.updateRenderedPortalCutoff();
}
private void createEntity(Level level, boolean loadPassengers) {
Entity entity = EntityType.loadEntityRecursive(serialisedEntity, level, e -> {
e.moveTo(positionAnchor);
return e;
});
if (!(entity instanceof CarriageContraptionEntity cce)) {
train.invalid = true;
return;
}
cce.setGraph(train.graph == null ? null : train.graph.id);
cce.setCarriage(Carriage.this);
cce.syncCarriage();
this.entity = new WeakReference<>(cce);
if (level instanceof ServerLevel sl)
sl.tryAddFreshEntityWithPassengers(entity);
updatePassengerLoadout();
}
private void removeAndSaveEntity(CarriageContraptionEntity entity, boolean portal) {
Contraption contraption = entity.getContraption();
if (contraption != null) {
Map<UUID, Integer> mapping = contraption.getSeatMapping();
for (Entity passenger : entity.getPassengers()) {
if (!mapping.containsKey(passenger.getUUID()))
continue;
Integer seat = mapping.get(passenger.getUUID());
if (passenger instanceof ServerPlayer sp) {
dismountPlayer(sp.getLevel(), sp, seat, portal);
continue;
}
serialisedPassengers.put(seat, passenger.serializeNBT());
}
}
for (Entity passenger : entity.getPassengers())
if (!(passenger instanceof Player))
passenger.discard();
serialize(entity);
entity.discard();
this.entity.clear();
}
public void alignEntity(CarriageContraptionEntity entity) {
if (rotationAnchors.either(Objects::isNull))
return;
Vec3 positionVec = rotationAnchors.getFirst();
Vec3 coupledVec = rotationAnchors.getSecond();
double diffX = positionVec.x - coupledVec.x;
double diffY = positionVec.y - coupledVec.y;
double diffZ = positionVec.z - coupledVec.z;
entity.setPos(positionAnchor);
entity.prevYaw = entity.yaw;
entity.prevPitch = entity.pitch;
entity.yaw = (float) (Mth.atan2(diffZ, diffX) * 180 / Math.PI) + 180;
entity.pitch = (float) (Math.atan2(diffY, Math.sqrt(diffX * diffX + diffZ * diffZ)) * 180 / Math.PI) * -1;
if (!entity.firstPositionUpdate)
return;
entity.xo = entity.getX();
entity.yo = entity.getY();
entity.zo = entity.getZ();
entity.prevYaw = entity.yaw;
entity.prevPitch = entity.pitch;
}
}
}

View file

@ -4,6 +4,7 @@ import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.MaterialManager;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.IBogeyBlock;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.foundation.utility.AngleHelper;
@ -15,8 +16,10 @@ import com.simibubi.create.foundation.utility.animation.LerpedFloat;
import net.minecraft.core.Direction.Axis;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.registries.ForgeRegistries;
@ -48,6 +51,20 @@ public class CarriageBogey {
couplingAnchors = Couple.create(null, null);
}
public ResourceKey<Level> getDimension() {
TravellingPoint leading = leading();
TravellingPoint trailing = trailing();
if (leading.edge == null || trailing.edge == null)
return null;
if (leading.edge.isInterDimensional() || trailing.edge.isInterDimensional())
return null;
ResourceKey<Level> dimension1 = leading.node1.getLocation().dimension;
ResourceKey<Level> dimension2 = trailing.node1.getLocation().dimension;
if (dimension1.equals(dimension2))
return dimension1;
return null;
}
public void updateAngles(CarriageContraptionEntity entity, double distanceMoved) {
double angleDiff = 360 * distanceMoved / (Math.PI * 2 * type.getWheelRadius());
@ -56,6 +73,10 @@ public class CarriageBogey {
if (leading().edge == null || carriage.train.derailed) {
yRot = -90 + entity.yaw - derailAngle;
} else if (!entity.level.dimension()
.equals(getDimension())) {
yRot = -90 + entity.yaw;
xRot = 0;
} else {
Vec3 positionVec = leading().getPosition();
Vec3 coupledVec = trailing().getPosition();
@ -86,6 +107,8 @@ public class CarriageBogey {
}
public double getStress() {
if (getDimension() == null)
return 0;
return type.getWheelPointSpacing() - leading().getPosition()
.distanceTo(trailing().getPosition());
}
@ -119,19 +142,19 @@ public class CarriageBogey {
couplingAnchors.set(leading, entityPos.add(thisOffset));
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag tag = new CompoundTag();
tag.putString("Type", ((Block) type).getRegistryName()
.toString());
tag.put("Points", points.serializeEach(TravellingPoint::write));
tag.put("Points", points.serializeEach(tp -> tp.write(dimensions)));
return tag;
}
public static CarriageBogey read(CompoundTag tag, TrackGraph graph) {
public static CarriageBogey read(CompoundTag tag, TrackGraph graph, DimensionPalette dimensions) {
ResourceLocation location = new ResourceLocation(tag.getString("Type"));
IBogeyBlock type = (IBogeyBlock) ForgeRegistries.BLOCKS.getValue(location);
Couple<TravellingPoint> points =
Couple.deserializeEach(tag.getList("Points", Tag.TAG_COMPOUND), c -> TravellingPoint.read(c, graph));
Couple<TravellingPoint> points = Couple.deserializeEach(tag.getList("Points", Tag.TAG_COMPOUND),
c -> TravellingPoint.read(c, graph, dimensions));
CarriageBogey carriageBogey = new CarriageBogey(type, points.getFirst(), points.getSecond());
return carriageBogey;
}

View file

@ -1,9 +1,12 @@
package com.simibubi.create.content.logistics.trains.entity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import org.apache.commons.lang3.tuple.Pair;
@ -25,14 +28,17 @@ import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis;
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;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@ -51,11 +57,17 @@ public class CarriageContraption extends Contraption {
private BlockPos secondBogeyPos;
private List<BlockPos> assembledBlazeBurners;
// render
public int portalCutoffMin;
public int portalCutoffMax;
public CarriageContraption() {
conductorSeats = new HashMap<>();
assembledBlazeBurners = new ArrayList<>();
blazeBurnerConductors = Couple.create(false, false);
soundQueue = new ArrivalSoundQueue();
portalCutoffMin = Integer.MIN_VALUE;
portalCutoffMax = Integer.MAX_VALUE;
}
public void setSoundQueueOffset(int offset) {
@ -216,4 +228,70 @@ public class CarriageContraption extends Contraption {
return secondBogeyPos;
}
private Collection<BlockEntity> specialRenderedTEsOutsidePortal = new ArrayList<>();
@Override
public Collection<StructureBlockInfo> getRenderedBlocks() {
if (notInPortal())
return super.getRenderedBlocks();
specialRenderedTEsOutsidePortal = new ArrayList<>();
specialRenderedTileEntities.stream()
.filter(te -> !isHiddenInPortal(te.getBlockPos()))
.forEach(specialRenderedTEsOutsidePortal::add);
Collection<StructureBlockInfo> values = new ArrayList<>();
for (Entry<BlockPos, StructureBlockInfo> entry : blocks.entrySet()) {
BlockPos pos = entry.getKey();
if (withinVisible(pos))
values.add(entry.getValue());
else if (atSeam(pos))
values.add(new StructureBlockInfo(pos, Blocks.PURPLE_STAINED_GLASS.defaultBlockState(), null));
}
return values;
}
@Override
public Collection<BlockEntity> getSpecialRenderedTEs() {
if (notInPortal())
return super.getSpecialRenderedTEs();
return specialRenderedTEsOutsidePortal;
}
@Override
public Optional<List<AABB>> getSimplifiedEntityColliders() {
if (notInPortal())
return super.getSimplifiedEntityColliders();
return Optional.empty();
}
@Override
public boolean isHiddenInPortal(BlockPos localPos) {
if (notInPortal())
return super.isHiddenInPortal(localPos);
return !withinVisible(localPos) || atSeam(localPos);
}
private boolean notInPortal() {
return portalCutoffMin == Integer.MIN_VALUE && portalCutoffMax == Integer.MAX_VALUE;
}
public boolean atSeam(BlockPos localPos) {
Direction facing = assemblyDirection;
Axis axis = facing.getClockWise()
.getAxis();
int coord = axis.choose(localPos.getZ(), localPos.getY(), localPos.getX()) * -facing.getAxisDirection()
.getStep();
return coord == portalCutoffMin || coord == portalCutoffMax;
}
public boolean withinVisible(BlockPos localPos) {
Direction facing = assemblyDirection;
Axis axis = facing.getClockWise()
.getAxis();
int coord = axis.choose(localPos.getZ(), localPos.getY(), localPos.getX()) * -facing.getAxisDirection()
.getStep();
return coord > portalCutoffMin && coord < portalCutoffMax;
}
}

View file

@ -2,8 +2,10 @@ package com.simibubi.create.content.logistics.trains.entity;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
@ -14,9 +16,13 @@ import com.simibubi.create.AllEntityDataSerializers;
import com.simibubi.create.AllEntityTypes;
import com.simibubi.create.Create;
import com.simibubi.create.CreateClient;
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.OrientedContraptionEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.interaction.controls.ControlsBlock;
import com.simibubi.create.content.contraptions.particle.CubeParticleData;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.entity.Carriage.DimensionalCarriageEntity;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.SteerDirection;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.GlobalStation;
import com.simibubi.create.foundation.config.AllConfigs;
@ -44,6 +50,8 @@ 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;
public class CarriageContraptionEntity extends OrientedContraptionEntity {
@ -125,6 +133,17 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
return entityData.get(SCHEDULED);
}
public boolean isLocalCoordWithin(BlockPos localPos, int min, int max) {
if (!(getContraption()instanceof CarriageContraption cc))
return false;
Direction facing = cc.getAssemblyDirection();
Axis axis = facing.getClockWise()
.getAxis();
int coord = axis.choose(localPos.getZ(), localPos.getY(), localPos.getX()) * -facing.getAxisDirection()
.getStep();
return coord >= min && coord <= max;
}
public static CarriageContraptionEntity create(Level world, CarriageContraption contraption) {
CarriageContraptionEntity entity =
new CarriageContraptionEntity(AllEntityTypes.CARRIAGE_CONTRAPTION.get(), world);
@ -162,8 +181,12 @@ 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);
if (carriage != null) {
DimensionalCarriageEntity dimensional = carriage.getDimensional(level);
dimensional.entity = new WeakReference<>(this);
dimensional.pivot = null;
carriage.updateContraptionAnchors();
}
updateTrackGraph();
} else
discard();
@ -201,7 +224,9 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
return;
}
if (!carriage.pointsInitialised)
DimensionalCarriageEntity dce = carriage.getDimensional(level);
if (!dce.pointsInitialised)
return;
carriageData.approach(this, carriage, 1f / getType().updateInterval());
@ -213,7 +238,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
yo = getY();
zo = getZ();
carriage.alignEntity(this);
dce.alignEntity(this);
double distanceTo = 0;
if (!firstPositionUpdate) {
@ -232,6 +257,8 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
if (carriage.train.derailed)
spawnDerailParticles(carriage);
if (dce.pivot != null)
spawnPortalParticles(dce);
firstPositionUpdate = false;
validForRender = true;
@ -247,7 +274,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
for (int index = 0; index < carriageCount; index++) {
int i = arrivalSoundReversed ? carriageCount - 1 - index : index;
Carriage carriage = carriages.get(i);
CarriageContraptionEntity entity = carriage.entity.get();
CarriageContraptionEntity entity = carriage.getDimensional(level).entity.get();
if (entity == null || !(entity.contraption instanceof CarriageContraption otherCC))
break;
tick = arrivalSoundReversed ? otherCC.soundQueue.lastTick() : otherCC.soundQueue.firstTick();
@ -268,7 +295,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
boolean keepTicking = false;
for (Carriage c : carriages) {
CarriageContraptionEntity entity = c.entity.get();
CarriageContraptionEntity entity = c.getDimensional(level).entity.get();
if (entity == null || !(entity.contraption instanceof CarriageContraption otherCC))
continue;
keepTicking |= otherCC.soundQueue.tick(entity, arrivalSoundTicks, arrivalSoundReversed);
@ -287,6 +314,11 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
super.tickActors();
}
@Override
protected boolean isActorActive(MovementContext context, MovementBehaviour actor) {
return !getContraption().isHiddenInPortal(context.localPos) && super.isActorActive(context, actor);
}
@Override
protected void handleStallInformation(float x, float y, float z, float angle) {}
@ -300,12 +332,49 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
}
}
private Set<BlockPos> particleSlice = new HashSet<>();
private float particleAvgY = 0;
private void spawnPortalParticles(DimensionalCarriageEntity dce) {
Vec3 pivot = dce.pivot.getLocation()
.add(0, 1.5f, 0);
if (particleSlice.isEmpty())
return;
boolean alongX = Mth.equal(pivot.x, Math.round(pivot.x));
int extraFlip = Direction.fromYRot(yaw)
.getAxisDirection()
.getStep();
Vec3 emitter = pivot.add(0, particleAvgY, 0);
double speed = position().distanceTo(getPrevPositionVec());
int size = (int) (particleSlice.size() * Mth.clamp(4 - speed * 4, 0, 4));
for (BlockPos pos : particleSlice) {
if (size != 0 && random.nextInt(size) != 0)
continue;
if (alongX)
pos = new BlockPos(0, pos.getY(), pos.getX());
Vec3 v = pivot.add(pos.getX() * extraFlip, pos.getY(), pos.getZ() * extraFlip);
CubeParticleData data =
new CubeParticleData(.25f, 0, .5f, .65f + (random.nextFloat() - .5f) * .25f, 4, false);
Vec3 m = v.subtract(emitter)
.normalize()
.scale(.325f);
m = VecHelper.rotate(m, random.nextFloat() * 360, alongX ? Axis.X : Axis.Z);
m = m.add(VecHelper.offsetRandomly(Vec3.ZERO, random, 0.25f));
level.addParticle(data, v.x, v.y, v.z, m.x, m.y, m.z);
}
}
@Override
public void onClientRemoval() {
super.onClientRemoval();
entityData.set(CARRIAGE_DATA, new CarriageSyncData());
if (carriage != null) {
carriage.pointsInitialised = false;
DimensionalCarriageEntity dce = carriage.getDimensional(level);
dce.pointsInitialised = false;
carriage.leadingBogey().couplingAnchors = Couple.create(null, null);
carriage.trailingBogey().couplingAnchors = Couple.create(null, null);
}
@ -467,7 +536,9 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
if (lookAhead != null) {
if (spaceDown) {
carriage.train.manualTick = true;
nav.startNavigation(lookAhead, -1, false);
carriage.train.manualTick = false;
navDistanceTotal = nav.distanceToDestination;
return true;
}
@ -541,4 +612,69 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
prevPosInvalid = true;
}
@Override
public boolean isReadyForRender() {
return super.isReadyForRender() && validForRender && !firstPositionUpdate;
}
@OnlyIn(Dist.CLIENT)
private WeakReference<CarriageContraptionInstance> instanceHolder;
@OnlyIn(Dist.CLIENT)
public void bindInstance(CarriageContraptionInstance instance) {
this.instanceHolder = new WeakReference<>(instance);
updateRenderedPortalCutoff();
}
@OnlyIn(Dist.CLIENT)
public void updateRenderedPortalCutoff() {
if (carriage == null)
return;
// update portal slice
particleSlice.clear();
particleAvgY = 0;
if (contraption instanceof CarriageContraption cc) {
Direction forward = cc.getAssemblyDirection()
.getClockWise();
Axis axis = forward.getAxis();
boolean x = axis == Axis.X;
boolean flip = true;
for (BlockPos pos : contraption.getBlocks()
.keySet()) {
if (!cc.atSeam(pos))
continue;
int pX = x ? pos.getX() : pos.getZ();
pX *= forward.getAxisDirection()
.getStep() * (flip ? 1 : -1);
pos = new BlockPos(pX, pos.getY(), 0);
particleSlice.add(pos);
particleAvgY += pos.getY();
}
}
if (particleSlice.size() > 0)
particleAvgY /= particleSlice.size();
// update hidden bogeys (if instanced)
if (instanceHolder == null)
return;
CarriageContraptionInstance instance = instanceHolder.get();
if (instance == null)
return;
int bogeySpacing = carriage.bogeySpacing;
carriage.bogeys.forEachWithContext((bogey, first) -> {
if (bogey == null)
return;
BlockPos bogeyPos = bogey.isLeading ? BlockPos.ZERO
: BlockPos.ZERO.relative(getInitialOrientation().getCounterClockWise(), bogeySpacing);
instance.setBogeyVisibility(first, !contraption.isHiddenInPortal(bogeyPos));
});
}
}

View file

@ -29,9 +29,7 @@ public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer
for (CarriageBogey bogey : carriage.bogeys)
if (bogey != null)
bogey.couplingAnchors.replace(v -> null);
if (!super.shouldRender(entity, clippingHelper, cameraX, cameraY, cameraZ))
return false;
return entity.validForRender && !entity.firstPositionUpdate;
return super.shouldRender(entity, clippingHelper, cameraX, cameraY, cameraZ);
}
@Override
@ -53,12 +51,19 @@ public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer
if (bogey == null)
return;
if (!Backend.isOn()) {
BlockPos bogeyPos = bogey.isLeading ? BlockPos.ZERO
: BlockPos.ZERO.relative(entity.getInitialOrientation()
.getCounterClockWise(), bogeySpacing);
if (!Backend.isOn() && !entity.getContraption()
.isHiddenInPortal(bogeyPos)) {
ms.pushPose();
translateBogey(ms, bogey, bogeySpacing, viewYRot, viewXRot, partialTicks);
int light = getBogeyLightCoords(entity, bogey, partialTicks);
bogey.type.render(null, bogey.wheelAngle.getValue(partialTicks), ms, partialTicks, buffers, light, overlay);
bogey.type.render(null, bogey.wheelAngle.getValue(partialTicks), ms, partialTicks, buffers, light,
overlay);
ms.popPose();
}
@ -70,25 +75,28 @@ public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer
});
}
public static void translateBogey(PoseStack ms, CarriageBogey bogey, int bogeySpacing, float viewYRot, float viewXRot, float partialTicks) {
public static void translateBogey(PoseStack ms, CarriageBogey bogey, int bogeySpacing, float viewYRot,
float viewXRot, float partialTicks) {
TransformStack.cast(ms)
.rotateY(viewYRot + 90)
.rotateX(-viewXRot)
.rotateY(180)
.translate(0, 0, bogey.isLeading ? 0 : -bogeySpacing)
.rotateY(-180)
.rotateX(viewXRot)
.rotateY(-viewYRot - 90)
.rotateY(bogey.yaw.getValue(partialTicks))
.rotateX(bogey.pitch.getValue(partialTicks))
.translate(0, .5f, 0);
.rotateY(viewYRot + 90)
.rotateX(-viewXRot)
.rotateY(180)
.translate(0, 0, bogey.isLeading ? 0 : -bogeySpacing)
.rotateY(-180)
.rotateX(viewXRot)
.rotateY(-viewYRot - 90)
.rotateY(bogey.yaw.getValue(partialTicks))
.rotateX(bogey.pitch.getValue(partialTicks))
.translate(0, .5f, 0);
}
public static int getBogeyLightCoords(CarriageContraptionEntity entity, CarriageBogey bogey, float partialTicks) {
var lightPos = new BlockPos(Objects.requireNonNullElseGet(bogey.getAnchorPosition(), () -> entity.getLightProbePosition(partialTicks)));
var lightPos = new BlockPos(
Objects.requireNonNullElseGet(bogey.getAnchorPosition(), () -> entity.getLightProbePosition(partialTicks)));
return LightTexture.pack(entity.level.getBrightness(LightLayer.BLOCK, lightPos), entity.level.getBrightness(LightLayer.SKY, lightPos));
return LightTexture.pack(entity.level.getBrightness(LightLayer.BLOCK, lightPos),
entity.level.getBrightness(LightLayer.SKY, lightPos));
}
}

View file

@ -7,6 +7,7 @@ import com.jozufozu.flywheel.util.AnimationTickHolder;
import com.jozufozu.flywheel.util.transform.TransformStack;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
public class CarriageContraptionInstance extends EntityInstance<CarriageContraptionEntity> implements DynamicInstance {
@ -14,25 +15,34 @@ public class CarriageContraptionInstance extends EntityInstance<CarriageContrapt
private Carriage carriage;
private Couple<BogeyInstance> bogeys;
private Couple<Boolean> bogeyHidden;
public CarriageContraptionInstance(MaterialManager materialManager, CarriageContraptionEntity entity) {
super(materialManager, entity);
bogeyHidden = Couple.create(() -> false);
entity.bindInstance(this);
}
@Override
public void init() {
carriage = entity.getCarriage();
if (carriage == null) return;
if (carriage == null)
return;
bogeys = carriage.bogeys.mapNotNullWithParam(CarriageBogey::createInstance, materialManager);
updateLight();
}
public void setBogeyVisibility(boolean first, boolean visible) {
bogeyHidden.set(first, !visible);
}
@Override
public void beginFrame() {
if (bogeys == null) {
init();
if (entity.isReadyForRender())
init();
return;
}
@ -47,18 +57,23 @@ public class CarriageContraptionInstance extends EntityInstance<CarriageContrapt
TransformStack.cast(ms)
.translate(getInstancePosition(partialTicks));
for (BogeyInstance instance : bogeys) {
if (instance != null) {
ms.pushPose();
CarriageBogey bogey = instance.bogey;
CarriageContraptionEntityRenderer.translateBogey(ms, bogey, bogeySpacing, viewYRot, viewXRot,
partialTicks);
ms.translate(0, -1.5 - 1 / 128f, 0);
instance.beginFrame(bogey.wheelAngle.getValue(partialTicks), ms);
ms.popPose();
for (boolean current : Iterate.trueAndFalse) {
BogeyInstance instance = bogeys.get(current);
if (instance == null)
continue;
if (bogeyHidden.get(current)) {
instance.hiddenFrame();
continue;
}
ms.pushPose();
CarriageBogey bogey = instance.bogey;
CarriageContraptionEntityRenderer.translateBogey(ms, bogey, bogeySpacing, viewYRot, viewXRot, partialTicks);
ms.translate(0, -1.5 - 1 / 128f, 0);
instance.beginFrame(bogey.wheelAngle.getValue(partialTicks), ms);
ms.popPose();
}
ms.popPose();
@ -66,7 +81,8 @@ public class CarriageContraptionInstance extends EntityInstance<CarriageContrapt
@Override
public void updateLight() {
if (bogeys == null) return;
if (bogeys == null)
return;
bogeys.forEach(instance -> {
if (instance != null)
@ -76,7 +92,8 @@ public class CarriageContraptionInstance extends EntityInstance<CarriageContrapt
@Override
public void remove() {
if (bogeys == null) return;
if (bogeys == null)
return;
bogeys.forEach(instance -> {
if (instance != null)

View file

@ -18,6 +18,7 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
@ -35,14 +36,15 @@ public class CarriageCouplingRenderer {
return;
Vec3 camera = cameraEntity.getPosition(partialTicks);
Level level = cameraEntity.level;
for (Train train : trains) {
List<Carriage> carriages = train.carriages;
for (int i = 0; i < carriages.size() - 1; i++) {
Carriage carriage = carriages.get(i);
CarriageContraptionEntity entity = carriage.entity.get();
CarriageContraptionEntity entity = carriage.getDimensional(level).entity.get();
Carriage carriage2 = carriages.get(i + 1);
CarriageContraptionEntity entity2 = carriage.entity.get();
CarriageContraptionEntity entity2 = carriage.getDimensional(level).entity.get();
if (entity == null || entity2 == null)
continue;
@ -51,7 +53,7 @@ public class CarriageCouplingRenderer {
CarriageBogey bogey2 = carriage2.leadingBogey();
Vec3 anchor = bogey1.couplingAnchors.getSecond();
Vec3 anchor2 = bogey2.couplingAnchors.getFirst();
if (anchor == null || anchor2 == null)
continue;
if (!anchor.closerThan(camera, 64))

View file

@ -16,6 +16,8 @@ import org.apache.commons.lang3.mutable.MutableBoolean;
import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.entity.Carriage.DimensionalCarriageEntity;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair;
@ -107,10 +109,12 @@ public class CarriageSyncData {
}
public void update(CarriageContraptionEntity entity, Carriage carriage) {
DimensionalCarriageEntity dce = carriage.getDimensional(entity.level);
TrackGraph graph = carriage.train.graph;
if (graph == null) {
fallbackLocations = Pair.of(carriage.positionAnchor, carriage.rotationAnchors);
carriage.pointsInitialised = true;
fallbackLocations = Pair.of(dce.positionAnchor, dce.rotationAnchors);
dce.pointsInitialised = true;
setDirty(true);
return;
}
@ -136,10 +140,12 @@ public class CarriageSyncData {
}
public void apply(CarriageContraptionEntity entity, Carriage carriage) {
DimensionalCarriageEntity dce = carriage.getDimensional(entity.level);
fallbackPointSnapshot = null;
if (fallbackLocations != null) {
fallbackPointSnapshot = Pair.of(carriage.positionAnchor, carriage.rotationAnchors);
carriage.pointsInitialised = true;
fallbackPointSnapshot = Pair.of(dce.positionAnchor, dce.rotationAnchors);
dce.pointsInitialised = true;
return;
}
@ -154,7 +160,7 @@ public class CarriageSyncData {
CarriageBogey bogey = carriage.bogeys.get(i / 2 == 0);
TravellingPoint bogeyPoint = bogey.points.get(i % 2 == 0);
TravellingPoint point = carriage.pointsInitialised ? pointsToApproach[i] : bogeyPoint;
TravellingPoint point = dce.pointsInitialised ? pointsToApproach[i] : bogeyPoint;
Couple<TrackNode> nodes = pair.getFirst()
.map(graph::getNode);
@ -170,7 +176,7 @@ public class CarriageSyncData {
point.edge = edge;
point.position = pair.getSecond();
if (carriage.pointsInitialised) {
if (dce.pointsInitialised) {
float foundDistance = -1;
boolean direction = false;
for (boolean forward : Iterate.trueAndFalse) {
@ -194,9 +200,9 @@ public class CarriageSyncData {
}
}
if (!carriage.pointsInitialised) {
if (!dce.pointsInitialised) {
carriage.train.navigation.distanceToDestination = distanceToDestination;
carriage.pointsInitialised = true;
dce.pointsInitialised = true;
return;
}
@ -207,10 +213,12 @@ public class CarriageSyncData {
}
public void approach(CarriageContraptionEntity entity, Carriage carriage, float partial) {
DimensionalCarriageEntity dce = carriage.getDimensional(entity.level);
if (fallbackLocations != null && fallbackPointSnapshot != null) {
carriage.positionAnchor = approachVector(partial, carriage.positionAnchor, fallbackLocations.getFirst(),
dce.positionAnchor = approachVector(partial, dce.positionAnchor, fallbackLocations.getFirst(),
fallbackPointSnapshot.getFirst());
carriage.rotationAnchors.replaceWithContext((current, first) -> approachVector(partial, current,
dce.rotationAnchors.replaceWithContext((current, first) -> approachVector(partial, current,
fallbackLocations.getSecond()
.get(first),
fallbackPointSnapshot.getSecond()
@ -238,9 +246,9 @@ public class CarriageSyncData {
MutableBoolean success = new MutableBoolean(true);
TravellingPoint toApproach = pointsToApproach[index];
point.travel(graph, partial * f,
point.follow(toApproach, b -> success.setValue(success.booleanValue() && b)),
point.ignoreEdgePoints(), point.ignoreTurns());
ITrackSelector trackSelector =
point.follow(toApproach, b -> success.setValue(success.booleanValue() && b));
point.travel(graph, partial * f, trackSelector);
// could not pathfind to server location
if (!success.booleanValue()) {

View file

@ -19,6 +19,7 @@ import org.apache.commons.lang3.mutable.MutableObject;
import com.jozufozu.flywheel.repack.joml.Math;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode;
@ -37,9 +38,7 @@ import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
@ -368,7 +367,7 @@ public class Navigation {
cancelNavigation();
return -1;
}
if (Math.abs(distanceToDestination) > 100)
announceArrival = true;
@ -460,7 +459,7 @@ public class Navigation {
return true;
});
if (!train.doubleEnded || !train.hasBackwardConductor())
if (!train.doubleEnded || !train.manualTick && !train.hasBackwardConductor())
break;
}
@ -621,14 +620,9 @@ public class Navigation {
List<Entry<TrackNode, TrackEdge>> validTargets = new ArrayList<>();
Map<TrackNode, TrackEdge> connectionsFrom = graph.getConnectionsFrom(node2);
for (Entry<TrackNode, TrackEdge> connection : connectionsFrom.entrySet()) {
TrackEdge newEdge = connection.getValue();
Vec3 currentDirection = edge.getDirection(false);
Vec3 newDirection = newEdge.getDirection(true);
if (currentDirection.dot(newDirection) < 7 / 8f)
continue;
validTargets.add(connection);
}
for (Entry<TrackNode, TrackEdge> connection : connectionsFrom.entrySet())
if (edge.canTravelTo(connection.getValue()))
validTargets.add(connection);
if (validTargets.isEmpty())
continue;
@ -673,7 +667,7 @@ public class Navigation {
Pair<Couple<TrackNode>, TrackEdge> current, GlobalStation station);
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag tag = new CompoundTag();
if (destination == null)
return tag;
@ -685,8 +679,7 @@ public class Navigation {
tag.put("Path", NBTHelper.writeCompoundList(currentPath, c -> {
CompoundTag nbt = new CompoundTag();
nbt.put("Nodes", c.map(TrackNode::getLocation)
.map(BlockPos::new)
.serializeEach(NbtUtils::writeBlockPos));
.serializeEach(loc -> loc.write(dimensions)));
return nbt;
}));
if (waitingForSignal == null)
@ -698,7 +691,7 @@ public class Navigation {
return tag;
}
public void read(CompoundTag tag, TrackGraph graph) {
public void read(CompoundTag tag, TrackGraph graph, DimensionPalette dimensions) {
destination = graph != null && tag.contains("Destination")
? graph.getPoint(EdgePointType.STATION, tag.getUUID("Destination"))
: null;
@ -713,8 +706,7 @@ public class Navigation {
currentPath.clear();
NBTHelper.iterateCompoundList(tag.getList("Path", Tag.TAG_COMPOUND),
c -> currentPath.add(Couple
.deserializeEach(c.getList("Nodes", Tag.TAG_COMPOUND),
c2 -> TrackNodeLocation.fromPackedPos(NbtUtils.readBlockPos(c2)))
.deserializeEach(c.getList("Nodes", Tag.TAG_COMPOUND), c2 -> TrackNodeLocation.read(c2, dimensions))
.map(graph::locateNode)));
waitingForSignal = tag.contains("BlockingSignal")
? Pair.of(tag.getUUID("BlockingSignal"), tag.getBoolean("BlockingSignalSide"))

View file

@ -20,10 +20,12 @@ import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.lang3.mutable.MutableObject;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.GraphLocation;
import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.entity.Carriage.DimensionalCarriageEntity;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.IEdgePointListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.SteerDirection;
@ -50,6 +52,7 @@ import net.minecraft.core.Direction.Axis;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
@ -72,6 +75,8 @@ public class Train {
public Component name;
public TrainStatus status;
public boolean invalid;
public SteerDirection manualSteer;
public boolean manualTick;
@ -154,7 +159,7 @@ public class Train {
Create.RAILWAYS.markTracksDirty();
if (graph == null) {
carriages.forEach(c -> c.manageEntity(level));
carriages.forEach(c -> c.manageEntities(level));
updateConductors();
return;
}
@ -175,9 +180,40 @@ public class Train {
Carriage carriage = carriages.get(i);
if (previousCarriage != null) {
int target = carriageSpacing.get(i - 1);
Vec3 leadingAnchor = carriage.leadingAnchor();
Vec3 trailingAnchor = previousCarriage.trailingAnchor();
double actual = leadingAnchor.distanceTo(trailingAnchor);
double actual = target;
TravellingPoint leadingPoint = carriage.getLeadingPoint();
TravellingPoint trailingPoint = previousCarriage.getTrailingPoint();
int entries = 0;
double total = 0;
if (leadingPoint.node1 != null && trailingPoint.node1 != null) {
ResourceKey<Level> d1 = leadingPoint.node1.getLocation().dimension;
ResourceKey<Level> d2 = trailingPoint.node1.getLocation().dimension;
for (boolean b : Iterate.trueAndFalse) {
ResourceKey<Level> d = b ? d1 : d2;
if (!b && d1.equals(d2))
continue;
DimensionalCarriageEntity dimensional = carriage.getDimensionalIfPresent(d);
DimensionalCarriageEntity dimensional2 = previousCarriage.getDimensionalIfPresent(d);
if (dimensional == null || dimensional2 == null)
continue;
Vec3 leadingAnchor = dimensional.leadingAnchor();
Vec3 trailingAnchor = dimensional2.trailingAnchor();
if (leadingAnchor == null || trailingAnchor == null)
continue;
total += leadingAnchor.distanceTo(trailingAnchor);
entries++;
}
}
if (entries > 0)
actual = total / entries;
stress[i - 1] = target - actual;
}
previousCarriage = carriage;
@ -383,10 +419,19 @@ public class Train {
if (derailed)
return;
Vec3 start = (speed < 0 ? carriage.getTrailingPoint() : carriage.getLeadingPoint()).getPosition();
Vec3 end = (speed < 0 ? carriage.getLeadingPoint() : carriage.getTrailingPoint()).getPosition();
TravellingPoint trailingPoint = carriage.getTrailingPoint();
TravellingPoint leadingPoint = carriage.getLeadingPoint();
Pair<Train, Vec3> collision = findCollidingTrain(level, start, end, this);
if (leadingPoint.node1 == null || trailingPoint.node1 == null)
return;
ResourceKey<Level> dimension = leadingPoint.node1.getLocation().dimension;
if (!dimension.equals(trailingPoint.node1.getLocation().dimension))
return;
Vec3 start = (speed < 0 ? trailingPoint : leadingPoint).getPosition();
Vec3 end = (speed < 0 ? leadingPoint : trailingPoint).getPosition();
Pair<Train, Vec3> collision = findCollidingTrain(level, start, end, this, dimension);
if (collision == null)
return;
@ -402,7 +447,8 @@ public class Train {
train.crash();
}
public static Pair<Train, Vec3> findCollidingTrain(Level level, Vec3 start, Vec3 end, Train ignore) {
public static Pair<Train, Vec3> findCollidingTrain(Level level, Vec3 start, Vec3 end, Train ignore,
ResourceKey<Level> dimension) {
for (Train train : Create.RAILWAYS.sided(level).trains.values()) {
if (train == ignore)
continue;
@ -419,6 +465,11 @@ public class Train {
TravellingPoint otherTrailing = otherCarriage.getTrailingPoint();
if (otherLeading.edge == null || otherTrailing.edge == null)
continue;
ResourceKey<Level> otherDimension = otherLeading.node1.getLocation().dimension;
if (!otherDimension.equals(otherTrailing.node1.getLocation().dimension))
continue;
if (!otherDimension.equals(dimension))
continue;
Vec3 start2 = otherLeading.getPosition();
Vec3 end2 = otherTrailing.getPosition();
@ -486,7 +537,7 @@ public class Train {
for (int i = 0; i < carriages.size(); i++) {
Carriage carriage = carriages.get(backwards ? carriages.size() - i - 1 : i);
CarriageContraptionEntity entity = carriage.entity.get();
CarriageContraptionEntity entity = carriage.anyAvailableEntity();
if (entity == null)
return false;
@ -511,7 +562,9 @@ public class Train {
public boolean canDisassemble() {
for (Carriage carriage : carriages) {
CarriageContraptionEntity entity = carriage.entity.get();
if (carriage.presentInMultipleDimensions())
return false;
CarriageContraptionEntity entity = carriage.anyAvailableEntity();
if (entity == null)
return false;
if (!Mth.equal(entity.pitch, 0))
@ -629,11 +682,8 @@ public class Train {
}
public void syncTrackGraphChanges() {
for (Carriage carriage : carriages) {
CarriageContraptionEntity entity = carriage.entity.get();
if (entity != null)
entity.setGraph(graph == null ? null : graph.id);
}
for (Carriage carriage : carriages)
carriage.forEachPresentEntity(e -> e.setGraph(graph == null ? null : graph.id));
}
public int getTotalLength() {
@ -681,7 +731,10 @@ public class Train {
public LivingEntity getOwner(Level level) {
try {
UUID uuid = owner;
return uuid == null ? null : level.getPlayerByUUID(uuid);
return uuid == null ? null
: level.getServer()
.getPlayerList()
.getPlayer(uuid);
} catch (IllegalArgumentException illegalargumentexception) {
return null;
}
@ -799,13 +852,13 @@ public class Train {
return Penalties.ANY_TRAIN;
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag tag = new CompoundTag();
tag.putUUID("Id", id);
tag.putUUID("Owner", owner);
if (graph != null)
tag.putUUID("Graph", graph.id);
tag.put("Carriages", NBTHelper.writeCompoundList(carriages, Carriage::write));
tag.put("Carriages", NBTHelper.writeCompoundList(carriages, c -> c.write(dimensions)));
tag.putIntArray("CarriageSpacing", carriageSpacing);
tag.putBoolean("DoubleEnded", doubleEnded);
tag.putDouble("Speed", speed);
@ -830,22 +883,22 @@ public class Train {
compoundTag.putUUID("Id", uid);
return compoundTag;
}));
tag.put("MigratingPoints", NBTHelper.writeCompoundList(migratingPoints, TrainMigration::write));
tag.put("MigratingPoints", NBTHelper.writeCompoundList(migratingPoints, tm -> tm.write(dimensions)));
tag.put("Runtime", runtime.write());
tag.put("Navigation", navigation.write());
tag.put("Navigation", navigation.write(dimensions));
return tag;
}
public static Train read(CompoundTag tag, Map<UUID, TrackGraph> trackNetworks) {
public static Train read(CompoundTag tag, Map<UUID, TrackGraph> trackNetworks, DimensionPalette dimensions) {
UUID id = tag.getUUID("Id");
UUID owner = tag.getUUID("Owner");
UUID graphId = tag.contains("Graph") ? tag.getUUID("Graph") : null;
TrackGraph graph = graphId == null ? null : trackNetworks.get(graphId);
List<Carriage> carriages = new ArrayList<>();
NBTHelper.iterateCompoundList(tag.getList("Carriages", Tag.TAG_COMPOUND),
c -> carriages.add(Carriage.read(c, graph)));
c -> carriages.add(Carriage.read(c, graph, dimensions)));
List<Integer> carriageSpacing = new ArrayList<>();
for (int i : tag.getIntArray("CarriageSpacing"))
carriageSpacing.add(i);
@ -868,10 +921,10 @@ public class Train {
NBTHelper.iterateCompoundList(tag.getList("ReservedSignalBlocks", Tag.TAG_COMPOUND),
c -> train.reservedSignalBlocks.add(c.getUUID("Id")));
NBTHelper.iterateCompoundList(tag.getList("MigratingPoints", Tag.TAG_COMPOUND),
c -> train.migratingPoints.add(TrainMigration.read(c)));
c -> train.migratingPoints.add(TrainMigration.read(c, dimensions)));
train.runtime.read(tag.getCompound("Runtime"));
train.navigation.read(tag.getCompound("Navigation"), graph);
train.navigation.read(tag.getCompound("Navigation"), graph, dimensions);
if (train.getCurrentStation() != null)
train.getCurrentStation()

View file

@ -2,6 +2,7 @@ package com.simibubi.create.content.logistics.trains.entity;
import java.util.Map.Entry;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.GraphLocation;
import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
@ -10,9 +11,7 @@ import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
@ -100,24 +99,22 @@ public class TrainMigration {
return null;
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag tag = new CompoundTag();
tag.putBoolean("Curve", curve);
tag.put("Fallback", VecHelper.writeNBT(fallback));
tag.putDouble("Position", positionOnOldEdge);
tag.put("Nodes", locations.map(BlockPos::new)
.serializeEach(NbtUtils::writeBlockPos));
tag.put("Nodes", locations.serializeEach(l -> l.write(dimensions)));
return tag;
}
public static TrainMigration read(CompoundTag tag) {
public static TrainMigration read(CompoundTag tag, DimensionPalette dimensions) {
TrainMigration trainMigration = new TrainMigration();
trainMigration.curve = tag.getBoolean("Curve");
trainMigration.fallback = VecHelper.readNBT(tag.getList("Fallback", Tag.TAG_DOUBLE));
trainMigration.positionOnOldEdge = tag.getDouble("Position");
trainMigration.locations =
Couple.deserializeEach(tag.getList("Nodes", Tag.TAG_COMPOUND), NbtUtils::readBlockPos)
.map(TrackNodeLocation::fromPackedPos);
Couple.deserializeEach(tag.getList("Nodes", Tag.TAG_COMPOUND), c -> TrackNodeLocation.read(c, dimensions));
return trainMigration;
}

View file

@ -189,14 +189,18 @@ public class TrainRelocator {
};
ITrackSelector steer = probe.steer(SteerDirection.NONE, track.getUpNormal(level, pos, blockState));
MutableBoolean blocked = new MutableBoolean(false);
MutableBoolean portal = new MutableBoolean(false);
MutableInt blockingIndex = new MutableInt(0);
train.forEachTravellingPointBackwards((tp, d) -> {
if (blocked.booleanValue())
return;
probe.travel(graph, d, steer, ignoreSignals, ignoreTurns);
probe.travel(graph, d, steer, ignoreSignals, ignoreTurns, $ -> {
portal.setTrue();
return true;
});
recorder.accept(probe);
if (probe.blocked) {
if (probe.blocked || portal.booleanValue()) {
blocked.setTrue();
return;
}
@ -212,7 +216,8 @@ public class TrainRelocator {
Vec3 vec1 = recordedVecs.get(i);
Vec3 vec2 = recordedVecs.get(i + 1);
boolean blocking = i >= blockingIndex.intValue() - 1;
boolean collided = !blocked.booleanValue() && Train.findCollidingTrain(level, vec1, vec2, train) != null;
boolean collided = !blocked.booleanValue()
&& Train.findCollidingTrain(level, vec1, vec2, train, level.dimension()) != null;
if (level.isClientSide && simulate)
toVisualise.add(vec2);
if (collided || blocking)

View file

@ -70,6 +70,13 @@ public class TrainStatus {
track = true;
}
public void doublePortal() {
if (track)
return;
displayInformation("A Carriage cannot enter a portal whilst leaving another.", false);
track = true;
}
public void endOfTrack() {
if (track)
return;

View file

@ -11,10 +11,12 @@ import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.GraphLocation;
import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
@ -25,9 +27,7 @@ import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
@ -59,6 +59,9 @@ public class TravellingPoint {
public static interface ITurnListener extends BiConsumer<Double, TrackEdge> {
};
public static interface IPortalListener extends Predicate<Couple<TrackNodeLocation>> {
};
public TravellingPoint() {}
public TravellingPoint(TrackNode node1, TrackNode node2, TrackEdge edge, double position) {
@ -77,6 +80,10 @@ public class TravellingPoint {
};
}
public IPortalListener ignorePortals() {
return $ -> false;
}
public ITrackSelector random() {
return (graph, pair) -> pair.getSecond()
.get(Create.RANDOM.nextInt(pair.getSecond()
@ -176,8 +183,22 @@ public class TravellingPoint {
};
}
public double travel(TrackGraph graph, double distance, ITrackSelector trackSelector) {
return travel(graph, distance, trackSelector, ignoreEdgePoints());
}
public double travel(TrackGraph graph, double distance, ITrackSelector trackSelector,
IEdgePointListener signalListener) {
return travel(graph, distance, trackSelector, signalListener, ignoreTurns());
}
public double travel(TrackGraph graph, double distance, ITrackSelector trackSelector,
IEdgePointListener signalListener, ITurnListener turnListener) {
return travel(graph, distance, trackSelector, signalListener, turnListener, ignorePortals());
}
public double travel(TrackGraph graph, double distance, ITrackSelector trackSelector,
IEdgePointListener signalListener, ITurnListener turnListener, IPortalListener portalListener) {
blocked = false;
double edgeLength = edge.getLength();
if (Mth.equal(distance, 0))
@ -185,7 +206,7 @@ public class TravellingPoint {
double prevPos = position;
double traveled = distance;
double currentT = position / edgeLength;
double currentT = edgeLength == 0 ? 0 : position / edgeLength;
double incrementT = edge.incrementT(currentT, distance);
position = incrementT * edgeLength;
@ -222,9 +243,7 @@ public class TravellingPoint {
continue;
TrackEdge newEdge = entry.getValue();
Vec3 currentDirection = edge.getDirection(false);
Vec3 newDirection = newEdge.getDirection(true);
if (currentDirection.dot(newDirection) < 7 / 8f)
if (!edge.canTravelTo(newEdge))
continue;
validTargets.add(entry);
@ -240,6 +259,16 @@ public class TravellingPoint {
Entry<TrackNode, TrackEdge> entry = validTargets.size() == 1 ? validTargets.get(0)
: trackSelector.apply(graph, Pair.of(true, validTargets));
if (entry.getValue()
.getLength() == 0 && portalListener.test(
Couple.create(node2.getLocation(), entry.getKey()
.getLocation()))) {
traveled -= position - edgeLength;
position = edgeLength;
blocked = true;
break;
}
node1 = node2;
node2 = entry.getKey();
edge = entry.getValue();
@ -272,12 +301,9 @@ public class TravellingPoint {
TrackNode newNode = entry.getKey();
if (newNode == node2)
continue;
TrackEdge newEdge = graph.getConnectionsFrom(newNode)
.get(node1);
Vec3 currentDirection = edge.getDirection(true);
Vec3 newDirection = newEdge.getDirection(false);
if (currentDirection.dot(newDirection) < 7 / 8f)
if (!graph.getConnectionsFrom(newNode)
.get(node1)
.canTravelTo(edge))
continue;
validTargets.add(entry);
@ -293,6 +319,16 @@ public class TravellingPoint {
Entry<TrackNode, TrackEdge> entry = validTargets.size() == 1 ? validTargets.get(0)
: trackSelector.apply(graph, Pair.of(false, validTargets));
if (entry.getValue()
.getLength() == 0 && portalListener.test(
Couple.create(entry.getKey()
.getLocation(), node1.getLocation()))) {
traveled -= position;
position = 0;
blocked = true;
break;
}
node2 = node1;
node1 = entry.getKey();
edge = graph.getConnectionsFrom(node1)
@ -322,9 +358,10 @@ public class TravellingPoint {
if (edge.isTurn())
turnListener.accept(Math.max(0, totalDistance), edge);
EdgeData edgeData = edge.getEdgeData();
double from = forward ? prevPos : position;
double to = forward ? position : prevPos;
EdgeData edgeData = edge.getEdgeData();
List<TrackEdgePoint> edgePoints = edgeData.getPoints();
double length = edge.getLength();
@ -353,7 +390,11 @@ public class TravellingPoint {
}
public Vec3 getPosition() {
double t = position / edge.getLength();
return getPositionWithOffset(0);
}
public Vec3 getPositionWithOffset(double offset) {
double t = (position + offset) / edge.getLength();
return edge.getPosition(t)
.add(edge.getNormal(node1, node2, t)
.scale(1));
@ -369,28 +410,25 @@ public class TravellingPoint {
.get(node2);
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag tag = new CompoundTag();
Couple<TrackNode> nodes = Couple.create(node1, node2);
if (nodes.either(Objects::isNull))
return tag;
tag.put("Nodes", nodes.map(TrackNode::getLocation)
.map(BlockPos::new)
.serializeEach(NbtUtils::writeBlockPos));
.serializeEach(loc -> loc.write(dimensions)));
tag.putDouble("Position", position);
return tag;
}
public static TravellingPoint read(CompoundTag tag, TrackGraph graph) {
public static TravellingPoint read(CompoundTag tag, TrackGraph graph, DimensionPalette dimensions) {
if (graph == null)
return new TravellingPoint(null, null, null, 0);
Couple<TrackNode> locs =
tag.contains("Nodes")
? Couple.deserializeEach(tag.getList("Nodes", Tag.TAG_COMPOUND), NbtUtils::readBlockPos)
.map(TrackNodeLocation::fromPackedPos)
.map(graph::locateNode)
: Couple.create(null, null);
Couple<TrackNode> locs = tag.contains("Nodes")
? Couple.deserializeEach(tag.getList("Nodes", Tag.TAG_COMPOUND), c -> TrackNodeLocation.read(c, dimensions))
.map(graph::locateNode)
: Couple.create(null, null);
if (locs.either(Objects::isNull))
return new TravellingPoint(null, null, null, 0);

View file

@ -10,6 +10,7 @@ import javax.annotation.Nullable;
import com.google.common.base.Objects;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode;
@ -177,7 +178,7 @@ public class EdgeData {
return null;
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag nbt = new CompoundTag();
if (singleSignalGroup == passiveGroup)
NBTHelper.putMarker(nbt, "PassiveGroup");
@ -194,11 +195,11 @@ public class EdgeData {
return tag;
}));
if (hasIntersections())
nbt.put("Intersections", NBTHelper.writeCompoundList(intersections, TrackEdgeIntersection::write));
nbt.put("Intersections", NBTHelper.writeCompoundList(intersections, tei -> tei.write(dimensions)));
return nbt;
}
public static EdgeData read(CompoundTag nbt, TrackEdge edge, TrackGraph graph) {
public static EdgeData read(CompoundTag nbt, TrackEdge edge, TrackGraph graph, DimensionPalette dimensions) {
EdgeData data = new EdgeData(edge);
if (nbt.contains("SignalGroup"))
data.singleSignalGroup = nbt.getUUID("SignalGroup");
@ -216,8 +217,8 @@ public class EdgeData {
data.points.add(point);
});
if (nbt.contains("Intersections"))
data.intersections =
NBTHelper.readCompoundList(nbt.getList("Intersections", Tag.TAG_COMPOUND), TrackEdgeIntersection::read);
data.intersections = NBTHelper.readCompoundList(nbt.getList("Intersections", Tag.TAG_COMPOUND),
c -> TrackEdgeIntersection.read(c, dimensions));
return data;
}

View file

@ -7,6 +7,7 @@ import java.util.Map.Entry;
import java.util.UUID;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.TrackEdgePoint;
import com.simibubi.create.foundation.utility.NBTHelper;
@ -65,14 +66,14 @@ public class EdgePointStorage {
pointsByType.clear();
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag nbt = new CompoundTag();
for (Entry<EdgePointType<?>, Map<UUID, TrackEdgePoint>> entry : pointsByType.entrySet()) {
EdgePointType<?> type = entry.getKey();
ListTag list = NBTHelper.writeCompoundList(entry.getValue()
.values(), edgePoint -> {
CompoundTag tag = new CompoundTag();
edgePoint.write(tag);
edgePoint.write(tag, dimensions);
return tag;
});
nbt.put(type.getId()
@ -81,14 +82,14 @@ public class EdgePointStorage {
return nbt;
}
public void read(CompoundTag nbt) {
public void read(CompoundTag nbt, DimensionPalette dimensions) {
for (EdgePointType<?> type : EdgePointType.TYPES.values()) {
ListTag list = nbt.getList(type.getId()
.toString(), Tag.TAG_COMPOUND);
Map<UUID, TrackEdgePoint> map = getMap(type);
NBTHelper.iterateCompoundList(list, tag -> {
TrackEdgePoint edgePoint = type.create();
edgePoint.read(tag, false);
edgePoint.read(tag, false, dimensions);
map.put(edgePoint.getId(), edgePoint);
});
}

View file

@ -5,6 +5,7 @@ import java.util.Map;
import java.util.function.Supplier;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalBoundary;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.TrackEdgePoint;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.GlobalStation;
@ -44,11 +45,11 @@ public class EdgePointType<T extends TrackEdgePoint> {
return id;
}
public static TrackEdgePoint read(FriendlyByteBuf buffer) {
public static TrackEdgePoint read(FriendlyByteBuf buffer, DimensionPalette dimensions) {
ResourceLocation type = buffer.readResourceLocation();
EdgePointType<?> edgePointType = TYPES.get(type);
TrackEdgePoint point = edgePointType.create();
point.read(buffer);
point.read(buffer, dimensions);
return point;
}

View file

@ -2,12 +2,11 @@ package com.simibubi.create.content.logistics.trains.management.edgePoint;
import java.util.UUID;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.foundation.utility.Couple;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
public class TrackEdgeIntersection {
@ -31,18 +30,18 @@ public class TrackEdgeIntersection {
|| target1.equals(target.getSecond()) && target2.equals(target.getFirst());
}
public CompoundTag write() {
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag nbt = new CompoundTag();
nbt.putUUID("Id", id);
if (groupId != null)
nbt.putUUID("GroupId", groupId);
nbt.putDouble("Location", location);
nbt.putDouble("TargetLocation", targetLocation);
nbt.put("TargetEdge", target.serializeEach(loc -> NbtUtils.writeBlockPos(new BlockPos(loc))));
nbt.put("TargetEdge", target.serializeEach(loc -> loc.write(dimensions)));
return nbt;
}
public static TrackEdgeIntersection read(CompoundTag nbt) {
public static TrackEdgeIntersection read(CompoundTag nbt, DimensionPalette dimensions) {
TrackEdgeIntersection intersection = new TrackEdgeIntersection();
intersection.id = nbt.getUUID("Id");
if (nbt.contains("GroupId"))
@ -50,7 +49,7 @@ public class TrackEdgeIntersection {
intersection.location = nbt.getDouble("Location");
intersection.targetLocation = nbt.getDouble("TargetLocation");
intersection.target = Couple.deserializeEach(nbt.getList("TargetEdge", Tag.TAG_COMPOUND),
tag -> TrackNodeLocation.fromPackedPos(NbtUtils.readBlockPos(tag)));
tag -> TrackNodeLocation.read(tag, dimensions));
return intersection;
}

View file

@ -8,6 +8,7 @@ import javax.annotation.Nullable;
import com.jozufozu.flywheel.core.PartialModel;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.GraphLocation;
import com.simibubi.create.content.logistics.trains.ITrackBlock;
import com.simibubi.create.content.logistics.trains.TrackEdge;
@ -186,7 +187,7 @@ public class TrackTargetingBehaviour<T extends TrackEdgePoint> extends TileEntit
boolean reverseEdge = front || point instanceof SingleTileEdgePoint;
if (data != null)
point.read(data, true);
point.read(data, true, DimensionPalette.read(data));
point.setId(id);
point.setLocation(reverseEdge ? loc.edge : loc.edge.swap(), reverseEdge ? loc.position : length - loc.position);

View file

@ -198,6 +198,9 @@ public class TrackTargetingBlockItem extends BlockItem {
Couple<TrackNode> nodes = location.edge.map(location.graph::locateNode);
TrackEdge edge = location.graph.getConnection(nodes);
if (edge == null)
return;
EdgeData edgeData = edge.getEdgeData();
double edgePosition = location.position;

View file

@ -8,6 +8,7 @@ import java.util.UUID;
import com.google.common.base.Objects;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.management.edgePoint.EdgePointType;
@ -237,8 +238,8 @@ public class SignalBoundary extends TrackEdgePoint {
}
@Override
public void read(CompoundTag nbt, boolean migration) {
super.read(nbt, migration);
public void read(CompoundTag nbt, boolean migration, DimensionPalette dimensions) {
super.read(nbt, migration, dimensions);
if (migration)
return;
@ -265,8 +266,8 @@ public class SignalBoundary extends TrackEdgePoint {
}
@Override
public void read(FriendlyByteBuf buffer) {
super.read(buffer);
public void read(FriendlyByteBuf buffer, DimensionPalette dimensions) {
super.read(buffer, dimensions);
for (int i = 1; i <= 2; i++) {
if (buffer.readBoolean())
groups.set(i == 1, buffer.readUUID());
@ -274,8 +275,8 @@ public class SignalBoundary extends TrackEdgePoint {
}
@Override
public void write(CompoundTag nbt) {
super.write(nbt);
public void write(CompoundTag nbt, DimensionPalette dimensions) {
super.write(nbt, dimensions);
for (int i = 1; i <= 2; i++)
if (!blockEntities.get(i == 1)
.isEmpty())
@ -293,8 +294,8 @@ public class SignalBoundary extends TrackEdgePoint {
}
@Override
public void write(FriendlyByteBuf buffer) {
super.write(buffer);
public void write(FriendlyByteBuf buffer, DimensionPalette dimensions) {
super.write(buffer, dimensions);
for (int i = 1; i <= 2; i++) {
boolean hasGroup = groups.get(i == 1) != null;
buffer.writeBoolean(hasGroup);

View file

@ -24,8 +24,6 @@ import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.world.phys.Vec3;
public class SignalPropagator {
public static void onSignalRemoved(TrackGraph graph, SignalBoundary signal) {
@ -103,7 +101,7 @@ public class SignalPropagator {
return true;
}, false);
group.resolveColor();
sync.edgeGroupCreated(groupId, group.color);
}
@ -180,14 +178,10 @@ public class SignalPropagator {
continue;
// chain signal: check if reachable
if (forCollection) {
Vec3 currentDirection = graph.getConnectionsFrom(prevNode)
.get(currentNode)
.getDirection(false);
Vec3 newDirection = edge.getDirection(true);
if (currentDirection.dot(newDirection) < 3 / 4f)
continue;
}
if (forCollection && !graph.getConnectionsFrom(prevNode)
.get(currentNode)
.canTravelTo(edge))
continue;
TrackEdge oppositeEdge = graph.getConnectionsFrom(nextNode)
.get(currentNode);

View file

@ -1,5 +1,7 @@
package com.simibubi.create.content.logistics.trains.management.edgePoint.signal;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
@ -35,16 +37,16 @@ public abstract class SingleTileEdgePoint extends TrackEdgePoint {
}
@Override
public void read(CompoundTag nbt, boolean migration) {
super.read(nbt, migration);
public void read(CompoundTag nbt, boolean migration, DimensionPalette dimensions) {
super.read(nbt, migration, dimensions);
if (migration)
return;
tilePos = NbtUtils.readBlockPos(nbt.getCompound("TilePos"));
}
@Override
public void write(CompoundTag nbt) {
super.write(nbt);
public void write(CompoundTag nbt, DimensionPalette dimensions) {
super.write(nbt, dimensions);
nbt.put("TilePos", NbtUtils.writeBlockPos(tilePos));
}

View file

@ -3,6 +3,7 @@ package com.simibubi.create.content.logistics.trains.management.edgePoint.signal
import java.util.UUID;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode;
@ -14,7 +15,6 @@ import com.simibubi.create.foundation.utility.Couple;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.level.LevelAccessor;
@ -56,7 +56,9 @@ public abstract class TrackEdgePoint {
if (behaviour == null)
return;
CompoundTag migrationData = new CompoundTag();
write(migrationData);
DimensionPalette dimensions = new DimensionPalette();
write(migrationData, dimensions);
dimensions.write(migrationData);
behaviour.invalidateEdgePoint(migrationData);
}
@ -84,32 +86,32 @@ public abstract class TrackEdgePoint {
.equals(node1.getLocation());
}
public void read(CompoundTag nbt, boolean migration) {
public void read(CompoundTag nbt, boolean migration, DimensionPalette dimensions) {
if (migration)
return;
id = nbt.getUUID("Id");
position = nbt.getDouble("Position");
edgeLocation = Couple.deserializeEach(nbt.getList("Edge", Tag.TAG_COMPOUND),
tag -> TrackNodeLocation.fromPackedPos(NbtUtils.readBlockPos(tag)));
tag -> TrackNodeLocation.read(tag, dimensions));
}
public void read(FriendlyByteBuf buffer) {
public void read(FriendlyByteBuf buffer, DimensionPalette dimensions) {
id = buffer.readUUID();
edgeLocation = Couple.create(() -> TrackNodeLocation.fromPackedPos(buffer.readBlockPos()));
edgeLocation = Couple.create(() -> TrackNodeLocation.receive(buffer, dimensions));
position = buffer.readDouble();
}
public void write(CompoundTag nbt) {
public void write(CompoundTag nbt, DimensionPalette dimensions) {
nbt.putUUID("Id", id);
nbt.putDouble("Position", position);
nbt.put("Edge", edgeLocation.serializeEach(loc -> NbtUtils.writeBlockPos(new BlockPos(loc))));
nbt.put("Edge", edgeLocation.serializeEach(loc -> loc.write(dimensions)));
}
public void write(FriendlyByteBuf buffer) {
public void write(FriendlyByteBuf buffer, DimensionPalette dimensions) {
buffer.writeResourceLocation(type.getId());
buffer.writeUUID(id);
edgeLocation.forEach(loc -> buffer.writeBlockPos(new BlockPos(loc)));
edgeLocation.forEach(loc -> loc.send(buffer, dimensions));
buffer.writeDouble(position);
}

View file

@ -4,6 +4,7 @@ import java.lang.ref.WeakReference;
import javax.annotation.Nullable;
import com.simibubi.create.content.logistics.trains.DimensionPalette;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SingleTileEdgePoint;
@ -33,16 +34,16 @@ public class GlobalStation extends SingleTileEdgePoint {
}
@Override
public void read(CompoundTag nbt, boolean migration) {
super.read(nbt, migration);
public void read(CompoundTag nbt, boolean migration, DimensionPalette dimensions) {
super.read(nbt, migration, dimensions);
name = nbt.getString("Name");
assembling = nbt.getBoolean("Assembling");
nearestTrain = new WeakReference<Train>(null);
}
@Override
public void read(FriendlyByteBuf buffer) {
super.read(buffer);
public void read(FriendlyByteBuf buffer, DimensionPalette dimensions) {
super.read(buffer, dimensions);
name = buffer.readUtf();
assembling = buffer.readBoolean();
if (buffer.readBoolean())
@ -50,15 +51,15 @@ public class GlobalStation extends SingleTileEdgePoint {
}
@Override
public void write(CompoundTag nbt) {
super.write(nbt);
public void write(CompoundTag nbt, DimensionPalette dimensions) {
super.write(nbt, dimensions);
nbt.putString("Name", name);
nbt.putBoolean("Assembling", assembling);
}
@Override
public void write(FriendlyByteBuf buffer) {
super.write(buffer);
public void write(FriendlyByteBuf buffer, DimensionPalette dimensions) {
super.write(buffer, dimensions);
buffer.writeUtf(name);
buffer.writeBoolean(assembling);
buffer.writeBoolean(tilePos != null);

View file

@ -22,6 +22,7 @@ import com.simibubi.create.content.logistics.trains.ITrackBlock;
import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
import com.simibubi.create.content.logistics.trains.entity.Carriage;
import com.simibubi.create.content.logistics.trains.entity.CarriageBogey;
@ -409,7 +410,7 @@ public class StationTileEntity extends SmartTileEntity {
ITrackBlock track = edgePoint.getTrack();
BlockPos bogeyOffset = new BlockPos(track.getUpNormal(level, trackPosition, trackState));
DiscoveredLocation location = null;
TrackNodeLocation location = null;
Vec3 centre = Vec3.atBottomCenterOf(trackPosition)
.add(0, track.getElevationAtCenter(level, trackPosition, trackState), 0);
Collection<DiscoveredLocation> ends = track.getConnected(level, trackPosition, trackState, true, null);
@ -442,9 +443,9 @@ public class StationTileEntity extends SmartTileEntity {
if (points.size() == pointOffsets.size())
break;
DiscoveredLocation currentLocation = location;
location = new DiscoveredLocation(location.getLocation()
.add(directionVec.scale(.5)));
TrackNodeLocation currentLocation = location;
location = new TrackNodeLocation(location.getLocation()
.add(directionVec.scale(.5))).in(location.dimension);
if (graph == null)
graph = Create.RAILWAYS.getGraph(level, currentLocation);

View file

@ -77,7 +77,7 @@ public class StandardBogeyBlock extends Block implements IBogeyBlock, ITE<Standa
@Override
public boolean allowsSingleBogeyCarriage() {
return !large;
return true;
}
@Override

View file

@ -1,10 +1,13 @@
package com.simibubi.create.content.logistics.trains.track;
import static com.simibubi.create.AllShapes.TRACK_ASC;
import static com.simibubi.create.AllShapes.TRACK_CROSS;
import static com.simibubi.create.AllShapes.TRACK_CROSS_DIAG;
import static com.simibubi.create.AllShapes.TRACK_CROSS_DIAG_ORTHO;
import static com.simibubi.create.AllShapes.TRACK_CROSS_ORTHO_DIAG;
import static com.simibubi.create.AllShapes.TRACK_DIAG;
import static com.simibubi.create.AllShapes.TRACK_ORTHO;
import static com.simibubi.create.AllShapes.TRACK_ORTHO_LONG;
import java.util.ArrayList;
import java.util.Collection;
@ -21,6 +24,8 @@ import com.simibubi.create.AllBlockPartials;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllShapes;
import com.simibubi.create.AllTileEntities;
import com.simibubi.create.content.contraptions.components.structureMovement.glue.SuperGlueEntity;
import com.simibubi.create.content.contraptions.particle.CubeParticleData;
import com.simibubi.create.content.contraptions.wrench.IWrenchable;
import com.simibubi.create.content.curiosities.girder.GirderBlock;
import com.simibubi.create.content.logistics.trains.BezierConnection;
@ -33,7 +38,9 @@ import com.simibubi.create.content.logistics.trains.management.edgePoint.station
import com.simibubi.create.foundation.block.render.DestroyProgressRenderingHandler;
import com.simibubi.create.foundation.block.render.ReducedDestroyEffects;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.BlockFace;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.client.multiplayer.ClientLevel;
@ -42,6 +49,8 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis;
import net.minecraft.core.Direction.AxisDirection;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
@ -51,10 +60,13 @@ import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.NetherPortalBlock;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
@ -64,6 +76,9 @@ import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.level.portal.PortalForcer;
import net.minecraft.world.level.portal.PortalInfo;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
@ -77,17 +92,17 @@ import net.minecraftforge.client.IBlockRenderProperties;
public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrackBlock {
public static final EnumProperty<TrackShape> SHAPE = EnumProperty.create("shape", TrackShape.class);
public static final BooleanProperty HAS_TURN = BooleanProperty.create("turn");
public static final BooleanProperty HAS_TE = BooleanProperty.create("turn");
public TrackBlock(Properties p_49795_) {
super(p_49795_);
registerDefaultState(defaultBlockState().setValue(SHAPE, TrackShape.ZO)
.setValue(HAS_TURN, false));
.setValue(HAS_TE, false));
}
@Override
protected void createBlockStateDefinition(Builder<Block, BlockState> p_49915_) {
super.createBlockStateDefinition(p_49915_.add(SHAPE, HAS_TURN));
super.createBlockStateDefinition(p_49915_.add(SHAPE, HAS_TE));
}
@OnlyIn(Dist.CLIENT)
@ -113,7 +128,7 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
TrackShape best = TrackShape.ZO;
double bestValue = Float.MAX_VALUE;
for (TrackShape shape : TrackShape.values()) {
if (shape.isJunction())
if (shape.isJunction() || shape.isPortal())
continue;
Vec3 axis = shape.getAxes()
.get(0);
@ -153,7 +168,7 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
@Override
public void onPlace(BlockState pState, Level pLevel, BlockPos pPos, BlockState pOldState, boolean pIsMoving) {
if (pOldState.getBlock() == this && pState.setValue(HAS_TURN, true) == pOldState.setValue(HAS_TURN, true))
if (pOldState.getBlock() == this && pState.setValue(HAS_TE, true) == pOldState.setValue(HAS_TE, true))
return;
if (pLevel.isClientSide)
return;
@ -164,14 +179,119 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
}
@Override
public void tick(BlockState p_60462_, ServerLevel p_60463_, BlockPos p_60464_, Random p_60465_) {
TrackPropagator.onRailAdded(p_60463_, p_60464_, p_60462_);
public void tick(BlockState state, ServerLevel level, BlockPos pos, Random p_60465_) {
TrackPropagator.onRailAdded(level, pos, state);
if (!state.getValue(SHAPE)
.isPortal())
connectToNether(level, pos, state);
}
protected void connectToNether(ServerLevel level, BlockPos pos, BlockState state) {
TrackShape shape = state.getValue(TrackBlock.SHAPE);
Axis portalTest = shape == TrackShape.XO ? Axis.X : shape == TrackShape.ZO ? Axis.Z : null;
if (portalTest == null)
return;
boolean pop = false;
for (Direction d : Iterate.directionsInAxis(portalTest)) {
BlockPos portalPos = pos.relative(d);
BlockState portalState = level.getBlockState(portalPos);
if (!(portalState.getBlock() instanceof NetherPortalBlock))
continue;
pop = true;
Pair<ServerLevel, BlockFace> otherSide = getOtherSide(level, new BlockFace(pos, d));
if (otherSide == null)
continue;
ServerLevel otherLevel = otherSide.getFirst();
BlockFace otherTrack = otherSide.getSecond();
BlockPos otherTrackPos = otherTrack.getPos();
BlockState existing = otherLevel.getBlockState(otherTrackPos);
if (!existing.getMaterial()
.isReplaceable())
continue;
level.setBlock(pos, state.setValue(SHAPE, TrackShape.asPortal(d))
.setValue(HAS_TE, true), 3);
BlockEntity te = level.getBlockEntity(pos);
if (te instanceof TrackTileEntity tte)
tte.bind(otherLevel.dimension(), otherTrackPos);
otherLevel.setBlock(otherTrackPos, state.setValue(SHAPE, TrackShape.asPortal(otherTrack.getFace()))
.setValue(HAS_TE, true), 3);
BlockEntity otherTe = otherLevel.getBlockEntity(otherTrackPos);
if (otherTe instanceof TrackTileEntity tte)
tte.bind(level.dimension(), pos);
pop = false;
}
if (pop)
level.destroyBlock(pos, true);
}
protected Pair<ServerLevel, BlockFace> getOtherSide(ServerLevel level, BlockFace inboundTrack) {
BlockPos portalPos = inboundTrack.getConnectedPos();
BlockState portalState = level.getBlockState(portalPos);
if (!(portalState.getBlock() instanceof NetherPortalBlock))
return null;
MinecraftServer minecraftserver = level.getServer();
ResourceKey<Level> resourcekey = level.dimension() == Level.NETHER ? Level.OVERWORLD : Level.NETHER;
ServerLevel otherLevel = minecraftserver.getLevel(resourcekey);
if (otherLevel == null || !minecraftserver.isNetherEnabled())
return null;
PortalForcer teleporter = otherLevel.getPortalForcer();
SuperGlueEntity probe = new SuperGlueEntity(level, new AABB(portalPos));
probe.setYRot(inboundTrack.getFace()
.toYRot());
PortalInfo portalinfo = teleporter.getPortalInfo(probe, otherLevel, probe::findDimensionEntryPoint);
if (portalinfo == null)
return null;
BlockPos otherPortalPos = new BlockPos(portalinfo.pos);
BlockState otherPortalState = otherLevel.getBlockState(otherPortalPos);
if (!(otherPortalState.getBlock() instanceof NetherPortalBlock))
return null;
Direction targetDirection = inboundTrack.getFace();
if (targetDirection.getAxis() == otherPortalState.getValue(NetherPortalBlock.AXIS))
targetDirection = targetDirection.getClockWise();
BlockPos otherPos = otherPortalPos.relative(targetDirection);
return Pair.of(otherLevel, new BlockFace(otherPos, targetDirection.getOpposite()));
}
@Override
public Collection<DiscoveredLocation> getConnected(BlockGetter world, BlockPos pos, BlockState state,
public BlockState updateShape(BlockState state, Direction pDirection, BlockState pNeighborState,
LevelAccessor level, BlockPos pCurrentPos, BlockPos pNeighborPos) {
TrackShape shape = state.getValue(SHAPE);
if (!shape.isPortal())
return state;
for (Direction d : Iterate.horizontalDirections) {
if (TrackShape.asPortal(d) != state.getValue(SHAPE))
continue;
if (pDirection != d)
continue;
BlockPos portalPos = pCurrentPos.relative(d);
BlockState portalState = level.getBlockState(portalPos);
if (!(portalState.getBlock() instanceof NetherPortalBlock))
return Blocks.AIR.defaultBlockState();
}
return state;
}
@Override
public Collection<DiscoveredLocation> getConnected(BlockGetter worldIn, BlockPos pos, BlockState state,
boolean linear, TrackNodeLocation connectedTo) {
Collection<DiscoveredLocation> list;
BlockGetter world = connectedTo != null && worldIn instanceof ServerLevel sl ? sl.getServer()
.getLevel(connectedTo.dimension) : worldIn;
if (getTrackAxes(world, pos, state).size() > 1) {
Vec3 center = Vec3.atBottomCenterOf(pos)
@ -183,11 +303,12 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
ITrackBlock.addToListIfConnected(connectedTo, list,
(d, b) -> axis.scale(b ? 0 : fromCenter ? -d : d)
.add(center),
b -> shape.getNormal(), axis, null);
b -> shape.getNormal(), b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD, axis,
null);
} else
list = ITrackBlock.super.getConnected(world, pos, state, linear, connectedTo);
if (!state.getValue(HAS_TURN))
if (!state.getValue(HAS_TE))
return list;
if (linear)
return list;
@ -198,22 +319,62 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
Map<BlockPos, BezierConnection> connections = trackTE.getConnections();
connections.forEach((connectedPos, bc) -> ITrackBlock.addToListIfConnected(connectedTo, list,
(d, b) -> d == 1 ? Vec3.atLowerCornerOf(bc.tePositions.get(b)) : bc.starts.get(b), bc.normals::get, null,
bc));
(d, b) -> d == 1 ? Vec3.atLowerCornerOf(bc.tePositions.get(b)) : bc.starts.get(b), bc.normals::get,
b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD, null, bc));
if (trackTE.boundLocation == null || !(world instanceof ServerLevel level))
return list;
ResourceKey<Level> otherDim = trackTE.boundLocation.getFirst();
ServerLevel otherLevel = level.getServer()
.getLevel(otherDim);
if (otherLevel == null)
return list;
BlockPos boundPos = trackTE.boundLocation.getSecond();
BlockState boundState = otherLevel.getBlockState(boundPos);
if (!AllBlocks.TRACK.has(boundState))
return list;
Vec3 center = Vec3.atBottomCenterOf(pos)
.add(0, getElevationAtCenter(world, pos, state), 0);
Vec3 boundCenter = Vec3.atBottomCenterOf(boundPos)
.add(0, getElevationAtCenter(otherLevel, boundPos, boundState), 0);
TrackShape shape = state.getValue(TrackBlock.SHAPE);
TrackShape boundShape = boundState.getValue(TrackBlock.SHAPE);
Vec3 boundAxis = getTrackAxes(otherLevel, boundPos, boundState).get(0);
getTrackAxes(world, pos, state).forEach(axis -> {
ITrackBlock.addToListIfConnected(connectedTo, list, (d, b) -> (b ? axis : boundAxis).scale(d)
.add(b ? center : boundCenter), b -> (b ? shape : boundShape).getNormal(),
b -> b ? level.dimension() : otherLevel.dimension(), axis, null);
});
return list;
}
public void animateTick(BlockState pState, Level pLevel, BlockPos pPos, Random pRand) {
if (!pState.getValue(SHAPE)
.isPortal())
return;
Vec3 v = Vec3.atLowerCornerOf(pPos)
.subtract(.125f, 0, .125f);
CubeParticleData data =
new CubeParticleData(1, pRand.nextFloat(), 1, .0125f + .0625f * pRand.nextFloat(), 30, false);
pLevel.addParticle(data, v.x + pRand.nextFloat() * 1.5f, v.y + .25f, v.z + pRand.nextFloat() * 1.5f, 0.0D,
0.04D, 0.0D);
}
@Override
public void onRemove(BlockState pState, Level pLevel, BlockPos pPos, BlockState pNewState, boolean pIsMoving) {
boolean removeTE = false;
if (pState.getValue(HAS_TURN) && (!pState.is(pNewState.getBlock()) || !pNewState.getValue(HAS_TURN))) {
if (pState.getValue(HAS_TE) && (!pState.is(pNewState.getBlock()) || !pNewState.getValue(HAS_TE))) {
BlockEntity blockEntity = pLevel.getBlockEntity(pPos);
if (blockEntity instanceof TrackTileEntity && !pLevel.isClientSide)
((TrackTileEntity) blockEntity).removeInboundConnections();
removeTE = true;
}
if (pNewState.getBlock() != this || pState.setValue(HAS_TURN, true) != pNewState.setValue(HAS_TURN, true))
if (pNewState.getBlock() != this || pState.setValue(HAS_TE, true) != pNewState.setValue(HAS_TE, true))
TrackPropagator.onRailRemoved(pLevel, pPos, pState);
if (removeTE)
pLevel.removeBlockEntity(pPos);
@ -282,13 +443,13 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
case AS:
return TRACK_ASC.get(Direction.SOUTH);
case CR_D:
return AllShapes.TRACK_CROSS_DIAG;
return TRACK_CROSS_DIAG;
case CR_NDX:
return TRACK_CROSS_ORTHO_DIAG.get(Direction.SOUTH);
case CR_NDZ:
return TRACK_CROSS_DIAG_ORTHO.get(Direction.SOUTH);
case CR_O:
return AllShapes.TRACK_CROSS;
return TRACK_CROSS;
case CR_PDX:
return TRACK_CROSS_DIAG_ORTHO.get(Direction.EAST);
case CR_PDZ:
@ -301,6 +462,14 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
return TRACK_ORTHO.get(Direction.EAST);
case ZO:
return TRACK_ORTHO.get(Direction.SOUTH);
case TE:
return TRACK_ORTHO_LONG.get(Direction.EAST);
case TW:
return TRACK_ORTHO_LONG.get(Direction.WEST);
case TS:
return TRACK_ORTHO_LONG.get(Direction.SOUTH);
case TN:
return TRACK_ORTHO_LONG.get(Direction.NORTH);
case NONE:
default:
}
@ -320,7 +489,7 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
@Override
public BlockEntity newBlockEntity(BlockPos p_153215_, BlockState state) {
if (!state.getValue(HAS_TURN))
if (!state.getValue(HAS_TE))
return null;
return AllTileEntities.TRACK.create(p_153215_, state);
}
@ -354,7 +523,7 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
public InteractionResult onSneakWrenched(BlockState state, UseOnContext context) {
Player player = context.getPlayer();
Level level = context.getLevel();
if (!level.isClientSide && !player.isCreative() && state.getValue(HAS_TURN)) {
if (!level.isClientSide && !player.isCreative() && state.getValue(HAS_TE)) {
BlockEntity blockEntity = level.getBlockEntity(context.getClickedPos());
if (blockEntity instanceof TrackTileEntity trackTE) {
trackTE.cancelDrops = true;
@ -489,7 +658,7 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
@Override
public boolean trackEquals(BlockState state1, BlockState state2) {
return state1.getBlock() == this && state2.getBlock() == this
&& state1.setValue(HAS_TURN, false) == state2.setValue(HAS_TURN, false);
&& state1.setValue(HAS_TE, false) == state2.setValue(HAS_TE, false);
}
public static class RenderProperties extends ReducedDestroyEffects implements DestroyProgressRenderingHandler {

View file

@ -13,6 +13,7 @@ import com.simibubi.create.AllShapes;
import com.simibubi.create.content.logistics.trains.BezierConnection;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.RaycastHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.foundation.utility.WorldAttached;
@ -237,6 +238,7 @@ public class TrackBlockOutline {
private static final VoxelShape LONG_CROSS =
Shapes.or(TrackVoxelShapes.longOrthogonalZ(), TrackVoxelShapes.longOrthogonalX());
private static final VoxelShape LONG_ORTHO = TrackVoxelShapes.longOrthogonalZ();
private static final VoxelShape LONG_ORTHO_OFFSET = TrackVoxelShapes.longOrthogonalZOffset();
private static void walkShapes(TrackShape shape, TransformStack msr, Consumer<VoxelShape> renderer) {
float angle45 = Mth.PI / 4;
@ -246,6 +248,16 @@ public class TrackBlockOutline {
else if (shape == TrackShape.ZO || shape == TrackShape.CR_NDZ || shape == TrackShape.CR_PDZ)
renderer.accept(AllShapes.TRACK_ORTHO.get(Direction.SOUTH));
if (shape.isPortal()) {
for (Direction d : Iterate.horizontalDirections) {
if (TrackShape.asPortal(d) != shape)
continue;
msr.rotateCentered(Direction.UP, AngleHelper.rad(AngleHelper.horizontalAngle(d)));
renderer.accept(LONG_ORTHO_OFFSET);
return;
}
}
if (shape == TrackShape.PD || shape == TrackShape.CR_PDX || shape == TrackShape.CR_PDZ) {
msr.rotateCentered(Direction.UP, angle45);
renderer.accept(LONG_ORTHO);

View file

@ -144,7 +144,7 @@ public class TrackPlacement {
if (pos1.distSqr(pos2) > 32 * 32)
return info.withMessage("too_far")
.tooJumbly();
if (!state1.hasProperty(TrackBlock.HAS_TURN))
if (!state1.hasProperty(TrackBlock.HAS_TE))
return info.withMessage("original_missing");
if (axis1.dot(end2.subtract(end1)) < 0) {
@ -469,8 +469,19 @@ public class TrackPlacement {
Vec3 axis = first ? info.axis1 : info.axis2;
BlockPos pos = first ? info.pos1 : info.pos2;
BlockState state = first ? state1 : state2;
if (state.hasProperty(TrackBlock.HAS_TURN) && !simulate)
state = state.setValue(TrackBlock.HAS_TURN, false);
if (state.hasProperty(TrackBlock.HAS_TE) && !simulate)
state = state.setValue(TrackBlock.HAS_TE, false);
switch (state.getValue(TrackBlock.SHAPE)) {
case TE, TW:
state = state.setValue(TrackBlock.SHAPE, TrackShape.XO);
break;
case TN, TS:
state = state.setValue(TrackBlock.SHAPE, TrackShape.ZO);
break;
default:
break;
}
for (int i = 0; i < (info.curve != null ? extent + 1 : extent); i++) {
Vec3 offset = axis.scale(i);
@ -501,12 +512,12 @@ public class TrackPlacement {
if (!simulate) {
BlockState stateAtPos = level.getBlockState(targetPos1);
level.setBlock(targetPos1,
(stateAtPos.getBlock() == state1.getBlock() ? stateAtPos : state1).setValue(TrackBlock.HAS_TURN, true),
(stateAtPos.getBlock() == state1.getBlock() ? stateAtPos : state1).setValue(TrackBlock.HAS_TE, true),
3);
stateAtPos = level.getBlockState(targetPos2);
level.setBlock(targetPos2,
(stateAtPos.getBlock() == state2.getBlock() ? stateAtPos : state2).setValue(TrackBlock.HAS_TURN, true),
(stateAtPos.getBlock() == state2.getBlock() ? stateAtPos : state2).setValue(TrackBlock.HAS_TE, true),
3);
}

View file

@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.core.Direction;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
@ -23,6 +24,11 @@ public enum TrackShape implements StringRepresentable {
AE("ascending", 270, new Vec3(1, 1, 0), new Vec3(-1, 1, 0)),
AW("ascending", 90, new Vec3(-1, 1, 0), new Vec3(1, 1, 0)),
TN("teleport", 180, new Vec3(0, 0, -1), new Vec3(0, 1, 0)),
TS("teleport", 0, new Vec3(0, 0, 1), new Vec3(0, 1, 0)),
TE("teleport", 270, new Vec3(1, 0, 0), new Vec3(0, 1, 0)),
TW("teleport", 90, new Vec3(-1, 0, 0), new Vec3(0, 1, 0)),
CR_O("cross_ortho", new Vec3(0, 0, 1), new Vec3(1, 0, 0)),
CR_D("cross_diag", new Vec3(1, 0, 1), new Vec3(-1, 0, 1)),
CR_PDX("cross_d1_xo", new Vec3(1, 0, 0), new Vec3(1, 0, 1)),
@ -60,7 +66,7 @@ public enum TrackShape implements StringRepresentable {
.put(CR_PDZ, CR_NDZ)
.put(CR_NDZ, CR_PDZ)
.build());
clockwise.putAll(ImmutableMap.<TrackShape, TrackShape>builder()
.put(PD, ND)
.put(ND, PD)
@ -112,6 +118,29 @@ public enum TrackShape implements StringRepresentable {
return axes.size() > 1;
}
public boolean isPortal() {
switch (this) {
case TE, TN, TS, TW:
return true;
default:
return false;
}
}
public static TrackShape asPortal(Direction horizontalFacing) {
switch (horizontalFacing) {
case EAST:
return TE;
case NORTH:
return TN;
case SOUTH:
return TS;
case WEST:
default:
return TW;
}
}
public Vec3 getNormal() {
return normal;
}

View file

@ -8,6 +8,7 @@ import java.util.Map.Entry;
import java.util.Set;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.contraptions.components.structureMovement.ITransformableTE;
import com.simibubi.create.content.contraptions.components.structureMovement.StructureTransform;
import com.simibubi.create.content.logistics.trains.BezierConnection;
@ -17,12 +18,19 @@ import com.simibubi.create.foundation.tileEntity.IMergeableTE;
import com.simibubi.create.foundation.tileEntity.RemoveTileEntityPacket;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction.Axis;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
@ -38,6 +46,8 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
boolean connectionsValidated;
boolean cancelDrops;
public Pair<ResourceKey<Level>, BlockPos> boundLocation;
public TrackTileEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
connections = new HashMap<>();
@ -84,12 +94,14 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
public void removeConnection(BlockPos target) {
connections.remove(target);
notifyUpdate();
if (!connections.isEmpty())
if (!connections.isEmpty() || getBlockState().getOptionalValue(TrackBlock.SHAPE)
.orElse(TrackShape.NONE)
.isPortal())
return;
BlockState blockState = level.getBlockState(worldPosition);
if (blockState.hasProperty(TrackBlock.HAS_TURN))
level.setBlockAndUpdate(worldPosition, blockState.setValue(TrackBlock.HAS_TURN, false));
if (blockState.hasProperty(TrackBlock.HAS_TE))
level.setBlockAndUpdate(worldPosition, blockState.setValue(TrackBlock.HAS_TE, false));
AllPackets.channel.send(packetTarget(), new RemoveTileEntityPacket(worldPosition));
}
@ -108,6 +120,11 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
AllPackets.channel.send(packetTarget(), new RemoveTileEntityPacket(worldPosition));
}
public void bind(ResourceKey<Level> boundDimension, BlockPos boundLocation) {
this.boundLocation = Pair.of(boundDimension, boundLocation);
setChanged();
}
@Override
protected void write(CompoundTag tag, boolean clientPacket) {
super.write(tag, clientPacket);
@ -115,6 +132,13 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
for (BezierConnection bezierConnection : connections.values())
listTag.add(bezierConnection.write(worldPosition));
tag.put("Connections", listTag);
if (boundLocation != null) {
tag.put("BoundLocation", NbtUtils.writeBlockPos(boundLocation.getSecond()));
tag.putString("BoundDimension", boundLocation.getFirst()
.location()
.toString());
}
}
@Override
@ -134,6 +158,11 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
registerToCurveInteraction();
else
removeFromCurveInteraction();
if (tag.contains("BoundLocation"))
boundLocation = Pair.of(
ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(tag.getString("BoundDimension"))),
NbtUtils.readBlockPos(tag.getCompound("BoundLocation")));
}
@Override
@ -204,6 +233,20 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
removeFromCurveInteraction();
}
@Override
protected void setRemovedNotDueToChunkUnload() {
super.setRemovedNotDueToChunkUnload();
if (boundLocation != null && level instanceof ServerLevel) {
ServerLevel otherLevel = level.getServer()
.getLevel(boundLocation.getFirst());
if (otherLevel == null)
return;
if (AllBlocks.TRACK.has(otherLevel.getBlockState(boundLocation.getSecond())))
otherLevel.destroyBlock(boundLocation.getSecond(), false);
}
}
private void registerToCurveInteraction() {
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> this::registerToCurveInteractionUnsafe);
}

View file

@ -19,6 +19,10 @@ public class TrackVoxelShapes {
return Block.box(-14, 0, -3.3, 16 + 14, 4, 19.3);
}
public static VoxelShape longOrthogonalZOffset() {
return Block.box(-14, 0, 0, 16 + 14, 4, 24);
}
public static VoxelShape ascending() {
VoxelShape shape = Block.box(-14, 0, 0, 16 + 14, 4, 4);
VoxelShape[] shapes = new VoxelShape[6];

View file

@ -0,0 +1,171 @@
{
"credit": "Made with Blockbench",
"ambientocclusion": false,
"texture_size": [32, 32],
"textures": {
"1": "create:block/portal_track",
"2": "create:block/portal_track_mip",
"3": "create:block/standard_track",
"particle": "create:block/palettes/stone_types/polished/andesite_cut_polished"
},
"elements": [
{
"name": "cube1",
"from": [8, -1.95625, 10],
"to": [29.95, 2.14375, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [11, 2, 0, 4], "texture": "#1"},
"south": {"uv": [0, 2, 11, 4], "texture": "#1"},
"down": {"uv": [0, 0, 11, 2], "texture": "#1"}
}
},
{
"name": "cube2",
"from": [8, -1.95625, 2],
"to": [29.95, 2.14375, 6],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [11, 2, 0, 4], "texture": "#3"},
"south": {"uv": [0, 2, 11, 4], "texture": "#3"},
"down": {"uv": [0, 4, 11, 6], "texture": "#3"}
}
},
{
"name": "cube3",
"from": [-13.95, -1.95625, 10],
"to": [8, 2.14375, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 2, 11, 4], "texture": "#1"},
"south": {"uv": [11, 2, 0, 4], "texture": "#1"},
"down": {"uv": [0, 0, 11, 2], "rotation": 180, "texture": "#1"}
}
},
{
"name": "cube2",
"from": [8, -1.95625, 18],
"to": [29.95, 2.14375, 22],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [11, 14, 0, 16], "texture": "#1"},
"south": {"uv": [0, 14, 11, 16], "texture": "#1"},
"down": {"uv": [0, 4, 11, 6], "texture": "#1"}
}
},
{
"name": "cube4",
"from": [-13.95, -1.95625, 18],
"to": [8, 2.14375, 22],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 14, 11, 16], "texture": "#1"},
"south": {"uv": [11, 14, 0, 16], "texture": "#1"},
"down": {"uv": [0, 4, 11, 6], "rotation": 180, "texture": "#1"}
}
},
{
"name": "cube4",
"from": [-13.95, -1.95625, 2],
"to": [8, 2.14375, 6],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [0, 2, 11, 4], "texture": "#3"},
"south": {"uv": [11, 2, 0, 4], "texture": "#3"},
"down": {"uv": [0, 4, 11, 6], "rotation": 180, "texture": "#3"}
}
},
{
"name": "cube5",
"from": [-13.95, -1.95625, 0],
"to": [8, 2.14375, 24],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"west": {"uv": [11, 12.5, 13.05, 0.5], "rotation": 90, "texture": "#2"},
"up": {"uv": [0, 12.5, 10.975, 0.5], "rotation": 180, "texture": "#2"}
}
},
{
"name": "cube6",
"from": [8, -1.95625, 0],
"to": [29.95, 2.14375, 24],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"east": {"uv": [11, 0.5, 13.05, 12.5], "rotation": 90, "texture": "#2"},
"up": {"uv": [0, 0.5, 10.975, 12.5], "texture": "#2"}
}
},
{
"name": "tie1",
"from": [21.35, 1.09375, 0],
"to": [25.55, 1.14375, 24],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"up": {"uv": [12, 8.5, 0, 10.5], "rotation": 270, "texture": "#1"},
"down": {"uv": [0, 8.5, 12, 10.5], "rotation": 270, "texture": "#1"}
}
},
{
"name": "tie2",
"from": [21.45, 5.49375, 0],
"to": [25.45, 5.54375, 24],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"up": {"uv": [12, 11.5, 0, 13.5], "rotation": 270, "texture": "#1"},
"down": {"uv": [0, 8.5, 12, 10.5], "rotation": 270, "texture": "#1"}
}
},
{
"name": "tie3",
"from": [21.9, 1.14375, 0],
"to": [25, 5.54375, 24],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [11, 2, 12.5, 4], "texture": "#3"},
"east": {"uv": [12, 6, 0, 8], "texture": "#1"},
"south": {"uv": [12.5, 2, 11, 4], "texture": "#1"},
"west": {"uv": [0, 6, 12, 8], "texture": "#1"}
}
},
{
"name": "tie4",
"from": [-9.45, 5.49375, 0],
"to": [-5.45, 5.54375, 24],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"up": {"uv": [12, 13.5, 0, 11.5], "rotation": 270, "texture": "#1"},
"down": {"uv": [0, 10.5, 12, 8.5], "rotation": 270, "texture": "#1"}
}
},
{
"name": "tie5",
"from": [-9, 1.14375, 0],
"to": [-5.9, 5.54375, 24],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"north": {"uv": [12.5, 2, 11, 4], "texture": "#3"},
"east": {"uv": [12, 6, 0, 8], "texture": "#1"},
"south": {"uv": [12.5, 2, 11, 4], "texture": "#1"},
"west": {"uv": [0, 6, 12, 8], "texture": "#1"}
}
},
{
"name": "tie6",
"from": [-9.55, 1.09375, 0],
"to": [-5.35, 1.14375, 24],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 0, 8]},
"faces": {
"up": {"uv": [12, 10.5, 0, 8.5], "rotation": 270, "texture": "#1"},
"down": {"uv": [0, 10.5, 12, 8.5], "rotation": 270, "texture": "#1"}
}
}
],
"groups": [
{
"name": "group",
"origin": [0, 0, 0],
"color": 0,
"children": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,004 B

After

Width:  |  Height:  |  Size: 1,004 B