Turn to target

- Bezier turn segments between tracks can now be hovered as if they were blocks
- Bezier turns can now be destroyed by breaking any of their segments
- Signals and Stations can now be pointed to anywhere on a track, not only to physical blocks
- Added a basic collision shape for straight tracks
This commit is contained in:
simibubi 2022-04-14 02:23:55 +02:00
parent 99a6836a1d
commit b970c0029f
40 changed files with 828 additions and 88 deletions

View file

@ -543,22 +543,22 @@ bf2b0310500213ff853c748c236eb5d01f61658e assets/create/blockstates/yellow_toolbo
7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json
b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json
7fbb25c577025ff61388c54c43401d8bb80723dd assets/create/lang/en_ud.json
0a3b09db8a3bd71570afc1452d62a1fa6bd22baa assets/create/lang/en_us.json
7ac6b939d4d1b574806ac3fa3ec495498c9c00a6 assets/create/lang/unfinished/de_de.json
58a1b86058fcebf323e2208ba26df584d42f9f97 assets/create/lang/unfinished/es_cl.json
1419151466490bfa42b84a99e0e296b54e1aeec5 assets/create/lang/unfinished/es_es.json
a366bd2c9b80079b9c42572623165497b6d49531 assets/create/lang/unfinished/fr_fr.json
b21c2d0a7cdf713492ab1eb85061df8c73a9d9b9 assets/create/lang/unfinished/it_it.json
9874ddfacd2effec2eb852ea9cc009923e372ae5 assets/create/lang/unfinished/ja_jp.json
0334b52e3e84870e84fce1e485c6153456dc84e9 assets/create/lang/unfinished/ko_kr.json
7f0bd618c72b9743832a7133db0359f546b48f5e assets/create/lang/unfinished/nl_nl.json
f6826d25ea7aca1db76e806a44434f64fb11e6b2 assets/create/lang/unfinished/pl_pl.json
5ff6c067e106418b32dbb97b3bce56a6990eaf67 assets/create/lang/unfinished/pt_br.json
8e639a6e5337e7723c454b3d01dd70e9715318fe assets/create/lang/unfinished/pt_pt.json
763413532441a056bd88bbcea394fcf9e50e8c29 assets/create/lang/unfinished/ro_ro.json
24430fe32f666e6df5fa9185dabedbe9746155d2 assets/create/lang/unfinished/ru_ru.json
9955890700831881c15b3df9d720448c09ae341e assets/create/lang/unfinished/zh_cn.json
7294335fc11c054fb761a251493b17866f01f0c2 assets/create/lang/unfinished/zh_tw.json
7cd7e879350cbb56393165c24062fda5676466b9 assets/create/lang/en_us.json
10e938a41280ab88265f59b293cf1a19ea012f18 assets/create/lang/unfinished/de_de.json
8b17b8042e844f53bf2ae605c0a7d5b6f9e470ee assets/create/lang/unfinished/es_cl.json
4bc7d56deb76a73fa7af16e21b7a95d00d1c1b3c assets/create/lang/unfinished/es_es.json
200deeabcf50a3d91387f90528154f3d0ebea8a4 assets/create/lang/unfinished/fr_fr.json
d806ae2e13f9415b79ce7c7928c8e93ec93dd9bb assets/create/lang/unfinished/it_it.json
5a0afa8b1ccc8b9dcbd0b0353aed8a3d14398059 assets/create/lang/unfinished/ja_jp.json
b851295ec91158c2f6e4b8433ff13057367c5694 assets/create/lang/unfinished/ko_kr.json
fe7e927998605dfa186b0bed7c179b0a92e5da5b assets/create/lang/unfinished/nl_nl.json
352ef01578bc2fedd31ad95c8f48069ee2714240 assets/create/lang/unfinished/pl_pl.json
bc5bddb12f2b01c034e7bf8619ebc8ea3136a526 assets/create/lang/unfinished/pt_br.json
67a9b9769fe289213f95c9f9680b4fa2922930de assets/create/lang/unfinished/pt_pt.json
38ec533de9f914ca5c8209e31cba9b3e06c734b7 assets/create/lang/unfinished/ro_ro.json
556d68b3d26152f1a3a25a568cd1b98b844c66a6 assets/create/lang/unfinished/ru_ru.json
4239813d57b74da7eb654a66342769e111679729 assets/create/lang/unfinished/zh_cn.json
78ef5f771b4d1bb62dc2d4eca06124d28696e8b2 assets/create/lang/unfinished/zh_tw.json
487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json
b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json
3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json

View file

