mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-29 08:26:37 +01:00
The Coupling Capability
- Fixed concurrency / race condition issues with contraptions loading in during a collision cycle - Reworked implementation model of minecart tracking and couplings - Coupling items now get consumed when used in survival mode - Added some player feedback when couplings cannot be created - Fixed couplings disappearing on the client due to sync issues - Wrenches can now remove minecarts in one hit - Wrenches can now be used to remove couplings from minecarts - Cart assemblers now attach themselves to the block above, no longer requiring active "sticky-ness" of the contraption towards it - Minecarts can no longer be moved while a contraption is stalling them
This commit is contained in:
parent
699dc7bb5c
commit
3956875334
40 changed files with 1316 additions and 1279 deletions
|
@ -368,16 +368,16 @@ a3a11524cd3515fc01d905767b4b7ea782adaf03 assets/create/blockstates/yellow_seat.j
|
|||
7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json
|
||||
b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json
|
||||
c113d0a180880243538e9b1c3019c863df3fbdc1 assets/create/lang/en_ud.json
|
||||
a4cd12907a1ddfd60883077b2d11c5459d436016 assets/create/lang/en_us.json
|
||||
063195daed96a4420588e6d6d13f4a9b1f099ff4 assets/create/lang/unfinished/de_de.json
|
||||
30da89bafac8a5ea4d82903928ba4d2a63385117 assets/create/lang/unfinished/fr_fr.json
|
||||
faddc5022cc10c8fca1649d66427e910cfa28286 assets/create/lang/unfinished/it_it.json
|
||||
7f448e863397725c11ab083d39af68d205d579d7 assets/create/lang/unfinished/ja_jp.json
|
||||
48d61d21ee096513f2b9e06df38882cf6ace96db assets/create/lang/unfinished/ko_kr.json
|
||||
a908ecd03bd7ede95b5e5f0698157353ae2bec6c assets/create/lang/unfinished/nl_nl.json
|
||||
ffa305b619c58bdff92d2e69b2bbc81ad5b8dcb6 assets/create/lang/unfinished/pt_br.json
|
||||
6588d559aaafb4f036ba47ba2414caaa9d2f6f6a assets/create/lang/unfinished/ru_ru.json
|
||||
4593a6013875039a6a30ac64d45bb4f140190fe1 assets/create/lang/unfinished/zh_cn.json
|
||||
ee39bea21ca57e7b61f1af0d609d63cbbf8c8425 assets/create/lang/en_us.json
|
||||
233e7fae9df99bcb79d3e08f57e6d5e02bdf49ad assets/create/lang/unfinished/de_de.json
|
||||
d56a40fa6097af1badd3ab24bbcfa191ea5074ca assets/create/lang/unfinished/fr_fr.json
|
||||
1945bf0aebfc97e6f2ee1ac7e4dd754e7fd19fa5 assets/create/lang/unfinished/it_it.json
|
||||
3f76057ebb1c0e9a5c285fb79c1ef72ddec83a32 assets/create/lang/unfinished/ja_jp.json
|
||||
504d30d63a19e117c51be32f16c778bc1a7114c7 assets/create/lang/unfinished/ko_kr.json
|
||||
654efcd49befcac96bd632e65f0a36247bd0fc2e assets/create/lang/unfinished/nl_nl.json
|
||||
2c0f9c853b85a47b91877cd7b89faddc918f0170 assets/create/lang/unfinished/pt_br.json
|
||||
7193bf2573473b3fa8ffd7c8a6dd58ce069201a7 assets/create/lang/unfinished/ru_ru.json
|
||||
28b75a0dd8c021a7e385b3479b91b1edd47f13f4 assets/create/lang/unfinished/zh_cn.json
|
||||
846200eb548d3bfa2e77b41039de159b4b6cfb45 assets/create/models/block/acacia_window.json
|
||||
1930fa3a3c98d53dd19e4ee7f55bc27fd47aa281 assets/create/models/block/acacia_window_pane_noside.json
|
||||
1763ea2c9b981d187f5031ba608f3d5d3be3986a assets/create/models/block/acacia_window_pane_noside_alt.json
|
||||
|
|
|
@ -646,6 +646,12 @@
|
|||
"create.blockzapper.leftClickToSet": "Left-Click a Block to set Material",
|
||||
"create.blockzapper.empty": "Out of Blocks!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "Movement Mode",
|
||||
"create.contraptions.movement_mode.move_place": "Always Place when Stopped",
|
||||
"create.contraptions.movement_mode.move_place_returned": "Place only in Starting Position",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_": "Missing Localizations: 853",
|
||||
"_": "Missing Localizations: 858",
|
||||
|
||||
"_": "->------------------------] Game Elements [------------------------<-",
|
||||
|
||||
|
@ -647,6 +647,12 @@
|
|||
"create.blockzapper.leftClickToSet": "Linksklick auf einen Block zum Auswählen",
|
||||
"create.blockzapper.empty": "Keine Blöcke übrig!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "UNLOCALIZED: Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "UNLOCALIZED: Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "UNLOCALIZED: Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "UNLOCALIZED: Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "UNLOCALIZED: Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "UNLOCALIZED: Movement Mode",
|
||||
"create.contraptions.movement_mode.move_place": "UNLOCALIZED: Always Place when Stopped",
|
||||
"create.contraptions.movement_mode.move_place_returned": "UNLOCALIZED: Place only in Starting Position",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_": "Missing Localizations: 477",
|
||||
"_": "Missing Localizations: 482",
|
||||
|
||||
"_": "->------------------------] Game Elements [------------------------<-",
|
||||
|
||||
|
@ -647,6 +647,12 @@
|
|||
"create.blockzapper.leftClickToSet": "Clic gauche sur un bloc pour en définir le matériau",
|
||||
"create.blockzapper.empty": "Plus de blocs!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "UNLOCALIZED: Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "UNLOCALIZED: Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "UNLOCALIZED: Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "UNLOCALIZED: Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "UNLOCALIZED: Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "Mode de mouvement",
|
||||
"create.contraptions.movement_mode.move_place": "Toujours placer à l'arrêt",
|
||||
"create.contraptions.movement_mode.move_place_returned": "Placer uniquement en position de départ",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_": "Missing Localizations: 461",
|
||||
"_": "Missing Localizations: 466",
|
||||
|
||||
"_": "->------------------------] Game Elements [------------------------<-",
|
||||
|
||||
|
@ -647,6 +647,12 @@
|
|||
"create.blockzapper.leftClickToSet": "Clic-Sinistro su un blocco per impostare il materiale",
|
||||
"create.blockzapper.empty": "Fuori dai Blocchi!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "UNLOCALIZED: Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "UNLOCALIZED: Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "UNLOCALIZED: Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "UNLOCALIZED: Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "UNLOCALIZED: Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "Modalità Movimento",
|
||||
"create.contraptions.movement_mode.move_place": "Posizionare Sempre quando è Fermo",
|
||||
"create.contraptions.movement_mode.move_place_returned": "Posiziona solo nella Posizione Iniziale",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_": "Missing Localizations: 456",
|
||||
"_": "Missing Localizations: 461",
|
||||
|
||||
"_": "->------------------------] Game Elements [------------------------<-",
|
||||
|
||||
|
@ -647,6 +647,12 @@
|
|||
"create.blockzapper.leftClickToSet": "ブロックをシフト-左クリックでマテリアルを選択",
|
||||
"create.blockzapper.empty": "ブロック不足!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "UNLOCALIZED: Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "UNLOCALIZED: Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "UNLOCALIZED: Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "UNLOCALIZED: Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "UNLOCALIZED: Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "移動モード",
|
||||
"create.contraptions.movement_mode.move_place": "停止時に常に配置",
|
||||
"create.contraptions.movement_mode.move_place_returned": "開始位置のみに配置",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_": "Missing Localizations: 461",
|
||||
"_": "Missing Localizations: 466",
|
||||
|
||||
"_": "->------------------------] Game Elements [------------------------<-",
|
||||
|
||||
|
@ -647,6 +647,12 @@
|
|||
"create.blockzapper.leftClickToSet": "좌클릭으로 블럭 설정하기",
|
||||
"create.blockzapper.empty": "블럭이 없습니다!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "UNLOCALIZED: Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "UNLOCALIZED: Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "UNLOCALIZED: Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "UNLOCALIZED: Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "UNLOCALIZED: Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "이동 설정",
|
||||
"create.contraptions.movement_mode.move_place": "멈췄을때 항상 블럭 설치하기",
|
||||
"create.contraptions.movement_mode.move_place_returned": "멈췄을떄 최초 위치에서만 블럭 설치하기",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_": "Missing Localizations: 791",
|
||||
"_": "Missing Localizations: 796",
|
||||
|
||||
"_": "->------------------------] Game Elements [------------------------<-",
|
||||
|
||||
|
@ -647,6 +647,12 @@
|
|||
"create.blockzapper.leftClickToSet": "Klik met links op een Blok om een Materiaal te kiezen",
|
||||
"create.blockzapper.empty": "De Blokken zijn op!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "UNLOCALIZED: Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "UNLOCALIZED: Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "UNLOCALIZED: Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "UNLOCALIZED: Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "UNLOCALIZED: Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "UNLOCALIZED: Movement Mode",
|
||||
"create.contraptions.movement_mode.move_place": "UNLOCALIZED: Always Place when Stopped",
|
||||
"create.contraptions.movement_mode.move_place_returned": "UNLOCALIZED: Place only in Starting Position",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_": "Missing Localizations: 860",
|
||||
"_": "Missing Localizations: 865",
|
||||
|
||||
"_": "->------------------------] Game Elements [------------------------<-",
|
||||
|
||||
|
@ -647,6 +647,12 @@
|
|||
"create.blockzapper.leftClickToSet": "Botão-Esquerdo em um Bloco para selecionar Material",
|
||||
"create.blockzapper.empty": "Sem Blocos!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "UNLOCALIZED: Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "UNLOCALIZED: Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "UNLOCALIZED: Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "UNLOCALIZED: Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "UNLOCALIZED: Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "UNLOCALIZED: Movement Mode",
|
||||
"create.contraptions.movement_mode.move_place": "UNLOCALIZED: Always Place when Stopped",
|
||||
"create.contraptions.movement_mode.move_place_returned": "UNLOCALIZED: Place only in Starting Position",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_": "Missing Localizations: 854",
|
||||
"_": "Missing Localizations: 859",
|
||||
|
||||
"_": "->------------------------] Game Elements [------------------------<-",
|
||||
|
||||
|
@ -647,6 +647,12 @@
|
|||
"create.blockzapper.leftClickToSet": "ЛКМ на блок, чтобы выбрать материал",
|
||||
"create.blockzapper.empty": "Закончились блоки!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "UNLOCALIZED: Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "UNLOCALIZED: Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "UNLOCALIZED: Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "UNLOCALIZED: Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "UNLOCALIZED: Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "UNLOCALIZED: Movement Mode",
|
||||
"create.contraptions.movement_mode.move_place": "UNLOCALIZED: Always Place when Stopped",
|
||||
"create.contraptions.movement_mode.move_place_returned": "UNLOCALIZED: Place only in Starting Position",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_": "Missing Localizations: 141",
|
||||
"_": "Missing Localizations: 146",
|
||||
|
||||
"_": "->------------------------] Game Elements [------------------------<-",
|
||||
|
||||
|
@ -647,6 +647,12 @@
|
|||
"create.blockzapper.leftClickToSet": "左键点击方块以设定方块",
|
||||
"create.blockzapper.empty": "方块不足!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "UNLOCALIZED: Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "UNLOCALIZED: Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "UNLOCALIZED: Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "UNLOCALIZED: Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "UNLOCALIZED: Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "运动模式",
|
||||
"create.contraptions.movement_mode.move_place": "停止时总是实体化方块",
|
||||
"create.contraptions.movement_mode.move_place_returned": "停止时只在初始位置实体化方块",
|
||||
|
|
|
@ -47,10 +47,10 @@ public class AllShapes {
|
|||
.forDirectional(),
|
||||
CRANK = shape(5, 0, 5, 11, 6, 11).add(1, 3, 1, 15, 8, 15)
|
||||
.forDirectional(),
|
||||
VALVE_HANDLE = shape(1, 0, 1, 15, 5, 15)
|
||||
.forDirectional(),
|
||||
VALVE_HANDLE = shape(1, 0, 1, 15, 5, 15).forDirectional(),
|
||||
CART_ASSEMBLER = shape(0, 12, 0, 16, 16, 16).add(-2, 0, 1, 18, 14, 15)
|
||||
.forHorizontalAxis(),
|
||||
CART_ASSEMBLER_PLAYER_COLLISION = shape(0, 0, 1, 16, 16, 15).forHorizontalAxis(),
|
||||
STOCKPILE_SWITCH = shape(0, 0, 0, 16, 2, 16).add(1, 0, 1, 15, 16, 15)
|
||||
.add(0, 14, 0, 16, 16, 16)
|
||||
.add(3, 3, -2, 13, 13, 2)
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.google.gson.Gson;
|
|||
import com.google.gson.GsonBuilder;
|
||||
import com.simibubi.create.content.CreateItemGroup;
|
||||
import com.simibubi.create.content.contraptions.TorquePropagator;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.CapabilityMinecartController;
|
||||
import com.simibubi.create.content.logistics.RedstoneLinkNetworkHandler;
|
||||
import com.simibubi.create.content.palettes.AllPaletteBlocks;
|
||||
import com.simibubi.create.content.palettes.PalettesItemGroup;
|
||||
|
@ -96,6 +97,7 @@ public class Create {
|
|||
}
|
||||
|
||||
public static void init(final FMLCommonSetupEvent event) {
|
||||
CapabilityMinecartController.register();
|
||||
schematicReceiver = new ServerSchematicLoader();
|
||||
redstoneLinkNetworkHandler = new RedstoneLinkNetworkHandler();
|
||||
torquePropagator = new TorquePropagator();
|
||||
|
|
|
@ -348,12 +348,14 @@ public abstract class Contraption {
|
|||
}
|
||||
|
||||
boolean wasVisited = visited.contains(offsetPos);
|
||||
boolean isMinecartAssembler = AllBlocks.CART_ASSEMBLER.has(blockState) && offset == Direction.DOWN;
|
||||
boolean faceHasGlue = superglue.containsKey(offset);
|
||||
boolean blockAttachedTowardsFace =
|
||||
BlockMovementTraits.isBlockAttachedTowards(blockState, offset.getOpposite());
|
||||
boolean brittle = BlockMovementTraits.isBrittle(blockState);
|
||||
|
||||
if (!wasVisited && ((isSlimeBlock && !brittle) || blockAttachedTowardsFace || faceHasGlue))
|
||||
if (!wasVisited
|
||||
&& ((isSlimeBlock && !brittle) || blockAttachedTowardsFace || faceHasGlue || isMinecartAssembler))
|
||||
frontier.add(offsetPos);
|
||||
|
||||
if (faceHasGlue)
|
||||
|
|
|
@ -3,9 +3,7 @@ package com.simibubi.create.content.contraptions.components.structureMovement;
|
|||
import static net.minecraft.entity.Entity.collideBoundingBoxHeuristically;
|
||||
import static net.minecraft.entity.Entity.horizontalMag;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -52,28 +50,11 @@ import net.minecraftforge.fml.DistExecutor;
|
|||
|
||||
public class ContraptionCollider {
|
||||
|
||||
public static void runCollisions(World world) {
|
||||
List<WeakReference<ContraptionEntity>> list = ContraptionHandler.activeContraptions.getIfPresent(world);
|
||||
if (list == null)
|
||||
return;
|
||||
for (Iterator<WeakReference<ContraptionEntity>> iterator = list.iterator(); iterator.hasNext();) {
|
||||
WeakReference<ContraptionEntity> weakReference = iterator.next();
|
||||
ContraptionEntity contraptionEntity = weakReference.get();
|
||||
if (contraptionEntity == null) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
if (!contraptionEntity.isAlive())
|
||||
continue;
|
||||
collideEntities(contraptionEntity);
|
||||
}
|
||||
}
|
||||
|
||||
enum PlayerType {
|
||||
NONE, CLIENT, REMOTE, SERVER
|
||||
}
|
||||
|
||||
private static void collideEntities(ContraptionEntity contraptionEntity) {
|
||||
static void collideEntities(ContraptionEntity contraptionEntity) {
|
||||
World world = contraptionEntity.getEntityWorld();
|
||||
Contraption contraption = contraptionEntity.getContraption();
|
||||
AxisAlignedBB bounds = contraptionEntity.getBoundingBox();
|
||||
|
|
|
@ -20,8 +20,8 @@ import com.simibubi.create.content.contraptions.components.structureMovement.glu
|
|||
import com.simibubi.create.content.contraptions.components.structureMovement.mounted.CartAssemblerTileEntity.CartMovementMode;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.mounted.MountedContraption;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionSeatMappingPacket;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.MinecartCoupling;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.MinecartCouplingHandler;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.CapabilityMinecartController;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.MinecartController;
|
||||
import com.simibubi.create.foundation.item.ItemHelper;
|
||||
import com.simibubi.create.foundation.networking.AllPackets;
|
||||
import com.simibubi.create.foundation.utility.AngleHelper;
|
||||
|
@ -62,6 +62,7 @@ import net.minecraft.world.World;
|
|||
import net.minecraft.world.gen.feature.template.Template.BlockInfo;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
|
||||
import net.minecraftforge.fml.network.NetworkHooks;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
@ -291,7 +292,7 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
|
|||
remove();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
prevPosX = getX();
|
||||
prevPosY = getY();
|
||||
prevPosZ = getZ();
|
||||
|
@ -310,13 +311,12 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
|
|||
|
||||
if (getMotion().length() < 1 / 4098f)
|
||||
setMotion(Vec3d.ZERO);
|
||||
|
||||
|
||||
move(getMotion().x, getMotion().y, getMotion().z);
|
||||
if (ContraptionCollider.collideBlocks(this))
|
||||
getController().collided();
|
||||
|
||||
Vec3d movement = getPositionVec().subtract(prevPosX, prevPosY, prevPosZ);
|
||||
tickActors(movement);
|
||||
tickActors();
|
||||
|
||||
prevYaw = yaw;
|
||||
prevPitch = pitch;
|
||||
|
@ -329,6 +329,7 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
|
|||
boolean rotationLock = false;
|
||||
boolean pauseWhileRotating = false;
|
||||
boolean rotating = false;
|
||||
boolean wasStalled = isStalled();
|
||||
|
||||
Entity riding = e;
|
||||
while (riding.getRidingEntity() != null)
|
||||
|
@ -338,39 +339,38 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
|
|||
attachedExtraInventories = true;
|
||||
}
|
||||
|
||||
boolean isOnCoupling = false;
|
||||
if (contraption instanceof MountedContraption) {
|
||||
MountedContraption mountedContraption = (MountedContraption) contraption;
|
||||
UUID couplingId = getCouplingId();
|
||||
isOnCoupling = couplingId != null && riding instanceof AbstractMinecartEntity;
|
||||
if (isOnCoupling) {
|
||||
MinecartCoupling coupling = MinecartCouplingHandler.getCoupling(world, couplingId);
|
||||
if (coupling != null && coupling.areBothEndsPresent()) {
|
||||
boolean notOnMainCart = !coupling.getId()
|
||||
.equals(riding.getUniqueID());
|
||||
Vec3d positionVec = coupling.asCouple()
|
||||
.get(notOnMainCart)
|
||||
.getPositionVec();
|
||||
prevYaw = yaw;
|
||||
prevPitch = pitch;
|
||||
double diffZ = positionVec.z - riding.getZ();
|
||||
double diffX = positionVec.x - riding.getX();
|
||||
yaw = (float) (MathHelper.atan2(diffZ, diffX) * 180 / Math.PI);
|
||||
pitch = (float) (Math.atan2(positionVec.y - getY(), Math.sqrt(diffX * diffX + diffZ * diffZ)) * 180
|
||||
/ Math.PI);
|
||||
|
||||
if (notOnMainCart) {
|
||||
yaw += 180;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rotationLock = mountedContraption.rotationMode == CartMovementMode.ROTATION_LOCKED;
|
||||
pauseWhileRotating = mountedContraption.rotationMode == CartMovementMode.ROTATE_PAUSED;
|
||||
}
|
||||
|
||||
Vec3d movementVector = riding.getMotion();
|
||||
if (!isOnCoupling) {
|
||||
boolean isOnCoupling = false;
|
||||
UUID couplingId = getCouplingId();
|
||||
isOnCoupling = couplingId != null && riding instanceof AbstractMinecartEntity;
|
||||
|
||||
if (isOnCoupling) {
|
||||
// MinecartCoupling coupling = MinecartCouplingHandler.getCoupling(world, couplingId);
|
||||
// if (coupling != null && coupling.areBothEndsPresent()) {
|
||||
// boolean notOnMainCart = !coupling.getId()
|
||||
// .equals(riding.getUniqueID());
|
||||
// Vec3d positionVec = coupling.asCouple()
|
||||
// .get(notOnMainCart)
|
||||
// .getPositionVec();
|
||||
// prevYaw = yaw;
|
||||
// prevPitch = pitch;
|
||||
// double diffZ = positionVec.z - riding.getZ();
|
||||
// double diffX = positionVec.x - riding.getX();
|
||||
// yaw = (float) (MathHelper.atan2(diffZ, diffX) * 180 / Math.PI);
|
||||
// pitch = (float) (Math.atan2(positionVec.y - getY(), Math.sqrt(diffX * diffX + diffZ * diffZ)) * 180
|
||||
// / Math.PI);
|
||||
//
|
||||
// if (notOnMainCart) {
|
||||
// yaw += 180;
|
||||
// }
|
||||
// }
|
||||
} else if (!wasStalled) {
|
||||
Vec3d movementVector = riding.getMotion();
|
||||
if (riding instanceof BoatEntity)
|
||||
movementVector = getPositionVec().subtract(prevPosX, prevPosY, prevPosZ);
|
||||
Vec3d motion = movementVector.normalize();
|
||||
|
@ -393,18 +393,26 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
|
|||
}
|
||||
}
|
||||
|
||||
boolean wasStalled = isStalled();
|
||||
if (!rotating || !pauseWhileRotating)
|
||||
tickActors(movementVector);
|
||||
if (isStalled()) {
|
||||
if (!wasStalled)
|
||||
motionBeforeStall = riding.getMotion();
|
||||
riding.setMotion(0, 0, 0);
|
||||
}
|
||||
tickActors();
|
||||
boolean isStalled = isStalled();
|
||||
|
||||
if (wasStalled && !isStalled()) {
|
||||
riding.setMotion(motionBeforeStall);
|
||||
motionBeforeStall = Vec3d.ZERO;
|
||||
LazyOptional<MinecartController> capability =
|
||||
riding.getCapability(CapabilityMinecartController.MINECART_CONTROLLER_CAPABILITY);
|
||||
if (capability.isPresent()) {
|
||||
if (!world.isRemote())
|
||||
capability.orElse(null)
|
||||
.setStalledExternally(isStalled);
|
||||
} else {
|
||||
if (isStalled) {
|
||||
if (!wasStalled)
|
||||
motionBeforeStall = riding.getMotion();
|
||||
riding.setMotion(0, 0, 0);
|
||||
}
|
||||
if (wasStalled && !isStalled) {
|
||||
riding.setMotion(motionBeforeStall);
|
||||
motionBeforeStall = Vec3d.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isStalled() && (riding instanceof FurnaceMinecartEntity)) {
|
||||
|
@ -449,7 +457,7 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
|
|||
}
|
||||
}
|
||||
|
||||
public void tickActors(Vec3d movementVector) {
|
||||
public void tickActors() {
|
||||
Vec3d rotationVec = getRotationVec();
|
||||
Vec3d reversedRotationVec = rotationVec.scale(-1);
|
||||
Vec3d rotationOffset = VecHelper.getCenterOf(BlockPos.ZERO);
|
||||
|
@ -508,6 +516,7 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
|
|||
|
||||
context.rotation = rotationVec;
|
||||
context.position = actorPosition;
|
||||
|
||||
if (actor.isActive(context)) {
|
||||
if (newPosVisited && !context.stall) {
|
||||
actor.visitNewPosition(context, gridPosition);
|
||||
|
@ -854,7 +863,7 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
|
|||
public Vec3d getContactPointMotion(Vec3d globalContactPoint) {
|
||||
if (prevPosInvalid)
|
||||
return Vec3d.ZERO;
|
||||
|
||||
|
||||
Vec3d positionVec = getPositionVec();
|
||||
Vec3d conMotion = positionVec.subtract(getPrevPositionVec());
|
||||
Vec3d conAngularMotion = getRotationVec().subtract(getPrevRotationVec());
|
||||
|
@ -915,7 +924,7 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
|
|||
public void setCoupledCart(UUID id) {
|
||||
dataManager.set(COUPLED_CART, Optional.ofNullable(id));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isOnePlayerRiding() {
|
||||
return false;
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.simibubi.create.foundation.utility.VecHelper;
|
||||
import com.simibubi.create.foundation.utility.WorldAttached;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectLists;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
|
@ -21,21 +19,39 @@ import net.minecraftforge.common.util.Constants.NBT;
|
|||
|
||||
public class ContraptionHandler {
|
||||
|
||||
public static Cache<World, List<WeakReference<ContraptionEntity>>> activeContraptions = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(400, SECONDS)
|
||||
.build();
|
||||
/* Global map of loaded contraptions */
|
||||
|
||||
public static WorldAttached<List<WeakReference<ContraptionEntity>>> loadedContraptions;
|
||||
static WorldAttached<List<ContraptionEntity>> queuedAdditions;
|
||||
|
||||
static {
|
||||
loadedContraptions = new WorldAttached<>(ArrayList::new);
|
||||
queuedAdditions = new WorldAttached<>(() -> ObjectLists.synchronize(new ObjectArrayList<>()));
|
||||
}
|
||||
|
||||
public static void tick(World world) {
|
||||
List<WeakReference<ContraptionEntity>> list = loadedContraptions.get(world);
|
||||
List<ContraptionEntity> queued = queuedAdditions.get(world);
|
||||
|
||||
for (ContraptionEntity contraptionEntity : queued)
|
||||
list.add(new WeakReference<>(contraptionEntity));
|
||||
queued.clear();
|
||||
|
||||
for (Iterator<WeakReference<ContraptionEntity>> iterator = list.iterator(); iterator.hasNext();) {
|
||||
WeakReference<ContraptionEntity> weakReference = iterator.next();
|
||||
ContraptionEntity contraptionEntity = weakReference.get();
|
||||
if (contraptionEntity == null || !contraptionEntity.isAlive()) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
ContraptionCollider.collideEntities(contraptionEntity);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addSpawnedContraptionsToCollisionList(Entity entity, World world) {
|
||||
if (!(entity instanceof ContraptionEntity))
|
||||
return;
|
||||
try {
|
||||
List<WeakReference<ContraptionEntity>> list =
|
||||
activeContraptions.get(world, () -> Collections.synchronizedList(new ArrayList<>()));
|
||||
ContraptionEntity contraption = (ContraptionEntity) entity;
|
||||
list.add(new WeakReference<>(contraption));
|
||||
} catch (ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (entity instanceof ContraptionEntity)
|
||||
queuedAdditions.get(world)
|
||||
.add((ContraptionEntity) entity);
|
||||
}
|
||||
|
||||
public static void entitiesWhoJustDismountedGetSentToTheRightLocation(LivingEntity entityLiving, World world) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import com.simibubi.create.AllShapes;
|
|||
import com.simibubi.create.AllTileEntities;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionEntity;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.mounted.CartAssemblerTileEntity.CartMovementMode;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.MinecartCouplingHandler;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingHandler;
|
||||
import com.simibubi.create.content.contraptions.wrench.IWrenchable;
|
||||
import com.simibubi.create.content.schematics.ISpecialBlockItemRequirement;
|
||||
import com.simibubi.create.content.schematics.ItemRequirement;
|
||||
|
@ -217,7 +217,7 @@ public class CartAssemblerBlock extends AbstractRailBlock
|
|||
|
||||
boolean couplingFound = contraption.connectedCart != null;
|
||||
if (couplingFound) {
|
||||
MinecartCouplingHandler.connectCarts(null, world, cart.getEntityId(),
|
||||
CouplingHandler.tryToCoupleCarts(null, world, cart.getEntityId(),
|
||||
contraption.connectedCart.getEntityId());
|
||||
Vec3d diff = contraption.connectedCart.getPositionVec()
|
||||
.subtract(cart.getPositionVec());
|
||||
|
@ -285,16 +285,22 @@ public class CartAssemblerBlock extends AbstractRailBlock
|
|||
@Nonnull
|
||||
public VoxelShape getShape(BlockState state, @Nonnull IBlockReader worldIn, @Nonnull BlockPos pos,
|
||||
@Nonnull ISelectionContext context) {
|
||||
return AllShapes.CART_ASSEMBLER
|
||||
.get(state.get(RAIL_SHAPE) == RailShape.NORTH_SOUTH ? Direction.Axis.Z : Direction.Axis.X);
|
||||
return AllShapes.CART_ASSEMBLER.get(getRailAxis(state));
|
||||
}
|
||||
|
||||
protected Axis getRailAxis(BlockState state) {
|
||||
return state.get(RAIL_SHAPE) == RailShape.NORTH_SOUTH ? Direction.Axis.Z : Direction.Axis.X;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public VoxelShape getCollisionShape(@Nonnull BlockState state, @Nonnull IBlockReader worldIn, @Nonnull BlockPos pos,
|
||||
ISelectionContext context) {
|
||||
if (context.getEntity() instanceof AbstractMinecartEntity)
|
||||
Entity entity = context.getEntity();
|
||||
if (entity instanceof AbstractMinecartEntity)
|
||||
return VoxelShapes.empty();
|
||||
if (entity instanceof PlayerEntity)
|
||||
return AllShapes.CART_ASSEMBLER_PLAYER_COLLISION.get(getRailAxis(state));
|
||||
return VoxelShapes.fullCube();
|
||||
}
|
||||
|
||||
|
|
|
@ -9,16 +9,16 @@ import net.minecraft.entity.player.ServerPlayerEntity;
|
|||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent.Context;
|
||||
|
||||
public class MinecartCouplingCreationPacket extends SimplePacketBase {
|
||||
public class CouplingCreationPacket extends SimplePacketBase {
|
||||
|
||||
int id1, id2;
|
||||
|
||||
public MinecartCouplingCreationPacket(AbstractMinecartEntity cart1, AbstractMinecartEntity cart2) {
|
||||
public CouplingCreationPacket(AbstractMinecartEntity cart1, AbstractMinecartEntity cart2) {
|
||||
id1 = cart1.getEntityId();
|
||||
id2 = cart2.getEntityId();
|
||||
}
|
||||
|
||||
public MinecartCouplingCreationPacket(PacketBuffer buffer) {
|
||||
public CouplingCreationPacket(PacketBuffer buffer) {
|
||||
id1 = buffer.readInt();
|
||||
id2 = buffer.readInt();
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public class MinecartCouplingCreationPacket extends SimplePacketBase {
|
|||
ServerPlayerEntity sender = context.get()
|
||||
.getSender();
|
||||
if (sender != null)
|
||||
MinecartCouplingHandler.connectCarts(sender, sender.world, id1, id2);
|
||||
CouplingHandler.tryToCoupleCarts(sender, sender.world, id1, id2);
|
||||
});
|
||||
context.get()
|
||||
.setPacketHandled(true);
|
|
@ -0,0 +1,160 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.simibubi.create.AllItems;
|
||||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.CapabilityMinecartController;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.MinecartController;
|
||||
import com.simibubi.create.foundation.config.AllConfigs;
|
||||
import com.simibubi.create.foundation.utility.Couple;
|
||||
import com.simibubi.create.foundation.utility.Iterate;
|
||||
import com.simibubi.create.foundation.utility.Lang;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class CouplingHandler {
|
||||
|
||||
public static void forEachLoadedCoupling(World world, Consumer<Couple<MinecartController>> consumer) {
|
||||
if (world == null)
|
||||
return;
|
||||
Set<UUID> cartsWithCoupling = CapabilityMinecartController.loadedMinecartsWithCoupling.get(world);
|
||||
if (cartsWithCoupling == null)
|
||||
return;
|
||||
cartsWithCoupling.forEach(id -> {
|
||||
MinecartController controller = CapabilityMinecartController.getIfPresent(world, id);
|
||||
if (controller == null)
|
||||
return;
|
||||
if (!controller.isLeadingCoupling())
|
||||
return;
|
||||
UUID coupledCart = controller.getCoupledCart(true);
|
||||
MinecartController coupledController = CapabilityMinecartController.getIfPresent(world, coupledCart);
|
||||
if (coupledController == null)
|
||||
return;
|
||||
consumer.accept(Couple.create(controller, coupledController));
|
||||
});
|
||||
}
|
||||
|
||||
public static void tryToCoupleCarts(@Nullable PlayerEntity player, World world, int cartId1, int cartId2) {
|
||||
Entity entity1 = world.getEntityByID(cartId1);
|
||||
Entity entity2 = world.getEntityByID(cartId2);
|
||||
|
||||
if (!(entity1 instanceof AbstractMinecartEntity))
|
||||
return;
|
||||
if (!(entity2 instanceof AbstractMinecartEntity))
|
||||
return;
|
||||
|
||||
String tooMany = "two_couplings_max";
|
||||
String unloaded = "unloaded";
|
||||
String noLoops = "no_loops";
|
||||
String tooFar = "too_far";
|
||||
|
||||
int distanceTo = (int) entity1.getPositionVec()
|
||||
.distanceTo(entity2.getPositionVec());
|
||||
|
||||
if (distanceTo < 2) {
|
||||
if (player == null)
|
||||
return; // dont allow train contraptions with <2 distance
|
||||
distanceTo = 2;
|
||||
}
|
||||
|
||||
if (distanceTo > AllConfigs.SERVER.kinetics.maxCartCouplingLength.get()) {
|
||||
status(player, tooFar);
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractMinecartEntity cart1 = (AbstractMinecartEntity) entity1;
|
||||
AbstractMinecartEntity cart2 = (AbstractMinecartEntity) entity2;
|
||||
UUID mainID = cart1.getUniqueID();
|
||||
UUID connectedID = cart2.getUniqueID();
|
||||
MinecartController mainController = CapabilityMinecartController.getIfPresent(world, mainID);
|
||||
MinecartController connectedController = CapabilityMinecartController.getIfPresent(world, connectedID);
|
||||
|
||||
if (mainController == null || connectedController == null) {
|
||||
status(player, unloaded);
|
||||
return;
|
||||
}
|
||||
if (mainController.isFullyCoupled() || connectedController.isFullyCoupled()) {
|
||||
status(player, tooMany);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainController.isLeadingCoupling() && mainController.getCoupledCart(true)
|
||||
.equals(connectedID) || connectedController.isLeadingCoupling()
|
||||
&& connectedController.getCoupledCart(true)
|
||||
.equals(mainID))
|
||||
return;
|
||||
|
||||
for (boolean main : Iterate.trueAndFalse) {
|
||||
MinecartController current = main ? mainController : connectedController;
|
||||
boolean forward = current.isLeadingCoupling();
|
||||
int safetyCount = 1000;
|
||||
|
||||
while (true) {
|
||||
if (safetyCount-- <= 0) {
|
||||
Create.logger.warn("Infinite loop in coupling iteration");
|
||||
return;
|
||||
}
|
||||
|
||||
current = getNextInCouplingChain(world, current, forward);
|
||||
if (current == null) {
|
||||
status(player, unloaded);
|
||||
return;
|
||||
}
|
||||
if (current == connectedController) {
|
||||
status(player, noLoops);
|
||||
return;
|
||||
}
|
||||
if (current == MinecartController.EMPTY)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (player != null) {
|
||||
for (Hand hand : Hand.values()) {
|
||||
if (player.isCreative())
|
||||
break;
|
||||
ItemStack heldItem = player.getHeldItem(hand);
|
||||
if (!AllItems.MINECART_COUPLING.isIn(heldItem))
|
||||
continue;
|
||||
heldItem.shrink(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mainController.prepareForCoupling(true);
|
||||
connectedController.prepareForCoupling(false);
|
||||
|
||||
mainController.coupleWith(true, connectedID, distanceTo);
|
||||
connectedController.coupleWith(false, mainID, distanceTo);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
/**
|
||||
* MinecartController.EMPTY if none connected, null if not yet loaded
|
||||
*/
|
||||
public static MinecartController getNextInCouplingChain(World world, MinecartController controller,
|
||||
boolean forward) {
|
||||
UUID coupledCart = controller.getCoupledCart(forward);
|
||||
if (coupledCart == null)
|
||||
return MinecartController.empty();
|
||||
return CapabilityMinecartController.getIfPresent(world, coupledCart);
|
||||
}
|
||||
|
||||
public static void status(PlayerEntity player, String key) {
|
||||
if (player == null)
|
||||
return;
|
||||
player.sendStatusMessage(new StringTextComponent(Lang.translate("minecart_coupling." + key)), true);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,7 +18,7 @@ import net.minecraft.particles.RedstoneParticleData;
|
|||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
|
||||
public class ClientMinecartCouplingHandler {
|
||||
public class CouplingHandlerClient {
|
||||
|
||||
static AbstractMinecartEntity selectedCart;
|
||||
static Random r = new Random();
|
||||
|
@ -44,7 +44,7 @@ public class ClientMinecartCouplingHandler {
|
|||
return;
|
||||
}
|
||||
spawnSelectionParticles(entity.getBoundingBox(), true);
|
||||
AllPackets.channel.sendToServer(new MinecartCouplingCreationPacket(selectedCart, entity));
|
||||
AllPackets.channel.sendToServer(new CouplingCreationPacket(selectedCart, entity));
|
||||
selectedCart = null;
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.MinecartController;
|
||||
import com.simibubi.create.foundation.utility.Couple;
|
||||
import com.simibubi.create.foundation.utility.Iterate;
|
||||
import com.simibubi.create.foundation.utility.VecHelper;
|
||||
|
||||
import net.minecraft.block.AbstractRailBlock;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.MoverType;
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.state.properties.RailShape;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class CouplingPhysics {
|
||||
|
||||
public static void tick(World world) {
|
||||
CouplingHandler.forEachLoadedCoupling(world, c -> tickCoupling(world, c));
|
||||
}
|
||||
|
||||
public static void tickCoupling(World world, Couple<MinecartController> c) {
|
||||
Couple<AbstractMinecartEntity> carts = c.map(MinecartController::cart);
|
||||
float couplingLength = c.getFirst().getCouplingLength(true);
|
||||
softCollisionStep(world, carts, couplingLength);
|
||||
hardCollisionStep(world, carts, couplingLength);
|
||||
}
|
||||
|
||||
public static void hardCollisionStep(World world, Couple<AbstractMinecartEntity> carts, double couplingLength) {
|
||||
Couple<Vec3d> corrections = Couple.create(null, null);
|
||||
Couple<Float> maxSpeed = carts.map(AbstractMinecartEntity::getMaxCartSpeedOnRail);
|
||||
boolean firstLoop = true;
|
||||
for (boolean current : new boolean[] { true, false, true }) {
|
||||
AbstractMinecartEntity cart = carts.get(current);
|
||||
AbstractMinecartEntity otherCart = carts.get(!current);
|
||||
|
||||
float stress = (float) (couplingLength - cart.getPositionVec()
|
||||
.distanceTo(otherCart.getPositionVec()));
|
||||
|
||||
RailShape shape = null;
|
||||
BlockPos railPosition = cart.getCurrentRailPosition();
|
||||
BlockState railState = world.getBlockState(railPosition.up());
|
||||
|
||||
if (railState.getBlock() instanceof AbstractRailBlock) {
|
||||
AbstractRailBlock block = (AbstractRailBlock) railState.getBlock();
|
||||
shape = block.getRailDirection(railState, world, railPosition, cart);
|
||||
}
|
||||
|
||||
Vec3d correction = Vec3d.ZERO;
|
||||
Vec3d pos = cart.getPositionVec();
|
||||
Vec3d link = otherCart.getPositionVec()
|
||||
.subtract(pos);
|
||||
float correctionMagnitude = firstLoop ? -stress / 2f : -stress;
|
||||
correction = shape != null ? followLinkOnRail(link, pos, correctionMagnitude, shape).subtract(pos)
|
||||
: link.normalize()
|
||||
.scale(correctionMagnitude);
|
||||
|
||||
float maxResolveSpeed = 1.75f;
|
||||
correction = VecHelper.clamp(correction, Math.min(maxResolveSpeed, maxSpeed.get(current)));
|
||||
|
||||
if (corrections.get(current) == null)
|
||||
corrections.set(current, correction);
|
||||
|
||||
if (shape != null)
|
||||
MinecartSim2020.moveCartAlongTrack(cart, correction, railPosition, railState);
|
||||
else {
|
||||
cart.move(MoverType.SELF, correction);
|
||||
cart.setMotion(cart.getMotion()
|
||||
.scale(0.5f));
|
||||
}
|
||||
firstLoop = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void softCollisionStep(World world, Couple<AbstractMinecartEntity> carts, double couplingLength) {
|
||||
|
||||
Couple<Vec3d> positions = carts.map(Entity::getPositionVector);
|
||||
Couple<Float> maxSpeed = carts.map(AbstractMinecartEntity::getMaxCartSpeedOnRail);
|
||||
Couple<Boolean> canAddmotion = carts.map(MinecartSim2020::canAddMotion);
|
||||
|
||||
Couple<RailShape> shapes = carts.map(current -> {
|
||||
BlockPos railPosition = current.getCurrentRailPosition();
|
||||
BlockState railState = world.getBlockState(railPosition.up());
|
||||
if (!(railState.getBlock() instanceof AbstractRailBlock))
|
||||
return null;
|
||||
AbstractRailBlock block = (AbstractRailBlock) railState.getBlock();
|
||||
return block.getRailDirection(railState, world, railPosition, current);
|
||||
});
|
||||
|
||||
Couple<Vec3d> motions = carts.map(MinecartSim2020::predictMotionOf);
|
||||
Couple<Vec3d> nextPositions = positions.copy();
|
||||
nextPositions.replaceWithParams(Vec3d::add, motions);
|
||||
|
||||
float futureStress = (float) (couplingLength - nextPositions.getFirst()
|
||||
.distanceTo(nextPositions.getSecond()));
|
||||
if (Math.abs(futureStress) < 1 / 128f)
|
||||
return;
|
||||
|
||||
for (boolean current : Iterate.trueAndFalse) {
|
||||
Vec3d correction = Vec3d.ZERO;
|
||||
Vec3d pos = nextPositions.get(current);
|
||||
Vec3d link = nextPositions.get(!current)
|
||||
.subtract(pos);
|
||||
float correctionMagnitude = -futureStress / 2f;
|
||||
|
||||
if (canAddmotion.get(current) != canAddmotion.get(!current))
|
||||
correctionMagnitude = !canAddmotion.get(current) ? 0 : correctionMagnitude * 2;
|
||||
|
||||
RailShape shape = shapes.get(current);
|
||||
correction = shape != null ? followLinkOnRail(link, pos, correctionMagnitude, shape).subtract(pos)
|
||||
: link.normalize()
|
||||
.scale(correctionMagnitude);
|
||||
correction = VecHelper.clamp(correction, maxSpeed.get(current));
|
||||
motions.set(current, motions.get(current)
|
||||
.add(correction));
|
||||
}
|
||||
|
||||
motions.replaceWithParams(VecHelper::clamp, maxSpeed);
|
||||
carts.forEachWithParams(Entity::setMotion, motions);
|
||||
}
|
||||
|
||||
public static Vec3d followLinkOnRail(Vec3d link, Vec3d cart, float diffToReduce, RailShape shape) {
|
||||
Vec3d railAxis = MinecartSim2020.getRailVec(shape);
|
||||
double dotProduct = railAxis.dotProduct(link);
|
||||
if (Double.isNaN(dotProduct) || dotProduct == 0 || diffToReduce == 0)
|
||||
return cart;
|
||||
|
||||
Vec3d axis = railAxis.scale(-Math.signum(dotProduct));
|
||||
Vec3d center = cart.add(link);
|
||||
double radius = link.length() - diffToReduce;
|
||||
Vec3d intersectSphere = VecHelper.intersectSphere(cart, axis, center, radius);
|
||||
|
||||
// Cannot satisfy on current rail vector
|
||||
if (intersectSphere == null)
|
||||
return cart.add(VecHelper.project(link, axis));
|
||||
|
||||
return intersectSphere;
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,10 @@ import static net.minecraft.util.math.MathHelper.lerp;
|
|||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.vertex.IVertexBuilder;
|
||||
import com.simibubi.create.AllBlockPartials;
|
||||
import com.simibubi.create.CreateClient;
|
||||
import com.simibubi.create.content.contraptions.KineticDebugger;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.MinecartController;
|
||||
import com.simibubi.create.foundation.utility.ColorHelper;
|
||||
import com.simibubi.create.foundation.utility.Couple;
|
||||
import com.simibubi.create.foundation.utility.MatrixStacker;
|
||||
import com.simibubi.create.foundation.utility.SuperByteBuffer;
|
||||
|
@ -24,13 +28,20 @@ import net.minecraft.util.math.BlockPos;
|
|||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
|
||||
public class MinecartCouplingRenderer {
|
||||
public class CouplingRenderer {
|
||||
|
||||
public static void renderCoupling(MatrixStack ms, IRenderTypeBuffer buffer, MinecartCoupling coupling) {
|
||||
if (!coupling.areBothEndsPresent())
|
||||
return;
|
||||
public static void renderAll(MatrixStack ms, IRenderTypeBuffer buffer) {
|
||||
CouplingHandler.forEachLoadedCoupling(Minecraft.getInstance().world,
|
||||
c -> CouplingRenderer.renderCoupling(ms, buffer, c.map(MinecartController::cart)));
|
||||
}
|
||||
|
||||
public static void tickDebugModeRenders() {
|
||||
if (KineticDebugger.isActive())
|
||||
CouplingHandler.forEachLoadedCoupling(Minecraft.getInstance().world, CouplingRenderer::doDebugRender);
|
||||
}
|
||||
|
||||
public static void renderCoupling(MatrixStack ms, IRenderTypeBuffer buffer, Couple<AbstractMinecartEntity> carts) {
|
||||
ClientWorld world = Minecraft.getInstance().world;
|
||||
Couple<AbstractMinecartEntity> carts = coupling.asCouple();
|
||||
Couple<Integer> lightValues =
|
||||
carts.map(c -> WorldRenderer.getLightmapCoordinates(world, new BlockPos(c.getBoundingBox()
|
||||
.getCenter())));
|
||||
|
@ -194,4 +205,29 @@ public class MinecartCouplingRenderer {
|
|||
|
||||
}
|
||||
|
||||
public static void doDebugRender(Couple<MinecartController> c) {
|
||||
int yOffset = 1;
|
||||
MinecartController first = c.getFirst();
|
||||
AbstractMinecartEntity mainCart = first.cart();
|
||||
Vec3d mainCenter = mainCart.getPositionVec()
|
||||
.add(0, yOffset, 0);
|
||||
Vec3d connectedCenter = c.getSecond()
|
||||
.cart()
|
||||
.getPositionVec()
|
||||
.add(0, yOffset, 0);
|
||||
|
||||
int color = ColorHelper.mixColors(0xabf0e9, 0xee8572, (float) MathHelper
|
||||
.clamp(Math.abs(first.getCouplingLength(true) - connectedCenter.distanceTo(mainCenter)) * 8, 0, 1));
|
||||
|
||||
CreateClient.outliner.showLine(mainCart.getEntityId() + "", mainCenter, connectedCenter)
|
||||
.colored(color)
|
||||
.lineWidth(1 / 8f);
|
||||
|
||||
Vec3d point = mainCart.getPositionVec()
|
||||
.add(0, yOffset, 0);
|
||||
CreateClient.outliner.showLine(mainCart.getEntityId() + "_dot", point, point.add(0, 1 / 128f, 0))
|
||||
.colored(0xffffff)
|
||||
.lineWidth(1 / 4f);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.MinecartCouplingSerializer.CouplingData;
|
||||
import com.simibubi.create.foundation.utility.Couple;
|
||||
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class MinecartCoupling {
|
||||
|
||||
WeakReference<AbstractMinecartEntity> mainCart;
|
||||
WeakReference<AbstractMinecartEntity> connectedCart;
|
||||
double length;
|
||||
|
||||
private MinecartCoupling(AbstractMinecartEntity mainCart, AbstractMinecartEntity connectedCart, double length) {
|
||||
this.mainCart = new WeakReference<>(mainCart);
|
||||
this.connectedCart = new WeakReference<>(connectedCart);
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public static MinecartCoupling create(AbstractMinecartEntity mainCart, AbstractMinecartEntity connectedCart) {
|
||||
return new MinecartCoupling(mainCart, connectedCart,
|
||||
Math.max(2, getDistanceBetweenCarts(mainCart, connectedCart)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static List<MinecartCoupling> loadAllAttached(World world, AbstractMinecartEntity minecart) {
|
||||
List<MinecartCoupling> loaded = new ArrayList<>(2);
|
||||
List<AbstractMinecartEntity> otherCartsInRange =
|
||||
world.getEntitiesWithinAABB(AbstractMinecartEntity.class, minecart.getBoundingBox()
|
||||
.grow(MinecartCouplingHandler.maxDistance()), c -> c != minecart);
|
||||
|
||||
List<CouplingData> couplingData = MinecartCouplingSerializer.getCouplingData(minecart);
|
||||
Connections: for (CouplingData connection : couplingData) {
|
||||
boolean cartIsMain = connection.main;
|
||||
UUID cartCouplingUUID = connection.id;
|
||||
|
||||
for (AbstractMinecartEntity otherCart : otherCartsInRange) {
|
||||
List<CouplingData> otherCouplingData = MinecartCouplingSerializer.getCouplingData(otherCart);
|
||||
for (CouplingData otherConnection : otherCouplingData) {
|
||||
boolean otherIsMain = otherConnection.main;
|
||||
UUID otherCouplingUUID = otherConnection.id;
|
||||
|
||||
if (cartIsMain == otherIsMain)
|
||||
continue;
|
||||
if (!otherCouplingUUID.equals(cartCouplingUUID))
|
||||
continue;
|
||||
|
||||
AbstractMinecartEntity mainCart = cartIsMain ? minecart : otherCart;
|
||||
AbstractMinecartEntity connectedCart = cartIsMain ? otherCart : minecart;
|
||||
loaded.add(new MinecartCoupling(mainCart, connectedCart, connection.length));
|
||||
continue Connections;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public void writeToCarts() {
|
||||
MinecartCouplingSerializer.removeCouplingFromCart(mainCart.get(), this);
|
||||
MinecartCouplingSerializer.removeCouplingFromCart(connectedCart.get(), this);
|
||||
|
||||
MinecartCouplingSerializer.addCouplingToCart(mainCart.get(), this);
|
||||
MinecartCouplingSerializer.addCouplingToCart(connectedCart.get(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap main and connected cart for aliging couplings of a train.<br>
|
||||
* Changes are written to the carts' nbt data!<br>
|
||||
* Id of this coupling will change!
|
||||
*/
|
||||
public void flip() {
|
||||
MinecartCouplingSerializer.removeCouplingFromCart(mainCart.get(), this);
|
||||
MinecartCouplingSerializer.removeCouplingFromCart(connectedCart.get(), this);
|
||||
|
||||
WeakReference<AbstractMinecartEntity> oldMain = mainCart;
|
||||
mainCart = connectedCart;
|
||||
connectedCart = oldMain;
|
||||
|
||||
MinecartCouplingSerializer.addCouplingToCart(mainCart.get(), this);
|
||||
MinecartCouplingSerializer.addCouplingToCart(connectedCart.get(), this);
|
||||
}
|
||||
|
||||
public static double getDistanceBetweenCarts(AbstractMinecartEntity mainCart,
|
||||
AbstractMinecartEntity connectedCart) {
|
||||
return mainCart.getBoundingBox()
|
||||
.getCenter()
|
||||
.subtract(connectedCart.getBoundingBox()
|
||||
.getCenter())
|
||||
.length();
|
||||
}
|
||||
|
||||
public boolean areBothEndsPresent() {
|
||||
return (mainCart.get() != null && mainCart.get()
|
||||
.isAlive()) && (connectedCart.get() != null
|
||||
&& connectedCart.get()
|
||||
.isAlive());
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return mainCart.get()
|
||||
.getUniqueID();
|
||||
}
|
||||
|
||||
public Couple<AbstractMinecartEntity> asCouple() {
|
||||
return Couple.create(mainCart.get(), connectedCart.get());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,404 +0,0 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionEntity;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.MinecartCouplingSerializer.CouplingData;
|
||||
import com.simibubi.create.foundation.config.AllConfigs;
|
||||
import com.simibubi.create.foundation.networking.AllPackets;
|
||||
import com.simibubi.create.foundation.utility.Couple;
|
||||
import com.simibubi.create.foundation.utility.WorldAttached;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectLists;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.IRenderTypeBuffer;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
/*
|
||||
*
|
||||
* Couplings are a directional connection of two Minecart entities
|
||||
* - ID and Key is the UUID of the main cart
|
||||
* - They are immediately written to both minecarts' nbt tags upon creation.
|
||||
* {Main: true, Id: {L: ", M: "}, Length: 5}
|
||||
*
|
||||
* Trains are an ordered list of Couplings
|
||||
* - ID and Key is the UUID of the main coupling
|
||||
* - Every coupling is part of exactly one train, lonely couplings are still treated as such
|
||||
* - When two trains are merged, the couplings have to be re-oriented to always point towards the main coupling
|
||||
*
|
||||
* Loaded carts are queued to be dealt with on world tick,
|
||||
* so that the world functions are not accessed during the chunk deserialization
|
||||
*
|
||||
* Challenges:
|
||||
* - Minecarts can only be corrected by changing their motion or their position
|
||||
* - A Minecarts' motion vector does not represent its actual movement next tick
|
||||
* - There is no accessible simulation step (can be copied / at'd)
|
||||
* - It is not always known which cart is ahead/behind
|
||||
* - If both ends keep a contant momentum, the link stress is not necessarily satisfied
|
||||
* - Carts cannot be "dragged" directly towards resolving link stress;
|
||||
* It is not entirely predictable how motions outside of the rail vector get projected
|
||||
*
|
||||
*
|
||||
*
|
||||
* Day III, couplings still too unstable. Why is that? What causes the instability, is it the general approach or a specific issue
|
||||
* Explored strategies:
|
||||
*
|
||||
* Acellerate against violation diff -> perpetual motion, Jittering, bouncyness
|
||||
* Brake and correct towards violation diff -> quick loss of momentum
|
||||
* Move against diff -> de-rails carts on tricky paths
|
||||
*
|
||||
* Not yet explored: running an actual simulation step for the minecarts' movement.
|
||||
*
|
||||
* - satisfied link
|
||||
* -- stretched link
|
||||
* . shortened link
|
||||
* ? not visible in ctx
|
||||
* = cart
|
||||
* => moving cart
|
||||
*
|
||||
* Create algorithm to find a tick order which maximizes resolved stress
|
||||
*
|
||||
* => ? <= ? = - => (@t)
|
||||
* ^ tick here first
|
||||
*
|
||||
* cart[], motion[], position[]
|
||||
* Predict through simulation + motion, that without any intervention, this happens:
|
||||
*
|
||||
* => ? <= ? = -- => (@t+1)
|
||||
*
|
||||
* Decision: Accelerate trailing? (free motion)
|
||||
* Brake leading? (loss of momentum)
|
||||
* -> Both?
|
||||
*
|
||||
* Soft collisions can always be resolved. Just have to adjust motions accordingly.
|
||||
* Hard collisions should never be resolved by the soft/motion resolver, as it would generate or void momentum!
|
||||
*
|
||||
* Approach: Hard pass then soft pass. two iterations of the coupling list
|
||||
*
|
||||
* find starting point of hard resolve: the center of balance
|
||||
* i from left, j from right
|
||||
* compare and approach the exact center of the resolved chain.
|
||||
*
|
||||
* -3 -2-10v 0
|
||||
* 0-----0-0-0-0
|
||||
* 0--0--0--0--0
|
||||
* 2 1
|
||||
* 0---0-0---0---0--0--0-0-0-0-0
|
||||
* 0--0--0--0--0--0--0--0--0--0--0
|
||||
*
|
||||
* v
|
||||
* 0-0-0
|
||||
* 0--0--0
|
||||
*
|
||||
* v
|
||||
* 0---0---0---0
|
||||
* 0-0-0-0
|
||||
*
|
||||
* -1 0 -1 0
|
||||
* 0-0---0--0---0-0
|
||||
* 0--0--0--0--0--0
|
||||
*
|
||||
*
|
||||
*
|
||||
* iterate both ways from the center and resolve hard collisions.
|
||||
*
|
||||
* <HARD Resolve>
|
||||
* if coupling is NOT ok @t {
|
||||
* Something unpredictable happened.
|
||||
* Try to reach soft state asap. every lost cycle in hard will act strangely and render inconsistently
|
||||
* Using motion to hard resolve is probably a bad idea
|
||||
*
|
||||
* if cart on rail -> force move along rail away from collision
|
||||
* else straight-up push it
|
||||
* use simulation to test if movements are possible
|
||||
*
|
||||
* hard resolves are usually quite tiny. If they go beyond 1m then something really messed up
|
||||
* }
|
||||
*
|
||||
* if coupling could not be fixed {
|
||||
* clear all motion of the two carts (?)
|
||||
* A failed hard collision implies that the Minecarts cannot be forced together/apart, might aswell not make things worse
|
||||
* }
|
||||
* </HARD Resolve>
|
||||
*
|
||||
* Soft collisions only mess with motion values. It is still good to find a good order of iteration-
|
||||
* that way predictions of earlier couplings in the loop are still accurate
|
||||
*
|
||||
* =>>> - = - <= - =>>
|
||||
*
|
||||
* left to right
|
||||
* =>> - = - => - =>
|
||||
* right to left
|
||||
* =>> - => - = - =>
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* if now coupling is ok @t {
|
||||
* <Soft Resolve>
|
||||
* Run Prediction I
|
||||
* if (coupling is ok @t+1)
|
||||
* my job here is done; return
|
||||
*
|
||||
* get required force to resolve (front or back)
|
||||
* distribute equally over both carts
|
||||
*
|
||||
* Run Prediction II
|
||||
* if (coupling is ok @t+1*)
|
||||
* looks good; return
|
||||
*
|
||||
* re-distribute force to other cart
|
||||
* all collisions should be fixed at this point. return;
|
||||
* (in case of sudden changes/ bad predictions, the next cycle can handle the hard resolve)
|
||||
* </Soft Resolve>
|
||||
* }
|
||||
*
|
||||
*
|
||||
*
|
||||
* NEXT STEPS
|
||||
* 1. normalize diagonal rail vectors and debug all possible rail motion transfers. The required tools have to work properly
|
||||
* 2. implement a prediction step
|
||||
* 3. find a suitable hard collision resolver
|
||||
* 4. find a suitable soft collision order
|
||||
*
|
||||
*/
|
||||
|
||||
public class MinecartCouplingHandler {
|
||||
|
||||
static WorldAttached<Map<UUID, MinecartCoupling>> loadedCouplings = new WorldAttached<>(HashMap::new);
|
||||
static WorldAttached<Map<UUID, MinecartTrain>> loadedTrains = new WorldAttached<>(HashMap::new);
|
||||
|
||||
static WorldAttached<List<AbstractMinecartEntity>> queuedCarts =
|
||||
new WorldAttached<>(() -> ObjectLists.synchronize(new ObjectArrayList<>()));
|
||||
|
||||
public static void connectCarts(@Nullable PlayerEntity player, World world, int cartId1, int cartId2) {
|
||||
Entity entity1 = world.getEntityByID(cartId1);
|
||||
Entity entity2 = world.getEntityByID(cartId2);
|
||||
|
||||
if (!(entity1 instanceof AbstractMinecartEntity))
|
||||
return;
|
||||
if (!(entity2 instanceof AbstractMinecartEntity))
|
||||
return;
|
||||
if ((int) entity1.getPositionVec()
|
||||
.distanceTo(entity2.getPositionVec()) > maxDistance())
|
||||
return;
|
||||
|
||||
AbstractMinecartEntity cart1 = (AbstractMinecartEntity) entity1;
|
||||
AbstractMinecartEntity cart2 = (AbstractMinecartEntity) entity2;
|
||||
|
||||
if (alreadyCoupled(world, cart1, cart2))
|
||||
return;
|
||||
|
||||
addCoupling(world, MinecartCoupling.create(cart1, cart2), false);
|
||||
|
||||
if (world.isRemote)
|
||||
return;
|
||||
AllPackets.channel.send(PacketDistributor.TRACKING_ENTITY.with(() -> cart1),
|
||||
new MinecartCouplingSyncPacket(cart1, cart2));
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static void render(MatrixStack ms, IRenderTypeBuffer buffer) {
|
||||
ClientWorld world = Minecraft.getInstance().world;
|
||||
if (world == null)
|
||||
return;
|
||||
loadedCouplings.get(world)
|
||||
.values()
|
||||
.forEach(c -> MinecartCouplingRenderer.renderCoupling(ms, buffer, c));
|
||||
}
|
||||
|
||||
public static void tick(World world) {
|
||||
initQueuedCarts(world);
|
||||
removeUnloadedCouplings(world);
|
||||
loadedTrains.get(world)
|
||||
.values()
|
||||
.forEach(t -> t.tickCouplings(world));
|
||||
}
|
||||
|
||||
private static void initQueuedCarts(World world) {
|
||||
List<AbstractMinecartEntity> queued = queuedCarts.get(world);
|
||||
if (queued == null)
|
||||
return;
|
||||
for (AbstractMinecartEntity minecart : queued)
|
||||
MinecartCoupling.loadAllAttached(world, minecart)
|
||||
.forEach(c -> addCoupling(world, c, true));
|
||||
queued.clear();
|
||||
}
|
||||
|
||||
private static void removeUnloadedCouplings(World world) {
|
||||
List<UUID> toRemove = new ArrayList<>();
|
||||
Map<UUID, MinecartCoupling> couplings = loadedCouplings.get(world);
|
||||
if (couplings == null)
|
||||
return;
|
||||
for (Entry<UUID, MinecartCoupling> entry : couplings.entrySet())
|
||||
if (!entry.getValue()
|
||||
.areBothEndsPresent())
|
||||
toRemove.add(entry.getKey());
|
||||
couplings.keySet()
|
||||
.removeAll(toRemove);
|
||||
}
|
||||
|
||||
public static void handleAddedMinecart(Entity entity, World world) {
|
||||
if (!(entity instanceof AbstractMinecartEntity))
|
||||
return;
|
||||
if (world.isRemote)
|
||||
queueLoadedMinecartClient(entity, world);
|
||||
else
|
||||
queueLoadedMinecart(entity, world);
|
||||
}
|
||||
|
||||
public static void queueLoadedMinecartClient(Entity entity, World world) {
|
||||
AllPackets.channel.sendToServer(new PersistantDataPacketRequest(entity));
|
||||
}
|
||||
|
||||
public static void queueLoadedMinecart(Entity entity, World world) {
|
||||
AbstractMinecartEntity minecart = (AbstractMinecartEntity) entity;
|
||||
CompoundNBT nbt = minecart.getPersistentData();
|
||||
if (!nbt.contains("Couplings"))
|
||||
return;
|
||||
queuedCarts.get(world)
|
||||
.add(minecart);
|
||||
}
|
||||
|
||||
static int maxDistance() {
|
||||
return AllConfigs.SERVER.kinetics.maxCartCouplingLength.get();
|
||||
}
|
||||
|
||||
public static Pair<UUID, Boolean> getTrainIfComplete(World world, AbstractMinecartEntity minecart,
|
||||
@Nullable UUID ignore) {
|
||||
AbstractMinecartEntity current = minecart;
|
||||
UUID trainId = current.getUniqueID();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
List<CouplingData> couplingData = MinecartCouplingSerializer.getCouplingData(current);
|
||||
for (CouplingData data : couplingData) {
|
||||
if (data.main)
|
||||
continue;
|
||||
if (ignore != null && ignore.equals(data.id))
|
||||
continue;
|
||||
trainId = data.id;
|
||||
MinecartCoupling coupling = loadedCouplings.get(world)
|
||||
.get(trainId);
|
||||
|
||||
// Not fully loaded in
|
||||
if (coupling == null)
|
||||
return Pair.of(trainId, false);
|
||||
|
||||
current = coupling.mainCart.get();
|
||||
}
|
||||
}
|
||||
|
||||
// Complete
|
||||
return Pair.of(trainId, true);
|
||||
}
|
||||
|
||||
private static boolean alreadyCoupled(World world, AbstractMinecartEntity cart1, AbstractMinecartEntity cart2) {
|
||||
Pair<UUID, Boolean> trainOf = getTrainIfComplete(world, cart1, null);
|
||||
Pair<UUID, Boolean> trainOf2 = getTrainIfComplete(world, cart2, null);
|
||||
return trainOf.getRight() && trainOf2.getRight() && trainOf.getLeft()
|
||||
.equals(trainOf2.getLeft());
|
||||
}
|
||||
|
||||
private static void addCoupling(World world, MinecartCoupling coupling, boolean loadedFromChunk) {
|
||||
MinecartTrain train = new MinecartTrain(coupling);
|
||||
Pair<UUID, Boolean> trainIdOfMain = getTrainIfComplete(world, coupling.mainCart.get(), null);
|
||||
Pair<UUID, Boolean> trainIdOfConnected =
|
||||
getTrainIfComplete(world, coupling.connectedCart.get(), loadedFromChunk ? coupling.getId() : null);
|
||||
|
||||
// Something is not loaded
|
||||
if (!loadedFromChunk && !(trainIdOfMain.getValue() && trainIdOfConnected.getValue()))
|
||||
return;
|
||||
|
||||
// Coupling was already loaded in
|
||||
if (loadedFromChunk && loadedCouplings.get(world)
|
||||
.containsKey(coupling.getId()))
|
||||
return;
|
||||
|
||||
if (!world.isRemote) {
|
||||
Map<UUID, MinecartTrain> trains = loadedTrains.get(world);
|
||||
MinecartTrain trainOfMain = trains.get(trainIdOfMain.getKey());
|
||||
MinecartTrain trainOfConnected = trains.get(trainIdOfConnected.getKey());
|
||||
|
||||
// Connected cart is part of a train, merge it onto the newly created one
|
||||
if (trainOfConnected != null)
|
||||
trains.remove(trainIdOfConnected.getKey())
|
||||
.mergeOnto(world, train);
|
||||
|
||||
// Main cart is part of a train, merge the newly created one onto it
|
||||
boolean mainCartHasTrain = trainOfMain != null && trainIdOfMain.getKey()
|
||||
.equals(coupling.getId());
|
||||
if (trainOfMain != null) {
|
||||
if (mainCartHasTrain && !loadedFromChunk)
|
||||
flipTrain(world, trainOfMain);
|
||||
train.mergeOnto(world, trainOfMain);
|
||||
train = null;
|
||||
}
|
||||
|
||||
// ...add the new train otherwise
|
||||
if (train != null)
|
||||
trains.put(train.getId(), train);
|
||||
}
|
||||
|
||||
loadedCouplings.get(world)
|
||||
.put(coupling.getId(), coupling);
|
||||
if (!loadedFromChunk)
|
||||
coupling.writeToCarts();
|
||||
}
|
||||
|
||||
public static void flipTrain(World world, MinecartTrain train) {
|
||||
Map<UUID, MinecartTrain> map = loadedTrains.get(world);
|
||||
map.remove(train.getId());
|
||||
train.flip(world);
|
||||
map.put(train.getId(), train);
|
||||
}
|
||||
|
||||
public static MinecartCoupling getCoupling(World world, UUID id) {
|
||||
Map<UUID, MinecartCoupling> map = loadedCouplings.get(world);
|
||||
return map.get(id);
|
||||
}
|
||||
|
||||
public static void flipCoupling(World world, MinecartCoupling coupling) {
|
||||
Map<UUID, MinecartCoupling> map = loadedCouplings.get(world);
|
||||
map.remove(coupling.getId());
|
||||
|
||||
if (coupling.areBothEndsPresent()) {
|
||||
Couple<AbstractMinecartEntity> carts = coupling.asCouple();
|
||||
Couple<UUID> ids = carts.map(Entity::getUniqueID);
|
||||
carts.map(c -> c.isBeingRidden() ? c.getPassengers()
|
||||
.get(0) : null)
|
||||
.map(c -> c instanceof ContraptionEntity ? (ContraptionEntity) c : null)
|
||||
.forEachWithContext((contraption, current) -> {
|
||||
if (contraption == null || contraption.getCouplingId() == null)
|
||||
return;
|
||||
boolean switchTo = contraption.getCouplingId()
|
||||
.equals(ids.get(current)) ? !current : current;
|
||||
if (!carts.get(switchTo).getUniqueID().equals(contraption.getCoupledCart()))
|
||||
return;
|
||||
contraption.setCouplingId(ids.get(switchTo));
|
||||
contraption.setCoupledCart(ids.get(!switchTo));
|
||||
});
|
||||
}
|
||||
|
||||
coupling.flip();
|
||||
map.put(coupling.getId(), coupling);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import com.simibubi.create.AllItems;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.CapabilityMinecartController;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.MinecartController;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
|
@ -11,7 +13,10 @@ import net.minecraft.util.ActionResultType;
|
|||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent.EntityInteract;
|
||||
import net.minecraftforge.eventbus.api.EventPriority;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
|
||||
|
@ -23,8 +28,8 @@ public class MinecartCouplingItem extends Item {
|
|||
super(p_i48487_1_);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void couplingItemCanBeUsedOnMinecarts(PlayerInteractEvent.EntityInteract event) {
|
||||
@SubscribeEvent(priority = EventPriority.HIGH)
|
||||
public static void handleInteractionWithMinecart(PlayerInteractEvent.EntityInteract event) {
|
||||
Entity interacted = event.getTarget();
|
||||
if (!(interacted instanceof AbstractMinecartEntity))
|
||||
return;
|
||||
|
@ -32,23 +37,58 @@ public class MinecartCouplingItem extends Item {
|
|||
PlayerEntity player = event.getPlayer();
|
||||
if (player == null)
|
||||
return;
|
||||
ItemStack heldItem = player.getHeldItem(event.getHand());
|
||||
if (!AllItems.MINECART_COUPLING.isIn(heldItem))
|
||||
LazyOptional<MinecartController> capability =
|
||||
minecart.getCapability(CapabilityMinecartController.MINECART_CONTROLLER_CAPABILITY);
|
||||
if (!capability.isPresent())
|
||||
return;
|
||||
|
||||
World world = event.getWorld();
|
||||
if (MinecartCouplingSerializer.getCouplingData(minecart).size() < 2) {
|
||||
if (world != null && world.isRemote)
|
||||
DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> cartClicked(player, minecart));
|
||||
}
|
||||
|
||||
MinecartController controller = capability.orElse(null);
|
||||
|
||||
ItemStack heldItem = player.getHeldItem(event.getHand());
|
||||
if (AllItems.MINECART_COUPLING.isIn(heldItem)) {
|
||||
if (!onCouplingInteractOnMinecart(event, minecart, player, controller))
|
||||
return;
|
||||
} else if (AllItems.WRENCH.isIn(heldItem)) {
|
||||
if (!onWrenchInteractOnMinecart(event, minecart, player, controller))
|
||||
return;
|
||||
} else
|
||||
return;
|
||||
|
||||
event.setCanceled(true);
|
||||
event.setCancellationResult(ActionResultType.SUCCESS);
|
||||
}
|
||||
|
||||
protected static boolean onCouplingInteractOnMinecart(PlayerInteractEvent.EntityInteract event,
|
||||
AbstractMinecartEntity minecart, PlayerEntity player, MinecartController controller) {
|
||||
World world = event.getWorld();
|
||||
if (controller.isFullyCoupled()) {
|
||||
if (!world.isRemote)
|
||||
CouplingHandler.status(player, "two_couplings_max");
|
||||
return true;
|
||||
}
|
||||
if (world != null && world.isRemote)
|
||||
DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> cartClicked(player, minecart));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean onWrenchInteractOnMinecart(EntityInteract event, AbstractMinecartEntity minecart,
|
||||
PlayerEntity player, MinecartController controller) {
|
||||
int couplings = (controller.isConnectedToCoupling() ? 1 : 0) + (controller.isLeadingCoupling() ? 1 : 0);
|
||||
if (couplings == 0)
|
||||
return false;
|
||||
if (event.getWorld().isRemote)
|
||||
return true;
|
||||
|
||||
CouplingHandler.status(player, "removed");
|
||||
controller.decouple();
|
||||
if (!player.isCreative())
|
||||
player.inventory.placeItemBackInInventory(event.getWorld(),
|
||||
new ItemStack(AllItems.MINECART_COUPLING.get(), couplings));
|
||||
return true;
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
private static void cartClicked(PlayerEntity player, AbstractMinecartEntity interacted) {
|
||||
ClientMinecartCouplingHandler.onCartClicked(player, (AbstractMinecartEntity) interacted);
|
||||
CouplingHandlerClient.onCartClicked(player, (AbstractMinecartEntity) interacted);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.simibubi.create.foundation.utility.NBTHelper;
|
||||
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.ListNBT;
|
||||
import net.minecraft.nbt.NBTUtil;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
|
||||
public class MinecartCouplingSerializer {
|
||||
|
||||
public static void addCouplingToCart(AbstractMinecartEntity minecart, MinecartCoupling coupling) {
|
||||
CompoundNBT nbt = minecart.getPersistentData();
|
||||
ListNBT couplingList = nbt.getList("Couplings", NBT.TAG_COMPOUND);
|
||||
boolean main = coupling.mainCart.get() == minecart;
|
||||
couplingList.add(createCouplingTag(main, coupling));
|
||||
nbt.put("Couplings", couplingList);
|
||||
}
|
||||
|
||||
public static void removeCouplingFromCart(AbstractMinecartEntity minecart, MinecartCoupling coupling) {
|
||||
CompoundNBT nbt = minecart.getPersistentData();
|
||||
ListNBT couplingList = nbt.getList("Couplings", NBT.TAG_COMPOUND);
|
||||
couplingList.removeIf(inbt -> coupling.getId()
|
||||
.equals(NBTUtil.readUniqueId(((CompoundNBT) inbt).getCompound("Id"))));
|
||||
nbt.put("Couplings", couplingList);
|
||||
}
|
||||
|
||||
private static CompoundNBT createCouplingTag(boolean main, MinecartCoupling coupling) {
|
||||
CompoundNBT nbt = new CompoundNBT();
|
||||
nbt.put("Id", NBTUtil.writeUniqueId(coupling.getId()));
|
||||
nbt.putBoolean("Main", main);
|
||||
nbt.putDouble("Length", coupling.length);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
public static List<CouplingData> getCouplingData(AbstractMinecartEntity minecart) {
|
||||
List<CouplingData> list = new ArrayList<>();
|
||||
CompoundNBT nbt = minecart.getPersistentData();
|
||||
NBTHelper.iterateCompoundList(nbt.getList("Couplings", NBT.TAG_COMPOUND), c -> {
|
||||
boolean main = c.getBoolean("Main");
|
||||
UUID id = NBTUtil.readUniqueId(c.getCompound("Id"));
|
||||
double length = c.getDouble("Length");
|
||||
list.add(new CouplingData(main, id, length));
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
static class CouplingData {
|
||||
boolean main;
|
||||
UUID id;
|
||||
double length;
|
||||
|
||||
public CouplingData(boolean main, UUID id, double length) {
|
||||
this.main = main;
|
||||
this.id = id;
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.network.NetworkEvent.Context;
|
||||
|
||||
public class MinecartCouplingSyncPacket extends MinecartCouplingCreationPacket {
|
||||
|
||||
public MinecartCouplingSyncPacket(AbstractMinecartEntity cart1, AbstractMinecartEntity cart2) {
|
||||
super(cart1, cart2);
|
||||
}
|
||||
|
||||
public MinecartCouplingSyncPacket(PacketBuffer buffer) {
|
||||
super(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public void handle(Supplier<Context> context) {
|
||||
context.get()
|
||||
.enqueueWork(() -> MinecartCouplingHandler.connectCarts(null, Minecraft.getInstance().world, id1, id2));
|
||||
context.get()
|
||||
.setPacketHandled(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -22,28 +22,28 @@ import net.minecraft.util.math.MathHelper;
|
|||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.util.math.Vec3i;
|
||||
|
||||
/**
|
||||
* Useful methods for dealing with Minecarts
|
||||
*
|
||||
*/
|
||||
public class MinecartSim2020 {
|
||||
|
||||
private static final Map<RailShape, Pair<Vec3i, Vec3i>> MATRIX =
|
||||
Util.make(Maps.newEnumMap(RailShape.class), (p_226574_0_) -> {
|
||||
Vec3i vec3i = Direction.WEST.getDirectionVec();
|
||||
Vec3i vec3i1 = Direction.EAST.getDirectionVec();
|
||||
Vec3i vec3i2 = Direction.NORTH.getDirectionVec();
|
||||
Vec3i vec3i3 = Direction.SOUTH.getDirectionVec();
|
||||
Vec3i vec3i4 = vec3i.down();
|
||||
Vec3i vec3i5 = vec3i1.down();
|
||||
Vec3i vec3i6 = vec3i2.down();
|
||||
Vec3i vec3i7 = vec3i3.down();
|
||||
p_226574_0_.put(RailShape.NORTH_SOUTH, Pair.of(vec3i2, vec3i3));
|
||||
p_226574_0_.put(RailShape.EAST_WEST, Pair.of(vec3i, vec3i1));
|
||||
p_226574_0_.put(RailShape.ASCENDING_EAST, Pair.of(vec3i4, vec3i1));
|
||||
p_226574_0_.put(RailShape.ASCENDING_WEST, Pair.of(vec3i, vec3i5));
|
||||
p_226574_0_.put(RailShape.ASCENDING_NORTH, Pair.of(vec3i2, vec3i7));
|
||||
p_226574_0_.put(RailShape.ASCENDING_SOUTH, Pair.of(vec3i6, vec3i3));
|
||||
p_226574_0_.put(RailShape.SOUTH_EAST, Pair.of(vec3i3, vec3i1));
|
||||
p_226574_0_.put(RailShape.SOUTH_WEST, Pair.of(vec3i3, vec3i));
|
||||
p_226574_0_.put(RailShape.NORTH_WEST, Pair.of(vec3i2, vec3i));
|
||||
p_226574_0_.put(RailShape.NORTH_EAST, Pair.of(vec3i2, vec3i1));
|
||||
Util.make(Maps.newEnumMap(RailShape.class), (map) -> {
|
||||
Vec3i west = Direction.WEST.getDirectionVec();
|
||||
Vec3i east = Direction.EAST.getDirectionVec();
|
||||
Vec3i north = Direction.NORTH.getDirectionVec();
|
||||
Vec3i south = Direction.SOUTH.getDirectionVec();
|
||||
map.put(RailShape.NORTH_SOUTH, Pair.of(north, south));
|
||||
map.put(RailShape.EAST_WEST, Pair.of(west, east));
|
||||
map.put(RailShape.ASCENDING_EAST, Pair.of(west.down(), east));
|
||||
map.put(RailShape.ASCENDING_WEST, Pair.of(west, east.down()));
|
||||
map.put(RailShape.ASCENDING_NORTH, Pair.of(north, south.down()));
|
||||
map.put(RailShape.ASCENDING_SOUTH, Pair.of(north.down(), south));
|
||||
map.put(RailShape.SOUTH_EAST, Pair.of(south, east));
|
||||
map.put(RailShape.SOUTH_WEST, Pair.of(south, west));
|
||||
map.put(RailShape.NORTH_WEST, Pair.of(north, west));
|
||||
map.put(RailShape.NORTH_EAST, Pair.of(north, east));
|
||||
});
|
||||
|
||||
public static Vec3d predictMotionOf(AbstractMinecartEntity cart) {
|
||||
|
@ -51,7 +51,7 @@ public class MinecartSim2020 {
|
|||
return cart.getPositionVec()
|
||||
.subtract(cart.lastTickPosX, cart.lastTickPosY, cart.lastTickPosZ);
|
||||
}
|
||||
return cart.getMotion().scale(1.03f);
|
||||
return cart.getMotion().scale(1f);
|
||||
// if (cart instanceof ContainerMinecartEntity) {
|
||||
// ContainerMinecartEntity containerCart = (ContainerMinecartEntity) cart;
|
||||
// float f = 0.98F;
|
||||
|
@ -219,4 +219,25 @@ public class MinecartSim2020 {
|
|||
}
|
||||
}
|
||||
|
||||
public static Vec3d getRailVec(RailShape shape) {
|
||||
switch (shape) {
|
||||
case ASCENDING_NORTH:
|
||||
case ASCENDING_SOUTH:
|
||||
case NORTH_SOUTH:
|
||||
return new Vec3d(0, 0, 1);
|
||||
case ASCENDING_EAST:
|
||||
case ASCENDING_WEST:
|
||||
case EAST_WEST:
|
||||
return new Vec3d(1, 0, 0);
|
||||
case NORTH_EAST:
|
||||
case SOUTH_WEST:
|
||||
return new Vec3d(1, 0, 1).normalize();
|
||||
case NORTH_WEST:
|
||||
case SOUTH_EAST:
|
||||
return new Vec3d(1, 0, -1).normalize();
|
||||
default:
|
||||
return new Vec3d(0, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,385 +0,0 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.simibubi.create.CreateClient;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionEntity;
|
||||
import com.simibubi.create.foundation.utility.ColorHelper;
|
||||
import com.simibubi.create.foundation.utility.Couple;
|
||||
import com.simibubi.create.foundation.utility.Iterate;
|
||||
import com.simibubi.create.foundation.utility.VecHelper;
|
||||
|
||||
import net.minecraft.block.AbstractRailBlock;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.MoverType;
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.state.properties.RailShape;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class MinecartTrain {
|
||||
|
||||
protected ArrayList<MinecartCoupling> couplings;
|
||||
protected double momentum;
|
||||
boolean complete;
|
||||
|
||||
public MinecartTrain(MinecartCoupling coupling) {
|
||||
couplings = new ArrayList<>();
|
||||
couplings.add(coupling);
|
||||
tickOrder = 1; // start at most stressed
|
||||
}
|
||||
|
||||
public void flip(World world) {
|
||||
Collections.reverse(couplings);
|
||||
couplings.forEach(c -> MinecartCouplingHandler.flipCoupling(world, c));
|
||||
}
|
||||
|
||||
public void mergeOnto(World world, MinecartTrain other) {
|
||||
AbstractMinecartEntity trailingOfOther = other.couplings.get(other.couplings.size() - 1).connectedCart.get();
|
||||
AbstractMinecartEntity leadingOfThis = couplings.get(0).mainCart.get();
|
||||
|
||||
if (trailingOfOther != leadingOfThis)
|
||||
flip(world);
|
||||
|
||||
other.couplings.addAll(couplings);
|
||||
}
|
||||
|
||||
public int tickOrder;
|
||||
|
||||
public void tickCouplings(World world) {
|
||||
|
||||
// SOFT collision - modify motion of carts with stressed links @t+1
|
||||
double sharedMotion = 0;
|
||||
int participants = 0;
|
||||
boolean stall = false;
|
||||
|
||||
for (int i = 0; i < couplings.size(); i++) {
|
||||
MinecartCoupling minecartCoupling = couplings.get(i);
|
||||
boolean last = i + 1 == couplings.size();
|
||||
if (!minecartCoupling.areBothEndsPresent())
|
||||
continue;
|
||||
participants++;
|
||||
sharedMotion += minecartCoupling.mainCart.get()
|
||||
.getMotion()
|
||||
.length();
|
||||
|
||||
List<Entity> passengers = minecartCoupling.mainCart.get().getPassengers();
|
||||
if (!passengers.isEmpty() && passengers.get(0) instanceof ContraptionEntity)
|
||||
if (((ContraptionEntity) passengers.get(0)).isStalled()) {
|
||||
stall = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (last) {
|
||||
participants++;
|
||||
sharedMotion += minecartCoupling.connectedCart.get()
|
||||
.getMotion()
|
||||
.length();
|
||||
}
|
||||
}
|
||||
|
||||
if (participants == 0)
|
||||
return;
|
||||
|
||||
sharedMotion /= participants;
|
||||
|
||||
/*
|
||||
* Tick order testing: 0: start from motion outlier 1: start at most stressed
|
||||
* coupling 2: start at front 3: start at back
|
||||
*/
|
||||
|
||||
if (tickOrder == 0) {
|
||||
// Iterate starting from biggest outlier in motion
|
||||
double maxDiff = 0;
|
||||
int argMax = 0;
|
||||
for (int i = 0; i < couplings.size(); i++) {
|
||||
MinecartCoupling minecartCoupling = couplings.get(i);
|
||||
boolean last = i + 1 == couplings.size();
|
||||
if (!minecartCoupling.areBothEndsPresent())
|
||||
continue;
|
||||
|
||||
double diff = Math.abs(minecartCoupling.mainCart.get()
|
||||
.getMotion()
|
||||
.length() - sharedMotion);
|
||||
if (diff > maxDiff) {
|
||||
maxDiff = diff;
|
||||
argMax = i;
|
||||
}
|
||||
|
||||
if (last) {
|
||||
diff = Math.abs(minecartCoupling.connectedCart.get()
|
||||
.getMotion()
|
||||
.length() - sharedMotion);
|
||||
if (diff > maxDiff) {
|
||||
maxDiff = diff;
|
||||
argMax = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (boolean hard : Iterate.trueAndFalse) {
|
||||
for (int i = argMax - 1; i >= 0; i--)
|
||||
if (couplings.get(i)
|
||||
.areBothEndsPresent())
|
||||
collisionStep(world, couplings.get(i)
|
||||
.asCouple()
|
||||
.swap(), couplings.get(i).length, hard);
|
||||
for (int i = argMax; i < couplings.size(); i++)
|
||||
if (couplings.get(i)
|
||||
.areBothEndsPresent())
|
||||
collisionStep(world, couplings.get(i)
|
||||
.asCouple()
|
||||
.swap(), couplings.get(i).length, hard);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (tickOrder == 1) {
|
||||
// Iterate starting from biggest stress
|
||||
double maxStress = 0;
|
||||
int argMax = 0;
|
||||
for (int i = 0; i < couplings.size(); i++) {
|
||||
MinecartCoupling minecartCoupling = couplings.get(i);
|
||||
if (!minecartCoupling.areBothEndsPresent())
|
||||
continue;
|
||||
|
||||
if (stall) {
|
||||
minecartCoupling.asCouple().forEach(ame -> ame.setMotion(Vec3d.ZERO));
|
||||
continue;
|
||||
}
|
||||
|
||||
double stress = getStressOfCoupling(minecartCoupling);
|
||||
if (stress > maxStress) {
|
||||
maxStress = stress;
|
||||
argMax = i;
|
||||
}
|
||||
}
|
||||
|
||||
for (boolean hard : Iterate.trueAndFalse) {
|
||||
for (int i = argMax - 1; i >= 0; i--)
|
||||
if (couplings.get(i)
|
||||
.areBothEndsPresent())
|
||||
collisionStep(world, couplings.get(i)
|
||||
.asCouple()
|
||||
.swap(), couplings.get(i).length, hard);
|
||||
for (int i = argMax; i < couplings.size(); i++)
|
||||
if (couplings.get(i)
|
||||
.areBothEndsPresent())
|
||||
collisionStep(world, couplings.get(i)
|
||||
.asCouple()
|
||||
.swap(), couplings.get(i).length, hard);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (momentum >= 0 == (tickOrder == 2)) {
|
||||
// Iterate front to back
|
||||
for (boolean hard : Iterate.trueAndFalse)
|
||||
for (int i = 0; i < couplings.size(); i++)
|
||||
if (couplings.get(i)
|
||||
.areBothEndsPresent())
|
||||
collisionStep(world, couplings.get(i)
|
||||
.asCouple()
|
||||
.swap(), couplings.get(i).length, hard);
|
||||
|
||||
} else {
|
||||
// Iterate back to front
|
||||
for (boolean hard : Iterate.trueAndFalse)
|
||||
for (int i = couplings.size() - 1; i >= 0; i--)
|
||||
if (couplings.get(i)
|
||||
.areBothEndsPresent())
|
||||
collisionStep(world, couplings.get(i)
|
||||
.asCouple()
|
||||
.swap(), couplings.get(i).length, hard);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private float getStressOfCoupling(MinecartCoupling coupling) {
|
||||
if (!coupling.areBothEndsPresent())
|
||||
return 0;
|
||||
return (float) (coupling.length - coupling.mainCart.get()
|
||||
.getPositionVec()
|
||||
.distanceTo(coupling.connectedCart.get()
|
||||
.getPositionVec()));
|
||||
}
|
||||
|
||||
public void collisionStep(World world, Couple<AbstractMinecartEntity> carts, double couplingLength, boolean hard) {
|
||||
if (hard)
|
||||
hardCollisionStep(world, carts, couplingLength);
|
||||
else
|
||||
softCollisionStep(world, carts, couplingLength);
|
||||
}
|
||||
|
||||
public void hardCollisionStep(World world, Couple<AbstractMinecartEntity> carts, double couplingLength) {
|
||||
Couple<Vec3d> corrections = Couple.create(null, null);
|
||||
Couple<Float> maxSpeed = carts.map(AbstractMinecartEntity::getMaxCartSpeedOnRail);
|
||||
boolean firstLoop = true;
|
||||
for (boolean current : new boolean[] { true, false, true }) {
|
||||
AbstractMinecartEntity cart = carts.get(current);
|
||||
AbstractMinecartEntity otherCart = carts.get(!current);
|
||||
|
||||
float stress = (float) (couplingLength - cart.getPositionVec()
|
||||
.distanceTo(otherCart.getPositionVec()));
|
||||
|
||||
RailShape shape = null;
|
||||
BlockPos railPosition = cart.getCurrentRailPosition();
|
||||
BlockState railState = world.getBlockState(railPosition.up());
|
||||
|
||||
if (railState.getBlock() instanceof AbstractRailBlock) {
|
||||
AbstractRailBlock block = (AbstractRailBlock) railState.getBlock();
|
||||
shape = block.getRailDirection(railState, world, railPosition, cart);
|
||||
}
|
||||
|
||||
Vec3d correction = Vec3d.ZERO;
|
||||
Vec3d pos = cart.getPositionVec();
|
||||
Vec3d link = otherCart.getPositionVec()
|
||||
.subtract(pos);
|
||||
float correctionMagnitude = firstLoop ? -stress / 2f : -stress;
|
||||
correction = shape != null ? followLinkOnRail(link, pos, correctionMagnitude, shape).subtract(pos)
|
||||
: link.normalize()
|
||||
.scale(correctionMagnitude);
|
||||
|
||||
float maxResolveSpeed = 1.75f;
|
||||
correction = VecHelper.clamp(correction, Math.min(maxResolveSpeed, maxSpeed.get(current)));
|
||||
|
||||
if (corrections.get(current) == null)
|
||||
corrections.set(current, correction);
|
||||
|
||||
if (shape != null)
|
||||
MinecartSim2020.moveCartAlongTrack(cart, correction, railPosition, railState);
|
||||
else {
|
||||
cart.move(MoverType.SELF, correction);
|
||||
cart.setMotion(cart.getMotion()
|
||||
.scale(0.5f));
|
||||
}
|
||||
firstLoop = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void softCollisionStep(World world, Couple<AbstractMinecartEntity> carts, double couplingLength) {
|
||||
|
||||
Couple<Vec3d> positions = carts.map(Entity::getPositionVector);
|
||||
Couple<Float> maxSpeed = carts.map(AbstractMinecartEntity::getMaxCartSpeedOnRail);
|
||||
Couple<Boolean> canAddmotion = carts.map(MinecartSim2020::canAddMotion);
|
||||
|
||||
Couple<RailShape> shapes = carts.map(current -> {
|
||||
BlockPos railPosition = current.getCurrentRailPosition();
|
||||
BlockState railState = world.getBlockState(railPosition.up());
|
||||
if (!(railState.getBlock() instanceof AbstractRailBlock))
|
||||
return null;
|
||||
AbstractRailBlock block = (AbstractRailBlock) railState.getBlock();
|
||||
return block.getRailDirection(railState, world, railPosition, current);
|
||||
});
|
||||
|
||||
Couple<Vec3d> motions = carts.map(MinecartSim2020::predictMotionOf);
|
||||
Couple<Vec3d> nextPositions = positions.copy();
|
||||
nextPositions.replaceWithParams(Vec3d::add, motions);
|
||||
|
||||
float futureStress = (float) (couplingLength - nextPositions.getFirst()
|
||||
.distanceTo(nextPositions.getSecond()));
|
||||
if (Math.abs(futureStress) < 1 / 128f)
|
||||
return;
|
||||
|
||||
for (boolean current : Iterate.trueAndFalse) {
|
||||
Vec3d correction = Vec3d.ZERO;
|
||||
Vec3d pos = nextPositions.get(current);
|
||||
Vec3d link = nextPositions.get(!current)
|
||||
.subtract(pos);
|
||||
float correctionMagnitude = -futureStress / 2f;
|
||||
|
||||
if (canAddmotion.get(current) != canAddmotion.get(!current))
|
||||
correctionMagnitude = !canAddmotion.get(current) ? 0 : correctionMagnitude * 2;
|
||||
|
||||
RailShape shape = shapes.get(current);
|
||||
correction = shape != null ? followLinkOnRail(link, pos, correctionMagnitude, shape).subtract(pos)
|
||||
: link.normalize()
|
||||
.scale(correctionMagnitude);
|
||||
correction = VecHelper.clamp(correction, maxSpeed.get(current));
|
||||
motions.set(current, motions.get(current)
|
||||
.add(correction));
|
||||
}
|
||||
|
||||
motions.replaceWithParams(VecHelper::clamp, maxSpeed);
|
||||
carts.forEachWithParams(Entity::setMotion, motions);
|
||||
}
|
||||
|
||||
public static Vec3d followLinkOnRail(Vec3d link, Vec3d cart, float diffToReduce, RailShape shape) {
|
||||
Vec3d railAxis = getRailVec(shape);
|
||||
double dotProduct = railAxis.dotProduct(link);
|
||||
if (Double.isNaN(dotProduct) || dotProduct == 0 || diffToReduce == 0)
|
||||
return cart;
|
||||
|
||||
Vec3d axis = railAxis.scale(-Math.signum(dotProduct));
|
||||
Vec3d center = cart.add(link);
|
||||
double radius = link.length() - diffToReduce;
|
||||
Vec3d intersectSphere = VecHelper.intersectSphere(cart, axis, center, radius);
|
||||
|
||||
// Cannot satisfy on current rail vector
|
||||
if (intersectSphere == null)
|
||||
return cart.add(VecHelper.project(link, axis));
|
||||
|
||||
return intersectSphere;
|
||||
}
|
||||
|
||||
private static Vec3d getRailVec(RailShape shape) {
|
||||
switch (shape) {
|
||||
case ASCENDING_NORTH:
|
||||
case ASCENDING_SOUTH:
|
||||
case NORTH_SOUTH:
|
||||
return new Vec3d(0, 0, 1);
|
||||
case ASCENDING_EAST:
|
||||
case ASCENDING_WEST:
|
||||
case EAST_WEST:
|
||||
return new Vec3d(1, 0, 0);
|
||||
case NORTH_EAST:
|
||||
case SOUTH_WEST:
|
||||
return new Vec3d(1, 0, 1).normalize();
|
||||
case NORTH_WEST:
|
||||
case SOUTH_EAST:
|
||||
return new Vec3d(1, 0, -1).normalize();
|
||||
default:
|
||||
return new Vec3d(0, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return couplings.get(0)
|
||||
.getId();
|
||||
}
|
||||
|
||||
public static void doDebugRender(World world, MinecartCoupling coupling, int index) {
|
||||
AbstractMinecartEntity mainCart = coupling.mainCart.get();
|
||||
AbstractMinecartEntity connectedCart = coupling.connectedCart.get();
|
||||
|
||||
if (!coupling.areBothEndsPresent())
|
||||
return;
|
||||
|
||||
int yOffset = 1;
|
||||
Vec3d mainCenter = mainCart.getPositionVec()
|
||||
.add(0, yOffset, 0);
|
||||
Vec3d connectedCenter = connectedCart.getPositionVec()
|
||||
.add(0, yOffset, 0);
|
||||
|
||||
int color = ColorHelper.mixColors(0xabf0e9, 0xee8572,
|
||||
(float) MathHelper.clamp(Math.abs(coupling.length - connectedCenter.distanceTo(mainCenter)) * 8, 0, 1));
|
||||
|
||||
CreateClient.outliner.showLine(coupling + "" + index, mainCenter, connectedCenter)
|
||||
.colored(color)
|
||||
.lineWidth(1 / 8f);
|
||||
|
||||
Vec3d point = mainCart.getPositionVec()
|
||||
.add(0, yOffset, 0);
|
||||
CreateClient.outliner.showLine(coupling.getId() + "" + index, point, point.add(0, 1 / 128f, 0))
|
||||
.colored(0xffffff)
|
||||
.lineWidth(1 / 4f);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.network.NetworkEvent.Context;
|
||||
|
||||
public class PersistantDataPacket extends PersistantDataPacketRequest {
|
||||
|
||||
CompoundNBT persistentData;
|
||||
|
||||
public PersistantDataPacket(Entity entity) {
|
||||
super(entity);
|
||||
persistentData = entity.getPersistentData();
|
||||
}
|
||||
|
||||
public PersistantDataPacket(PacketBuffer buffer) {
|
||||
super(buffer);
|
||||
persistentData = buffer.readCompoundTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketBuffer buffer) {
|
||||
super.write(buffer);
|
||||
buffer.writeCompoundTag(persistentData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public void handle(Supplier<Context> context) {
|
||||
context.get()
|
||||
.enqueueWork(() -> {
|
||||
ClientWorld world = Minecraft.getInstance().world;
|
||||
if (world == null)
|
||||
return;
|
||||
Entity entityByID = world.getEntityByID(entityId);
|
||||
if (entityByID == null)
|
||||
return;
|
||||
CompoundNBT persistentData = entityByID.getPersistentData();
|
||||
persistentData.merge(this.persistentData);
|
||||
MinecartCouplingHandler.queueLoadedMinecart(entityByID, world);
|
||||
});
|
||||
context.get()
|
||||
.setPacketHandled(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.simibubi.create.foundation.networking.AllPackets;
|
||||
import com.simibubi.create.foundation.networking.SimplePacketBase;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent.Context;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
public class PersistantDataPacketRequest extends SimplePacketBase {
|
||||
|
||||
int entityId;
|
||||
|
||||
public PersistantDataPacketRequest(Entity entity) {
|
||||
entityId = entity.getEntityId();
|
||||
}
|
||||
|
||||
public PersistantDataPacketRequest(PacketBuffer buffer) {
|
||||
entityId = buffer.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketBuffer buffer) {
|
||||
buffer.writeInt(entityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Supplier<Context> context) {
|
||||
context.get()
|
||||
.enqueueWork(() -> {
|
||||
ServerPlayerEntity sender = context.get()
|
||||
.getSender();
|
||||
if (sender == null || sender.world == null)
|
||||
return;
|
||||
Entity entityByID = sender.world.getEntityByID(entityId);
|
||||
if (entityByID == null)
|
||||
return;
|
||||
AllPackets.channel.send(PacketDistributor.PLAYER.with(() -> sender),
|
||||
new PersistantDataPacket(entityByID));
|
||||
});
|
||||
context.get()
|
||||
.setPacketHandled(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train.capability;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.simibubi.create.AllItems;
|
||||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingHandler;
|
||||
import com.simibubi.create.foundation.utility.Iterate;
|
||||
import com.simibubi.create.foundation.utility.WorldAttached;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectLists;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.item.ItemEntity;
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.INBT;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityInject;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
|
||||
public class CapabilityMinecartController implements ICapabilitySerializable<CompoundNBT> {
|
||||
|
||||
/* Global map of loaded carts */
|
||||
|
||||
public static WorldAttached<Map<UUID, MinecartController>> loadedMinecartsByUUID;
|
||||
public static WorldAttached<Set<UUID>> loadedMinecartsWithCoupling;
|
||||
static WorldAttached<List<AbstractMinecartEntity>> queuedAdditions;
|
||||
static WorldAttached<List<UUID>> queuedUnloads;
|
||||
|
||||
static {
|
||||
loadedMinecartsByUUID = new WorldAttached<>(HashMap::new);
|
||||
loadedMinecartsWithCoupling = new WorldAttached<>(HashSet::new);
|
||||
queuedAdditions = new WorldAttached<>(() -> ObjectLists.synchronize(new ObjectArrayList<>()));
|
||||
queuedUnloads = new WorldAttached<>(() -> ObjectLists.synchronize(new ObjectArrayList<>()));
|
||||
}
|
||||
|
||||
public static void tick(World world) {
|
||||
List<UUID> toRemove = new ArrayList<>();
|
||||
Map<UUID, MinecartController> carts = loadedMinecartsByUUID.get(world);
|
||||
List<AbstractMinecartEntity> queued = queuedAdditions.get(world);
|
||||
List<UUID> queuedRemovals = queuedUnloads.get(world);
|
||||
Set<UUID> cartsWithCoupling = loadedMinecartsWithCoupling.get(world);
|
||||
Set<UUID> keySet = carts.keySet();
|
||||
|
||||
keySet.removeAll(queuedRemovals);
|
||||
cartsWithCoupling.removeAll(queuedRemovals);
|
||||
|
||||
for (AbstractMinecartEntity cart : queued) {
|
||||
UUID uniqueID = cart.getUniqueID();
|
||||
cartsWithCoupling.remove(uniqueID);
|
||||
LazyOptional<MinecartController> capability = cart.getCapability(MINECART_CONTROLLER_CAPABILITY);
|
||||
MinecartController controller = capability.orElse(null);
|
||||
capability.addListener(cap -> onCartRemoved(world, cart));
|
||||
carts.put(uniqueID, controller);
|
||||
capability.ifPresent(mc -> {
|
||||
if (mc.isLeadingCoupling())
|
||||
cartsWithCoupling.add(uniqueID);
|
||||
});
|
||||
if (!world.isRemote && controller != null)
|
||||
controller.sendData();
|
||||
}
|
||||
|
||||
queuedRemovals.clear();
|
||||
queued.clear();
|
||||
|
||||
for (Entry<UUID, MinecartController> entry : carts.entrySet()) {
|
||||
MinecartController controller = entry.getValue();
|
||||
if (controller != null) {
|
||||
if (controller.isPresent()) {
|
||||
controller.tick();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
toRemove.add(entry.getKey());
|
||||
}
|
||||
|
||||
cartsWithCoupling.removeAll(toRemove);
|
||||
keySet.removeAll(toRemove);
|
||||
}
|
||||
|
||||
public static void onChunkUnloaded(ChunkEvent.Unload event) {
|
||||
ChunkPos chunkPos = event.getChunk()
|
||||
.getPos();
|
||||
Map<UUID, MinecartController> carts = loadedMinecartsByUUID.get(event.getWorld());
|
||||
for (MinecartController minecartController : carts.values()) {
|
||||
if (!minecartController.isPresent())
|
||||
continue;
|
||||
AbstractMinecartEntity cart = minecartController.cart();
|
||||
if (cart.chunkCoordX == chunkPos.x && cart.chunkCoordZ == chunkPos.z)
|
||||
queuedUnloads.get(event.getWorld())
|
||||
.add(cart.getUniqueID());
|
||||
}
|
||||
}
|
||||
|
||||
protected static void onCartRemoved(World world, AbstractMinecartEntity entity) {
|
||||
Map<UUID, MinecartController> carts = loadedMinecartsByUUID.get(world);
|
||||
List<UUID> unloads = queuedUnloads.get(world);
|
||||
UUID uniqueID = entity.getUniqueID();
|
||||
if (!carts.containsKey(uniqueID) || unloads.contains(uniqueID))
|
||||
return;
|
||||
if (world.isRemote)
|
||||
return;
|
||||
handleKilledMinecart(world, carts.get(uniqueID), entity.getPositionVec());
|
||||
}
|
||||
|
||||
protected static void handleKilledMinecart(World world, MinecartController controller, Vec3d removedPos) {
|
||||
if (controller == null)
|
||||
return;
|
||||
for (boolean forward : Iterate.trueAndFalse) {
|
||||
MinecartController next = CouplingHandler.getNextInCouplingChain(world, controller, forward);
|
||||
if (next == null || next == MinecartController.EMPTY)
|
||||
continue;
|
||||
|
||||
next.removeConnection(!forward);
|
||||
AbstractMinecartEntity cart = next.cart();
|
||||
if (cart == null)
|
||||
continue;
|
||||
|
||||
Vec3d itemPos = cart.getPositionVec()
|
||||
.add(removedPos)
|
||||
.scale(.5f);
|
||||
ItemEntity itemEntity =
|
||||
new ItemEntity(world, itemPos.x, itemPos.y, itemPos.z, AllItems.MINECART_COUPLING.asStack());
|
||||
itemEntity.setDefaultPickupDelay();
|
||||
world.addEntity(itemEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static MinecartController getIfPresent(World world, UUID cartId) {
|
||||
Map<UUID, MinecartController> carts = loadedMinecartsByUUID.get(world);
|
||||
if (carts == null)
|
||||
return null;
|
||||
if (!carts.containsKey(cartId))
|
||||
return null;
|
||||
return carts.get(cartId);
|
||||
}
|
||||
|
||||
/* Capability management */
|
||||
|
||||
@CapabilityInject(MinecartController.class)
|
||||
public static Capability<MinecartController> MINECART_CONTROLLER_CAPABILITY = null;
|
||||
|
||||
public static void attach(AttachCapabilitiesEvent<Entity> event) {
|
||||
Entity entity = event.getObject();
|
||||
if (!(entity instanceof AbstractMinecartEntity))
|
||||
return;
|
||||
event.addCapability(Create.asResource("minecart_controller"),
|
||||
new CapabilityMinecartController((AbstractMinecartEntity) entity));
|
||||
queuedAdditions.get(entity.getEntityWorld())
|
||||
.add((AbstractMinecartEntity) entity);
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
CapabilityManager.INSTANCE.register(MinecartController.class, new Capability.IStorage<MinecartController>() {
|
||||
|
||||
@Override
|
||||
public INBT writeNBT(Capability<MinecartController> capability, MinecartController instance,
|
||||
Direction side) {
|
||||
return instance.serializeNBT();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readNBT(Capability<MinecartController> capability, MinecartController instance, Direction side,
|
||||
INBT base) {
|
||||
instance.deserializeNBT((CompoundNBT) base);
|
||||
}
|
||||
|
||||
}, MinecartController::empty);
|
||||
}
|
||||
|
||||
/* Capability provider */
|
||||
|
||||
private final LazyOptional<MinecartController> cap;
|
||||
private MinecartController handler;
|
||||
|
||||
public CapabilityMinecartController(AbstractMinecartEntity minecart) {
|
||||
handler = new MinecartController(minecart);
|
||||
cap = LazyOptional.of(() -> handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
|
||||
if (cap == MINECART_CONTROLLER_CAPABILITY)
|
||||
return this.cap.cast();
|
||||
return LazyOptional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundNBT serializeNBT() {
|
||||
return handler.serializeNBT();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(CompoundNBT nbt) {
|
||||
handler.deserializeNBT(nbt);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train.capability;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
|
||||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingHandler;
|
||||
import com.simibubi.create.foundation.networking.AllPackets;
|
||||
import com.simibubi.create.foundation.utility.Couple;
|
||||
import com.simibubi.create.foundation.utility.VecHelper;
|
||||
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.NBTUtil;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
import net.minecraftforge.common.util.INBTSerializable;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
/**
|
||||
* Extended code for Minecarts, this allows for handling stalled carts and
|
||||
* coupled trains
|
||||
*/
|
||||
public class MinecartController implements INBTSerializable<CompoundNBT> {
|
||||
|
||||
public static MinecartController EMPTY;
|
||||
private boolean needsEntryRefresh;
|
||||
private WeakReference<AbstractMinecartEntity> weakRef;
|
||||
|
||||
/*
|
||||
* Stall information, <Internal (waiting couplings), External (stalled
|
||||
* contraptions)>
|
||||
*/
|
||||
private Couple<Optional<StallData>> stallData;
|
||||
|
||||
/*
|
||||
* Coupling information, <Main (helmed by this cart), Connected (handled by
|
||||
* other cart)>
|
||||
*/
|
||||
private Couple<Optional<CouplingData>> couplings;
|
||||
|
||||
public MinecartController(AbstractMinecartEntity minecart) {
|
||||
weakRef = new WeakReference<>(minecart);
|
||||
stallData = Couple.create(Optional::empty);
|
||||
couplings = Couple.create(Optional::empty);
|
||||
needsEntryRefresh = true;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
AbstractMinecartEntity cart = cart();
|
||||
World world = getWorld();
|
||||
|
||||
if (needsEntryRefresh) {
|
||||
List<AbstractMinecartEntity> list = CapabilityMinecartController.queuedAdditions.get(world);
|
||||
if (list != null)
|
||||
list.add(cart);
|
||||
}
|
||||
|
||||
stallData.forEach(opt -> opt.ifPresent(sd -> sd.tick(cart)));
|
||||
|
||||
MutableBoolean internalStall = new MutableBoolean(false);
|
||||
couplings.forEachWithContext((opt, main) -> opt.ifPresent(cd -> {
|
||||
|
||||
UUID idOfOther = cd.idOfCart(!main);
|
||||
MinecartController otherCart = CapabilityMinecartController.getIfPresent(world, idOfOther);
|
||||
internalStall.setValue(
|
||||
internalStall.booleanValue() || otherCart == null || !otherCart.isPresent() || otherCart.isStalled());
|
||||
|
||||
}));
|
||||
if (!world.isRemote)
|
||||
setStalled(internalStall.booleanValue(), true);
|
||||
}
|
||||
|
||||
public boolean isFullyCoupled() {
|
||||
return isLeadingCoupling() && isConnectedToCoupling();
|
||||
}
|
||||
|
||||
public boolean isLeadingCoupling() {
|
||||
return couplings.get(true)
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
public boolean isConnectedToCoupling() {
|
||||
return couplings.get(false)
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
public float getCouplingLength(boolean leading) {
|
||||
Optional<CouplingData> optional = couplings.get(leading);
|
||||
if (optional.isPresent())
|
||||
return optional.get().length;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void decouple() {
|
||||
couplings.forEachWithContext((opt, main) -> opt.ifPresent(cd -> {
|
||||
UUID idOfOther = cd.idOfCart(!main);
|
||||
MinecartController otherCart = CapabilityMinecartController.getIfPresent(getWorld(), idOfOther);
|
||||
if (otherCart == null)
|
||||
return;
|
||||
|
||||
removeConnection(main);
|
||||
otherCart.removeConnection(!main);
|
||||
}));
|
||||
}
|
||||
|
||||
public void removeConnection(boolean main) {
|
||||
couplings.set(main, Optional.empty());
|
||||
needsEntryRefresh |= main;
|
||||
sendData();
|
||||
}
|
||||
|
||||
public void prepareForCoupling(boolean isLeading) {
|
||||
// reverse existing chain if necessary
|
||||
if (isLeading && isLeadingCoupling() || !isLeading && isConnectedToCoupling()) {
|
||||
|
||||
List<MinecartController> cartsToFlip = new ArrayList<>();
|
||||
MinecartController current = this;
|
||||
boolean forward = current.isLeadingCoupling();
|
||||
int safetyCount = 1000;
|
||||
|
||||
while (true) {
|
||||
if (safetyCount-- <= 0) {
|
||||
Create.logger.warn("Infinite loop in coupling iteration");
|
||||
return;
|
||||
}
|
||||
cartsToFlip.add(current);
|
||||
current = CouplingHandler.getNextInCouplingChain(getWorld(), current, forward);
|
||||
if (current == null || current == MinecartController.EMPTY)
|
||||
break;
|
||||
}
|
||||
|
||||
for (MinecartController minecartController : cartsToFlip) {
|
||||
MinecartController mc = minecartController;
|
||||
mc.couplings.forEach(opt -> opt.ifPresent(CouplingData::flip));
|
||||
mc.couplings = mc.couplings.swap();
|
||||
if (mc == this)
|
||||
continue;
|
||||
mc.needsEntryRefresh = true;
|
||||
mc.sendData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void coupleWith(boolean isLeading, UUID coupled, float length) {
|
||||
UUID mainID = isLeading ? cart().getUniqueID() : coupled;
|
||||
UUID connectedID = isLeading ? coupled : cart().getUniqueID();
|
||||
couplings.set(isLeading, Optional.of(new CouplingData(mainID, connectedID, length)));
|
||||
needsEntryRefresh |= isLeading;
|
||||
sendData();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UUID getCoupledCart(boolean asMain) {
|
||||
Optional<CouplingData> optional = couplings.get(asMain);
|
||||
if (!optional.isPresent())
|
||||
return null;
|
||||
CouplingData couplingData = optional.get();
|
||||
return asMain ? couplingData.connectedCartID : couplingData.mainCartID;
|
||||
}
|
||||
|
||||
public boolean isStalled() {
|
||||
return isStalled(true) || isStalled(false);
|
||||
}
|
||||
|
||||
private boolean isStalled(boolean internal) {
|
||||
return stallData.get(internal)
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
public void setStalledExternally(boolean stall) {
|
||||
setStalled(stall, false);
|
||||
}
|
||||
|
||||
private void setStalled(boolean stall, boolean internal) {
|
||||
if (isStalled(internal) == stall)
|
||||
return;
|
||||
|
||||
AbstractMinecartEntity cart = cart();
|
||||
if (stall) {
|
||||
stallData.set(internal, Optional.of(new StallData(cart)));
|
||||
sendData();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isStalled(!internal))
|
||||
stallData.get(internal)
|
||||
.get()
|
||||
.release(cart);
|
||||
stallData.set(internal, Optional.empty());
|
||||
|
||||
sendData();
|
||||
}
|
||||
|
||||
public void sendData() {
|
||||
if (getWorld().isRemote)
|
||||
return;
|
||||
AllPackets.channel.send(PacketDistributor.TRACKING_ENTITY.with(this::cart),
|
||||
new MinecartControllerUpdatePacket(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundNBT serializeNBT() {
|
||||
CompoundNBT compoundNBT = new CompoundNBT();
|
||||
|
||||
stallData.forEachWithContext((opt, internal) -> opt
|
||||
.ifPresent(sd -> compoundNBT.put(internal ? "InternalStallData" : "StallData", sd.serialize())));
|
||||
couplings.forEachWithContext((opt, main) -> opt
|
||||
.ifPresent(cd -> compoundNBT.put(main ? "MainCoupling" : "ConnectedCoupling", cd.serialize())));
|
||||
|
||||
return compoundNBT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(CompoundNBT nbt) {
|
||||
Optional<StallData> internalSD = Optional.empty();
|
||||
Optional<StallData> externalSD = Optional.empty();
|
||||
Optional<CouplingData> mainCD = Optional.empty();
|
||||
Optional<CouplingData> connectedCD = Optional.empty();
|
||||
|
||||
if (nbt.contains("InternalStallData"))
|
||||
internalSD = Optional.of(StallData.read(nbt.getCompound("InternalStallData")));
|
||||
if (nbt.contains("StallData"))
|
||||
externalSD = Optional.of(StallData.read(nbt.getCompound("StallData")));
|
||||
if (nbt.contains("MainCoupling"))
|
||||
mainCD = Optional.of(CouplingData.read(nbt.getCompound("MainCoupling")));
|
||||
if (nbt.contains("ConnectedCoupling"))
|
||||
connectedCD = Optional.of(CouplingData.read(nbt.getCompound("ConnectedCoupling")));
|
||||
|
||||
stallData = Couple.create(internalSD, externalSD);
|
||||
couplings = Couple.create(mainCD, connectedCD);
|
||||
needsEntryRefresh = true;
|
||||
}
|
||||
|
||||
public boolean isPresent() {
|
||||
return weakRef.get() != null && cart().isAlive();
|
||||
}
|
||||
|
||||
public AbstractMinecartEntity cart() {
|
||||
return weakRef.get();
|
||||
}
|
||||
|
||||
public static MinecartController empty() {
|
||||
return EMPTY != null ? EMPTY : (EMPTY = new MinecartController(null));
|
||||
}
|
||||
|
||||
private World getWorld() {
|
||||
return cart().getEntityWorld();
|
||||
}
|
||||
|
||||
private static class CouplingData {
|
||||
|
||||
private UUID mainCartID;
|
||||
private UUID connectedCartID;
|
||||
private float length;
|
||||
|
||||
public CouplingData(UUID mainCartID, UUID connectedCartID, float length) {
|
||||
this.mainCartID = mainCartID;
|
||||
this.connectedCartID = connectedCartID;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
void flip() {
|
||||
UUID swap = mainCartID;
|
||||
mainCartID = connectedCartID;
|
||||
connectedCartID = swap;
|
||||
}
|
||||
|
||||
CompoundNBT serialize() {
|
||||
CompoundNBT nbt = new CompoundNBT();
|
||||
nbt.put("Main", NBTUtil.writeUniqueId(mainCartID));
|
||||
nbt.put("Connected", NBTUtil.writeUniqueId(connectedCartID));
|
||||
nbt.putFloat("Length", length);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
static CouplingData read(CompoundNBT nbt) {
|
||||
UUID mainCartID = NBTUtil.readUniqueId(nbt.getCompound("Main"));
|
||||
UUID connectedCartID = NBTUtil.readUniqueId(nbt.getCompound("Connected"));
|
||||
return new CouplingData(mainCartID, connectedCartID, nbt.getFloat("Length"));
|
||||
}
|
||||
|
||||
public UUID idOfCart(boolean main) {
|
||||
return main ? mainCartID : connectedCartID;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class StallData {
|
||||
Vec3d position;
|
||||
Vec3d motion;
|
||||
float yaw, pitch;
|
||||
|
||||
private StallData() {}
|
||||
|
||||
StallData(AbstractMinecartEntity entity) {
|
||||
position = entity.getPositionVec();
|
||||
motion = entity.getMotion();
|
||||
yaw = entity.rotationYaw;
|
||||
pitch = entity.rotationPitch;
|
||||
tick(entity);
|
||||
}
|
||||
|
||||
void tick(AbstractMinecartEntity entity) {
|
||||
entity.setPosition(position.x, position.y, position.z);
|
||||
entity.setMotion(Vec3d.ZERO);
|
||||
entity.rotationYaw = yaw;
|
||||
entity.rotationPitch = pitch;
|
||||
}
|
||||
|
||||
void release(AbstractMinecartEntity entity) {
|
||||
entity.setMotion(motion);
|
||||
}
|
||||
|
||||
CompoundNBT serialize() {
|
||||
CompoundNBT nbt = new CompoundNBT();
|
||||
nbt.put("Pos", VecHelper.writeNBT(position));
|
||||
nbt.put("Motion", VecHelper.writeNBT(motion));
|
||||
nbt.putFloat("Yaw", yaw);
|
||||
nbt.putFloat("Pitch", pitch);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
static StallData read(CompoundNBT nbt) {
|
||||
StallData stallData = new StallData();
|
||||
stallData.position = VecHelper.readNBT(nbt.getList("Pos", NBT.TAG_DOUBLE));
|
||||
stallData.motion = VecHelper.readNBT(nbt.getList("Motion", NBT.TAG_DOUBLE));
|
||||
stallData.yaw = nbt.getFloat("Yaw");
|
||||
stallData.pitch = nbt.getFloat("Pitch");
|
||||
return stallData;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package com.simibubi.create.content.contraptions.components.structureMovement.train.capability;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.simibubi.create.foundation.networking.SimplePacketBase;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.fml.network.NetworkEvent.Context;
|
||||
|
||||
public class MinecartControllerUpdatePacket extends SimplePacketBase {
|
||||
|
||||
int entityID;
|
||||
CompoundNBT nbt;
|
||||
|
||||
public MinecartControllerUpdatePacket(MinecartController controller) {
|
||||
entityID = controller.cart()
|
||||
.getEntityId();
|
||||
nbt = controller.serializeNBT();
|
||||
}
|
||||
|
||||
public MinecartControllerUpdatePacket(PacketBuffer buffer) {
|
||||
entityID = buffer.readInt();
|
||||
nbt = buffer.readCompoundTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketBuffer buffer) {
|
||||
buffer.writeInt(entityID);
|
||||
buffer.writeCompoundTag(nbt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Supplier<Context> context) {
|
||||
context.get()
|
||||
.enqueueWork(() -> DistExecutor.runWhenOn(Dist.CLIENT, () -> this::handleCL));
|
||||
context.get()
|
||||
.setPacketHandled(true);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
private void handleCL() {
|
||||
ClientWorld world = Minecraft.getInstance().world;
|
||||
if (world == null)
|
||||
return;
|
||||
Entity entityByID = world.getEntityByID(entityID);
|
||||
if (entityByID == null)
|
||||
return;
|
||||
entityByID.getCapability(CapabilityMinecartController.MINECART_CONTROLLER_CAPABILITY)
|
||||
.ifPresent(mc -> mc.deserializeNBT(nbt));
|
||||
}
|
||||
|
||||
}
|
|
@ -2,11 +2,18 @@ package com.simibubi.create.content.contraptions.wrench;
|
|||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import com.simibubi.create.AllItems;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.item.minecart.AbstractMinecartEntity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.ItemUseContext;
|
||||
import net.minecraft.util.ActionResultType;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraftforge.event.entity.player.AttackEntityEvent;
|
||||
|
||||
public class WrenchItem extends Item {
|
||||
|
||||
|
@ -31,4 +38,19 @@ public class WrenchItem extends Item {
|
|||
return actor.onSneakWrenched(state, context);
|
||||
return actor.onWrenched(state, context);
|
||||
}
|
||||
|
||||
public static void wrenchInstaKillsMinecarts(AttackEntityEvent event) {
|
||||
Entity target = event.getTarget();
|
||||
if (!(target instanceof AbstractMinecartEntity))
|
||||
return;
|
||||
PlayerEntity player = event.getPlayer();
|
||||
ItemStack heldItem = player.getHeldItemMainhand();
|
||||
if (!AllItems.WRENCH.isIn(heldItem))
|
||||
return;
|
||||
if (player.isCreative())
|
||||
return;
|
||||
AbstractMinecartEntity minecart = (AbstractMinecartEntity) target;
|
||||
minecart.attackEntityFrom(DamageSource.causePlayerDamage(player), 100);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,10 +7,12 @@ import com.mojang.blaze3d.matrix.MatrixStack;
|
|||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.CreateClient;
|
||||
import com.simibubi.create.content.contraptions.KineticDebugger;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionCollider;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionHandler;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.chassis.ChassisRangeDisplay;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.ClientMinecartCouplingHandler;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.MinecartCouplingHandler;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingHandlerClient;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingPhysics;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingRenderer;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.CapabilityMinecartController;
|
||||
import com.simibubi.create.content.contraptions.components.turntable.TurntableHandler;
|
||||
import com.simibubi.create.content.contraptions.relays.belt.item.BeltConnectorHandler;
|
||||
import com.simibubi.create.content.curiosities.tools.ExtendoGripRenderHandler;
|
||||
|
@ -70,8 +72,10 @@ public class ClientEvents {
|
|||
CreateClient.schematicAndQuillHandler.tick();
|
||||
CreateClient.schematicHandler.tick();
|
||||
|
||||
ContraptionCollider.runCollisions(world);
|
||||
MinecartCouplingHandler.tick(world);
|
||||
ContraptionHandler.tick(world);
|
||||
CapabilityMinecartController.tick(world);
|
||||
CouplingPhysics.tick(world);
|
||||
|
||||
ScreenOpener.tick();
|
||||
ServerSpeedProvider.clientTick();
|
||||
BeltConnectorHandler.tick();
|
||||
|
@ -82,7 +86,8 @@ public class ClientEvents {
|
|||
EdgeInteractionRenderer.tick();
|
||||
WorldshaperRenderHandler.tick();
|
||||
BlockzapperRenderHandler.tick();
|
||||
ClientMinecartCouplingHandler.tick();
|
||||
CouplingHandlerClient.tick();
|
||||
CouplingRenderer.tickDebugModeRenders();
|
||||
KineticDebugger.tick();
|
||||
ZapperRenderHandler.tick();
|
||||
ExtendoGripRenderHandler.tick();
|
||||
|
@ -105,7 +110,7 @@ public class ClientEvents {
|
|||
ms.translate(-view.getX(), -view.getY(), -view.getZ());
|
||||
SuperRenderTypeBuffer buffer = SuperRenderTypeBuffer.getInstance();
|
||||
|
||||
MinecartCouplingHandler.render(ms, buffer);
|
||||
CouplingRenderer.renderAll(ms, buffer);
|
||||
CreateClient.schematicHandler.render(ms, buffer);
|
||||
CreateClient.outliner.renderOutlines(ms, buffer);
|
||||
// CollisionDebugger.render(ms, buffer);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package com.simibubi.create.events;
|
||||
|
||||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionCollider;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionHandler;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.MinecartCouplingHandler;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingPhysics;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.CapabilityMinecartController;
|
||||
import com.simibubi.create.content.contraptions.wrench.WrenchItem;
|
||||
import com.simibubi.create.content.schematics.ServerSchematicLoader;
|
||||
import com.simibubi.create.foundation.command.AllCommands;
|
||||
import com.simibubi.create.foundation.utility.ServerSpeedProvider;
|
||||
|
@ -14,11 +15,14 @@ import net.minecraft.entity.Entity;
|
|||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.world.IWorld;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||
import net.minecraftforge.event.TickEvent.Phase;
|
||||
import net.minecraftforge.event.TickEvent.ServerTickEvent;
|
||||
import net.minecraftforge.event.TickEvent.WorldTickEvent;
|
||||
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
|
||||
import net.minecraftforge.event.entity.player.AttackEntityEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
|
||||
|
@ -39,14 +43,20 @@ public class CommonEvents {
|
|||
Create.lagger.tick();
|
||||
ServerSpeedProvider.serverTick();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onChunkUnloaded(ChunkEvent.Unload event) {
|
||||
CapabilityMinecartController.onChunkUnloaded(event);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onWorldTick(WorldTickEvent event) {
|
||||
if (event.phase == Phase.START)
|
||||
return;
|
||||
World world = event.world;
|
||||
ContraptionCollider.runCollisions(world);
|
||||
MinecartCouplingHandler.tick(world);
|
||||
ContraptionHandler.tick(world);
|
||||
CapabilityMinecartController.tick(world);
|
||||
CouplingPhysics.tick(world);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
@ -63,7 +73,11 @@ public class CommonEvents {
|
|||
Entity entity = event.getEntity();
|
||||
World world = event.getWorld();
|
||||
ContraptionHandler.addSpawnedContraptionsToCollisionList(entity, world);
|
||||
MinecartCouplingHandler.handleAddedMinecart(entity, world);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onEntityAttackedByPlayer(AttackEntityEvent event) {
|
||||
WrenchItem.wrenchInstaKillsMinecarts(event);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
@ -97,5 +111,10 @@ public class CommonEvents {
|
|||
Create.torquePropagator.onUnloadWorld(world);
|
||||
WorldAttached.invalidateWorld(world);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void attachCapabilities(AttachCapabilitiesEvent<Entity> event) {
|
||||
CapabilityMinecartController.attach(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,10 +11,8 @@ import com.simibubi.create.content.contraptions.components.structureMovement.syn
|
|||
import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionInteractionPacket;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.sync.ContraptionSeatMappingPacket;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.sync.LimbSwingUpdatePacket;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.MinecartCouplingCreationPacket;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.MinecartCouplingSyncPacket;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.PersistantDataPacket;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.PersistantDataPacketRequest;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.CouplingCreationPacket;
|
||||
import com.simibubi.create.content.contraptions.components.structureMovement.train.capability.MinecartControllerUpdatePacket;
|
||||
import com.simibubi.create.content.contraptions.relays.advanced.sequencer.ConfigureSequencedGearshiftPacket;
|
||||
import com.simibubi.create.content.curiosities.symmetry.SymmetryEffectPacket;
|
||||
import com.simibubi.create.content.curiosities.tools.ExtendoGripInteractionPacket;
|
||||
|
@ -55,8 +53,7 @@ public enum AllPackets {
|
|||
CONTRAPTION_INTERACT(ContraptionInteractionPacket.class, ContraptionInteractionPacket::new),
|
||||
CLIENT_MOTION(ClientMotionPacket.class, ClientMotionPacket::new),
|
||||
PLACE_ARM(ArmPlacementPacket.class, ArmPlacementPacket::new),
|
||||
MINECART_COUPLING_CREATION(MinecartCouplingCreationPacket.class, MinecartCouplingCreationPacket::new),
|
||||
PERSISTANT_DATA_REQUEST(PersistantDataPacketRequest.class, PersistantDataPacketRequest::new),
|
||||
MINECART_COUPLING_CREATION(CouplingCreationPacket.class, CouplingCreationPacket::new),
|
||||
INSTANT_SCHEMATIC(InstantSchematicPacket.class, InstantSchematicPacket::new),
|
||||
|
||||
// Server to Client
|
||||
|
@ -66,10 +63,9 @@ public enum AllPackets {
|
|||
CONFIGURE_CONFIG(ConfigureConfigPacket.class, ConfigureConfigPacket::new),
|
||||
CONTRAPTION_STALL(ContraptionStallPacket.class, ContraptionStallPacket::new),
|
||||
GLUE_EFFECT(GlueEffectPacket.class, GlueEffectPacket::new),
|
||||
MINECART_COUPLING_SYNC(MinecartCouplingSyncPacket.class, MinecartCouplingSyncPacket::new),
|
||||
CONTRAPTION_SEAT_MAPPING(ContraptionSeatMappingPacket.class, ContraptionSeatMappingPacket::new),
|
||||
PERSISTANT_DATA(PersistantDataPacket.class, PersistantDataPacket::new),
|
||||
LIMBSWING_UPDATE(LimbSwingUpdatePacket.class, LimbSwingUpdatePacket::new),
|
||||
MINECART_CONTROLLER(MinecartControllerUpdatePacket.class, MinecartControllerUpdatePacket::new),
|
||||
|
||||
;
|
||||
|
||||
|
|
|
@ -121,6 +121,12 @@
|
|||
"create.blockzapper.componentTier.chromatic": "Chromatic",
|
||||
"create.blockzapper.leftClickToSet": "Left-Click a Block to set Material",
|
||||
"create.blockzapper.empty": "Out of Blocks!",
|
||||
|
||||
"create.minecart_coupling.two_couplings_max": "Minecarts cannot have more than two couplings each",
|
||||
"create.minecart_coupling.unloaded": "Parts of your train seem to be in unloaded chunks",
|
||||
"create.minecart_coupling.no_loops": "Couplings cannot form a loop",
|
||||
"create.minecart_coupling.removed": "Removed all couplings from minecart",
|
||||
"create.minecart_coupling.too_far": "Minecarts are too far apart",
|
||||
|
||||
"create.contraptions.movement_mode": "Movement Mode",
|
||||
"create.contraptions.movement_mode.move_place": "Always Place when Stopped",
|
||||
|
|
Loading…
Reference in a new issue