From f6f81dcf8c5084e0e8636158cfa0aecfe1aa5fef Mon Sep 17 00:00:00 2001 From: ThePlasticPotato <77798144+ThePlasticPotato@users.noreply.github.com> Date: Sat, 21 Oct 2023 20:49:46 -0400 Subject: [PATCH 1/3] Conn setup --- .../mixin/server/world/MixinServerLevel.java | 85 +++++++++ .../mod/common/hooks/VSGameEvents.kt | 12 ++ .../valkyrienskies/mod/util/ShipSplitter.kt | 173 ++++++++++++++++++ gradle.properties | 2 +- 4 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 common/src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt 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 01f1cfd30..e78c42bff 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 @@ -6,6 +6,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -37,6 +38,8 @@ import org.joml.Vector3d; import org.joml.Vector3i; import org.joml.Vector3ic; +import org.joml.primitives.AABBi; +import org.joml.primitives.AABBic; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -49,6 +52,11 @@ import org.valkyrienskies.core.api.ships.WingManager; import org.valkyrienskies.core.apigame.world.ServerShipWorldCore; import org.valkyrienskies.core.apigame.world.chunks.TerrainUpdate; +import org.valkyrienskies.core.impl.config.VSCoreConfig; +import org.valkyrienskies.core.impl.datastructures.dynconn.BlockPosVertex; +import org.valkyrienskies.core.impl.game.ships.AirPocketForest; +import org.valkyrienskies.core.impl.game.ships.ConnectivityForest; +import org.valkyrienskies.core.util.VectorConversionsKt; import org.valkyrienskies.mod.common.IShipObjectWorldServerProvider; import org.valkyrienskies.mod.common.VSGameUtilsKt; import org.valkyrienskies.mod.common.block.WingBlock; @@ -56,6 +64,7 @@ import org.valkyrienskies.mod.common.util.VectorConversionsMCKt; import org.valkyrienskies.mod.mixin.accessors.server.level.ChunkMapAccessor; import org.valkyrienskies.mod.mixin.accessors.server.level.DistanceManagerAccessor; +import org.valkyrienskies.mod.util.ShipSplitter; @Mixin(ServerLevel.class) public abstract class MixinServerLevel implements IShipObjectWorldServerProvider, VSServerLevel { @@ -130,6 +139,7 @@ private boolean includeShipsInParticleDistanceCheck( private void loadChunk(@NotNull final ChunkAccess worldChunk, final List voxelShapeUpdates) { if (!knownChunks.containsKey(worldChunk.getPos())) { final List voxelChunkPositions = new ArrayList<>(); + final ServerLevel self = ServerLevel.class.cast(this); final int chunkX = worldChunk.getPos().x; final int chunkZ = worldChunk.getPos().z; @@ -156,6 +166,8 @@ private void loadChunk(@NotNull final ChunkAccess worldChunk, final List newOutsideAirVertices = new HashSet(); + for (int direction = 1 ; direction <= 6 ; direction++) { + + int minX = switch (direction) { + case 1, 2, 3, 4 -> aabbToCheck.minX(); + default -> aabbToCheck.minZ(); + }; + int maxX = switch (direction) { + case 1, 2, 3, 4 -> aabbToCheck.maxX(); + default -> aabbToCheck.maxZ(); + }; + + int minY = switch (direction) { + case 1, 2 -> aabbToCheck.minZ(); + default -> aabbToCheck.minY(); + }; + + int maxY = switch (direction) { + case 1, 2 -> aabbToCheck.maxZ(); + default -> aabbToCheck.maxY(); + }; + + int offset = switch (direction) { + case 1 -> aabbToCheck.maxY(); + case 2 -> aabbToCheck.minY(); + case 3 -> aabbToCheck.minZ(); + case 4 -> aabbToCheck.maxZ(); + case 5 -> aabbToCheck.maxX(); + default -> aabbToCheck.minX(); + }; + + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + Vector3i pos = new Vector3i(); + switch (direction) { + case 1, 2 -> pos.set(x, offset, y); + case 3, 4 -> pos.set(x, y, offset); + default -> pos.set(offset, x, y); + } + newOutsideAirVertices.add(pos); + } + } + } + shipAsAirPocketForest.updateOutsideAirVertices(newOutsideAirVertices); + } + + shipAsConnectivityForest.getGraph().optimize(); + shipAsAirPocketForest.getGraph().optimize(); + boolean foundChecker = false; + while (!foundChecker) { + final BlockPosVertex + vertexToCheck = shipAsConnectivityForest.getVertices().values().iterator().next(); + if (!self.getBlockState(new BlockPos(vertexToCheck.getPosX(), vertexToCheck.getPosY(), vertexToCheck.getPosZ())).isAir()) { + shipAsConnectivityForest.verifyIntactOnLoad(vertexToCheck); + foundChecker = true; + } + } } // endregion } else { @@ -225,6 +304,12 @@ private void postTick(final BooleanSupplier shouldKeepTicking, final CallbackInf knownChunkPosIterator.remove(); } } + if (VSCoreConfig.SERVER.getShouldDoShipSplitting()) { + ShipSplitter.INSTANCE.splitShips(shipObjectWorld, self); + } + if (VSCoreConfig.SERVER.getShouldDetectAirPockets()) { + ShipSplitter.INSTANCE.airHandle(shipObjectWorld, self); + } // Send new loaded chunks updates to the ship world shipObjectWorld.addTerrainUpdates( 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 fd3a1ccaf..bb868b491 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 @@ -6,7 +6,10 @@ import it.unimi.dsi.fastutil.objects.ObjectList import net.minecraft.client.renderer.LevelRenderer import net.minecraft.client.renderer.LevelRenderer.RenderChunkInfo import net.minecraft.client.renderer.RenderType +import org.joml.Vector3ic import org.valkyrienskies.core.api.ships.ClientShip +import org.valkyrienskies.core.api.ships.LoadedServerShip +import org.valkyrienskies.core.api.ships.ServerShip import org.valkyrienskies.core.impl.util.events.EventEmitterImpl object VSGameEvents { @@ -18,6 +21,8 @@ object VSGameEvents { val postRenderShip = EventEmitterImpl() val shipsStartRendering = EventEmitterImpl() + val shipSplit = EventEmitterImpl() + data class ShipStartRenderEvent( val renderer: LevelRenderer, val renderType: RenderType, @@ -35,5 +40,12 @@ object VSGameEvents { val ship: ClientShip, val chunks: ObjectList ) + + data class ShipSplitEvent( + val baseShip: LoadedServerShip, + val newShip: ServerShip, + val splitPoint: Vector3ic, + val newShipSplitPoint: Vector3ic + ) } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt b/common/src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt new file mode 100644 index 000000000..275d2db87 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt @@ -0,0 +1,173 @@ +package org.valkyrienskies.mod.util + +import net.minecraft.core.BlockPos +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.level.block.Rotation +import org.joml.Vector3d +import org.joml.Vector3dc +import org.joml.Vector3i +import org.joml.Vector3ic +import org.valkyrienskies.core.api.ships.LoadedServerShip +import org.valkyrienskies.core.api.ships.ServerShip +import org.valkyrienskies.core.apigame.world.ServerShipWorldCore +import org.valkyrienskies.core.impl.datastructures.dynconn.BlockPosVertex +import org.valkyrienskies.core.impl.game.ships.AirPocketForest +import org.valkyrienskies.core.impl.game.ships.AirPocketForestImpl +import org.valkyrienskies.core.impl.game.ships.ConnectivityForest +import org.valkyrienskies.core.impl.game.ships.ConnectivityForestImpl +import org.valkyrienskies.core.impl.game.ships.ShipDataCommon +import org.valkyrienskies.core.impl.game.ships.ShipTransformImpl +import org.valkyrienskies.core.util.datastructures.DenseBlockPosSet +import org.valkyrienskies.mod.common.assembly.createNewShipWithBlocks +import org.valkyrienskies.mod.common.hooks.VSGameEvents +import org.valkyrienskies.mod.common.hooks.VSGameEvents.ShipSplitEvent +import org.valkyrienskies.mod.common.shipObjectWorld +import org.valkyrienskies.mod.common.util.toBlockPos +import org.valkyrienskies.mod.common.util.toJOML + +object ShipSplitter { + fun splitShips(shipObjectWorld: ServerShipWorldCore, self: ServerLevel) { + for (ship: LoadedServerShip? in shipObjectWorld.loadedShips) { + if (ship != null) { + if (ship.getAttachment(ConnectivityForest::class.java) != null) { + val forest : ConnectivityForestImpl = ship.getAttachment(ConnectivityForest::class.java) as ConnectivityForestImpl + forest.gameTick() + if (forest.getBreakQueue().isNotEmpty()) { + val logger by logger("ShipSplitter") + + for (breakage in forest.getBreakQueue()) { + + val shipsToSplit = forest.split(breakage) + for (snap in shipsToSplit.first) { + val toRemove = DenseBlockPosSet() + for (vertex in snap.first.values.toList()) { + if (vertex != null) { + if (!self.getBlockState(BlockPos(vertex.posX, vertex.posY, vertex.posZ)).isAir) { + toRemove.add(vertex.posX, vertex.posY, vertex.posZ) + } + } + } + + val centerPos = snap.second + if (toRemove.isEmpty()) { + // logger.error("List of blocks to assemble is empty... how did we get here? Aborting.") + + forest.removeFromBreakQueue(breakage) + return + } + + val newShip : ServerShip = createNewShipWithBlocks(centerPos.toBlockPos(), toRemove, self) + + val centerPosCentered = Vector3d(centerPos).add(0.5, 0.5, 0.5) + + logger.info("Split a segment of a ship with ID ${ship.id} into new ship with ID ${newShip.id} and slug ${newShip.slug}!") + + val shipChunkX = newShip.chunkClaim.xMiddle + val shipChunkZ = newShip.chunkClaim.zMiddle + + val centerInShip: Vector3dc = Vector3d( + ((shipChunkX shl 4) + (centerPos.x() and 15).toDouble()), + centerPos.y().toDouble(), + (shipChunkZ shl 4) + (centerPos.z() and 15).toDouble() + ) + + val scaling = ship.transform.shipToWorldScaling + val offset: Vector3dc = + newShip.inertiaData.centerOfMassInShip.sub(centerInShip, Vector3d()) + + val posInWorld = ship.transform.shipToWorld.transformPosition(centerPosCentered.add(offset, Vector3d()), Vector3d()) + // val offset = newShip.inertiaData.centerOfMassInShip.sub(oldPosInWorld , Vector3d()) + // val posInWorld = ship.transform.shipToWorld.transformPosition(centerPosCentered.add(offset, Vector3d()), Vector3d()) + + + val rotInWorld = ship.transform.shipToWorldRotation + + val velVec = Vector3d(ship.velocity) + val omegaVec = Vector3d(ship.omega) + + val newShipTransform = ShipTransformImpl(posInWorld, newShip.inertiaData.centerOfMassInShip, rotInWorld, scaling) + + (newShip as ShipDataCommon).transform = newShipTransform + (newShip as ShipDataCommon).physicsData.linearVelocity = velVec + (newShip as ShipDataCommon).physicsData.angularVelocity = omegaVec + + verifyShip(self, newShip, centerPos) + + val event = ShipSplitEvent(ship, newShip, shipsToSplit.second, snap.second) + VSGameEvents.shipSplit.emit(event) + } + + verifyShip(self, ship, shipsToSplit.second) + + forest.removeFromBreakQueue(breakage) + } + } + } + } + } + } + + fun verifyShip(level: ServerLevel, newShip: ServerShip, centerPos: Vector3ic) { + val newLoadedShip: LoadedServerShip = level.shipObjectWorld.loadedShips.getById(newShip.id) ?: return + + val forest: ConnectivityForestImpl = newLoadedShip.getAttachment(ConnectivityForest::class.java) as ConnectivityForestImpl ?: return + + val root: BlockPosVertex = forest.vertices[centerPos] ?: return + + forest.vertices.values.forEach { + if (!forest.graph.connected(it, root)) { + val list: ArrayList = ArrayList() + list.add(it.toJOML()) + list.add(root.toJOML()) + forest.addToBreakQueue(list) + } + } + } + + fun fuseShips(level: ServerLevel, baseShip: LoadedServerShip, shipToFuse: LoadedServerShip, fuseTo: Vector3ic, fuseFrom: Vector3ic): Boolean { + val fuseForest: ConnectivityForestImpl = shipToFuse.getAttachment(ConnectivityForest::class.java) as ConnectivityForestImpl + val logger by logger("ShipFuser") + + logger.info("Fusing ship ${shipToFuse.id} into ship ${baseShip.id} at point ${fuseTo}.") + + val verticesSafe = fuseForest.vertices.values.toList() + + verticesSafe.forEach { vertex -> + val pos = BlockPos(vertex.posX, vertex.posY, vertex.posZ) + + val newPos = (fuseFrom.sub(pos.toJOML(), Vector3i()).add(fuseTo, Vector3i())).toBlockPos() + val oldChunk = level.getChunkAt(pos) + val newChunk = level.getChunkAt(newPos) + + relocateBlock(oldChunk, pos, newChunk, newPos, true, baseShip, Rotation.NONE) + } + return true + } + + fun airHandle(shipObjectWorld: ServerShipWorldCore, self: ServerLevel) { + for (loadedShip in shipObjectWorld.loadedShips) { + if (loadedShip.getAttachment(AirPocketForest::class.java) != null) { + val airPocketForest : AirPocketForestImpl = loadedShip.getAttachment(AirPocketForest::class.java) as AirPocketForestImpl + + if (airPocketForest.toUpdateOutsideAir()) { + val toAdd: HashSet = HashSet() + for (x in loadedShip.shipAABB!!.minX()..loadedShip.shipAABB!!.maxX()) { + for (y in loadedShip.shipAABB!!.minY()..loadedShip.shipAABB!!.maxY()) { + for (z in loadedShip.shipAABB!!.minZ()..loadedShip.shipAABB!!.maxZ()) { + val pos = Vector3i(x, y, z) + val blockPos: BlockPos = pos.toBlockPos() + if (self.getBlockState(blockPos).isAir) { + toAdd.add(pos) + } + } + } + } + for (vertex in toAdd) { + airPocketForest.newVertex(vertex.x(), vertex.y(), vertex.z(), true) + } + airPocketForest.setUpdateOutsideAir(false) + } + } + } + } +} diff --git a/gradle.properties b/gradle.properties index c103bd78d..7f2dc7be1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ forge_version=1.18.2-40.2.4 create_fabric_version=0.5.1-c-build.1092+mc1.18.2 flywheel_version_fabric=0.6.9-38 createbigcannons_version= 0.5.2-nightly-e815ca4 -vs_core_version=1.1.0+9ee5a1c6f8 +vs_core_version=1.1.0+70a262cfdf # 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 From c22f0b27e4bf8d4394de37387d112d071bf49096 Mon Sep 17 00:00:00 2001 From: ThePlasticPotato <77798144+ThePlasticPotato@users.noreply.github.com> Date: Sat, 21 Oct 2023 20:57:19 -0400 Subject: [PATCH 2/3] Update vscore --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7f2dc7be1..761efcfd5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ forge_version=1.18.2-40.2.4 create_fabric_version=0.5.1-c-build.1092+mc1.18.2 flywheel_version_fabric=0.6.9-38 createbigcannons_version= 0.5.2-nightly-e815ca4 -vs_core_version=1.1.0+70a262cfdf +vs_core_version=1.1.0+dffe4441d1 # 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 From 8230826ec36c2b72a981ad6425fb369baddeb289 Mon Sep 17 00:00:00 2001 From: ThePlasticPotato <77798144+ThePlasticPotato@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:24:49 -0400 Subject: [PATCH 3/3] forgot a null check tee hee --- .../src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt b/common/src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt index 275d2db87..b292697e2 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt @@ -149,7 +149,7 @@ object ShipSplitter { if (loadedShip.getAttachment(AirPocketForest::class.java) != null) { val airPocketForest : AirPocketForestImpl = loadedShip.getAttachment(AirPocketForest::class.java) as AirPocketForestImpl - if (airPocketForest.toUpdateOutsideAir()) { + if (airPocketForest.toUpdateOutsideAir() && loadedShip.shipAABB != null) { val toAdd: HashSet = HashSet() for (x in loadedShip.shipAABB!!.minX()..loadedShip.shipAABB!!.maxX()) { for (y in loadedShip.shipAABB!!.minY()..loadedShip.shipAABB!!.maxY()) {