Grass generation
This commit is contained in:
parent
a839af1041
commit
6f6150475e
@ -24,7 +24,7 @@ val Registry.Ref<TileDefinition>.isObjectPlatformTile: Boolean
|
||||
get() = entry == BuiltinMetaMaterials.OBJECT_PLATFORM
|
||||
|
||||
val Registry.Entry<TileDefinition>.isEmptyTile: Boolean
|
||||
get() = this == BuiltinMetaMaterials.EMPTY
|
||||
get() = this == BuiltinMetaMaterials.EMPTY || this == BuiltinMetaMaterials.NULL
|
||||
|
||||
val Registry.Entry<TileDefinition>.isNullTile: Boolean
|
||||
get() = this == BuiltinMetaMaterials.NULL
|
||||
@ -52,6 +52,21 @@ val Registry.Entry<LiquidDefinition>.isEmptyLiquid: Boolean
|
||||
val Registry.Ref<LiquidDefinition>.isEmptyLiquid: Boolean
|
||||
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 {
|
||||
private fun make(id: Int, name: String, collisionType: CollisionType) = Registries.tiles.add(name, id, TileDefinition(
|
||||
materialId = id,
|
||||
@ -117,7 +132,7 @@ object BuiltinMetaMaterials {
|
||||
OBJECT_PLATFORM,
|
||||
)
|
||||
|
||||
val EMPTY_MOD = makeMod(65535, "empty")
|
||||
val EMPTY_MOD = makeMod(65535, "none")
|
||||
val BIOME_MOD = makeMod(65534, "biome")
|
||||
val UNDERGROUND_BIOME_MOD = makeMod(65533, "underground_biome")
|
||||
|
||||
|
@ -1,32 +1,27 @@
|
||||
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 ru.dbotthepony.kommons.arrays.Object2DArray
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.AABBi
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Globals
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
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.math.quintic2
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.world.Universe
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
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 java.time.Duration
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
class WorldTemplate(val geometry: WorldGeometry) {
|
||||
@ -156,6 +151,38 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
||||
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) {
|
||||
var foreground: Registry.Ref<TileDefinition> = BuiltinMetaMaterials.EMPTY.ref
|
||||
var foregroundMod: Registry.Ref<TileModifierDefinition> = BuiltinMetaMaterials.EMPTY_MOD.ref
|
||||
@ -177,7 +204,18 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
||||
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 {
|
||||
return cellCache.get(Vector2i(x, y))
|
||||
}
|
||||
|
||||
private fun cellInfo0(x: Int, y: Int): CellInfo {
|
||||
val info = CellInfo(x, y)
|
||||
val layout = worldLayout ?: return info
|
||||
|
||||
|
@ -54,7 +54,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
// packets which interact with world must be
|
||||
// executed on world's thread
|
||||
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
|
||||
|
@ -4,6 +4,8 @@ import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.launch
|
||||
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.kstarbound.Starbound
|
||||
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.TileDamageResult
|
||||
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.network.LegacyNetworkCellState
|
||||
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
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
@ -33,6 +37,7 @@ import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Predicate
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
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,
|
||||
MICRO_DUNGEONS,
|
||||
CAVE_LIQUID,
|
||||
TILE_ENTITIES,
|
||||
ENTITIES,
|
||||
|
||||
FULL; // indicates everything has been loaded
|
||||
}
|
||||
|
||||
@ -81,9 +83,44 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
while (state < targetState) {
|
||||
isBusy = true
|
||||
|
||||
val nextState = ServerChunk.State.entries[state.ordinal + 1]
|
||||
val nextState = State.entries[state.ordinal + 1]
|
||||
|
||||
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) {
|
||||
State.TILES -> {
|
||||
// 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")
|
||||
}
|
||||
|
||||
State.TILE_ENTITIES -> {
|
||||
//LOGGER.error("NYI: Generating tile entities for $chunk")
|
||||
State.FULL -> {
|
||||
CompletableFuture.runAsync(Runnable { placeGrass() }, Starbound.EXECUTOR).await()
|
||||
}
|
||||
|
||||
State.ENTITIES -> {
|
||||
//LOGGER.error("NYI: Placing entities for $chunk")
|
||||
}
|
||||
|
||||
else -> {}
|
||||
State.FRESH -> throw RuntimeException()
|
||||
}
|
||||
|
||||
bumpState(nextState)
|
||||
@ -145,7 +178,11 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
chunkGeneratorLoop()
|
||||
}
|
||||
} 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) {
|
||||
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) {
|
||||
init {
|
||||
permanent.add(this)
|
||||
|
||||
this@ServerChunk.targetState.trySend(targetState)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
override val timeRemaining: Int
|
||||
@ -263,6 +300,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
|
||||
init {
|
||||
temporary.add(this)
|
||||
this@ServerChunk.targetState.trySend(targetState)
|
||||
}
|
||||
|
||||
override fun cancel0() {
|
||||
@ -284,14 +322,8 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
|
||||
private fun bumpState(newState: State) {
|
||||
if (newState == state) return
|
||||
|
||||
require(newState >= state) { "Tried to downgrade $this state from $state to $newState" }
|
||||
|
||||
if (newState >= State.ENTITIES) {
|
||||
this.state = State.FULL
|
||||
} else {
|
||||
this.state = newState
|
||||
}
|
||||
this.state = newState
|
||||
|
||||
val permanent: List<Ticket>
|
||||
val temporary: List<TimedTicket>
|
||||
@ -432,7 +464,9 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
if (shouldUnload) {
|
||||
idleTicks++
|
||||
// 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 {
|
||||
idleTicks = 0
|
||||
}
|
||||
@ -507,7 +541,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareCells() {
|
||||
private fun prepareCells() {
|
||||
val cells = cells.value
|
||||
|
||||
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 {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ class ServerWorld private constructor(
|
||||
if (action != null)
|
||||
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))
|
||||
} finally {
|
||||
isBusy--
|
||||
|
@ -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)) {
|
||||
// erase all tasks just to be sure
|
||||
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.tracker = null
|
||||
client.playerEntity = null
|
||||
client.worldID = WorldID.Limbo
|
||||
if (nullifyVariables) {
|
||||
client.tracker = null
|
||||
client.playerEntity = null
|
||||
client.worldID = WorldID.Limbo
|
||||
}
|
||||
|
||||
client.send(WorldStopPacket(reason))
|
||||
|
||||
// this handles case where player is removed from world and
|
||||
|
@ -71,6 +71,10 @@ data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
|
||||
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 {
|
||||
if (other is ChunkPos)
|
||||
return other.x == x && other.y == y
|
||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Interner
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
||||
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
@ -11,7 +12,7 @@ sealed class AbstractCell {
|
||||
abstract val background: AbstractTileState
|
||||
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
|
||||
// by players or other means
|
||||
abstract val dungeonId: Int
|
||||
@ -63,7 +64,7 @@ sealed class AbstractCell {
|
||||
@JvmStatic
|
||||
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 NULL: ImmutableCell = POOL.intern(ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, 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, NO_DUNGEON_ID, 0, 0, false))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
||||
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||||
|
||||
data class ImmutableCell(
|
||||
@ -7,7 +8,7 @@ data class ImmutableCell(
|
||||
override val background: ImmutableTileState = AbstractTileState.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 envBiome: Int = 0,
|
||||
override val isIndestructible: Boolean = false,
|
||||
|
@ -7,11 +7,11 @@ import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.network.LegacyNetworkTileState
|
||||
|
||||
data class ImmutableTileState(
|
||||
override var material: Registry.Entry<TileDefinition> = BuiltinMetaMaterials.NULL,
|
||||
override var modifier: Registry.Entry<TileModifierDefinition> = BuiltinMetaMaterials.EMPTY_MOD,
|
||||
override var color: TileColor = TileColor.DEFAULT,
|
||||
override var hueShift: Float = 0f,
|
||||
override var modifierHueShift: Float = 0f,
|
||||
override val material: Registry.Entry<TileDefinition> = BuiltinMetaMaterials.NULL,
|
||||
override val modifier: Registry.Entry<TileModifierDefinition> = BuiltinMetaMaterials.EMPTY_MOD,
|
||||
override val color: TileColor = TileColor.DEFAULT,
|
||||
override val hueShift: Float = 0f,
|
||||
override val modifierHueShift: Float = 0f,
|
||||
) : AbstractTileState() {
|
||||
override fun immutable(): ImmutableTileState {
|
||||
return this
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
||||
import java.io.DataInputStream
|
||||
|
||||
data class MutableCell(
|
||||
@ -7,7 +8,7 @@ data class MutableCell(
|
||||
override val background: MutableTileState = MutableTileState(),
|
||||
override val liquid: MutableLiquidState = MutableLiquidState(),
|
||||
|
||||
override var dungeonId: Int = 0,
|
||||
override var dungeonId: Int = NO_DUNGEON_ID,
|
||||
override var blockBiome: Int = 0,
|
||||
override var envBiome: Int = 0,
|
||||
override var isIndestructible: Boolean = false,
|
||||
|
Loading…
Reference in New Issue
Block a user