Grass generation

This commit is contained in:
DBotThePony 2024-04-05 23:35:35 +07:00
parent a839af1041
commit 6f6150475e
Signed by: DBot
GPG Key ID: DCC23B5715498507
11 changed files with 232 additions and 50 deletions

View File

@ -24,7 +24,7 @@ val Registry.Ref<TileDefinition>.isObjectPlatformTile: Boolean
get() = entry == BuiltinMetaMaterials.OBJECT_PLATFORM get() = entry == BuiltinMetaMaterials.OBJECT_PLATFORM
val Registry.Entry<TileDefinition>.isEmptyTile: Boolean val Registry.Entry<TileDefinition>.isEmptyTile: Boolean
get() = this == BuiltinMetaMaterials.EMPTY get() = this == BuiltinMetaMaterials.EMPTY || this == BuiltinMetaMaterials.NULL
val Registry.Entry<TileDefinition>.isNullTile: Boolean val Registry.Entry<TileDefinition>.isNullTile: Boolean
get() = this == BuiltinMetaMaterials.NULL get() = this == BuiltinMetaMaterials.NULL
@ -52,6 +52,21 @@ val Registry.Entry<LiquidDefinition>.isEmptyLiquid: Boolean
val Registry.Ref<LiquidDefinition>.isEmptyLiquid: Boolean val Registry.Ref<LiquidDefinition>.isEmptyLiquid: Boolean
get() = entry == null || entry == BuiltinMetaMaterials.NO_LIQUID get() = entry == null || entry == BuiltinMetaMaterials.NO_LIQUID
// these are hardcoded way harder than any Hard-Coder:tm:
// considering there is no way you gonna mod-in this many (16 bit uint) dungeons
const val NO_DUNGEON_ID = 65535
const val SPAWN_DUNGEON_ID = 65534
const val MICRO_DUNGEON_ID = 65533
// meta dungeon signalling player built structures
const val ARTIFICIAL_DUNGEON_ID = 65532
// indicates a block that has been destroyed
const val DESTROYED_BLOCK_ID = 65531
// dungeonId for zero-g areas with and without tile protection
const val ZERO_GRAVITY_DUNGEON_ID = 65525
const val PROTECTED_ZERO_GRAVITY_DUNGEON_ID = 65524
const val FIRST_RESERVED_DUNGEON_ID = 65520
object BuiltinMetaMaterials { object BuiltinMetaMaterials {
private fun make(id: Int, name: String, collisionType: CollisionType) = Registries.tiles.add(name, id, TileDefinition( private fun make(id: Int, name: String, collisionType: CollisionType) = Registries.tiles.add(name, id, TileDefinition(
materialId = id, materialId = id,
@ -117,7 +132,7 @@ object BuiltinMetaMaterials {
OBJECT_PLATFORM, OBJECT_PLATFORM,
) )
val EMPTY_MOD = makeMod(65535, "empty") val EMPTY_MOD = makeMod(65535, "none")
val BIOME_MOD = makeMod(65534, "biome") val BIOME_MOD = makeMod(65534, "biome")
val UNDERGROUND_BIOME_MOD = makeMod(65533, "underground_biome") val UNDERGROUND_BIOME_MOD = makeMod(65533, "underground_biome")

View File

@ -1,32 +1,27 @@
package ru.dbotthepony.kstarbound.defs.world package ru.dbotthepony.kstarbound.defs.world
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.Scheduler
import com.google.gson.JsonObject import com.google.gson.JsonObject
import ru.dbotthepony.kommons.arrays.Object2DArray
import ru.dbotthepony.kommons.util.AABB
import ru.dbotthepony.kommons.util.AABBi import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.Globals import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
import ru.dbotthepony.kstarbound.defs.tile.isNullTile
import ru.dbotthepony.kstarbound.defs.tile.supportsModifier
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.math.quintic2 import ru.dbotthepony.kstarbound.math.quintic2
import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.Universe import ru.dbotthepony.kstarbound.world.Universe
import ru.dbotthepony.kstarbound.world.UniversePos import ru.dbotthepony.kstarbound.world.UniversePos
import ru.dbotthepony.kstarbound.world.WorldGeometry import ru.dbotthepony.kstarbound.world.WorldGeometry
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kstarbound.world.physics.Poly import ru.dbotthepony.kstarbound.world.physics.Poly
import java.time.Duration
import java.util.random.RandomGenerator import java.util.random.RandomGenerator
class WorldTemplate(val geometry: WorldGeometry) { class WorldTemplate(val geometry: WorldGeometry) {
@ -156,6 +151,38 @@ class WorldTemplate(val geometry: WorldGeometry) {
return geometry.size.y / 2 return geometry.size.y / 2
} }
data class PotentialBiomeItems(
// Potential items that would spawn at the given block assuming it is at
val surfaceBiomeItems: List<BiomePlaceables.DistributionItem>,
// ... Or on a cave surface.
val caveSurfaceBiomeItems: List<BiomePlaceables.DistributionItem>,
// ... Or on a cave ceiling.
val caveCeilingBiomeItems: List<BiomePlaceables.DistributionItem>,
// ... Or on a cave background wall.
val caveBackgroundBiomeItems: List<BiomePlaceables.DistributionItem>,
// ... Or in the ocean
val oceanItems: List<BiomePlaceables.DistributionItem>,
)
fun potentialBiomeItemsAt(x: Int, y: Int): PotentialBiomeItems {
val thisBlockBiome = cellInfo(geometry.x.cell(x), geometry.y.cell(y)).blockBiome
val lowerBlockBiome = cellInfo(geometry.x.cell(x), geometry.y.cell(y - 1)).blockBiome
val upperBlockBiome = cellInfo(geometry.x.cell(x), geometry.y.cell(y + 1)).blockBiome
return PotentialBiomeItems(
surfaceBiomeItems = lowerBlockBiome?.surfacePlaceables?.itemDistributions?.filter { it.mode == BiomePlaceablesDefinition.Placement.FLOOR } ?: listOf(),
oceanItems = thisBlockBiome?.surfacePlaceables?.itemDistributions?.filter { it.mode == BiomePlaceablesDefinition.Placement.OCEAN } ?: listOf(),
caveSurfaceBiomeItems = lowerBlockBiome?.undergroundPlaceables?.itemDistributions?.filter { it.mode == BiomePlaceablesDefinition.Placement.FLOOR } ?: listOf(),
caveCeilingBiomeItems = upperBlockBiome?.undergroundPlaceables?.itemDistributions?.filter { it.mode == BiomePlaceablesDefinition.Placement.CEILING } ?: listOf(),
caveBackgroundBiomeItems = thisBlockBiome?.undergroundPlaceables?.itemDistributions?.filter { it.mode == BiomePlaceablesDefinition.Placement.BACKGROUND } ?: listOf(),
)
}
class CellInfo(val x: Int, val y: Int) { class CellInfo(val x: Int, val y: Int) {
var foreground: Registry.Ref<TileDefinition> = BuiltinMetaMaterials.EMPTY.ref var foreground: Registry.Ref<TileDefinition> = BuiltinMetaMaterials.EMPTY.ref
var foregroundMod: Registry.Ref<TileModifierDefinition> = BuiltinMetaMaterials.EMPTY_MOD.ref var foregroundMod: Registry.Ref<TileModifierDefinition> = BuiltinMetaMaterials.EMPTY_MOD.ref
@ -177,7 +204,18 @@ class WorldTemplate(val geometry: WorldGeometry) {
var backgroundCave = false var backgroundCave = false
} }
private val cellCache = Caffeine.newBuilder()
.maximumSize(125_000L)
.expireAfterAccess(Duration.ofMinutes(2))
.executor(Starbound.EXECUTOR)
.scheduler(Scheduler.systemScheduler())
.build<Vector2i, CellInfo> { (x, y) -> cellInfo0(x, y) }
fun cellInfo(x: Int, y: Int): CellInfo { fun cellInfo(x: Int, y: Int): CellInfo {
return cellCache.get(Vector2i(x, y))
}
private fun cellInfo0(x: Int, y: Int): CellInfo {
val info = CellInfo(x, y) val info = CellInfo(x, y)
val layout = worldLayout ?: return info val layout = worldLayout ?: return info

View File

@ -54,7 +54,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
// packets which interact with world must be // packets which interact with world must be
// executed on world's thread // executed on world's thread
fun enqueue(task: ServerWorld.() -> Unit) { fun enqueue(task: ServerWorld.() -> Unit) {
return tracker?.enqueue(task) ?: throw IllegalStateException("Not in world.") return tracker?.enqueue(task) ?: LOGGER.warn("$this tried to interact with world, but they are not in one.")
} }
lateinit var shipWorld: ServerWorld lateinit var shipWorld: ServerWorld

View File

@ -4,6 +4,8 @@ import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
import it.unimi.dsi.fastutil.objects.ObjectArrayList import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.future.await import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
@ -12,6 +14,7 @@ import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
import ru.dbotthepony.kstarbound.defs.tile.TileDamage import ru.dbotthepony.kstarbound.defs.tile.TileDamage
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
@ -20,6 +23,7 @@ import ru.dbotthepony.kstarbound.defs.tile.isNullTile
import ru.dbotthepony.kstarbound.defs.tile.supportsModifier import ru.dbotthepony.kstarbound.defs.tile.supportsModifier
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket
import ru.dbotthepony.kstarbound.util.random.staticRandomDouble
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
import ru.dbotthepony.kstarbound.world.Chunk import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
@ -33,6 +37,7 @@ import java.util.concurrent.CompletableFuture
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import java.util.function.Predicate import java.util.function.Predicate
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
import kotlin.coroutines.cancellation.CancellationException
class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, ServerChunk>(world, pos) { class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, ServerChunk>(world, pos) {
/** /**
@ -46,9 +51,6 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
TILES, TILES,
MICRO_DUNGEONS, MICRO_DUNGEONS,
CAVE_LIQUID, CAVE_LIQUID,
TILE_ENTITIES,
ENTITIES,
FULL; // indicates everything has been loaded FULL; // indicates everything has been loaded
} }
@ -81,9 +83,44 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
while (state < targetState) { while (state < targetState) {
isBusy = true isBusy = true
val nextState = ServerChunk.State.entries[state.ordinal + 1] val nextState = State.entries[state.ordinal + 1]
try { try {
if (nextState >= State.MICRO_DUNGEONS) {
val neighbours = ArrayList<ITicket>()
try {
// wait for neighbouring chunks to be as generated as our current state
// before we advance to next stage
// Yes, this will create quite big loading rectangle around
// players and other watching entities, but this is required
// to avoid generation artifacts, where this chunk tries to read
// from not sufficiently generated neighbours
for (neighbour in pos.neighbours()) {
val ticket = world.permanentChunkTicket(neighbour, state) ?: continue
neighbours.add(ticket)
}
for (neighbour in neighbours) {
var i = 0
while (!neighbour.chunk.isDone && ++i < 20) {
delay(500L)
}
if (!neighbour.chunk.isDone) {
LOGGER.error("Giving up waiting on ${neighbour.pos} while advancing generation stage of $this to $nextState (neighbour chunk was in state ${world.chunkMap[neighbour.pos]?.state}, expected $state)")
}
}
} catch (err: Throwable) {
LOGGER.error("Error while waiting on neighbouring chunks while generating this $this", err)
throw err
} finally {
neighbours.forEach { it.cancel() }
}
}
when (nextState) { when (nextState) {
State.TILES -> { State.TILES -> {
// tiles can be generated concurrently without any consequences // tiles can be generated concurrently without any consequences
@ -98,15 +135,11 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
//LOGGER.error("NYI: Generating cave liquids for $chunk") //LOGGER.error("NYI: Generating cave liquids for $chunk")
} }
State.TILE_ENTITIES -> { State.FULL -> {
//LOGGER.error("NYI: Generating tile entities for $chunk") CompletableFuture.runAsync(Runnable { placeGrass() }, Starbound.EXECUTOR).await()
} }
State.ENTITIES -> { State.FRESH -> throw RuntimeException()
//LOGGER.error("NYI: Placing entities for $chunk")
}
else -> {}
} }
bumpState(nextState) bumpState(nextState)
@ -145,7 +178,11 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
chunkGeneratorLoop() chunkGeneratorLoop()
} }
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Exception while loading chunk $this", err) if (err is CancellationException) {
// harmless
} else {
LOGGER.error("Exception while loading chunk $this", err)
}
} }
} }
@ -224,8 +261,6 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
if (this@ServerChunk.state >= targetState) { if (this@ServerChunk.state >= targetState) {
chunk.complete(this@ServerChunk) chunk.complete(this@ServerChunk)
} else {
this@ServerChunk.targetState.trySend(targetState)
} }
} }
@ -248,6 +283,8 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
private inner class Ticket(state: State) : AbstractTicket(state) { private inner class Ticket(state: State) : AbstractTicket(state) {
init { init {
permanent.add(this) permanent.add(this)
this@ServerChunk.targetState.trySend(targetState)
} }
override fun cancel0() { override fun cancel0() {
@ -255,7 +292,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
} }
} }
private inner class TimedTicket(expiresAt: Int, state: ServerChunk.State) : AbstractTicket(state), ITimedTicket { private inner class TimedTicket(expiresAt: Int, state: State) : AbstractTicket(state), ITimedTicket {
var expiresAt = expiresAt + ticks var expiresAt = expiresAt + ticks
override val timeRemaining: Int override val timeRemaining: Int
@ -263,6 +300,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
init { init {
temporary.add(this) temporary.add(this)
this@ServerChunk.targetState.trySend(targetState)
} }
override fun cancel0() { override fun cancel0() {
@ -284,14 +322,8 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
private fun bumpState(newState: State) { private fun bumpState(newState: State) {
if (newState == state) return if (newState == state) return
require(newState >= state) { "Tried to downgrade $this state from $state to $newState" } require(newState >= state) { "Tried to downgrade $this state from $state to $newState" }
this.state = newState
if (newState >= State.ENTITIES) {
this.state = State.FULL
} else {
this.state = newState
}
val permanent: List<Ticket> val permanent: List<Ticket>
val temporary: List<TimedTicket> val temporary: List<TimedTicket>
@ -432,7 +464,9 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
if (shouldUnload) { if (shouldUnload) {
idleTicks++ idleTicks++
// don't load-save-load-save too frequently // don't load-save-load-save too frequently
shouldUnload = idleTicks > 600 // also make partially-generated chunks stay in memory for way longer, because re-generating
// them is very costly operation
shouldUnload = if (state == State.FULL) idleTicks > 600 else idleTicks > 32000
} else { } else {
idleTicks = 0 idleTicks = 0
} }
@ -507,7 +541,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
} }
} }
fun prepareCells() { private fun prepareCells() {
val cells = cells.value val cells = cells.value
for (x in 0 until width) { for (x in 0 until width) {
@ -563,6 +597,91 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
} }
} }
private fun placeGrass() {
for (x in 0 until width) {
for (y in 0 until height) {
placeGrass(x, y)
}
}
}
private fun placeGrass(x: Int, y: Int) {
val biome = world.template.cellInfo(pos.tileX + x, pos.tileY + y).blockBiome ?: return
val cell = cells.value[x, y]
// determine layer for grass mod calculation
val isBackground = cell.foreground.material.isEmptyTile
val tile = cell.tile(isBackground)
val tileInv = cell.tile(!isBackground)
// don't place mods in dungeons unless explicitly specified, also don't
// touch non-grass mods
if (
tile.modifier == BuiltinMetaMaterials.BIOME_MOD ||
tile.modifier == BuiltinMetaMaterials.UNDERGROUND_BIOME_MOD ||
(cell.dungeonId == NO_DUNGEON_ID && tile.modifier == BuiltinMetaMaterials.EMPTY_MOD)
) {
// check whether we're floor or ceiling
// NOTE: we are querying other chunks while generating,
// and we might read stale data if we are reading neighbouring chunks
// since they might be in process of generation, too
// (only if they are in process of generating someing significant, which modify terrain)
// shouldn't be an issue though
val cellAbove = world.chunkMap.getCell(pos.tileX + x, pos.tileY + y + 1)
val cellBelow = world.chunkMap.getCell(pos.tileX + x, pos.tileY + y - 1)
val tileAbove = cell.tile(isBackground)
val tileBelow = cell.tile(isBackground)
val tileAboveInv = cell.tile(!isBackground)
val tileBelowInv = cell.tile(!isBackground)
val isFloor = (!cell.foreground.material.isEmptyTile && cellAbove.foreground.material.isEmptyTile) || (!cell.background.material.isEmptyTile && cellAbove.background.material.isEmptyTile)
val isCeiling = !isFloor && ((!cell.foreground.material.isEmptyTile && cellBelow.foreground.material.isEmptyTile) || (!cell.background.material.isEmptyTile && cellBelow.background.material.isEmptyTile))
// I might be stupid, but in original code the check above is completely wrong
// because it will result in buried glass under tiles
//val isFloor = !tile.material.isEmptyTile && tileAbove.material.isEmptyTile
//val isCeiling = !isFloor && !tile.material.isEmptyTile && tileBelow.material.isEmptyTile
// get the appropriate placeables for above/below ground
val placeables = if (isFloor && !cellAbove.background.material.isEmptyTile || isCeiling && !cellBelow.background.material.isEmptyTile) {
biome.undergroundPlaceables
} else {
biome.surfacePlaceables
}
// determine the proper grass mod or lack thereof
var grassMod = BuiltinMetaMaterials.EMPTY_MOD
if (isFloor) {
if (staticRandomDouble(world.template.seed, pos.tileX + x, pos.tileY + y, "grass") <= placeables.grassModDensity) {
grassMod = placeables.grassMod.native.entry ?: BuiltinMetaMaterials.EMPTY_MOD
}
} else if (isCeiling) {
if (staticRandomDouble(world.template.seed, pos.tileX + x, pos.tileY + y, "grass") <= placeables.ceilingGrassModDensity) {
grassMod = placeables.ceilingGrassMod.native.entry ?: BuiltinMetaMaterials.EMPTY_MOD
}
}
val modify = cell.mutable()
if (isBackground) {
modify.background.modifier = grassMod
modify.foreground.modifier = BuiltinMetaMaterials.EMPTY_MOD
} else {
modify.foreground.modifier = grassMod
modify.background.modifier = BuiltinMetaMaterials.EMPTY_MOD
}
modify.background.modifierHueShift = biome.hueShift(modify.background.modifier)
modify.foreground.modifierHueShift = biome.hueShift(modify.foreground.modifier)
cells.value[x, y] = modify.immutable()
}
}
companion object { companion object {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
} }

View File

@ -91,7 +91,7 @@ class ServerWorld private constructor(
if (action != null) if (action != null)
client.send(PlayerWarpResultPacket(true, action, false)) client.send(PlayerWarpResultPacket(true, action, false))
client.tracker?.remove("Transiting to new world") client.tracker?.remove("Transiting to new world", false)
clients.add(ServerWorldTracker(this, client, start)) clients.add(ServerWorldTracker(this, client, start))
} finally { } finally {
isBusy-- isBusy--

View File

@ -268,7 +268,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
} }
} }
fun remove(reason: String = "ServerWorldTracker got removed") { fun remove(reason: String = "ServerWorldTracker got removed", nullifyVariables: Boolean = true) {
if (isRemoved.compareAndSet(false, true)) { if (isRemoved.compareAndSet(false, true)) {
// erase all tasks just to be sure // erase all tasks just to be sure
tasks.clear() tasks.clear()
@ -279,9 +279,12 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
client.returnWarp = WarpAction.World(world.worldID, SpawnTarget.Position(playerEntity.position)) client.returnWarp = WarpAction.World(world.worldID, SpawnTarget.Position(playerEntity.position))
} }
client.tracker = null if (nullifyVariables) {
client.playerEntity = null client.tracker = null
client.worldID = WorldID.Limbo client.playerEntity = null
client.worldID = WorldID.Limbo
}
client.send(WorldStopPacket(reason)) client.send(WorldStopPacket(reason))
// this handles case where player is removed from world and // this handles case where player is removed from world and

View File

@ -71,6 +71,10 @@ data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
return ChunkPos(x + 1, y) return ChunkPos(x + 1, y)
} }
fun neighbours(): List<ChunkPos> {
return listOf(top, bottom, left, right, topLeft, topRight, bottomLeft, bottomRight)
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other is ChunkPos) if (other is ChunkPos)
return other.x == x && other.y == y return other.x == x && other.y == y

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.world.api
import com.github.benmanes.caffeine.cache.Interner import com.github.benmanes.caffeine.cache.Interner
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
@ -11,7 +12,7 @@ sealed class AbstractCell {
abstract val background: AbstractTileState abstract val background: AbstractTileState
abstract val liquid: AbstractLiquidState abstract val liquid: AbstractLiquidState
// ushort, can be anything (but 65535), mostly used by placed dungeons // ushort, can be anything, mostly used by placed dungeons
// this value determines special logic behind this cell, such as if it is protected from modifications // this value determines special logic behind this cell, such as if it is protected from modifications
// by players or other means // by players or other means
abstract val dungeonId: Int abstract val dungeonId: Int
@ -63,7 +64,7 @@ sealed class AbstractCell {
@JvmStatic @JvmStatic
protected val POOL: Interner<ImmutableCell> = if (Starbound.DEDUP_CELL_STATES) Starbound.interner() else Interner { it } protected val POOL: Interner<ImmutableCell> = if (Starbound.DEDUP_CELL_STATES) Starbound.interner() else Interner { it }
val EMPTY: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.EMPTY, AbstractTileState.EMPTY, AbstractLiquidState.EMPTY, 0, 0, 0, false)) val EMPTY: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.EMPTY, AbstractTileState.EMPTY, AbstractLiquidState.EMPTY, NO_DUNGEON_ID, 0, 0, false))
val NULL: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, AbstractLiquidState.EMPTY, 0, 0, 0, false)) val NULL: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, AbstractLiquidState.EMPTY, NO_DUNGEON_ID, 0, 0, false))
} }
} }

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.world.api package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
data class ImmutableCell( data class ImmutableCell(
@ -7,7 +8,7 @@ data class ImmutableCell(
override val background: ImmutableTileState = AbstractTileState.EMPTY, override val background: ImmutableTileState = AbstractTileState.EMPTY,
override val liquid: ImmutableLiquidState = AbstractLiquidState.EMPTY, override val liquid: ImmutableLiquidState = AbstractLiquidState.EMPTY,
override val dungeonId: Int = 0, override val dungeonId: Int = NO_DUNGEON_ID,
override val blockBiome: Int = 0, override val blockBiome: Int = 0,
override val envBiome: Int = 0, override val envBiome: Int = 0,
override val isIndestructible: Boolean = false, override val isIndestructible: Boolean = false,

View File

@ -7,11 +7,11 @@ import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.network.LegacyNetworkTileState import ru.dbotthepony.kstarbound.network.LegacyNetworkTileState
data class ImmutableTileState( data class ImmutableTileState(
override var material: Registry.Entry<TileDefinition> = BuiltinMetaMaterials.NULL, override val material: Registry.Entry<TileDefinition> = BuiltinMetaMaterials.NULL,
override var modifier: Registry.Entry<TileModifierDefinition> = BuiltinMetaMaterials.EMPTY_MOD, override val modifier: Registry.Entry<TileModifierDefinition> = BuiltinMetaMaterials.EMPTY_MOD,
override var color: TileColor = TileColor.DEFAULT, override val color: TileColor = TileColor.DEFAULT,
override var hueShift: Float = 0f, override val hueShift: Float = 0f,
override var modifierHueShift: Float = 0f, override val modifierHueShift: Float = 0f,
) : AbstractTileState() { ) : AbstractTileState() {
override fun immutable(): ImmutableTileState { override fun immutable(): ImmutableTileState {
return this return this

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.world.api package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
import java.io.DataInputStream import java.io.DataInputStream
data class MutableCell( data class MutableCell(
@ -7,7 +8,7 @@ data class MutableCell(
override val background: MutableTileState = MutableTileState(), override val background: MutableTileState = MutableTileState(),
override val liquid: MutableLiquidState = MutableLiquidState(), override val liquid: MutableLiquidState = MutableLiquidState(),
override var dungeonId: Int = 0, override var dungeonId: Int = NO_DUNGEON_ID,
override var blockBiome: Int = 0, override var blockBiome: Int = 0,
override var envBiome: Int = 0, override var envBiome: Int = 0,
override var isIndestructible: Boolean = false, override var isIndestructible: Boolean = false,