Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.18.x/feature/dynconn #607

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -49,13 +52,19 @@
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;
import org.valkyrienskies.mod.common.util.VSServerLevel;
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 {
Expand Down Expand Up @@ -130,6 +139,7 @@ private boolean includeShipsInParticleDistanceCheck(
private void loadChunk(@NotNull final ChunkAccess worldChunk, final List<TerrainUpdate> voxelShapeUpdates) {
if (!knownChunks.containsKey(worldChunk.getPos())) {
final List<Vector3ic> voxelChunkPositions = new ArrayList<>();
final ServerLevel self = ServerLevel.class.cast(this);

final int chunkX = worldChunk.getPos().x;
final int chunkZ = worldChunk.getPos().z;
Expand All @@ -156,13 +166,22 @@ private void loadChunk(@NotNull final ChunkAccess worldChunk, final List<Terrain
// Sussy cast, but I don't want to expose this directly through the vs-core api
final WingManager shipAsWingManager = ship.getAttachment(WingManager.class);
final MutableBlockPos mutableBlockPos = new MutableBlockPos();
final ConnectivityForest shipAsConnectivityForest = ship.getAttachment(ConnectivityForest.class);
final AirPocketForest shipAsAirPocketForest = ship.getAttachment(AirPocketForest.class);
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
final BlockState blockState = chunkSection.getBlockState(x, y, z);
final int posX = (chunkX << 4) + x;
final int posY = chunkSection.bottomBlockY() + y;
final int posZ = (chunkZ << 4) + z;
if (!blockState.isAir()) {
shipAsConnectivityForest.newVertex(posX, posY, posZ);
} else {
if (VectorConversionsKt.expand(ship.getShipAABB(), 1, new AABBi()).containsPoint(posX, posY, posZ)) {
shipAsAirPocketForest.newVertex(posX, posY, posZ, false);
}
}
if (blockState.getBlock() instanceof WingBlock) {
mutableBlockPos.set(posX, posY, posZ);
final Wing wing =
Expand All @@ -176,6 +195,66 @@ private void loadChunk(@NotNull final ChunkAccess worldChunk, final List<Terrain
}
}
}
if (ship.getShipAABB() != null) {
final AABBic aabbToCheck = VectorConversionsKt.expand(ship.getShipAABB(), 1, new AABBi());

final HashSet<Vector3ic> newOutsideAirVertices = new HashSet<Vector3ic>();
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 {
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -18,6 +21,8 @@ object VSGameEvents {
val postRenderShip = EventEmitterImpl<ShipRenderEvent>()
val shipsStartRendering = EventEmitterImpl<ShipStartRenderEvent>()

val shipSplit = EventEmitterImpl<ShipSplitEvent>()

data class ShipStartRenderEvent(
val renderer: LevelRenderer,
val renderType: RenderType,
Expand All @@ -35,5 +40,12 @@ object VSGameEvents {
val ship: ClientShip,
val chunks: ObjectList<RenderChunkInfo>
)

data class ShipSplitEvent(
val baseShip: LoadedServerShip,
val newShip: ServerShip,
val splitPoint: Vector3ic,
val newShipSplitPoint: Vector3ic
)
}

173 changes: 173 additions & 0 deletions common/src/main/kotlin/org/valkyrienskies/mod/util/ShipSplitter.kt
Original file line number Diff line number Diff line change
@@ -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>(ConnectivityForest::class.java) != null) {
val forest : ConnectivityForestImpl = ship.getAttachment<ConnectivityForest>(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>(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<Vector3ic?> = 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>(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() && loadedShip.shipAABB != null) {
val toAdd: HashSet<Vector3ic> = 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)
}
}
}
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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+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
Expand Down