@ -1412,6 +1412,8 @@
"create.track.leave_slope_ascending": "Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "Cannot leave this slope while descending",
"create.track.turn_90": "Can only turn up to 90 Degrees",
"create.track.junction_start": "Cannot start connection from a Junction",
"create.track.turn_start": "Cannot start connection from a Turn",
"create.station.create_train": "Create new Train",
"create.station.disassemble_train": "Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1518",
"_": "Missing Localizations: 1520",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 529",
"_": "Missing Localizations: 531",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 200",
"_": "Missing Localizations: 202",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1780",
"_": "Missing Localizations: 1782",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1469",
"_": "Missing Localizations: 1471",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 195",
"_": "Missing Localizations: 197",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 195",
"_": "Missing Localizations: 197",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 2133",
"_": "Missing Localizations: 2135",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 568",
"_": "Missing Localizations: 570",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1380",
"_": "Missing Localizations: 1382",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 1752",
"_": "Missing Localizations: 1754",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 196",
"_": "Missing Localizations: 198",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 573",
"_": "Missing Localizations: 575",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 195",
"_": "Missing Localizations: 197",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -1,5 +1,5 @@
{
"_": "Missing Localizations: 587",
"_": "Missing Localizations: 589",
"_": "->------------------------] Game Elements [------------------------<-",
@ -1413,6 +1413,8 @@
"create.track.leave_slope_ascending": "UNLOCALIZED: Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "UNLOCALIZED: Cannot leave this slope while descending",
"create.track.turn_90": "UNLOCALIZED: Can only turn up to 90 Degrees",
"create.track.junction_start": "UNLOCALIZED: Cannot start connection from a Junction",
"create.track.turn_start": "UNLOCALIZED: Cannot start connection from a Turn",
"create.station.create_train": "UNLOCALIZED: Create new Train",
"create.station.disassemble_train": "UNLOCALIZED: Disassemble Train",

View file

@ -234,6 +234,7 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.properties.PistonType;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.level.material.MaterialColor;
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.LootTable;
@ -1336,7 +1337,10 @@ public class AllBlocks {
.register();
public static final BlockEntry<TrackBlock> TRACK = REGISTRATE.block("track", TrackBlock::new)
.initialProperties(() -> Blocks.RAIL)
.initialProperties(Material.DECORATION)
.properties(p -> p.strength(0.8F)
.sound(SoundType.METAL)
.noOcclusion())
.addLayer(() -> RenderType::cutoutMipped)
.transform(pickaxeOnly())
.blockstate(new TrackBlockStateGenerator()::generate)

View file

@ -178,6 +178,8 @@ public class AllShapes {
TRACK_CROSS_DIAG = shape(TRACK_DIAG.get(SOUTH)).add(TRACK_DIAG.get(EAST))
.build(),
TRACK_COLLISION = shape(0, 0, 0, 16, 2, 16).build(),
TRACK_FALLBACK = shape(0, 0, 0, 16, 4, 16).build(),
BASIN_BLOCK_SHAPE = shape(0, 2, 0, 16, 16, 16).erase(2, 2, 2, 14, 16, 14)

View file

@ -17,6 +17,7 @@ import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@ -42,6 +43,8 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
private double radius;
private double handleLength;
private AABB bounds;
public BezierConnection(Couple<BlockPos> positions, Couple<Vec3> starts, Couple<Vec3> axes, Couple<Vec3> normals,
boolean primary, boolean girder) {
tePositions = positions;
@ -149,6 +152,11 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
}
public AABB getBounds() {
resolve();
return bounds;
}
public Vec3 getNormal(double t) {
resolve();
Vec3 end1 = starts.getFirst();
@ -202,12 +210,15 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
stepLUT[0] = 1;
float combinedDistance = 0;
bounds = new AABB(end1, end2);
// determine step lut
{
Vec3 previous = end1;
for (int i = 0; i <= segments; i++) {
float t = i / (float) segments;
Vec3 result = VecHelper.bezier(end1, end2, finish1, finish2, t);
bounds = bounds.minmax(new AABB(result, result));
if (i > 0) {
combinedDistance += result.distanceTo(previous) / length;
stepLUT[i] = (float) (t / combinedDistance);
@ -215,6 +226,8 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
previous = result;
}
}
bounds = bounds.inflate(1.375f);
}
private void determineHandles(Vec3 end1, Vec3 end2, Vec3 axis1, Vec3 axis2) {

View file

@ -12,6 +12,7 @@ import com.jozufozu.flywheel.core.PartialModel;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
import com.simibubi.create.content.logistics.trains.management.edgePoint.TrackTargetingBehaviour.RenderedTrackOverlayType;
import com.simibubi.create.content.logistics.trains.track.BezierTrackPointLocation;
import com.simibubi.create.content.logistics.trains.track.TrackBlock;
import com.simibubi.create.content.logistics.trains.track.TrackShape;
import com.simibubi.create.foundation.utility.Iterate;
@ -105,7 +106,7 @@ public interface ITrackBlock {
@OnlyIn(Dist.CLIENT)
public PartialModel prepareTrackOverlay(BlockGetter world, BlockPos pos, BlockState state,
AxisDirection direction, PoseStack transform, RenderedTrackOverlayType type);
BezierTrackPointLocation bezierPoint, AxisDirection direction, PoseStack transform, RenderedTrackOverlayType type);
@OnlyIn(Dist.CLIENT)
public PartialModel prepareAssemblyOverlay(BlockGetter world, BlockPos pos, BlockState state, Direction direction,

View file

@ -0,0 +1,88 @@
package com.simibubi.create.content.logistics.trains.management.edgePoint;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.track.TrackTileEntity;
import com.simibubi.create.foundation.networking.TileEntityConfigurationPacket;
import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
public class CurvedTrackSelectionPacket extends TileEntityConfigurationPacket<TrackTileEntity> {
private BlockPos targetPos;
private boolean front;
private int segment;
private int slot;
public CurvedTrackSelectionPacket(BlockPos pos, BlockPos targetPos, int segment, boolean front, int slot) {
super(pos);
this.targetPos = targetPos;
this.segment = segment;
this.front = front;
this.slot = slot;
}
public CurvedTrackSelectionPacket(FriendlyByteBuf buffer) {
super(buffer);
}
@Override
protected void writeSettings(FriendlyByteBuf buffer) {
buffer.writeBlockPos(targetPos);
buffer.writeVarInt(segment);
buffer.writeBoolean(front);
buffer.writeVarInt(slot);
}
@Override
protected void readSettings(FriendlyByteBuf buffer) {
targetPos = buffer.readBlockPos();
segment = buffer.readVarInt();
front = buffer.readBoolean();
slot = buffer.readVarInt();
}
@Override
protected void applySettings(ServerPlayer player, TrackTileEntity te) {
if (!te.getBlockPos()
.closerThan(player.blockPosition(), 48)) {
Create.LOGGER.warn(player.getScoreboardName() + " too far away from targeted track");
return;
}
if (player.getInventory().selected != slot)
return;
ItemStack stack = player.getInventory()
.getItem(slot);
if (!(stack.getItem() instanceof TrackTargetingBlockItem))
return;
if (player.isSteppingCarefully() && stack.hasTag()) {
player.displayClientMessage(Lang.translate("track_target.clear"), true);
stack.setTag(null);
return;
}
CompoundTag stackTag = stack.getOrCreateTag();
stackTag.put("SelectedPos", NbtUtils.writeBlockPos(pos));
stackTag.putBoolean("SelectedDirection", front);
CompoundTag bezierNbt = new CompoundTag();
bezierNbt.putInt("Segment", segment);
bezierNbt.put("Key", NbtUtils.writeBlockPos(targetPos));
bezierNbt.putBoolean("FromStack", true);
stackTag.put("Bezier", bezierNbt);
player.displayClientMessage(Lang.translate("track_target.set"), true);
stack.setTag(stackTag);
}
@Override
protected void applySettings(TrackTileEntity te) {}
}

View file

@ -6,19 +6,25 @@ import javax.annotation.Nullable;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.BezierConnection;
import com.simibubi.create.content.logistics.trains.GraphLocation;
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.TrackGraphHelper;
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.management.edgePoint.signal.SingleTileEdgePoint;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.TrackEdgePoint;
import com.simibubi.create.content.logistics.trains.track.BezierTrackPointLocation;
import com.simibubi.create.content.logistics.trains.track.TrackTileEntity;
import com.simibubi.create.foundation.render.CachedBufferer;
import com.simibubi.create.foundation.render.SuperByteBuffer;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
import com.simibubi.create.foundation.utility.Couple;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
@ -39,6 +45,7 @@ public class TrackTargetingBehaviour<T extends TrackEdgePoint> extends TileEntit
public static final BehaviourType<TrackTargetingBehaviour<?>> TYPE = new BehaviourType<>();
private BlockPos targetTrack;
private BezierTrackPointLocation targetBezier;
private AxisDirection targetDirection;
private UUID id;
@ -62,6 +69,13 @@ public class TrackTargetingBehaviour<T extends TrackEdgePoint> extends TileEntit
nbt.putBoolean("TargetDirection", targetDirection == AxisDirection.POSITIVE);
if (migrationData != null && !clientPacket)
nbt.put("Migrate", migrationData);
if (targetBezier != null) {
CompoundTag bezierNbt = new CompoundTag();
bezierNbt.putInt("Segment", targetBezier.segment());
bezierNbt.put("Key", NbtUtils.writeBlockPos(targetBezier.curveTarget()
.subtract(getPos())));
nbt.put("Bezier", bezierNbt);
}
super.write(nbt, clientPacket);
}
@ -74,6 +88,12 @@ public class TrackTargetingBehaviour<T extends TrackEdgePoint> extends TileEntit
migrationData = nbt.getCompound("Migrate");
if (clientPacket)
edgePoint = null;
if (nbt.contains("Bezier")) {
CompoundTag bezierNbt = nbt.getCompound("Bezier");
BlockPos key = NbtUtils.readBlockPos(bezierNbt.getCompound("Key"));
targetBezier = new BezierTrackPointLocation(bezierNbt.contains("FromStack") ? key : key.offset(getPos()),
bezierNbt.getInt("Segment"));
}
super.read(nbt, clientPacket);
}
@ -195,7 +215,13 @@ public class TrackTargetingBehaviour<T extends TrackEdgePoint> extends TileEntit
return targetDirection;
}
public BezierTrackPointLocation getTargetBezier() {
return targetBezier;
}
public GraphLocation determineGraphLocation() {
if (targetBezier != null)
return determineBezierGraphLocation();
Level level = getWorld();
BlockPos pos = getGlobalPosition();
BlockState state = getTrackBlockState();
@ -204,13 +230,57 @@ public class TrackTargetingBehaviour<T extends TrackEdgePoint> extends TileEntit
.get(0));
}
public GraphLocation determineBezierGraphLocation() {
Level level = getWorld();
BlockPos pos = getGlobalPosition();
BlockState state = getTrackBlockState();
if (!(state.getBlock() instanceof ITrackBlock track))
return null;
if (!(level.getBlockEntity(pos) instanceof TrackTileEntity trackTE))
return null;
BezierConnection bc = trackTE.getConnections()
.get(targetBezier.curveTarget());
if (bc == null || !bc.isPrimary())
return null;
TrackNodeLocation targetLoc = new TrackNodeLocation(bc.starts.getSecond());
for (DiscoveredLocation location : track.getConnected(level, pos, state, true, null)) {
TrackGraph graph = Create.RAILWAYS.sided(level)
.getGraph(level, location);
if (graph == null)
continue;
TrackNode targetNode = graph.locateNode(targetLoc);
if (targetNode == null)
continue;
TrackNode node = graph.locateNode(location);
TrackEdge edge = graph.getConnectionsFrom(node)
.get(targetNode);
if (edge == null)
continue;
GraphLocation graphLocation = new GraphLocation();
graphLocation.graph = graph;
graphLocation.edge = Couple.create(location, targetLoc);
graphLocation.position = (targetBezier.segment() + 1) / 2f;
if (targetDirection == AxisDirection.POSITIVE) {
graphLocation.edge = graphLocation.edge.swap();
graphLocation.position = edge.getLength(node, targetNode) - graphLocation.position;
}
return graphLocation;
}
return null;
}
public static enum RenderedTrackOverlayType {
STATION, SIGNAL, DUAL_SIGNAL;
}
@OnlyIn(Dist.CLIENT)
public static void render(LevelAccessor level, BlockPos pos, AxisDirection direction, int tintColor, PoseStack ms,
MultiBufferSource buffer, int light, int overlay, RenderedTrackOverlayType type) {
public static void render(LevelAccessor level, BlockPos pos, AxisDirection direction,
BezierTrackPointLocation bezier, int tintColor, PoseStack ms, MultiBufferSource buffer, int light, int overlay,
RenderedTrackOverlayType type) {
BlockState trackState = level.getBlockState(pos);
Block block = trackState.getBlock();
if (!(block instanceof ITrackBlock))
@ -220,8 +290,8 @@ public class TrackTargetingBehaviour<T extends TrackEdgePoint> extends TileEntit
ms.translate(pos.getX(), pos.getY(), pos.getZ());
ITrackBlock track = (ITrackBlock) block;
SuperByteBuffer sbb =
CachedBufferer.partial(track.prepareTrackOverlay(level, pos, trackState, direction, ms, type), trackState);
SuperByteBuffer sbb = CachedBufferer
.partial(track.prepareTrackOverlay(level, pos, trackState, bezier, direction, ms, type), trackState);
sbb.light(LevelRenderer.getLightColor(level, pos));
sbb.renderInto(ms, buffer.getBuffer(RenderType.cutoutMipped()));

View file

@ -2,10 +2,16 @@ package com.simibubi.create.content.logistics.trains.management.edgePoint;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.content.logistics.trains.ITrackBlock;
import com.simibubi.create.content.logistics.trains.track.BezierTrackPointLocation;
import com.simibubi.create.content.logistics.trains.track.TrackBlockOutline.BezierPointSelection;
import com.simibubi.create.content.logistics.trains.track.TrackTileEntity;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.render.SuperRenderTypeBuffer;
import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction.AxisDirection;
import net.minecraft.nbt.CompoundTag;
@ -19,6 +25,8 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
public class TrackTargetingBlockItem extends BlockItem {
@ -45,8 +53,9 @@ public class TrackTargetingBlockItem extends BlockItem {
return InteractionResult.SUCCESS;
}
if (state.getBlock()instanceof ITrackBlock track) {
if (track.getTrackAxes(level, pos, state).size() > 1) {
if (state.getBlock() instanceof ITrackBlock track) {
if (track.getTrackAxes(level, pos, state)
.size() > 1) {
player.displayClientMessage(Lang.translate("track_target.no_junctions")
.withStyle(ChatFormatting.RED), true);
return InteractionResult.FAIL;
@ -84,6 +93,9 @@ public class TrackTargetingBlockItem extends BlockItem {
return InteractionResult.FAIL;
}
if (tag.contains("Bezier"))
teTag.put("Bezier", tag.getCompound("Bezier"));
teTag.put("TargetTrack", NbtUtils.writeBlockPos(selectedPos.subtract(placedPos)));
tag.put("BlockEntityTag", teTag);
@ -99,6 +111,20 @@ public class TrackTargetingBlockItem extends BlockItem {
return useOn;
}
@OnlyIn(Dist.CLIENT)
public boolean useOnCurve(BezierPointSelection selection, ItemStack stack) {
Minecraft mc = Minecraft.getInstance();
LocalPlayer player = mc.player;
TrackTileEntity te = selection.te();
BezierTrackPointLocation loc = selection.loc();
boolean front = player.getLookAngle()
.dot(selection.direction()) < 0;
AllPackets.channel.sendToServer(new CurvedTrackSelectionPacket(te.getBlockPos(), loc.curveTarget(),
loc.segment(), front, player.getInventory().selected));
return true;
}
public static void clientTick() {
}

View file

@ -53,8 +53,8 @@ public class SignalRenderer extends SafeTileEntityRenderer<SignalTileEntity> {
ms.pushPose();
ms.translate(-pos.getX(), -pos.getY(), -pos.getZ());
TrackTargetingBehaviour.render(level, targetPosition, target.getTargetDirection(), 0xd0cccc, ms, buffer, light,
overlay,
TrackTargetingBehaviour.render(level, targetPosition, target.getTargetDirection(), target.getTargetBezier(),
0xd0cccc, ms, buffer, light, overlay,
overlayState == OverlayState.DUAL ? RenderedTrackOverlayType.DUAL_SIGNAL : RenderedTrackOverlayType.SIGNAL);
ms.popPose();

View file

@ -63,8 +63,8 @@ public class StationRenderer extends SafeTileEntityRenderer<StationTileEntity> {
partialTicks, ms, buffer, light, overlay);
ms.pushPose();
ms.translate(-pos.getX(), -pos.getY(), -pos.getZ());
TrackTargetingBehaviour.render(level, targetPosition, target.getTargetDirection(), 0xCC993B, ms, buffer,
light, overlay, RenderedTrackOverlayType.STATION);
TrackTargetingBehaviour.render(level, targetPosition, target.getTargetDirection(), target.getTargetBezier(),
0xCC993B, ms, buffer, light, overlay, RenderedTrackOverlayType.STATION);
ms.popPose();
return;
}

View file

@ -319,7 +319,7 @@ public class ScheduleScreen extends AbstractSimiContainerScreen<ScheduleContaine
if (schedule.savedProgress == i && !schedule.entries.isEmpty()) {
matrixStack.pushPose();
float expectedY = scrollOffset + topPos + yOffset + 4;
float actualY = Mth.clamp(expectedY, 49, 197);
float actualY = Mth.clamp(expectedY, topPos + 18, topPos + 170);
matrixStack.translate(0, actualY, 0);
(expectedY == actualY ? AllGuiTextures.SCHEDULE_POINTER : AllGuiTextures.SCHEDULE_POINTER_OFFSCREEN)
.render(matrixStack, leftPos, 0);

View file

@ -0,0 +1,6 @@
package com.simibubi.create.content.logistics.trains.track;
import net.minecraft.core.BlockPos;
public record BezierTrackPointLocation(BlockPos curveTarget, int segment) {
}

View file

@ -0,0 +1,69 @@
package com.simibubi.create.content.logistics.trains.track;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.TrackPropagator;
import com.simibubi.create.foundation.networking.TileEntityConfigurationPacket;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState;
public class CurvedTrackDestroyPacket extends TileEntityConfigurationPacket<TrackTileEntity> {
private BlockPos targetPos;
private BlockPos soundSource;
public CurvedTrackDestroyPacket(BlockPos pos, BlockPos targetPos, BlockPos soundSource) {
super(pos);
this.targetPos = targetPos;
this.soundSource = soundSource;
}
public CurvedTrackDestroyPacket(FriendlyByteBuf buffer) {
super(buffer);
}
@Override
protected void writeSettings(FriendlyByteBuf buffer) {
buffer.writeBlockPos(targetPos);
buffer.writeBlockPos(soundSource);
}
@Override
protected void readSettings(FriendlyByteBuf buffer) {
targetPos = buffer.readBlockPos();
soundSource = buffer.readBlockPos();
}
@Override
protected void applySettings(ServerPlayer player, TrackTileEntity te) {
if (!te.getBlockPos()
.closerThan(player.blockPosition(), 48)) {
Create.LOGGER.warn(player.getScoreboardName() + " too far away from destroyed Curve track");
return;
}
Level level = te.getLevel();
te.removeConnection(targetPos);
if (level.getBlockEntity(targetPos) instanceof TrackTileEntity other)
other.removeConnection(pos);
BlockState blockState = te.getBlockState();
TrackPropagator.onRailRemoved(level, pos, blockState);
SoundType soundtype = blockState.getSoundType(level, pos, player);
if (soundtype == null)
return;
level.playSound(null, soundSource, soundtype.getBreakSound(), SoundSource.BLOCKS,
(soundtype.getVolume() + 1.0F) / 2.0F, soundtype.getPitch() * 0.8F);
}
@Override
protected void applySettings(TrackTileEntity te) {}
}

View file

@ -0,0 +1,132 @@
package com.simibubi.create.content.logistics.trains.track;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.logistics.trains.management.edgePoint.TrackTargetingBlockItem;
import com.simibubi.create.content.logistics.trains.track.TrackBlockOutline.BezierPointSelection;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.client.event.InputEvent.ClickInputEvent;
public class CurvedTrackInteraction {
static final int breakerId = new Object().hashCode();
static int breakTicks;
static int breakTimeout;
static float breakProgress;
static BlockPos breakPos;
public static void clientTick() {
BezierPointSelection result = TrackBlockOutline.result;
Minecraft mc = Minecraft.getInstance();
LocalPlayer player = mc.player;
ClientLevel level = mc.level;
if (mc.options.keyAttack.isDown() && result != null) {
breakPos = result.te()
.getBlockPos();
BlockState blockState = level.getBlockState(breakPos);
if (blockState.isAir()) {
resetBreakProgress();
return;
}
if (breakTicks % 4.0F == 0.0F) {
SoundType soundtype = blockState.getSoundType(level, breakPos, player);
mc.getSoundManager()
.play(new SimpleSoundInstance(soundtype.getHitSound(), SoundSource.BLOCKS,
(soundtype.getVolume() + 1.0F) / 8.0F, soundtype.getPitch() * 0.5F,
new BlockPos(result.vec())));
}
boolean creative = player.getAbilities().instabuild;
breakTicks++;
breakTimeout = 2;
breakProgress += creative ? 0.25f : blockState.getDestroyProgress(player, level, breakPos);
Vec3 vec = VecHelper.offsetRandomly(result.vec(), level.random, 0.25f);
level.addParticle(new BlockParticleOption(ParticleTypes.BLOCK, blockState), vec.x, vec.y, vec.z, 0, 0, 0);
int progress = (int) (breakProgress * 10.0F) - 1;
level.destroyBlockProgress(player.getId(), breakPos, progress);
player.swing(InteractionHand.MAIN_HAND);
if (breakProgress >= 1) {
AllPackets.channel.sendToServer(new CurvedTrackDestroyPacket(breakPos, result.loc()
.curveTarget(), new BlockPos(result.vec())));
resetBreakProgress();
}
return;
}
if (breakTimeout == 0)
return;
if (--breakTimeout > 0)
return;
resetBreakProgress();
}
private static void resetBreakProgress() {
Minecraft mc = Minecraft.getInstance();
ClientLevel level = mc.level;
if (breakPos != null && level != null)
level.destroyBlockProgress(mc.player.getId(), breakPos, -1);
breakProgress = 0;
breakTicks = 0;
breakPos = null;
}
public static boolean onClickInput(ClickInputEvent event) {
BezierPointSelection result = TrackBlockOutline.result;
if (result == null)
return false;
Minecraft mc = Minecraft.getInstance();
LocalPlayer player = mc.player;
ClientLevel level = mc.level;
if (player == null || level == null)
return false;
if (event.isUseItem()) {
ItemStack heldItem = player.getMainHandItem();
if (AllBlocks.TRACK.isIn(heldItem)) {
player.displayClientMessage(Lang.translate("track.turn_start")
.withStyle(ChatFormatting.RED), true);
player.swing(InteractionHand.MAIN_HAND);
return true;
}
if (heldItem.getItem() instanceof TrackTargetingBlockItem ttbi && ttbi.useOnCurve(result, heldItem)) {
player.swing(InteractionHand.MAIN_HAND);
return true;
}
}
if (event.isAttack())
return true;
return false;
}
}

View file

@ -32,11 +32,14 @@ import com.simibubi.create.content.logistics.trains.TrackNodeLocation.Discovered
import com.simibubi.create.content.logistics.trains.TrackPropagator;
import com.simibubi.create.content.logistics.trains.management.edgePoint.TrackTargetingBehaviour.RenderedTrackOverlayType;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationTileEntity;
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.Iterate;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis;
@ -68,6 +71,7 @@ import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.ticks.LevelTickAccess;
import net.minecraftforge.api.distmarker.Dist;
@ -92,7 +96,7 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
@OnlyIn(Dist.CLIENT)
public void initializeClient(Consumer<IBlockRenderProperties> consumer) {
consumer.accept(new ReducedDestroyEffects());
consumer.accept(new RenderProperties());
}
@Override
@ -318,6 +322,17 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
return AllShapes.TRACK_FALLBACK;
}
@Override
public VoxelShape getCollisionShape(BlockState pState, BlockGetter pLevel, BlockPos pPos,
CollisionContext pContext) {
switch (pState.getValue(SHAPE)) {
case AE, AW, AN, AS:
return Shapes.empty();
default:
return AllShapes.TRACK_COLLISION;
}
}
@Override
public BlockEntity newBlockEntity(BlockPos p_153215_, BlockState state) {
if (!state.getValue(HAS_TURN))
@ -419,24 +434,60 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
@Override
@OnlyIn(Dist.CLIENT)
public PartialModel prepareTrackOverlay(BlockGetter world, BlockPos pos, BlockState state, AxisDirection direction,
PoseStack ms, RenderedTrackOverlayType type) {
Vec3 axis = state.getValue(SHAPE)
public PartialModel prepareTrackOverlay(BlockGetter world, BlockPos pos, BlockState state,
BezierTrackPointLocation bezierPoint, AxisDirection direction, PoseStack ms, RenderedTrackOverlayType type) {
TransformStack msr = TransformStack.cast(ms);
Vec3 axis = null;
Vec3 diff = null;
Vec3 normal = null;
Vec3 offset = null;
if (bezierPoint != null && world.getBlockEntity(pos) instanceof TrackTileEntity trackTE) {
BezierConnection bc = trackTE.connections.get(bezierPoint.curveTarget());
if (bc != null) {
double length = Mth.floor(bc.getLength() * 2);
int seg = bezierPoint.segment() + 1;
double t = seg / length;
double tpre = (seg - 1) / length;
double tpost = (seg + 1) / length;
offset = bc.getPosition(t);
normal = bc.getNormal(t);
diff = bc.getPosition(tpost)
.subtract(bc.getPosition(tpre))
.normalize();
msr.translate(offset.subtract(Vec3.atBottomCenterOf(pos)));
msr.translate(0, -4 / 16f, 0);
}
}
if (normal == null) {
axis = state.getValue(SHAPE)
.getAxes()
.get(0);
Vec3 directionVec = axis.scale(direction.getStep())
diff = axis.scale(direction.getStep())
.normalize();
Vec3 normal = getUpNormal(world, pos, state);
Vec3 angles = TrackRenderer.getModelAngles(normal, directionVec);
normal = getUpNormal(world, pos, state);
}
TransformStack.cast(ms)
.centre()
Vec3 angles = TrackRenderer.getModelAngles(normal, diff);
msr.centre()
.rotateYRadians(angles.y)
.rotateXRadians(angles.x)
.unCentre()
.translate(0, axis.y != 0 ? 7 / 16f : 0, axis.y != 0 ? direction.getStep() * 2.5f / 16f : 0)
.scale(type == RenderedTrackOverlayType.STATION ? 1 + 1 / 512f : 1);
.unCentre();
if (axis != null)
msr.translate(0, axis.y != 0 ? 7 / 16f : 0, axis.y != 0 ? direction.getStep() * 2.5f / 16f : 0);
else {
msr.translate(0, 4 / 16f, 0);
if (direction == AxisDirection.NEGATIVE)
msr.rotateCentered(Direction.UP, Mth.PI);
}
msr.scale(type == RenderedTrackOverlayType.STATION ? 1 + 1 / 512f : 1);
return type == RenderedTrackOverlayType.STATION ? AllBlockPartials.TRACK_STATION_OVERLAY
: type == RenderedTrackOverlayType.SIGNAL ? AllBlockPartials.TRACK_SIGNAL_OVERLAY
: AllBlockPartials.TRACK_SIGNAL_DUAL_OVERLAY;
@ -448,4 +499,16 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
&& state1.setValue(HAS_TURN, false) == state2.setValue(HAS_TURN, false);
}
public static class RenderProperties extends ReducedDestroyEffects implements DestroyProgressRenderingHandler {
@Override
public boolean renderDestroyProgress(ClientLevel level, LevelRenderer renderer, int breakerId, BlockPos pos,
int progress, BlockState blockState) {
BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity instanceof TrackTileEntity track)
for (BlockPos trackPos : track.connections.keySet())
renderer.destroyBlockProgress(trackPos.hashCode(), trackPos, progress);
return false;
}
}
}

View file

@ -8,6 +8,7 @@ import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction.AxisDirection;
import net.minecraft.nbt.CompoundTag;
@ -45,6 +46,14 @@ public class TrackBlockItem extends BlockItem {
Vec3 lookAngle = player.getLookAngle();
if (!isFoil(stack)) {
if (state.getBlock() instanceof TrackBlock track && track.getTrackAxes(level, pos, state)
.size() > 1) {
if (!level.isClientSide)
player.displayClientMessage(Lang.translate("track.junction_start")
.withStyle(ChatFormatting.RED), true);
return InteractionResult.SUCCESS;
}
if (select(level, pos, lookAngle, stack))
return InteractionResult.SUCCESS;
return super.useOn(pContext);
@ -81,7 +90,7 @@ public class TrackBlockItem extends BlockItem {
if (level.isClientSide)
return InteractionResult.SUCCESS;
if (offhandItem.getItem()instanceof BlockItem blockItem) {
if (offhandItem.getItem() instanceof BlockItem blockItem) {
Block block = blockItem.getBlock();
if (block == null)
return InteractionResult.SUCCESS;

View file

@ -1,31 +1,165 @@
package com.simibubi.create.content.logistics.trains.track;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import com.jozufozu.flywheel.util.transform.TransformStack;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.simibubi.create.AllBlocks;
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.RaycastHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.foundation.utility.WorldAttached;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult.Type;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.DrawSelectionEvent;
import net.minecraftforge.common.ForgeMod;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
@EventBusSubscriber(Dist.CLIENT)
public class TrackBlockOutline {
public static WorldAttached<Map<BlockPos, TrackTileEntity>> TRACKS_WITH_TURNS =
new WorldAttached<>(w -> new HashMap<>());
static BezierPointSelection result;
public static void pickCurves() {
Minecraft mc = Minecraft.getInstance();
if (!(mc.cameraEntity instanceof LocalPlayer player))
return;
if (mc.level == null)
return;
Vec3 origin = player.getEyePosition(AnimationTickHolder.getPartialTicks(mc.level));
double maxRange = mc.hitResult == null ? Double.MAX_VALUE
: mc.hitResult.getLocation()
.distanceToSqr(origin);
result = null;
AttributeInstance range = player.getAttribute(ForgeMod.REACH_DISTANCE.get());
Vec3 target = RaycastHelper.getTraceTarget(player, Math.min(maxRange, range.getValue()) + 1, origin);
Map<BlockPos, TrackTileEntity> turns = TRACKS_WITH_TURNS.get(mc.level);
for (TrackTileEntity te : turns.values()) {
for (BezierConnection bc : te.connections.values()) {
if (!bc.isPrimary())
continue;
AABB bounds = bc.getBounds();
if (!bounds.contains(origin) && bounds.clip(origin, target)
.isEmpty())
continue;
float[] stepLUT = bc.getStepLUT();
int segments = (int) (bc.getLength() * 2);
AABB segmentBounds = AllShapes.TRACK_ORTHO.get(Direction.SOUTH)
.bounds();
segmentBounds = segmentBounds.move(-.5, segmentBounds.getYsize() / -2, -.5);
int bestSegment = -1;
double bestDistance = Double.MAX_VALUE;
double newMaxRange = maxRange;
for (int i = 0; i < stepLUT.length - 2; i++) {
float t = stepLUT[i] * i / segments;
float t1 = stepLUT[i + 1] * (i + 1) / segments;
float t2 = stepLUT[i + 2] * (i + 2) / segments;
Vec3 v1 = bc.getPosition(t);
Vec3 v2 = bc.getPosition(t2);
Vec3 diff = v2.subtract(v1);
Vec3 angles = TrackRenderer.getModelAngles(bc.getNormal(t1), diff);
Vec3 anchor = v1.add(diff.scale(.5));
Vec3 localOrigin = origin.subtract(anchor);
Vec3 localDirection = target.subtract(origin);
localOrigin = VecHelper.rotate(localOrigin, AngleHelper.deg(-angles.x), Axis.X);
localOrigin = VecHelper.rotate(localOrigin, AngleHelper.deg(-angles.y), Axis.Y);
localDirection = VecHelper.rotate(localDirection, AngleHelper.deg(-angles.x), Axis.X);
localDirection = VecHelper.rotate(localDirection, AngleHelper.deg(-angles.y), Axis.Y);
Optional<Vec3> clip = segmentBounds.clip(localOrigin, localOrigin.add(localDirection));
if (clip.isEmpty())
continue;
if (bestSegment != -1 && bestDistance < clip.get()
.distanceToSqr(0, 0.25f, 0))
continue;
double distanceToSqr = clip.get()
.distanceToSqr(localOrigin);
if (distanceToSqr > maxRange)
continue;
bestSegment = i;
newMaxRange = distanceToSqr;
bestDistance = clip.get()
.distanceToSqr(0, 0.25f, 0);
BezierTrackPointLocation location = new BezierTrackPointLocation(bc.getKey(), i);
result = new BezierPointSelection(te, location, anchor, angles, diff.normalize());
}
if (bestSegment != -1)
maxRange = newMaxRange;
}
}
if (result == null)
return;
if (mc.hitResult != null && mc.hitResult.getType() != Type.MISS) {
Vec3 priorLoc = mc.hitResult.getLocation();
mc.hitResult = BlockHitResult.miss(priorLoc, Direction.UP, new BlockPos(priorLoc));
}
}
public static void drawCurveSelection(PoseStack ms, MultiBufferSource buffer) {
BezierPointSelection result = TrackBlockOutline.result;
if (result == null)
return;
VertexConsumer vb = buffer.getBuffer(RenderType.lines());
Vec3 vec = result.vec();
Vec3 angles = result.angles();
TransformStack.cast(ms)
.pushPose()
.translate(vec.x, vec.y + .125f, vec.z)
.rotateYRadians(angles.y)
.rotateXRadians(angles.x)
.translate(-.5, -.125f, -.5);
boolean holdingTrack = AllBlocks.TRACK.isIn(Minecraft.getInstance().player.getMainHandItem());
renderShape(AllShapes.TRACK_ORTHO.get(Direction.SOUTH), ms, vb, holdingTrack ? false : null);
ms.popPose();
}
@SubscribeEvent
public static void drawCustomBlockSelection(DrawSelectionEvent.HighlightBlock event) {
Minecraft mc = Minecraft.getInstance();
@ -41,16 +175,26 @@ public class TrackBlockOutline {
VertexConsumer vb = event.getMultiBufferSource()
.getBuffer(RenderType.lines());
PoseStack ms = event.getPoseStack();
ms.pushPose();
Vec3 camPos = event.getCamera()
.getPosition();
PoseStack ms = event.getPoseStack();
ms.pushPose();
ms.translate(pos.getX() - camPos.x, pos.getY() - camPos.y, pos.getZ() - camPos.z);
walkShapes(blockstate.getValue(TrackBlock.SHAPE), TransformStack.cast(ms), s -> {
boolean holdingTrack = AllBlocks.TRACK.isIn(Minecraft.getInstance().player.getMainHandItem());
TrackShape shape = blockstate.getValue(TrackBlock.SHAPE);
boolean isJunction = shape.isJunction();
walkShapes(shape, TransformStack.cast(ms), s -> {
renderShape(s, ms, vb, holdingTrack ? !isJunction : null);
event.setCanceled(true);
});
ms.popPose();
}
private static void renderShape(VoxelShape s, PoseStack ms, VertexConsumer vb, Boolean valid) {
PoseStack.Pose transform = ms.last();
s.forAllEdges((x1, y1, z1, x2, y2, z2) -> {
float xDiff = (float) (x2 - x1);
@ -62,20 +206,32 @@ public class TrackBlockOutline {
yDiff /= length;
zDiff /= length;
float r = 0f;
float g = 0f;
float b = 0f;
if (valid != null && valid) {
g = 1f;
b = 1f;
r = 1f;
}
if (valid != null && !valid) {
r = 1f;
b = 0.125f;
g = 0.25f;
}
vb.vertex(transform.pose(), (float) x1, (float) y1, (float) z1)
.color(0f, 0f, 0f, .4f)
.color(r, g, b, .4f)
.normal(transform.normal(), xDiff, yDiff, zDiff)
.endVertex();
vb.vertex(transform.pose(), (float) x2, (float) y2, (float) z2)
.color(0f, 0f, 0f, .4f)
.color(r, g, b, .4f)
.normal(transform.normal(), xDiff, yDiff, zDiff)
.endVertex();
});
});
ms.popPose();
}
private static final VoxelShape LONG_CROSS =
@ -115,4 +271,8 @@ public class TrackBlockOutline {
renderer.accept(LONG_ORTHO);
}
public static record BezierPointSelection(TrackTileEntity te, BezierTrackPointLocation loc, Vec3 vec, Vec3 angles,
Vec3 direction) {
}
}

View file

@ -334,7 +334,7 @@ public class TrackPlacement {
boolean canPlace = stateAtPos.getMaterial()
.isReplaceable();
if (stateAtPos.getBlock()instanceof ITrackBlock trackAtPos) {
if (stateAtPos.getBlock() instanceof ITrackBlock trackAtPos) {
toPlace = trackAtPos.overlay(level, offsetPos, stateAtPos, toPlace);
canPlace = true;
}
@ -413,7 +413,8 @@ public class TrackPlacement {
.withStyle(ChatFormatting.GREEN), true);
else if (info.message != null)
player.displayClientMessage(Lang.translate(info.message)
.withStyle(ChatFormatting.RED), true);
.withStyle(info.message.equals("track.second_point") ? ChatFormatting.WHITE : ChatFormatting.RED),
true);
animation.chase(info.valid ? 1 : 0, 0.25, Chaser.EXP);
animation.tickChaser();

View file

@ -49,6 +49,13 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
return connections;
}
@Override
public void initialize() {
super.initialize();
if (!level.isClientSide && hasInteractableConnections())
registerToCurveInteraction();
}
private void validateConnections() {
Set<BlockPos> invalid = new HashSet<>();
for (Entry<BlockPos, BezierConnection> entry : connections.entrySet()) {
@ -117,6 +124,11 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
}
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> InstancedRenderDispatcher.enqueueUpdate(this));
if (hasInteractableConnections())
registerToCurveInteraction();
else
removeFromCurveInteraction();
}
@Override
@ -136,6 +148,13 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
level.scheduleTick(worldPosition, getBlockState().getBlock(), 1);
}
public boolean hasInteractableConnections() {
for (BezierConnection connection : connections.values())
if (connection.isPrimary())
return true;
return false;
}
@Override
public void transform(StructureTransform transform) {
if (transform.rotationAxis != Axis.Y)
@ -173,4 +192,31 @@ public class TrackTileEntity extends SmartTileEntity implements ITransformableTE
connections = transformedConnections;
}
@Override
public void setRemoved() {
super.setRemoved();
if (level.isClientSide)
removeFromCurveInteraction();
}
private void registerToCurveInteraction() {
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> this::registerToCurveInteractionUnsafe);
}
private void removeFromCurveInteraction() {
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> this::removeFromCurveInteractionUnsafe);
}
@OnlyIn(Dist.CLIENT)
private void registerToCurveInteractionUnsafe() {
TrackBlockOutline.TRACKS_WITH_TURNS.get(level)
.put(worldPosition, this);
}
@OnlyIn(Dist.CLIENT)
private void removeFromCurveInteractionUnsafe() {
TrackBlockOutline.TRACKS_WITH_TURNS.get(level)
.remove(worldPosition);
}
}

View file

@ -38,6 +38,8 @@ import com.simibubi.create.content.logistics.trains.entity.CarriageCouplingRende
import com.simibubi.create.content.logistics.trains.entity.TrainRelocator;
import com.simibubi.create.content.logistics.trains.management.edgePoint.TrackTargetingBlockItem;
import com.simibubi.create.content.logistics.trains.management.schedule.TrainHatArmorLayer;
import com.simibubi.create.content.logistics.trains.track.CurvedTrackInteraction;
import com.simibubi.create.content.logistics.trains.track.TrackBlockOutline;
import com.simibubi.create.content.logistics.trains.track.TrackPlacement;
import com.simibubi.create.content.logistics.trains.track.TrackRemoval;
import com.simibubi.create.foundation.config.AllConfigs;
@ -159,6 +161,7 @@ public class ClientEvents {
TrackRemoval.clientTick();
TrainRelocator.clientTick();
DataGathererBlockItem.clientTick();
CurvedTrackInteraction.clientTick();
}
@SubscribeEvent
@ -205,6 +208,7 @@ public class ClientEvents {
ms.translate(-cameraPos.x(), -cameraPos.y(), -cameraPos.z());
SuperRenderTypeBuffer buffer = SuperRenderTypeBuffer.getInstance();
TrackBlockOutline.drawCurveSelection(ms, buffer);
TrackTargetingBlockItem.render(ms, buffer);
CouplingRenderer.renderAll(ms, buffer);
CarriageCouplingRenderer.renderAll(ms, buffer);

View file

@ -4,6 +4,7 @@ import com.simibubi.create.CreateClient;
import com.simibubi.create.content.curiosities.toolbox.ToolboxHandlerClient;
import com.simibubi.create.content.logistics.item.LinkedControllerClientHandler;
import com.simibubi.create.content.logistics.trains.entity.TrainRelocator;
import com.simibubi.create.content.logistics.trains.track.CurvedTrackInteraction;
import com.simibubi.create.foundation.tileEntity.behaviour.filtering.FilteringHandler;
import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollValueHandler;
@ -61,6 +62,11 @@ public class InputEvents {
if (Minecraft.getInstance().screen != null)
return;
if (CurvedTrackInteraction.onClickInput(event)) {
event.setCanceled(true);
return;
}
if (event.getKeyMapping() == Minecraft.getInstance().options.keyPickItem) {
if (ToolboxHandlerClient.onPickItem())
event.setCanceled(true);

View file

@ -5,6 +5,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.simibubi.create.content.logistics.trains.track.TrackBlockOutline;
import com.simibubi.create.foundation.block.BigOutlines;
import net.minecraft.client.renderer.GameRenderer;
@ -18,6 +19,7 @@ public class GameRendererMixin {
@Inject(at = @At("TAIL"), method = "pick")
private void bigShapePick(CallbackInfo ci) {
BigOutlines.pick();
TrackBlockOutline.pickCurves();
}
}

View file

@ -49,11 +49,13 @@ import com.simibubi.create.content.logistics.packet.TunnelFlapPacket;
import com.simibubi.create.content.logistics.trains.TrackGraphSyncPacket;
import com.simibubi.create.content.logistics.trains.entity.TrainPacket;
import com.simibubi.create.content.logistics.trains.entity.TrainRelocationPacket;
import com.simibubi.create.content.logistics.trains.management.edgePoint.CurvedTrackSelectionPacket;
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalEdgeGroupPacket;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.StationEditPacket;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.TrainEditPacket;
import com.simibubi.create.content.logistics.trains.management.edgePoint.station.TrainEditPacket.TrainEditReturnPacket;
import com.simibubi.create.content.logistics.trains.management.schedule.ScheduleEditPacket;
import com.simibubi.create.content.logistics.trains.track.CurvedTrackDestroyPacket;
import com.simibubi.create.content.logistics.trains.track.TrackRemovalPacket;
import com.simibubi.create.content.schematics.packet.ConfigureSchematicannonPacket;
import com.simibubi.create.content.schematics.packet.InstantSchematicPacket;
@ -122,6 +124,8 @@ public enum AllPackets {
CONTROLS_INPUT(ControlsInputPacket.class, ControlsInputPacket::new, PLAY_TO_SERVER),
REMOVE_TRACKS(TrackRemovalPacket.class, TrackRemovalPacket::new, PLAY_TO_SERVER),
CONFIGURE_DATA_GATHERER(DataGathererConfigurationPacket.class, DataGathererConfigurationPacket::new, PLAY_TO_SERVER),
DESTROY_CURVED_TRACK(CurvedTrackDestroyPacket.class, CurvedTrackDestroyPacket::new, PLAY_TO_SERVER),
SELECT_CURVED_TRACK(CurvedTrackSelectionPacket.class, CurvedTrackSelectionPacket::new, PLAY_TO_SERVER),
// Server to Client
SYMMETRY_EFFECT(SymmetryEffectPacket.class, SymmetryEffectPacket::new, PLAY_TO_CLIENT),

View file

@ -636,6 +636,8 @@
"create.track.leave_slope_ascending": "Cannot leave this slope while ascending",
"create.track.leave_slope_descending": "Cannot leave this slope while descending",
"create.track.turn_90": "Can only turn up to 90 Degrees",
"create.track.junction_start": "Cannot start connection from a Junction",
"create.track.turn_start": "Cannot start connection from a Turn",
"create.station.create_train": "Create new Train",
"create.station.disassemble_train": "Disassemble Train",