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 471c709de..c88be2f65 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/BlockStateInfoProvider.kt @@ -3,10 +3,8 @@ package org.valkyrienskies.mod.common import com.mojang.serialization.Lifecycle import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.core.MappedRegistry import net.minecraft.core.Registry -import net.minecraft.core.Vec3i import net.minecraft.resources.ResourceKey import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerLevel @@ -15,15 +13,10 @@ import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState import org.valkyrienskies.core.api.ships.Wing import org.valkyrienskies.core.api.ships.WingManager -import org.valkyrienskies.core.api.world.connectivity.ConnectionStatus.CONNECTED -import org.valkyrienskies.core.api.world.connectivity.ConnectionStatus.DISCONNECTED import org.valkyrienskies.core.apigame.world.chunks.BlockType -import org.valkyrienskies.core.util.expand -import org.valkyrienskies.mod.common.assembly.ShipAssembler import org.valkyrienskies.mod.common.block.WingBlock import org.valkyrienskies.mod.common.config.MassDatapackResolver import org.valkyrienskies.mod.common.hooks.VSGameEvents -import org.valkyrienskies.mod.common.util.toJOML import java.util.function.IntFunction // Other mods can then provide weights and types based on their added content @@ -142,172 +135,9 @@ object BlockStateInfo { newBlockMass ) - if (level is ServerLevel) { - val loadedShip = level.getShipObjectManagingPos(x shr 4, z shr 4) - if (loadedShip != null) { - if (!prevBlockState.isAir && newBlockState.isAir){ - val blockNeighbors: HashSet = HashSet() - for (dir in Direction.entries) { - val shipBox = loadedShip.shipAABB?.expand(1) ?: continue - val neighborPos = BlockPos(x, y, z).relative(dir) - val neighborState = level.getBlockState(neighborPos) - if (!neighborState.isAir && neighborPos != BlockPos(x, y, z) && shipBox.containsPoint(neighborPos.toJOML())) { - blockNeighbors.add(neighborPos) - } - if (true) { //later: check block edge connectivity config - for (secondDir in Direction.entries) { - if (dir.axis != secondDir.axis) { - val secondNeighborPos = neighborPos.relative(secondDir) - val secondNeighborState = level.getBlockState(secondNeighborPos) - if (!secondNeighborState.isAir && secondNeighborPos != BlockPos(x, y, z) && shipBox.containsPoint(neighborPos.toJOML())) { - blockNeighbors.add(secondNeighborPos) - } - if (true) { //later: check block corner connectivity config - for (thirdDir in Direction.entries) { - if (dir.axis != secondDir.axis && dir.axis != thirdDir.axis && secondDir.axis != thirdDir.axis) { - val thirdNeighborPos = secondNeighborPos.relative(thirdDir) - val thirdNeighborState = level.getBlockState(thirdNeighborPos) - if (!thirdNeighborState.isAir && thirdNeighborPos != BlockPos(x, y, z) && shipBox.containsPoint(neighborPos.toJOML())) { - blockNeighbors.add(thirdNeighborPos) - } - } - } - } - } - } - } - } - - if (blockNeighbors.isNotEmpty()) { - //find largest remaining component - var largestComponentNode: BlockPos = blockNeighbors.first() - var largestComponentSize: Long = -1 - - for (neighborPos in blockNeighbors) { - print(level.shipObjectWorld.isIsolatedSolid(neighborPos.x, neighborPos.y, neighborPos.z, level.dimensionId)) - 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) { - val component = HashSet() - disconnected.add(neighborPos) - } - println("this is " + level.shipObjectWorld.isConnectedBySolid(largestComponentNode.x, largestComponentNode.y, largestComponentNode.z, neighborPos.x, neighborPos.y, neighborPos.z, level.dimensionId).toString()) - } - } - } - - //check if any disconnected components are connected - val toIgnore: HashSet = HashSet() - 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 - } - - //begin the DFSing - - 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) - ) - if (true) { //later: check block edge connectivity config - offsetsToCheck.add(Vec3i(1, 1, 0)) - offsetsToCheck.add(Vec3i(1, -1, 0)) - offsetsToCheck.add(Vec3i(-1, 1, 0)) - offsetsToCheck.add(Vec3i(-1, -1, 0)) - offsetsToCheck.add(Vec3i(1, 0, 1)) - offsetsToCheck.add(Vec3i(1, 0, -1)) - offsetsToCheck.add(Vec3i(-1, 0, 1)) - offsetsToCheck.add(Vec3i(-1, 0, -1)) - offsetsToCheck.add(Vec3i(0, 1, 1)) - offsetsToCheck.add(Vec3i(0, 1, -1)) - offsetsToCheck.add(Vec3i(0, -1, 1)) - offsetsToCheck.add(Vec3i(0, -1, -1)) - } - if (true) { //later: check block corner connectivity config - offsetsToCheck.add(Vec3i(1, 1, 1)) - offsetsToCheck.add(Vec3i(1, 1, -1)) - offsetsToCheck.add(Vec3i(1, -1, 1)) - offsetsToCheck.add(Vec3i(1, -1, -1)) - offsetsToCheck.add(Vec3i(-1, 1, 1)) - offsetsToCheck.add(Vec3i(-1, 1, -1)) - offsetsToCheck.add(Vec3i(-1, -1, 1)) - offsetsToCheck.add(Vec3i(-1, -1, -1)) - } - - 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 offsetsToCheck) { - 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()) { - return - } - - for (component in toAssemble) { - ShipAssembler.assembleToShip(level, component, true, 1.0) - } - } - } - } + // todo check if splitting is enabled, dolt + if (true) { + 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 8adf43287..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" @@ -28,6 +30,7 @@ object ValkyrienSkiesMod { 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 @@ -42,6 +45,9 @@ object ValkyrienSkiesMod { @JvmStatic val vsCoreClient get() = vsCore as VSCoreClient + @JvmStatic + lateinit var splitHandler: SplitHandler + fun init(core: VSCore) { this.vsCore = core @@ -50,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 2f3b7e397..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 @@ -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) { 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 c7af8af27..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()) @@ -84,9 +92,9 @@ object ShipAssembler { } // 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/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 index db86a4a09..4c141aa5d 100644 --- a/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ConnectionCheckerItem.kt +++ b/common/src/main/kotlin/org/valkyrienskies/mod/common/item/ConnectionCheckerItem.kt @@ -53,7 +53,7 @@ class ConnectionCheckerItem( putInt("firstPosY", blockPos.y) putInt("firstPosZ", blockPos.z) } - ctx.player?.sendMessage(TextComponent("First block selected: (${blockPos.x}, ${blockPos.y}, ${blockPos.z}"), Util.NIL_UUID) + ctx.player?.sendMessage(TextComponent("First block selected: (${blockPos.x}, ${blockPos.y}, ${blockPos.z})"), Util.NIL_UUID) } } } 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 49a430a42..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,7 @@ "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", 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/fabric/src/main/kotlin/org/valkyrienskies/mod/fabric/common/ValkyrienSkiesModFabric.kt b/fabric/src/main/kotlin/org/valkyrienskies/mod/fabric/common/ValkyrienSkiesModFabric.kt index 55484159c..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,7 @@ 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 @@ -75,6 +76,11 @@ class ValkyrienSkiesModFabric : ModInitializer { { 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 }, @@ -124,6 +130,10 @@ class ValkyrienSkiesModFabric : ModInitializer { 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 601c4dd57..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,7 @@ 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 @@ -66,6 +67,7 @@ class ValkyrienSkiesModForge { 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> @@ -137,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( @@ -217,5 +225,6 @@ class ValkyrienSkiesModForge { 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 d496f87d5..0396b7a8c 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+9d576c0e71 +vs_core_version=1.1.0+6df41559a1 # 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