diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/accessors/ChuteBlockEntityAccessor.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/accessors/ChuteBlockEntityAccessor.java new file mode 100644 index 000000000..917b5c175 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/accessors/ChuteBlockEntityAccessor.java @@ -0,0 +1,16 @@ +package org.valkyrienskies.mod.mixin.mod_compat.create.accessors; + +import com.simibubi.create.content.logistics.chute.ChuteBlockEntity; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(ChuteBlockEntity.class) +public interface ChuteBlockEntityAccessor { + @Accessor("bottomPullDistance") + float getBottomPullDistance(); + + @Invoker + boolean callCanAcceptItem(ItemStack item); +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/blockentity/MixinChuteBlockEntity.java b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/blockentity/MixinChuteBlockEntity.java new file mode 100644 index 000000000..10ed17e72 --- /dev/null +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/mod_compat/create/blockentity/MixinChuteBlockEntity.java @@ -0,0 +1,61 @@ +package org.valkyrienskies.mod.mixin.mod_compat.create.blockentity; + +import com.simibubi.create.content.logistics.chute.ChuteBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.joml.Vector3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import org.valkyrienskies.core.api.ships.ServerShip; +import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.mixin.mod_compat.create.accessors.ChuteBlockEntityAccessor; + +@Mixin(ChuteBlockEntity.class) +public class MixinChuteBlockEntity { + @Inject(method = "findEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/AABB;(Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec3;)V", shift = Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true, remap = false) + private void preFindEntities(float itemSpeed, CallbackInfo ci, Vec3 center) { + if (((ChuteBlockEntity) (Object) this).getLevel() != null) { + + final ChuteBlockEntity be = (ChuteBlockEntity) (Object) this; + final ChuteBlockEntityAccessor bea = (ChuteBlockEntityAccessor) be; + Level level = ((ChuteBlockEntity) (Object) this).getLevel(); + + BlockPos pos = be.getBlockPos(); + + AABB searchArea = new AABB(center.add(0, -bea.getBottomPullDistance() - 0.5, 0), center.add(0, -0.5, 0)).inflate(.45f); + + if (VSGameUtilsKt.getShipObjectManagingPos(level, pos) instanceof ServerShip ship) { + Vector3d searchAreaMin = new Vector3d(searchArea.minX, searchArea.minY, searchArea.minZ); + Vector3d searchAreaMax = new Vector3d(searchArea.maxX, searchArea.maxY, searchArea.maxZ); + + Vector3d searchAreaReturnMin = new Vector3d(); + Vector3d searchAreaReturnMax = new Vector3d(); + + ship.getTransform().getShipToWorld().transformAab(searchAreaMin, searchAreaMax, searchAreaReturnMin, searchAreaReturnMax); + + searchArea = new AABB(searchAreaReturnMin.x, searchAreaReturnMin.y, searchAreaReturnMin.z, searchAreaReturnMax.x, searchAreaReturnMax.y, searchAreaReturnMax.z); + + for (ItemEntity itemEntity : level.getEntitiesOfClass(ItemEntity.class, searchArea)) { + if (!itemEntity.isAlive()) + continue; + ItemStack entityItem = itemEntity.getItem(); + if (!bea.callCanAcceptItem(entityItem)) + continue; + be.setItem(entityItem.copy(), (float) (itemEntity.getBoundingBox() + .getCenter().y - be.getBlockPos().getY())); + itemEntity.discard(); + break; + } + ci.cancel(); + } + } + } +} diff --git a/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinServerLevel.java b/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinServerLevel.java index 3971c68fd..e1a9c2eca 100644 --- a/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinServerLevel.java +++ b/common/src/main/java/org/valkyrienskies/mod/mixin/server/world/MixinServerLevel.java @@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -40,10 +41,13 @@ import org.valkyrienskies.core.api.ships.LoadedServerShip; import org.valkyrienskies.core.api.ships.Wing; import org.valkyrienskies.core.api.ships.WingManager; +import org.valkyrienskies.core.api.ships.datastructures.ShipConnDataAttachment; import org.valkyrienskies.core.apigame.world.ServerShipWorldCore; import org.valkyrienskies.core.apigame.world.chunks.TerrainUpdate; +import org.valkyrienskies.core.util.datastructures.Breakage; import org.valkyrienskies.mod.common.IShipObjectWorldServerProvider; import org.valkyrienskies.mod.common.VSGameUtilsKt; +import org.valkyrienskies.mod.common.assembly.SubShipAssemblyKt; import org.valkyrienskies.mod.common.block.WingBlock; import org.valkyrienskies.mod.common.util.VSServerLevel; import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; @@ -231,6 +235,24 @@ private void postTick(final BooleanSupplier shouldKeepTicking, final CallbackInf VSGameUtilsKt.getDimensionId(self), voxelShapeUpdates ); + + // Process pending ship breakages + for (final LoadedServerShip loadedShip : shipObjectWorld.getLoadedShips()) { + if (loadedShip.getAttachment(ShipConnDataAttachment.class) instanceof ShipConnDataAttachment) { + ShipConnDataAttachment connData = loadedShip.getAttachment(ShipConnDataAttachment.class); + assert connData != null; + HashSet shipBreakages = (HashSet) connData.getBreakages(); + Iterator breakageIterator = shipBreakages.iterator(); + + while (breakageIterator.hasNext()) { + Object breakage = breakageIterator.next(); + if (breakage instanceof Breakage breaking) { + SubShipAssemblyKt.splitShip(VectorConversionsMCKt.toBlockPos(breaking.component1()), breaking.component2(), self, loadedShip); + breakageIterator.remove(); + } + } + } + } } @Override diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/DefaultBlockStateInfoProvider.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/DefaultBlockStateInfoProvider.kt index 54f5ce073..fbb3587ec 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/DefaultBlockStateInfoProvider.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/DefaultBlockStateInfoProvider.kt @@ -32,6 +32,6 @@ object DefaultBlockStateInfoProvider : BlockStateInfoProvider { val block = blockState.block if (block is LiquidBlock) return if (block == Blocks.LAVA) vsCore.blockTypes.lava else vsCore.blockTypes.water - return if (blockState.isSolid) vsCore.blockTypes.solid else vsCore.blockTypes.air + return if (blockState.isSolid) vsCore.blockTypes.solid else vsCore.blockTypes.noCollision } } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/SubShipAssembly.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/SubShipAssembly.kt new file mode 100644 index 000000000..ff859a5c1 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/SubShipAssembly.kt @@ -0,0 +1,140 @@ +package org.valkyrienskies.mod.common.assembly + +import net.minecraft.core.BlockPos +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.level.ChunkPos +import org.joml.Vector3d +import org.joml.Vector3dc +import org.valkyrienskies.core.api.ships.ServerShip +import org.valkyrienskies.core.impl.game.ships.ShipData +import org.valkyrienskies.core.impl.game.ships.ShipTransformImpl +import org.valkyrienskies.core.impl.networking.simple.sendToClient +import org.valkyrienskies.core.util.datastructures.DenseBlockPosSet +import org.valkyrienskies.core.util.x +import org.valkyrienskies.mod.common.dimensionId +import org.valkyrienskies.mod.common.executeIf +import org.valkyrienskies.mod.common.hooks.VSGameEvents +import org.valkyrienskies.mod.common.isTickingChunk +import org.valkyrienskies.mod.common.networking.PacketRestartChunkUpdates +import org.valkyrienskies.mod.common.networking.PacketStopChunkUpdates +import org.valkyrienskies.mod.common.playerWrapper +import org.valkyrienskies.mod.common.shipObjectWorld +import org.valkyrienskies.mod.common.util.settings +import org.valkyrienskies.mod.common.util.toJOML +import org.valkyrienskies.mod.common.util.toJOMLD +import org.valkyrienskies.mod.util.relocateBlock +import org.valkyrienskies.mod.util.updateBlock + +fun splitShip(centerBlock: BlockPos, blocks: DenseBlockPosSet, level: ServerLevel, originalShip: ServerShip): ServerShip { + if (blocks.isEmpty()) throw IllegalArgumentException() + + val ship = level.shipObjectWorld.createNewShipAtBlock(centerBlock.toJOML(), false, 1.0, level.dimensionId) + + val shipChunkX = ship.chunkClaim.xMiddle + val shipChunkZ = ship.chunkClaim.zMiddle + + val worldChunkX = centerBlock.x shr 4 + val worldChunkZ = centerBlock.z shr 4 + + val deltaX = worldChunkX - shipChunkX + val deltaZ = worldChunkZ - shipChunkZ + + val chunksToBeUpdated = mutableMapOf>() + blocks.forEachChunk { x, _, z, _ -> + val sourcePos = ChunkPos(x, z) + val destPos = ChunkPos(x - deltaX, z - deltaZ) + chunksToBeUpdated[sourcePos] = Pair(sourcePos, destPos) + } + val chunkPairs = chunksToBeUpdated.values.toList() + val chunkPoses = chunkPairs.flatMap { it.toList() } + val chunkPosesJOML = chunkPoses.map { it.toJOML() } + + // Send a list of all the chunks that we plan on updating to players, so that they + // defer all updates until assembly is finished + level.players().forEach { player -> + PacketStopChunkUpdates(chunkPosesJOML).sendToClient(player.playerWrapper) + } + + // Use relocateBlock to copy all the blocks into the ship + blocks.forEachChunk { chunkX, chunkY, chunkZ, chunk -> + val sourceChunk = level.getChunk(chunkX, chunkZ) + val destChunk = level.getChunk(chunkX - deltaX, chunkZ - deltaZ) + + chunk.forEach { x, y, z -> + val fromPos = BlockPos((sourceChunk.pos.x shl 4) + x, (chunkY shl 4) + y, (sourceChunk.pos.z shl 4) + z) + val toPos = BlockPos((destChunk.pos.x shl 4) + x, (chunkY shl 4) + y, (destChunk.pos.z shl 4) + z) + + relocateBlock(sourceChunk, fromPos, destChunk, toPos, false, ship) + } + } + + // Use updateBlock to update blocks after copying + blocks.forEachChunk { chunkX, chunkY, chunkZ, chunk -> + val sourceChunk = level.getChunk(chunkX, chunkZ) + val destChunk = level.getChunk(chunkX - deltaX, chunkZ - deltaZ) + + chunk.forEach { x, y, z -> + val fromPos = BlockPos((sourceChunk.pos.x shl 4) + x, (chunkY shl 4) + y, (sourceChunk.pos.z shl 4) + z) + val toPos = BlockPos((destChunk.pos.x shl 4) + x, (chunkY shl 4) + y, (destChunk.pos.z shl 4) + z) + + updateBlock(destChunk.level, fromPos, toPos, destChunk.getBlockState(toPos)) + } + } + + // Calculate the position of the block that the player clicked after it has been assembled + val centerInShip = Vector3d( + ((shipChunkX shl 4) + (centerBlock.x and 15)).toDouble(), + centerBlock.y.toDouble(), + ((shipChunkZ shl 4) + (centerBlock.z and 15)).toDouble() + ) + + // The ship's position has shifted from the center block since we assembled the ship, compensate for that + val centerBlockPosInWorld = ship.inertiaData.centerOfMassInShip.sub(centerInShip, Vector3d()) + .add(ship.transform.positionInWorld) + + // Put the ship into the compensated position, so that all the assembled blocks stay in the same place + // TODO: AAAAAAAAA THIS IS HORRIBLE how can the API support this? + (ship as ShipData).transform = (ship.transform as ShipTransformImpl).copy(positionInWorld = centerBlockPosInWorld) + + level.server.executeIf( + // This condition will return true if all modified chunks have been both loaded AND + // chunk update packets were sent to players + { chunkPoses.all(level::isTickingChunk) } + ) { + // Once all the chunk updates are sent to players, we can tell them to restart chunk updates + level.players().forEach { player -> + PacketRestartChunkUpdates(chunkPosesJOML).sendToClient(player.playerWrapper) + } + } + + val shipChunkXUpdated = ship.chunkClaim.xMiddle + val shipChunkZUpdated = ship.chunkClaim.zMiddle + + val centerPosCentered = centerBlock.toJOMLD().add(0.5, 0.5, 0.5) + + val centerInShipUpdated: Vector3dc = Vector3d( + ((shipChunkXUpdated shl 4) + (centerBlock.x and 15).toDouble()), + centerBlock.y.toDouble(), + (shipChunkZUpdated shl 4) + (centerBlock.z and 15).toDouble() + ) + + val scaling = ship.transform.shipToWorldScaling + val offset: Vector3dc = + ship.inertiaData.centerOfMassInShip.sub(centerInShipUpdated, Vector3d()) + + val posInWorld = originalShip.transform.shipToWorld.transformPosition(centerPosCentered.add(offset, Vector3d()), Vector3d()) + val rotInWorld = originalShip.transform.shipToWorldRotation + val velVec = Vector3d(originalShip.velocity) + val omegaVec = Vector3d(originalShip.omega) + + val newShipTransform = ShipTransformImpl(posInWorld, ship.inertiaData.centerOfMassInShip, rotInWorld, scaling) + + ship.transform = newShipTransform + ship.physicsData.linearVelocity = velVec + ship.physicsData.angularVelocity = omegaVec + + val event = VSGameEvents.ShipSplitEvent(originalShip.id, ship.id, blocks) + VSGameEvents.shipSplit.emit(event) + + return ship +} diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/VSGameEvents.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/VSGameEvents.kt index 65124d70f..5da849142 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/VSGameEvents.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/VSGameEvents.kt @@ -7,7 +7,9 @@ import net.minecraft.client.renderer.LevelRenderer.RenderChunkInfo import net.minecraft.client.renderer.RenderType import org.joml.Matrix4f import org.valkyrienskies.core.api.ships.ClientShip +import org.valkyrienskies.core.api.ships.properties.ShipId import org.valkyrienskies.core.impl.util.events.EventEmitterImpl +import org.valkyrienskies.core.util.datastructures.DenseBlockPosSet object VSGameEvents { @@ -18,6 +20,8 @@ object VSGameEvents { val postRenderShip = EventEmitterImpl() val shipsStartRendering = EventEmitterImpl() + val shipSplit = EventEmitterImpl() + data class ShipStartRenderEvent( val renderer: LevelRenderer, val renderType: RenderType, @@ -35,5 +39,11 @@ object VSGameEvents { val ship: ClientShip, val chunks: ObjectList ) + + data class ShipSplitEvent( + val ship: ShipId, + val newShip: ShipId, + val blocks: DenseBlockPosSet + ) } diff --git a/common/src/main/resources/valkyrienskies-common.mixins.json b/common/src/main/resources/valkyrienskies-common.mixins.json index 4d7c28650..3627169c5 100644 --- a/common/src/main/resources/valkyrienskies-common.mixins.json +++ b/common/src/main/resources/valkyrienskies-common.mixins.json @@ -61,6 +61,7 @@ "mod_compat.create.MixinEntityLauncher", "mod_compat.create.MixinRedstoneLinkNetworkHandler", "mod_compat.create.MixinSharedDepotBlockMethods", + "mod_compat.create.accessors.ChuteBlockEntityAccessor", "mod_compat.create.accessors.Matrix3dAccessor", "mod_compat.create.accessors.OutlineParamsAccessor", "mod_compat.create.behaviour.MixinBlockBreakingMovementBehaviour", @@ -73,6 +74,7 @@ "mod_compat.create.block.MixinRedstoneContactBlock", "mod_compat.create.block.MixinStickerBlock", "mod_compat.create.blockentity.IMixinMechanicalBearingTileEntity", + "mod_compat.create.blockentity.MixinChuteBlockEntity", "mod_compat.create.blockentity.MixinCrushingWheelControllerTileEntity", "mod_compat.create.blockentity.MixinEjectorTileEntity", "mod_compat.create.blockentity.MixinEncasedFanTileEntity", diff --git a/forge/gradle.properties b/forge/gradle.properties index 5471ecadc..aac779c6e 100644 --- a/forge/gradle.properties +++ b/forge/gradle.properties @@ -2,7 +2,7 @@ loader_platform=Forge loom.platform=forge kotlin.stdlib.default.dependency=false #Deps -kotlin_version = 3.12.0 +kotlin_version = 4.3.0 cloth_config_version = 11.1.106 #Compat diff --git a/gradle.properties b/gradle.properties index e6d2c283f..dd33285cf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ flywheel_version_fabric=0.6.9-6 # https://modrinth.com/mod/create-big-cannons/versions createbigcannons_version= 0.5.2.a -vs_core_version=1.1.0+b19b27c4a4 +vs_core_version=1.1.0+7dbd2ee63a # Prevent kotlin from autoincluding stdlib as a dependency, which breaks # gradle's composite builds (includeBuild) for some reason. We'll add it manually kotlin.stdlib.default.dependency=false