Functional cell rootSource mechanics

This commit is contained in:
DBotThePony 2024-04-22 17:19:05 +07:00
parent 74bbc58c60
commit b50f356f7e
Signed by: DBot
GPG Key ID: DCC23B5715498507
5 changed files with 140 additions and 13 deletions

View File

@ -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<Pair<Vector2i, ServerChunk?>>()
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<TileEntity>()
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)) {

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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()
}

View File

@ -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<ServerChunk.ITicket>()
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<Vector2i>): Boolean {
val toRemove = ArrayList<Vector2i>()
val toPlace = ArrayList<Vector2i>()
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 {