diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt index 129d47e3a..9178dfb7a 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt @@ -134,5 +134,9 @@ object BlockStateInfo { x, y, z, level.dimensionId, prevBlockType, newBlockType, prevBlockMass, newBlockMass ) + + if (ValkyrienSkiesMod.vsCore.hooks.enableConnectivity) { + ValkyrienSkiesMod.splitHandler.split(level, x, y, z, prevBlockState, newBlockState) + } } } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/ValkyrienSkiesMod.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/ValkyrienSkiesMod.kt index f5f20b0b7..312af3a0d 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/ValkyrienSkiesMod.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/ValkyrienSkiesMod.kt @@ -15,6 +15,8 @@ import org.valkyrienskies.mod.common.entity.ShipMountingEntity import org.valkyrienskies.mod.common.entity.VSPhysicsEntity import org.valkyrienskies.mod.common.networking.VSGamePackets import org.valkyrienskies.mod.common.util.GameTickForceApplier +import org.valkyrienskies.mod.common.util.SplitHandler +import org.valkyrienskies.mod.common.util.SplittingDisablerAttachment object ValkyrienSkiesMod { const val MOD_ID = "valkyrienskies" @@ -24,9 +26,11 @@ object ValkyrienSkiesMod { lateinit var TEST_FLAP: Block lateinit var TEST_WING: Block lateinit var TEST_SPHERE: Block + lateinit var CONNECTION_CHECKER_ITEM: Item lateinit var SHIP_CREATOR_ITEM: Item lateinit var SHIP_ASSEMBLER_ITEM: Item lateinit var SHIP_CREATOR_ITEM_SMALLER: Item + lateinit var AREA_ASSEMBLER_ITEM: Item lateinit var PHYSICS_ENTITY_CREATOR_ITEM: Item lateinit var SHIP_MOUNTING_ENTITY_TYPE: EntityType lateinit var PHYSICS_ENTITY_TYPE: EntityType @@ -41,6 +45,9 @@ object ValkyrienSkiesMod { @JvmStatic val vsCoreClient get() = vsCore as VSCoreClient + @JvmStatic + lateinit var splitHandler: SplitHandler + fun init(core: VSCore) { this.vsCore = core @@ -49,8 +56,12 @@ object ValkyrienSkiesMod { VSGamePackets.registerHandlers() core.registerConfigLegacy("vs", VSGameConfig::class.java) + + splitHandler = SplitHandler(this.vsCore.hooks.enableBlockEdgeConnectivity, this.vsCore.hooks.enableBlockCornerConnectivity) + VSEvents.ShipLoadEvent.on { event -> event.ship.setAttachment(GameTickForceApplier()) + event.ship.setAttachment(SplittingDisablerAttachment(true)) } } } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/AssemblyUtil.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/AssemblyUtil.kt index 78199a624..326c333ea 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/AssemblyUtil.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/AssemblyUtil.kt @@ -16,7 +16,7 @@ import org.joml.Vector3i private val AIR = Blocks.AIR.defaultBlockState() object AssemblyUtil { - fun setBlock(level: Level, pos: BlockPos, state: BlockState?) { + fun setBlock(level: Level, pos: BlockPos, state: BlockState) { val chunk = level.getChunk(pos) as LevelChunk val section = chunk.getSection(chunk.getSectionIndex(pos.y)) val oldState = level.getBlockState(pos) @@ -26,13 +26,13 @@ object AssemblyUtil { fun removeBlock(level: Level, pos: BlockPos) { level.removeBlockEntity(pos) - setBlock(level, pos, Blocks.AIR.defaultBlockState()) + level.getChunk(pos).setBlockState(pos, Blocks.AIR.defaultBlockState(), false) } - fun copyBlock(level: Level, from: BlockPos?, to: BlockPos) { + fun copyBlock(level: Level, from: BlockPos, to: BlockPos) { val state = level.getBlockState(from) val blockentity = level.getBlockEntity(from) - setBlock(level, to, state) + level.getChunk(to).setBlockState(to, state, false) // Transfer pending schedule-ticks if (level.blockTicks.hasScheduledTick(from, state.block)) { diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembler.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembler.kt index 7ee1c79b1..31f00fd00 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembler.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembler.kt @@ -6,15 +6,20 @@ import net.minecraft.world.level.Level import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.state.BlockState import org.joml.Vector3d +import org.joml.Vector3dc import org.joml.Vector3i import org.joml.Vector3ic import org.valkyrienskies.core.api.ships.ServerShip import org.valkyrienskies.core.api.ships.Ship +import org.valkyrienskies.core.api.ships.getAttachment import org.valkyrienskies.core.impl.game.ShipTeleportDataImpl import org.valkyrienskies.mod.common.BlockStateInfo.onSetBlock import org.valkyrienskies.mod.common.dimensionId import org.valkyrienskies.mod.common.getShipObjectManagingPos import org.valkyrienskies.mod.common.shipObjectWorld +import org.valkyrienskies.mod.common.util.SplittingDisablerAttachment +import org.valkyrienskies.mod.common.util.toJOMLD +import org.valkyrienskies.mod.util.logger object ShipAssembler { @@ -31,11 +36,11 @@ object ShipAssembler { } - fun assembleToShip(level: Level, blocks: List, removeOriginal: Boolean, scale: Double): ServerShip { - assert(level is ServerLevel) { "Can't manage contraptions on client side!" } + fun assembleToShip(level: Level, blocks: List, removeOriginal: Boolean, scale: Double = 1.0, shouldDisableSplitting: Boolean = false): ServerShip { + assert(level is ServerLevel) { "Can't create ships clientside!" } val sLevel: ServerLevel = level as ServerLevel if (blocks.isEmpty()) { - throw IllegalArgumentException() + throw IllegalArgumentException("No blocks to assemble.") } val existingShip = sLevel.getShipObjectManagingPos(blocks.find { !sLevel.getBlockState(it).isAir } ?: throw IllegalArgumentException()) @@ -66,7 +71,10 @@ object ShipAssembler { val newShip: Ship = (level as ServerLevel).server.shipObjectWorld .createNewShipAtBlock(contraptionWorldPos, false, scale, level.dimensionId) - // Stone for safety reasons + if (shouldDisableSplitting) { + level.shipObjectWorld.loadedShips.getById(newShip.id)?.getAttachment()?.disableSplitting() + + } val contraptionShipPos = newShip.worldToShip.transformPosition(Vector3d(contraptionWorldPos.x.toDouble(),contraptionWorldPos.y.toDouble(),contraptionWorldPos.z.toDouble())) val contraptionBlockPos = BlockPos(contraptionShipPos.x.toInt(),contraptionShipPos.y.toInt(),contraptionShipPos.z.toInt()) @@ -79,14 +87,14 @@ object ShipAssembler { val relative: BlockPos = itPos.subtract( BlockPos(contraptionOGPos.x(),contraptionOGPos.y(),contraptionOGPos.z())) val shipPos: BlockPos = contraptionBlockPos.offset(relative) AssemblyUtil.copyBlock(level, itPos, shipPos) - if (relative == BlockPos.ZERO) centerBlockReplaced = true + if (relative.equals(BlockPos.ZERO)) centerBlockReplaced = true } } // If center block got not replaced, remove the stone block - if (!centerBlockReplaced) { - level.setBlock(contraptionBlockPos, Blocks.AIR.defaultBlockState(), 3) - } + // if (!centerBlockReplaced) { + // level.setBlock(contraptionBlockPos, Blocks.AIR.defaultBlockState(), 3) + // } // Remove original blocks if (removeOriginal) { @@ -104,9 +112,19 @@ object ShipAssembler { AssemblyUtil.updateBlock(level,itPos,shipPos,level.getBlockState(shipPos)) } + val spawnWorldPos: Vector3dc = AssemblyUtil.getMiddle(structureCornerMin.toJOMLD(), structureCornerMax.toJOMLD()).add(0.5, 0.5, 0.5) + + if (existingShip != null) { + sLevel.server.shipObjectWorld + .teleportShip(newShip as ServerShip, ShipTeleportDataImpl(existingShip.shipToWorld.transformPosition(spawnWorldPos, Vector3d()), existingShip.transform.shipToWorldRotation, existingShip.velocity, existingShip.omega, existingShip.chunkClaimDimension)) - sLevel.server.shipObjectWorld - .teleportShip(newShip as ServerShip, ShipTeleportDataImpl(Vector3d(contraptionWorldPos.x.toDouble(),contraptionWorldPos.y.toDouble(),contraptionWorldPos.z.toDouble()))) + } else { + sLevel.server.shipObjectWorld + .teleportShip(newShip as ServerShip, ShipTeleportDataImpl(spawnWorldPos)) + } + if (shouldDisableSplitting) { + level.shipObjectWorld.loadedShips.getById(newShip.id)?.getAttachment()?.enableSplitting() + } return newShip as ServerShip } diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembly.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembly.kt index b3ea1c640..e297942fc 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembly.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/assembly/ShipAssembly.kt @@ -2,26 +2,11 @@ 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.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.util.datastructures.DenseBlockPosSet -import org.valkyrienskies.mod.common.dimensionId -import org.valkyrienskies.mod.common.executeIf -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.toBlockPos -import org.valkyrienskies.mod.common.util.toJOML -import org.valkyrienskies.mod.common.vsCore -import org.valkyrienskies.mod.util.relocateBlock -import org.valkyrienskies.mod.util.updateBlock -@Deprecated("Use ShipAssembler.assembleToShip instead") +@Deprecated("Use [ShipAssembler.assembleToShip] instead") fun createNewShipWithBlocks( centerBlock: BlockPos, blocks: DenseBlockPosSet, level: ServerLevel ): ServerShip { diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/CommonHooksImpl.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/CommonHooksImpl.kt index 980a24c0d..490f306f5 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/CommonHooksImpl.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/hooks/CommonHooksImpl.kt @@ -10,9 +10,26 @@ import org.valkyrienskies.core.apigame.hooks.PlayState.CLIENT_TITLESCREEN import org.valkyrienskies.core.apigame.hooks.PlayState.SERVERSIDE import org.valkyrienskies.mod.common.ValkyrienSkiesMod import org.valkyrienskies.mod.common.shipObjectWorld +import org.valkyrienskies.mod.common.vsCore abstract class CommonHooksImpl : CoreHooksOut { + override var enableBlockEdgeConnectivity: Boolean + get() = vsCore.hooks.enableBlockEdgeConnectivity + set(value) {} + + override var enableBlockCornerConnectivity: Boolean + get() = vsCore.hooks.enableBlockCornerConnectivity + set(value) {} + + override var enableConnectivity: Boolean + get() = vsCore.hooks.enableConnectivity + set(value) {} + + override var enableWorldConnectivity: Boolean + get() = vsCore.hooks.enableWorldConnectivity + set(value) {} + override val playState: PlayState get() { if (!isPhysicalClient) { diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/AreaAssemblerItem.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/AreaAssemblerItem.kt new file mode 100644 index 000000000..51c64bdc4 --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/AreaAssemblerItem.kt @@ -0,0 +1,88 @@ +package org.valkyrienskies.mod.common.item + +import net.minecraft.Util +import net.minecraft.core.BlockPos +import net.minecraft.core.Vec3i +import net.minecraft.network.chat.TextComponent +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.InteractionResult +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.context.UseOnContext +import net.minecraft.world.level.block.state.BlockState +import org.joml.primitives.AABBi +import org.valkyrienskies.mod.common.assembly.ShipAssembler +import org.valkyrienskies.mod.common.dimensionId +import org.valkyrienskies.mod.common.getShipManagingPos +import org.valkyrienskies.mod.common.getShipObjectManagingPos +import org.valkyrienskies.mod.common.shipObjectWorld +import org.valkyrienskies.mod.common.util.toJOML +import java.util.function.DoubleSupplier + +class AreaAssemblerItem( + properties: Properties, private val scale: DoubleSupplier, private val minScaling: DoubleSupplier +) : Item(properties) { + + override fun isFoil(stack: ItemStack): Boolean { + return true + } + + override fun useOn(ctx: UseOnContext): InteractionResult { + val level = ctx.level as? ServerLevel ?: return super.useOn(ctx) + val blockPos = ctx.clickedPos + val blockState: BlockState = level.getBlockState(blockPos) + val item = ctx.itemInHand + + if (item.item !is AreaAssemblerItem) { + return InteractionResult.FAIL + } + + if (!level.isClientSide) { + if (!blockState.isAir) { + // Make a ship + val dimensionId = level.dimensionId + + if (item.tag != null && item.tag!!.contains("firstPosX")) { + val firstPosX = item.tag!!.getInt("firstPosX") + val firstPosY = item.tag!!.getInt("firstPosY") + val firstPosZ = item.tag!!.getInt("firstPosZ") + if (level.shipObjectWorld.isBlockInShipyard(blockPos.x, blockPos.y, blockPos.z, dimensionId) != level.shipObjectWorld.isBlockInShipyard(firstPosX, firstPosY, firstPosZ, dimensionId)) { + ctx.player?.sendMessage(TextComponent("Cannot assemble between ship and world!"), Util.NIL_UUID) + } else if (level.getShipObjectManagingPos(blockPos) != level.getShipObjectManagingPos(Vec3i(firstPosX, firstPosY, firstPosZ))) { + ctx.player?.sendMessage(TextComponent("Cannot assemble something between two ships!"), Util.NIL_UUID) + } else { + val blockAABB = AABBi(blockPos.toJOML(), Vec3i(firstPosX, firstPosY, firstPosZ).toJOML()) + blockAABB.correctBounds() + val blocks = ArrayList() + + for (x in blockAABB.minX..blockAABB.maxX) { + for (y in blockAABB.minY..blockAABB.maxY) { + for (z in blockAABB.minZ..blockAABB.maxZ) { + if (level.getBlockState(BlockPos(x, y, z)).isAir) { + continue + } + blocks.add(BlockPos(x, y, z)) + } + } + } + ctx.player?.sendMessage(TextComponent("Assembling (${blockPos.x}, ${blockPos.y}, ${blockPos.z}) to ($firstPosX, $firstPosY, $firstPosZ)!"), Util.NIL_UUID) + ShipAssembler.assembleToShip(level, blocks, true, scale.asDouble) + } + item.tag!!.remove("firstPosX") + item.tag!!.remove("firstPosY") + item.tag!!.remove("firstPosZ") + } else { + item.tag = item.orCreateTag.apply { + putInt("firstPosX", blockPos.x) + putInt("firstPosY", blockPos.y) + putInt("firstPosZ", blockPos.z) + } + ctx.player?.sendMessage( + TextComponent("First block selected: (${blockPos.x}, ${blockPos.y}, ${blockPos.z})"), Util.NIL_UUID) + } + } + } + + return super.useOn(ctx) + } +} diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ConnectionCheckerItem.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ConnectionCheckerItem.kt new file mode 100644 index 000000000..4c141aa5d --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ConnectionCheckerItem.kt @@ -0,0 +1,64 @@ +package org.valkyrienskies.mod.common.item + +import net.minecraft.Util +import net.minecraft.network.chat.TextComponent +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.InteractionResult +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.context.UseOnContext +import net.minecraft.world.level.block.state.BlockState +import org.valkyrienskies.mod.common.dimensionId +import org.valkyrienskies.mod.common.getShipManagingPos +import org.valkyrienskies.mod.common.shipObjectWorld +import java.util.function.DoubleSupplier + +class ConnectionCheckerItem( + properties: Properties, private val scale: DoubleSupplier, private val minScaling: DoubleSupplier +) : Item(properties) { + + override fun isFoil(stack: ItemStack): Boolean { + return true + } + + override fun useOn(ctx: UseOnContext): InteractionResult { + val level = ctx.level as? ServerLevel ?: return super.useOn(ctx) + val blockPos = ctx.clickedPos + val blockState: BlockState = level.getBlockState(blockPos) + val item = ctx.itemInHand + + if (item.item !is ConnectionCheckerItem) { + return InteractionResult.FAIL + } + + if (!level.isClientSide) { + val parentShip = ctx.level.getShipManagingPos(blockPos) + if (!blockState.isAir) { + // Make a ship + val dimensionId = level.dimensionId + + if (parentShip != null) { + if (item.tag != null && item.tag!!.contains("firstPosX")) { + val firstPosX = item.tag!!.getInt("firstPosX") + val firstPosY = item.tag!!.getInt("firstPosY") + val firstPosZ = item.tag!!.getInt("firstPosZ") + val connected = level.shipObjectWorld.isConnectedBySolid(blockPos.x, blockPos.y, blockPos.z, firstPosX, firstPosY, firstPosZ, dimensionId) + ctx.player?.sendMessage(TextComponent("Connected: $connected"), Util.NIL_UUID) + item.tag!!.remove("firstPosX") + item.tag!!.remove("firstPosY") + item.tag!!.remove("firstPosZ") + } else { + item.tag = item.orCreateTag.apply { + putInt("firstPosX", blockPos.x) + putInt("firstPosY", blockPos.y) + putInt("firstPosZ", blockPos.z) + } + ctx.player?.sendMessage(TextComponent("First block selected: (${blockPos.x}, ${blockPos.y}, ${blockPos.z})"), Util.NIL_UUID) + } + } + } + } + + return super.useOn(ctx) + } +} diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplitHandler.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplitHandler.kt new file mode 100644 index 000000000..48f34c8ee --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplitHandler.kt @@ -0,0 +1,181 @@ +package org.valkyrienskies.mod.common.util + +import net.minecraft.core.BlockPos +import net.minecraft.core.Vec3i +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.state.BlockState +import org.joml.primitives.AABBi +import org.valkyrienskies.core.api.ships.getAttachment +import org.valkyrienskies.core.api.world.connectivity.ConnectionStatus.CONNECTED +import org.valkyrienskies.core.api.world.connectivity.ConnectionStatus.DISCONNECTED +import org.valkyrienskies.core.util.expand +import org.valkyrienskies.mod.common.assembly.ShipAssembler +import org.valkyrienskies.mod.common.dimensionId +import org.valkyrienskies.mod.common.getShipObjectManagingPos +import org.valkyrienskies.mod.common.shipObjectWorld + +class SplitHandler(private val doEdges: Boolean, private val doCorners: Boolean) { + + fun split(level: Level, x: Int, y: Int, z: Int, prevBlockState: BlockState, newBlockState: BlockState) { + if (level is ServerLevel) { + val loadedShip = level.getShipObjectManagingPos(x shr 4, z shr 4) + if (loadedShip != null && loadedShip.getAttachment()?.canSplit() != false) { + if (!prevBlockState.isAir && newBlockState.isAir) { + val blockNeighbors: HashSet = HashSet() + + val shipBox = loadedShip.shipAABB?.expand(1, AABBi()) ?: return + + for (neighborOffset in getOffsets(doEdges, doCorners)) { + val neighborPos = BlockPos(x + neighborOffset.x, y + neighborOffset.y, z + neighborOffset.z) + val neighborState = level.getBlockState(neighborPos) + if (!neighborState.isAir && neighborPos != BlockPos(x, y, z) && shipBox.containsPoint(neighborPos.toJOML())) { + blockNeighbors.add(neighborPos) + } + } + + if (blockNeighbors.isNotEmpty()) { + //find largest remaining component + var largestComponentNode: BlockPos = blockNeighbors.first() + var largestComponentSize: Long = -1 + + for (neighborPos in blockNeighbors) { + if (level.shipObjectWorld.isIsolatedSolid(neighborPos.x, neighborPos.y, neighborPos.z, level.dimensionId) == DISCONNECTED) { + val size = level.shipObjectWorld.getSolidComponentSize(neighborPos.x, neighborPos.y, neighborPos.z, level.dimensionId) + if (size > largestComponentSize) { + largestComponentNode = neighborPos + largestComponentSize = size + } + } + } + + if (largestComponentSize == -1L) { + return + } + + blockNeighbors.remove(largestComponentNode) + + // use largest as base + + //find all disconnected components + + val disconnected = HashSet() + for (neighborPos in blockNeighbors) { + if (level.shipObjectWorld.isIsolatedSolid(neighborPos.x, neighborPos.y, neighborPos.z, level.dimensionId) == DISCONNECTED) { + if (neighborPos != largestComponentNode) { + if (level.shipObjectWorld.isConnectedBySolid(largestComponentNode.x, largestComponentNode.y, largestComponentNode.z, neighborPos.x, neighborPos.y, neighborPos.z, level.dimensionId) == DISCONNECTED) { + disconnected.add(neighborPos) + } + } + } + } + + //check if any disconnected components are connected + val toIgnore: HashSet = HashSet() + toIgnore.add(BlockPos(x, y, z)) + for (component in disconnected) { + for (otherComponent in disconnected) { + if (component == otherComponent) { + continue + } + if (level.shipObjectWorld.isConnectedBySolid(component.x, component.y, component.z, otherComponent.x, otherComponent.y, otherComponent.z, level.dimensionId) == CONNECTED) { + if (!toIgnore.contains(otherComponent) && !toIgnore.contains(component)) { + toIgnore.add(component) + } + } + } + } + + disconnected.removeAll(toIgnore) + + if (disconnected.isEmpty()) { + return + } else { + loadedShip.getAttachment(SplittingDisablerAttachment::class.java)?.disableSplitting() + } + + //begin the DFSing + + val toAssemble = HashSet>() + + for (starter in disconnected) { + val visited = HashSet() + val queuedPositions = HashSet() + queuedPositions.add(starter) + + while (queuedPositions.isNotEmpty()) { + val current = queuedPositions.first() + queuedPositions.remove(current) + visited.add(current) + val toCheck = HashSet() + for (offset in getOffsets(doEdges, doCorners)) { + toCheck.add( + BlockPos(current.x + offset.x, current.y + offset.y, current.z + offset.z) + ) + } + for (check in toCheck) { + if (!visited.contains(check) && !level.getBlockState(check).isAir) { + queuedPositions.add(check) + } + } + } + //if we have visited all blocks in the component, we can split it + toAssemble.add(visited.toList()) + } + + if (toAssemble.isEmpty()) { + loadedShip.getAttachment(SplittingDisablerAttachment::class.java)?.enableSplitting() + return + } + + for (component in toAssemble) { + ShipAssembler.assembleToShip(level, component, true, 1.0, true) + } + + loadedShip.getAttachment(SplittingDisablerAttachment::class.java)?.enableSplitting() + } + } + } + } + } + + companion object { + val offsetsToCheck: ArrayList = arrayListOf( + Vec3i(1, 0, 0), + Vec3i(-1, 0, 0), + Vec3i(0, 1, 0), + Vec3i(0, -1, 0), + Vec3i(0, 0, 1), + Vec3i(0, 0, -1) + ) + + fun getOffsets(doEdges: Boolean, doCorners: Boolean): ArrayList { + val list = ArrayList(offsetsToCheck) + if (doEdges) { //later: check block edge connectivity config + list.add(Vec3i(1, 1, 0)) + list.add(Vec3i(1, -1, 0)) + list.add(Vec3i(-1, 1, 0)) + list.add(Vec3i(-1, -1, 0)) + list.add(Vec3i(1, 0, 1)) + list.add(Vec3i(1, 0, -1)) + list.add(Vec3i(-1, 0, 1)) + list.add(Vec3i(-1, 0, -1)) + list.add(Vec3i(0, 1, 1)) + list.add(Vec3i(0, 1, -1)) + list.add(Vec3i(0, -1, 1)) + list.add(Vec3i(0, -1, -1)) + } + if (doCorners) { //later: check block corner connectivity config + list.add(Vec3i(1, 1, 1)) + list.add(Vec3i(1, 1, -1)) + list.add(Vec3i(1, -1, 1)) + list.add(Vec3i(1, -1, -1)) + list.add(Vec3i(-1, 1, 1)) + list.add(Vec3i(-1, 1, -1)) + list.add(Vec3i(-1, -1, 1)) + list.add(Vec3i(-1, -1, -1)) + } + return list + } + } +} diff --git a/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplittingDisablerAttachment.kt b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplittingDisablerAttachment.kt new file mode 100644 index 000000000..42959258a --- /dev/null +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/util/SplittingDisablerAttachment.kt @@ -0,0 +1,15 @@ +package org.valkyrienskies.mod.common.util + +data class SplittingDisablerAttachment(private var splitt: Boolean) { + fun enableSplitting() { + splitt = true + } + + fun disableSplitting() { + splitt = false + } + + fun canSplit(): Boolean { + return splitt + } +} diff --git a/common/src/main/resources/assets/valkyrienskies/lang/en_us.json b/common/src/main/resources/assets/valkyrienskies/lang/en_us.json index ddd33df22..96ccb3af3 100644 --- a/common/src/main/resources/assets/valkyrienskies/lang/en_us.json +++ b/common/src/main/resources/assets/valkyrienskies/lang/en_us.json @@ -7,6 +7,8 @@ "block.valkyrienskies.test_flap": "Debug Flap", "block.valkyrienskies.test_wing": "Debug Wing", "item.valkyrienskies.ship_creator": "Ship Creator", + "item.valkyrienskies.area_assembler": "Area Assembler", + "item.valkyrienskies.connection_checker": "Connection Checker", "item.valkyrienskies.ship_creator_smaller": "Mini Ship Creator", "item.valkyrienskies.physics_entity_creator": "Physics Entity Creator", "argument.valkyrienskies.ship.no_found": "Ship not found", diff --git a/common/src/main/resources/assets/valkyrienskies/models/item/area_assembler.json b/common/src/main/resources/assets/valkyrienskies/models/item/area_assembler.json new file mode 100644 index 000000000..f0dc3b971 --- /dev/null +++ b/common/src/main/resources/assets/valkyrienskies/models/item/area_assembler.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "minecraft:item/stick" + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/valkyrienskies/models/item/connection_checker.json b/common/src/main/resources/assets/valkyrienskies/models/item/connection_checker.json new file mode 100644 index 000000000..f0dc3b971 --- /dev/null +++ b/common/src/main/resources/assets/valkyrienskies/models/item/connection_checker.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "minecraft:item/stick" + } +} \ No newline at end of file diff --git a/fabric/src/main/kotlin/org/valkyrienskies/mod/fabric/common/ValkyrienSkiesModFabric.kt b/fabric/src/main/kotlin/org/valkyrienskies/mod/fabric/common/ValkyrienSkiesModFabric.kt index 5ae9cea1f..cdee8650b 100644 --- a/fabric/src/main/kotlin/org/valkyrienskies/mod/fabric/common/ValkyrienSkiesModFabric.kt +++ b/fabric/src/main/kotlin/org/valkyrienskies/mod/fabric/common/ValkyrienSkiesModFabric.kt @@ -42,6 +42,8 @@ import org.valkyrienskies.mod.common.entity.ShipMountingEntity import org.valkyrienskies.mod.common.entity.VSPhysicsEntity import org.valkyrienskies.mod.common.entity.handling.VSEntityManager import org.valkyrienskies.mod.common.hooks.VSGameEvents +import org.valkyrienskies.mod.common.item.AreaAssemblerItem +import org.valkyrienskies.mod.common.item.ConnectionCheckerItem import org.valkyrienskies.mod.common.item.PhysicsEntityCreatorItem import org.valkyrienskies.mod.common.item.ShipAssemblerItem import org.valkyrienskies.mod.common.item.ShipCreatorItem @@ -63,12 +65,22 @@ class ValkyrienSkiesModFabric : ModInitializer { ValkyrienSkiesMod.TEST_FLAP = TestFlapBlock ValkyrienSkiesMod.TEST_WING = TestWingBlock ValkyrienSkiesMod.TEST_SPHERE = TestSphereBlock + ValkyrienSkiesMod.CONNECTION_CHECKER_ITEM = ConnectionCheckerItem( + Properties().tab(CreativeModeTab.TAB_MISC), + { 1.0 }, + { VSGameConfig.SERVER.minScaling } + ) ValkyrienSkiesMod.SHIP_CREATOR_ITEM = ShipCreatorItem( Properties().tab(CreativeModeTab.TAB_MISC), { 1.0 }, { VSGameConfig.SERVER.minScaling } ) ValkyrienSkiesMod.SHIP_ASSEMBLER_ITEM = ShipAssemblerItem(Properties().tab(CreativeModeTab.TAB_MISC)) + ValkyrienSkiesMod.AREA_ASSEMBLER_ITEM = AreaAssemblerItem( + Properties().tab(CreativeModeTab.TAB_MISC), + { 1.0 }, + { VSGameConfig.SERVER.minScaling } + ) ValkyrienSkiesMod.SHIP_CREATOR_ITEM_SMALLER = ShipCreatorItem( Properties().tab(CreativeModeTab.TAB_MISC), { VSGameConfig.SERVER.miniShipSize }, @@ -114,6 +126,14 @@ class ValkyrienSkiesModFabric : ModInitializer { registerBlockAndItem("test_flap", ValkyrienSkiesMod.TEST_FLAP) registerBlockAndItem("test_wing", ValkyrienSkiesMod.TEST_WING) registerBlockAndItem("test_sphere", ValkyrienSkiesMod.TEST_SPHERE) + Registry.register( + Registry.ITEM, ResourceLocation(ValkyrienSkiesMod.MOD_ID, "connection_checker"), + ValkyrienSkiesMod.CONNECTION_CHECKER_ITEM + ) + Registry.register( + Registry.ITEM, ResourceLocation(ValkyrienSkiesMod.MOD_ID, "area_assembler"), + ValkyrienSkiesMod.AREA_ASSEMBLER_ITEM + ) Registry.register( Registry.ITEM, ResourceLocation(ValkyrienSkiesMod.MOD_ID, "ship_assembler"), ValkyrienSkiesMod.SHIP_ASSEMBLER_ITEM diff --git a/forge/src/main/kotlin/org/valkyrienskies/mod/forge/common/ValkyrienSkiesModForge.kt b/forge/src/main/kotlin/org/valkyrienskies/mod/forge/common/ValkyrienSkiesModForge.kt index b72fa2397..438bfec90 100644 --- a/forge/src/main/kotlin/org/valkyrienskies/mod/forge/common/ValkyrienSkiesModForge.kt +++ b/forge/src/main/kotlin/org/valkyrienskies/mod/forge/common/ValkyrienSkiesModForge.kt @@ -46,6 +46,8 @@ import org.valkyrienskies.mod.common.entity.ShipMountingEntity import org.valkyrienskies.mod.common.entity.VSPhysicsEntity import org.valkyrienskies.mod.common.entity.handling.VSEntityManager import org.valkyrienskies.mod.common.hooks.VSGameEvents +import org.valkyrienskies.mod.common.item.AreaAssemblerItem +import org.valkyrienskies.mod.common.item.ConnectionCheckerItem import org.valkyrienskies.mod.common.item.PhysicsEntityCreatorItem import org.valkyrienskies.mod.common.item.ShipAssemblerItem import org.valkyrienskies.mod.common.item.ShipCreatorItem @@ -62,8 +64,10 @@ class ValkyrienSkiesModForge { private val TEST_FLAP_REGISTRY: RegistryObject private val TEST_WING_REGISTRY: RegistryObject private val TEST_SPHERE_REGISTRY: RegistryObject + private val CONNECTION_CHECKER_ITEM_REGISTRY: RegistryObject private val SHIP_CREATOR_ITEM_REGISTRY: RegistryObject private val SHIP_CREATOR_SMALLER_ITEM_REGISTRY: RegistryObject + private val AREA_ASSEMBLER_ITEM_REGISTRY: RegistryObject private val PHYSICS_ENTITY_CREATOR_ITEM_REGISTRY: RegistryObject private val SHIP_MOUNTING_ENTITY_REGISTRY: RegistryObject> private val PHYSICS_ENTITY_TYPE_REGISTRY: RegistryObject> @@ -119,6 +123,14 @@ class ValkyrienSkiesModForge { { 1.0 }, { VSGameConfig.SERVER.minScaling }) } + CONNECTION_CHECKER_ITEM_REGISTRY = + ITEMS.register("connection_checker") { + ConnectionCheckerItem( + Properties().tab(CreativeModeTab.TAB_MISC), + { 1.0 }, + { VSGameConfig.SERVER.minScaling } + ) + } SHIP_CREATOR_SMALLER_ITEM_REGISTRY = ITEMS.register("ship_creator_smaller") { ShipCreatorItem( @@ -127,7 +139,13 @@ class ValkyrienSkiesModForge { { VSGameConfig.SERVER.minScaling } ) } - + AREA_ASSEMBLER_ITEM_REGISTRY = ITEMS.register("area_assembler") { + AreaAssemblerItem( + Properties().tab(CreativeModeTab.TAB_MISC), + { 1.0 }, + { VSGameConfig.SERVER.minScaling } + ) + } PHYSICS_ENTITY_CREATOR_ITEM_REGISTRY = ITEMS.register("physics_entity_creator") { PhysicsEntityCreatorItem( @@ -206,5 +224,7 @@ class ValkyrienSkiesModForge { ValkyrienSkiesMod.PHYSICS_ENTITY_TYPE = PHYSICS_ENTITY_TYPE_REGISTRY.get() ValkyrienSkiesMod.SHIP_ASSEMBLER_ITEM = SHIP_ASSEMBLER_ITEM_REGISTRY.get() ValkyrienSkiesMod.TEST_HINGE_BLOCK_ENTITY_TYPE = TEST_HINGE_BLOCK_ENTITY_TYPE_REGISTRY.get() + ValkyrienSkiesMod.CONNECTION_CHECKER_ITEM = CONNECTION_CHECKER_ITEM_REGISTRY.get() + ValkyrienSkiesMod.AREA_ASSEMBLER_ITEM = AREA_ASSEMBLER_ITEM_REGISTRY.get() } } diff --git a/gradle.properties b/gradle.properties index e1e2e5bb4..7c52e1b13 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-f-build.1333+mc1.18.2 flywheel_version_fabric=0.6.9-38 createbigcannons_version= 0.5.2-nightly-e815ca4 -vs_core_version=1.1.0+bf19ff814e +vs_core_version=1.1.0+13b73ea871 # 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