diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt index 18274dee..973854b6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.server.world import com.google.gson.JsonElement import com.google.gson.JsonObject import it.unimi.dsi.fastutil.ints.IntArraySet +import it.unimi.dsi.fastutil.objects.ObjectArrayList import it.unimi.dsi.fastutil.objects.ObjectArraySet import kotlinx.coroutines.async import kotlinx.coroutines.future.asCompletableFuture @@ -51,6 +52,7 @@ import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.RejectedExecutionException import java.util.concurrent.TimeUnit import java.util.function.Supplier +import java.util.stream.Collectors class ServerWorld private constructor( val server: StarboundServer, @@ -167,22 +169,39 @@ class ServerWorld private constructor( if (damage.amount <= 0.0) return TileDamageResult.NONE - val actualPositions = positions.stream() - .map { geometry.wrap(it) } - .distinct() - .map { it to chunkMap[geometry.chunkFromCell(it)] } - .toList() + val actualPositions = ObjectArraySet>() + + for (pos in positions) { + val wrapped = geometry.wrap(pos) + val chunk = chunkMap[geometry.chunkFromCell(wrapped)] + + if (chunk != null) { + val cell = chunk.getCell(wrapped - chunk.pos.tile) + + if (cell.rootSource != null) { + actualPositions.add(geometry.wrap(cell.rootSource!!) to chunk) + } else { + actualPositions.add(wrapped to chunk) + } + } else { + actualPositions.add(wrapped to chunk) + } + } var topMost = TileDamageResult.NONE - val damagedEntities = ObjectArraySet() for ((pos, chunk) in actualPositions) { var damage = damage var tileEntityResult = TileDamageResult.NONE - if (chunk?.getCell(pos - chunk.pos.tile)?.dungeonId in protectedDungeonIDs) - damage = damage.copy(type = TileDamageType.PROTECTED) + val getCell = chunk?.getCell(pos - chunk.pos.tile) + + if (getCell != null) { + if (getCell.dungeonId in protectedDungeonIDs) { + damage = damage.copy(type = TileDamageType.PROTECTED) + } + } if (!isBackground) { for (entity in entitiesAtTile(pos)) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableCell.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableCell.kt index cc888de5..b7db69c8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableCell.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableCell.kt @@ -33,6 +33,6 @@ data class ImmutableCell( } override fun mutable(): MutableCell { - return MutableCell(foreground.mutable(), background.mutable(), liquid.mutable(), dungeonId, blockBiome, envBiome, biomeTransition) + return MutableCell(foreground.mutable(), background.mutable(), liquid.mutable(), dungeonId, blockBiome, envBiome, biomeTransition, rootSource) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableCell.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableCell.kt index e977b476..e1ccae00 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableCell.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableCell.kt @@ -55,7 +55,7 @@ data class MutableCell( } override fun immutable(): ImmutableCell { - return POOL.intern(ImmutableCell(foreground.immutable(), background.immutable(), liquid.immutable(), dungeonId, blockBiome, envBiome, biomeTransition)) + return POOL.intern(ImmutableCell(foreground.immutable(), background.immutable(), liquid.immutable(), dungeonId, blockBiome, envBiome, biomeTransition, rootSource)) } override fun mutable(): MutableCell { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/PlantEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/PlantEntity.kt index e315f75a..32e6f5bc 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/PlantEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/PlantEntity.kt @@ -654,9 +654,10 @@ class PlantEntity() : TileEntity() { if (world.getCell(root).foreground.material.isEmptyTile) { if (fallsWhenDead) { breakAtPosition(tilePosition, position) - } else { - remove(RemovalReason.DYING) } + + remove(RemovalReason.DYING) + return } } } @@ -672,7 +673,7 @@ class PlantEntity() : TileEntity() { this.metaBoundingBox = this.calculatedBoundingBox + this.position this.occupySpaces = this.calculatedOccupySpaces.stream().map { it + tilePosition }.collect(ImmutableSet.toImmutableSet()) this.roots = this.calculatedRoots.stream().map { it + tilePosition }.collect(ImmutableSet.toImmutableSet()) - markSpacesDirty() + markRootsDirty() updateSpatialPosition() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt index b5d2cee9..b9d5ba63 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt @@ -113,12 +113,14 @@ abstract class TileEntity : AbstractEntity() { protected open fun onPositionUpdated() { updateSpatialPosition() markSpacesDirty() + markRootsDirty() } override fun onJoinWorld(world: World<*, *>) { super.onJoinWorld(world) updateSpatialPosition() markSpacesDirty() + markRootsDirty() } override fun onRemove(world: World<*, *>, reason: RemovalReason) { @@ -157,6 +159,41 @@ abstract class TileEntity : AbstractEntity() { } } } + + // remove roots + if (!updateRoots(listOf())) { + // we were unable to remove all roots... + // create a task to try not to leave world in inconsistent state! + val currentRoots = currentRoots + val tilePosition = tilePosition + + world.eventLoop.scope.launch { + world as ServerWorld + val tickets = ArrayList() + + try { + currentRoots.forEach { p -> + tickets.add(world.permanentChunkTicket(world.geometry.chunkFromCell(p.x, p.y), ChunkState.EMPTY).await() ?: return@forEach) + } + + tickets.forEach { it.chunk.await() } + + for (space in currentRoots) { + val cell = world.getCell(space).mutable() + + if (cell.rootSource == tilePosition) { + cell.rootSource = null + } + + if (!world.setCell(space, cell)) { + LOGGER.warn("Unable to clear root source at ${space}, world left in inconsistent state.") + } + } + } finally { + tickets.forEach { it.cancel() } + } + } + } } } @@ -222,6 +259,62 @@ abstract class TileEntity : AbstractEntity() { return clear } + protected fun updateRoots(desired: Collection): Boolean { + val toRemove = ArrayList() + val toPlace = ArrayList() + + for (current in currentRoots) { + if (current !in desired) { + toRemove.add(current) + } + } + + for (pos in desired) { + if (pos !in currentRoots) { + toPlace.add(pos) + } + } + + var clear = true + + for (pos in toRemove) { + val cell = world.getCell(pos).mutable() + + if (cell.rootSource == tilePosition) { + cell.rootSource = null + + if (world.setCell(pos, cell)) { + currentRoots.remove(pos) + } else { + clear = false + } + } else { + currentRoots.remove(pos) + } + } + + for (pos in toPlace) { + val cell = world.getCell(pos).mutable() + + if (cell.rootSource == null) { + cell.rootSource = tilePosition + + if (world.setCell(pos, cell)) { + currentRoots.add(pos) + } else { + clear = false + } + } else { + // bugger, someone rooted onto our tile! + // will try again... + // TODO: however, shouldn't we just ignore this and move on? + clear = false + } + } + + return clear + } + fun updateMaterialSpacesNow() { needToUpdateSpaces = false @@ -232,12 +325,26 @@ abstract class TileEntity : AbstractEntity() { } } + fun updateRootsNow() { + needToUpdateRoots = false + + // only server can update entity tiles + // even if this tile entity is owned by client + if (world.isServer) { + needToUpdateRoots = !updateRoots(roots) + } + } + override fun tick(delta: Double) { super.tick(delta) if (world.isServer && needToUpdateSpaces) { updateMaterialSpacesNow() } + + if (world.isServer && needToUpdateRoots) { + updateRootsNow() + } } companion object {