Functional plants
This commit is contained in:
parent
17a3de38bc
commit
74bbc58c60
@ -147,3 +147,6 @@ val color: TileColor = TileColor.DEFAULT
|
|||||||
|
|
||||||
#### Dungeons
|
#### Dungeons
|
||||||
* All brushes are now deterministic
|
* All brushes are now deterministic
|
||||||
|
|
||||||
|
#### Plant drop entities (vines or steps dropping on ground)
|
||||||
|
* Collision is now determined using hull instead of rectangle
|
||||||
|
@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
|
|||||||
|
|
||||||
kotlinVersion=1.9.10
|
kotlinVersion=1.9.10
|
||||||
kotlinCoroutinesVersion=1.8.0
|
kotlinCoroutinesVersion=1.8.0
|
||||||
kommonsVersion=2.15.0
|
kommonsVersion=2.15.1
|
||||||
|
|
||||||
ffiVersion=2.2.13
|
ffiVersion=2.2.13
|
||||||
lwjglVersion=3.3.0
|
lwjglVersion=3.3.0
|
||||||
|
@ -6,6 +6,8 @@ import com.github.benmanes.caffeine.cache.Scheduler
|
|||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
|
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
||||||
|
import kotlinx.coroutines.Runnable
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.classdump.luna.compiler.CompilerChunkLoader
|
import org.classdump.luna.compiler.CompilerChunkLoader
|
||||||
@ -76,6 +78,7 @@ import java.io.*
|
|||||||
import java.lang.ref.Cleaner
|
import java.lang.ref.Cleaner
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.util.Collections
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
@ -135,7 +138,7 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
|||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val IO_EXECUTOR: ExecutorService = ThreadPoolExecutor(0, 64, 30L, TimeUnit.SECONDS, LinkedBlockingQueue(), ThreadFactory {
|
val IO_EXECUTOR: ExecutorService = ThreadPoolExecutor(0, 64, 30L, TimeUnit.SECONDS, LinkedBlockingQueue(), ThreadFactory {
|
||||||
val thread = Thread(it, "Starbound Storage IO ${ioPoolCounter.getAndIncrement()}")
|
val thread = Thread(it, "IO Worker ${ioPoolCounter.getAndIncrement()}")
|
||||||
thread.isDaemon = true
|
thread.isDaemon = true
|
||||||
thread.priority = Thread.MIN_PRIORITY
|
thread.priority = Thread.MIN_PRIORITY
|
||||||
|
|
||||||
@ -151,12 +154,6 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
|||||||
@JvmField
|
@JvmField
|
||||||
val COROUTINE_EXECUTOR = ExecutorWithScheduler(EXECUTOR, this).asCoroutineDispatcher()
|
val COROUTINE_EXECUTOR = ExecutorWithScheduler(EXECUTOR, this).asCoroutineDispatcher()
|
||||||
|
|
||||||
// this is required for Caffeine since it ignores scheduler
|
|
||||||
// (and suffers noticeable throughput penalty) in rescheduleCleanUpIfIncomplete()
|
|
||||||
// if executor is specified as ForkJoinPool.commonPool()
|
|
||||||
@JvmField
|
|
||||||
val SCREENED_EXECUTOR: ExecutorService = object : ExecutorService by EXECUTOR {}
|
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val CLEANER: Cleaner = Cleaner.create {
|
val CLEANER: Cleaner = Cleaner.create {
|
||||||
val t = Thread(it, "Starbound Global Cleaner")
|
val t = Thread(it, "Starbound Global Cleaner")
|
||||||
|
@ -1,41 +1,45 @@
|
|||||||
package ru.dbotthepony.kstarbound.collect
|
package ru.dbotthepony.kstarbound.collect
|
||||||
|
|
||||||
class RandomListIterator<E>(private val elements: MutableList<E>, index: Int = 0) : MutableListIterator<E> {
|
class RandomListIterator<E>(private val elements: MutableList<E>, private var index: Int = 0) : MutableListIterator<E> {
|
||||||
private var index = index - 1
|
|
||||||
|
|
||||||
override fun hasPrevious(): Boolean {
|
override fun hasPrevious(): Boolean {
|
||||||
return this.index > 0
|
return this.index > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nextIndex(): Int {
|
override fun nextIndex(): Int {
|
||||||
return this.index + 1
|
return this.index
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun previous(): E {
|
override fun previous(): E {
|
||||||
return elements[--this.index]
|
lastIndex = --this.index
|
||||||
|
return elements[lastIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun previousIndex(): Int {
|
override fun previousIndex(): Int {
|
||||||
return (this.index - 1).coerceAtLeast(-1)
|
return this.index - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun add(element: E) {
|
override fun add(element: E) {
|
||||||
elements.add(this.index++, element)
|
elements.add(this.index++, element)
|
||||||
|
lastIndex = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasNext(): Boolean {
|
override fun hasNext(): Boolean {
|
||||||
return this.index < elements.size - 1
|
return this.index < elements.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var lastIndex = -1
|
||||||
|
|
||||||
override fun next(): E {
|
override fun next(): E {
|
||||||
return elements[++this.index]
|
lastIndex = this.index++
|
||||||
|
return elements[lastIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun remove() {
|
override fun remove() {
|
||||||
elements.removeAt(this.index--)
|
elements.removeAt(lastIndex)
|
||||||
|
lastIndex = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun set(element: E) {
|
override fun set(element: E) {
|
||||||
elements[this.index] = element
|
elements[lastIndex] = element
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
import com.google.gson.stream.JsonWriter
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
|
||||||
enum class EntityType(override val jsonName: String, val storeName: String) : IStringSerializable {
|
enum class EntityType(override val jsonName: String, val storeName: String) : IStringSerializable {
|
||||||
@ -8,7 +7,7 @@ enum class EntityType(override val jsonName: String, val storeName: String) : IS
|
|||||||
OBJECT("object", "ObjectEntity"),
|
OBJECT("object", "ObjectEntity"),
|
||||||
VEHICLE("vehicle", "VehicleEntity"),
|
VEHICLE("vehicle", "VehicleEntity"),
|
||||||
ITEM_DROP("itemDrop", "ItemDropEntity"),
|
ITEM_DROP("itemDrop", "ItemDropEntity"),
|
||||||
PLANT_DROP("plantDrop", "PlantDropEntity"), // wat
|
PLANT_DROP("plantDrop", "PlantDropEntity"),
|
||||||
PROJECTILE("projectile", "ProjectileEntity"),
|
PROJECTILE("projectile", "ProjectileEntity"),
|
||||||
STAGEHAND("stagehand", "StagehandEntity"),
|
STAGEHAND("stagehand", "StagehandEntity"),
|
||||||
MONSTER("monster", "MonsterEntity"),
|
MONSTER("monster", "MonsterEntity"),
|
||||||
|
@ -10,6 +10,7 @@ import com.google.gson.stream.JsonReader
|
|||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import kotlinx.coroutines.future.await
|
import kotlinx.coroutines.future.await
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||||
import ru.dbotthepony.kstarbound.math.AABBi
|
import ru.dbotthepony.kstarbound.math.AABBi
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
@ -225,8 +226,13 @@ class DungeonPart(data: JsonData) {
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
return world.waitForRegionAndJoin(Vector2i(x, y), reader.size) {
|
return world.waitForRegionAndJoin(Vector2i(x, y), reader.size) {
|
||||||
|
val cells = Object2DArray(reader.size.x, reader.size.y) { tx, ty ->
|
||||||
|
world.parent.getCell(x + tx, y + ty)
|
||||||
|
}
|
||||||
|
|
||||||
reader.walkTiles<Boolean> { tx, ty, tile ->
|
reader.walkTiles<Boolean> { tx, ty, tile ->
|
||||||
if (!tile.canPlace(x + tx, y + ty, world)) {
|
// TMX allows to define objects with out-of-bounds coordinates...
|
||||||
|
if (!tile.canPlace(x + tx, y + ty, world, cells.getOrNull(tx, ty) ?: world.parent.getCell(x + tx, y + ty))) {
|
||||||
return@walkTiles KOptional(false)
|
return@walkTiles KOptional(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,12 +241,17 @@ class DungeonPart(data: JsonData) {
|
|||||||
}.orElse(true)
|
}.orElse(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canPlace(x: Int, y: Int, world: ServerWorld): Boolean {
|
fun canPlace(x: Int, y: Int, world: ServerWorld, allowAlways: Boolean = this.overrideAllowAlways): Boolean {
|
||||||
if (overrideAllowAlways || reader.size.x == 0 || reader.size.y == 0)
|
if (allowAlways || reader.size.x == 0 || reader.size.y == 0)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
val cells = Object2DArray(reader.size.x, reader.size.y) { tx, ty ->
|
||||||
|
world.getCell(x + tx, y + ty)
|
||||||
|
}
|
||||||
|
|
||||||
return reader.walkTiles<Boolean> { tx, ty, tile ->
|
return reader.walkTiles<Boolean> { tx, ty, tile ->
|
||||||
if (!tile.canPlace(x + tx, y + ty, world)) {
|
// TMX allows to define objects with out-of-bounds coordinates...
|
||||||
|
if (!tile.canPlace(x + tx, y + ty, world, cells.getOrNull(tx, ty) ?: world.getCell(x + tx, y + ty))) {
|
||||||
return@walkTiles KOptional(false)
|
return@walkTiles KOptional(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import ru.dbotthepony.kstarbound.defs.tile.isObjectTile
|
|||||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
import ru.dbotthepony.kstarbound.json.stream
|
import ru.dbotthepony.kstarbound.json.stream
|
||||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.AbstractCell
|
||||||
|
|
||||||
@JsonAdapter(DungeonRule.Adapter::class)
|
@JsonAdapter(DungeonRule.Adapter::class)
|
||||||
abstract class DungeonRule {
|
abstract class DungeonRule {
|
||||||
@ -139,11 +140,11 @@ abstract class DungeonRule {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean {
|
open fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld, cell: AbstractCell = world.parent.getCell(x, y)): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean {
|
open fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld, cell: AbstractCell = world.getCell(x, y)): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +162,7 @@ abstract class DungeonRule {
|
|||||||
override val requiresLiquid: Boolean
|
override val requiresLiquid: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld, cell: AbstractCell): Boolean {
|
||||||
val cell = world.parent.template.cellInfo(x, y)
|
val cell = world.parent.template.cellInfo(x, y)
|
||||||
return cell.oceanLiquid.isNotEmptyLiquid && cell.oceanLiquidLevel > y
|
return cell.oceanLiquid.isNotEmptyLiquid && cell.oceanLiquidLevel > y
|
||||||
}
|
}
|
||||||
@ -172,7 +173,7 @@ abstract class DungeonRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object MustNotContainLiquid : DungeonRule() {
|
object MustNotContainLiquid : DungeonRule() {
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld, cell: AbstractCell): Boolean {
|
||||||
val cell = world.parent.template.cellInfo(x, y)
|
val cell = world.parent.template.cellInfo(x, y)
|
||||||
return cell.oceanLiquid.isEmptyLiquid || cell.oceanLiquidLevel <= y
|
return cell.oceanLiquid.isEmptyLiquid || cell.oceanLiquidLevel <= y
|
||||||
}
|
}
|
||||||
@ -186,20 +187,17 @@ abstract class DungeonRule {
|
|||||||
override val requiresSolid: Boolean
|
override val requiresSolid: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld, cell: AbstractCell): Boolean {
|
||||||
if (world.markSurfaceLevel != null)
|
if (world.markSurfaceLevel != null)
|
||||||
return y < world.markSurfaceLevel
|
return y < world.markSurfaceLevel
|
||||||
|
|
||||||
val cell = world.parent.getCell(x, y)
|
|
||||||
|
|
||||||
if (cell.foreground.material.isObjectTile && world.isClearingTileEntityAt(x, y))
|
if (cell.foreground.material.isObjectTile && world.isClearingTileEntityAt(x, y))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return cell.foreground.material.isNotEmptyTile && !world.isClearingTileEntityAt(x, y)
|
return cell.foreground.material.isNotEmptyTile && !world.isClearingTileEntityAt(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld, cell: AbstractCell): Boolean {
|
||||||
val cell = world.getCell(x, y)
|
|
||||||
return cell.foreground.material.isNotEmptyTile
|
return cell.foreground.material.isNotEmptyTile
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,16 +210,14 @@ abstract class DungeonRule {
|
|||||||
override val requiresOpen: Boolean
|
override val requiresOpen: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld, cell: AbstractCell): Boolean {
|
||||||
if (world.markSurfaceLevel != null)
|
if (world.markSurfaceLevel != null)
|
||||||
return y >= world.markSurfaceLevel
|
return y >= world.markSurfaceLevel
|
||||||
|
|
||||||
val cell = world.parent.getCell(x, y)
|
|
||||||
return cell.foreground.material.isEmptyTile || cell.foreground.material.isObjectTile && world.isClearingTileEntityAt(x, y)
|
return cell.foreground.material.isEmptyTile || cell.foreground.material.isObjectTile && world.isClearingTileEntityAt(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld, cell: AbstractCell): Boolean {
|
||||||
val cell = world.getCell(x, y)
|
|
||||||
return cell.foreground.material.isEmptyTile
|
return cell.foreground.material.isEmptyTile
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,20 +230,17 @@ abstract class DungeonRule {
|
|||||||
override val requiresSolid: Boolean
|
override val requiresSolid: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld, cell: AbstractCell): Boolean {
|
||||||
if (world.markSurfaceLevel != null)
|
if (world.markSurfaceLevel != null)
|
||||||
return y < world.markSurfaceLevel
|
return y < world.markSurfaceLevel
|
||||||
|
|
||||||
val cell = world.parent.getCell(x, y)
|
|
||||||
|
|
||||||
if (cell.background.material.isObjectTile && world.isClearingTileEntityAt(x, y))
|
if (cell.background.material.isObjectTile && world.isClearingTileEntityAt(x, y))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return cell.background.material.isNotEmptyTile
|
return cell.background.material.isNotEmptyTile
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld, cell: AbstractCell): Boolean {
|
||||||
val cell = world.getCell(x, y)
|
|
||||||
return cell.background.material.isNotEmptyTile
|
return cell.background.material.isNotEmptyTile
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,16 +253,14 @@ abstract class DungeonRule {
|
|||||||
override val requiresOpen: Boolean
|
override val requiresOpen: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: DungeonWorld, cell: AbstractCell): Boolean {
|
||||||
if (world.markSurfaceLevel != null)
|
if (world.markSurfaceLevel != null)
|
||||||
return y >= world.markSurfaceLevel
|
return y >= world.markSurfaceLevel
|
||||||
|
|
||||||
val cell = world.parent.getCell(x, y)
|
|
||||||
return cell.background.material.isEmptyTile || cell.background.material.isObjectTile && world.isClearingTileEntityAt(x, y)
|
return cell.background.material.isEmptyTile || cell.background.material.isObjectTile && world.isClearingTileEntityAt(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld): Boolean {
|
override fun checkTileCanPlace(x: Int, y: Int, world: ServerWorld, cell: AbstractCell): Boolean {
|
||||||
val cell = world.getCell(x, y)
|
|
||||||
return cell.background.material.isEmptyTile
|
return cell.background.material.isEmptyTile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
|||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.json.getAdapter
|
import ru.dbotthepony.kstarbound.json.getAdapter
|
||||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.AbstractCell
|
||||||
|
|
||||||
@JsonAdapter(DungeonTile.Adapter::class)
|
@JsonAdapter(DungeonTile.Adapter::class)
|
||||||
data class DungeonTile(
|
data class DungeonTile(
|
||||||
@ -68,28 +69,24 @@ data class DungeonTile(
|
|||||||
// TODO: find a way around this, to make dungeons less restricted by this
|
// TODO: find a way around this, to make dungeons less restricted by this
|
||||||
// but thats also not a priority, since this check happens quite quickly
|
// but thats also not a priority, since this check happens quite quickly
|
||||||
// to have any noticeable impact on world's performance
|
// to have any noticeable impact on world's performance
|
||||||
fun canPlace(x: Int, y: Int, world: DungeonWorld): Boolean {
|
fun canPlace(x: Int, y: Int, world: DungeonWorld, cell: AbstractCell = world.parent.getCell(x, y)): Boolean {
|
||||||
val cell = world.parent.getCell(x, y)
|
|
||||||
|
|
||||||
if (cell.dungeonId != NO_DUNGEON_ID)
|
if (cell.dungeonId != NO_DUNGEON_ID)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if (!world.geometry.x.isValidCellIndex(x) || !world.geometry.y.isValidCellIndex(y))
|
if (!world.geometry.x.isValidCellIndex(x) || !world.geometry.y.isValidCellIndex(y))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return rules.none { !it.checkTileCanPlace(x, y, world) }
|
return rules.none { !it.checkTileCanPlace(x, y, world, cell) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canPlace(x: Int, y: Int, world: ServerWorld): Boolean {
|
fun canPlace(x: Int, y: Int, world: ServerWorld, cell: AbstractCell = world.getCell(x, y)): Boolean {
|
||||||
val cell = world.getCell(x, y)
|
|
||||||
|
|
||||||
if (cell.dungeonId != NO_DUNGEON_ID)
|
if (cell.dungeonId != NO_DUNGEON_ID)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if (!world.geometry.x.isValidCellIndex(x) || !world.geometry.y.isValidCellIndex(y))
|
if (!world.geometry.x.isValidCellIndex(x) || !world.geometry.y.isValidCellIndex(y))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return rules.none { !it.checkTileCanPlace(x, y, world) }
|
return rules.none { !it.checkTileCanPlace(x, y, world, cell) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun place(x: Int, y: Int, phase: DungeonBrush.Phase, world: DungeonWorld) {
|
fun place(x: Int, y: Int, phase: DungeonBrush.Phase, world: DungeonWorld) {
|
||||||
|
@ -15,8 +15,12 @@ import ru.dbotthepony.kstarbound.defs.image.Image
|
|||||||
import java.lang.ref.Reference
|
import java.lang.ref.Reference
|
||||||
|
|
||||||
class ImagePartReader(part: DungeonPart, val images: ImmutableList<Image>) : PartReader(part) {
|
class ImagePartReader(part: DungeonPart, val images: ImmutableList<Image>) : PartReader(part) {
|
||||||
override val size: Vector2i
|
override val size: Vector2i by lazy {
|
||||||
get() = if (images.isEmpty()) Vector2i.ZERO else images.first().size
|
if (images.isEmpty())
|
||||||
|
return@lazy Vector2i.ZERO
|
||||||
|
|
||||||
|
Vector2i(images.maxOf { it.size.x }, images.maxOf { it.size.y })
|
||||||
|
}
|
||||||
|
|
||||||
// ObjectArrayList doesn't check for concurrent modifications
|
// ObjectArrayList doesn't check for concurrent modifications
|
||||||
private val layers = ObjectArrayList<Layer>()
|
private val layers = ObjectArrayList<Layer>()
|
||||||
|
@ -26,8 +26,12 @@ class TiledPartReader(part: DungeonPart, parts: Stream<String>) : PartReader(par
|
|||||||
// also why would you ever want multiple maps specified lmao
|
// also why would you ever want multiple maps specified lmao
|
||||||
// it already has layers and everything else you would ever need
|
// it already has layers and everything else you would ever need
|
||||||
|
|
||||||
override val size: Vector2i
|
override val size: Vector2i by lazy {
|
||||||
get() = maps.firstOrNull()?.size ?: Vector2i.ZERO
|
if (maps.isEmpty())
|
||||||
|
return@lazy Vector2i.ZERO
|
||||||
|
|
||||||
|
Vector2i(maps.maxOf { it.size.x }, maps.maxOf { it.size.y })
|
||||||
|
}
|
||||||
|
|
||||||
override fun bind(def: DungeonDefinition) {
|
override fun bind(def: DungeonDefinition) {
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import com.github.benmanes.caffeine.cache.AsyncLoadingCache
|
|||||||
import com.github.benmanes.caffeine.cache.CacheLoader
|
import com.github.benmanes.caffeine.cache.CacheLoader
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.github.benmanes.caffeine.cache.LoadingCache
|
import com.github.benmanes.caffeine.cache.LoadingCache
|
||||||
import com.github.benmanes.caffeine.cache.Scheduler
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
@ -15,7 +14,6 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap
|
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.lwjgl.opengl.GL45
|
import org.lwjgl.opengl.GL45
|
||||||
@ -50,7 +48,7 @@ import java.util.Collections
|
|||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.function.Consumer
|
||||||
|
|
||||||
class Image private constructor(
|
class Image private constructor(
|
||||||
val source: IStarboundFile,
|
val source: IStarboundFile,
|
||||||
@ -108,6 +106,9 @@ class Image private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val data: ByteBuffer
|
val data: ByteBuffer
|
||||||
|
get() = dataCache.get(source).join()
|
||||||
|
|
||||||
|
val dataFuture: CompletableFuture<ByteBuffer>
|
||||||
get() = dataCache.get(source)
|
get() = dataCache.get(source)
|
||||||
|
|
||||||
val texture: GLTexture2D get() {
|
val texture: GLTexture2D get() {
|
||||||
@ -125,10 +126,12 @@ class Image private constructor(
|
|||||||
client.named2DTextures1.get(this) {
|
client.named2DTextures1.get(this) {
|
||||||
val tex = GLTexture2D(width, height, GL45.GL_RGBA8)
|
val tex = GLTexture2D(width, height, GL45.GL_RGBA8)
|
||||||
|
|
||||||
tex.upload(GL45.GL_RGBA, GL45.GL_UNSIGNED_BYTE, data)
|
dataFuture.thenAcceptAsync(Consumer {
|
||||||
|
tex.upload(GL45.GL_RGBA, GL45.GL_UNSIGNED_BYTE, data)
|
||||||
|
|
||||||
tex.textureMinFilter = GL45.GL_NEAREST
|
tex.textureMinFilter = GL45.GL_NEAREST
|
||||||
tex.textureMagFilter = GL45.GL_NEAREST
|
tex.textureMagFilter = GL45.GL_NEAREST
|
||||||
|
}, client)
|
||||||
|
|
||||||
tex
|
tex
|
||||||
}
|
}
|
||||||
@ -334,19 +337,19 @@ class Image private constructor(
|
|||||||
return ReadDirectData(data, getWidth[0], getHeight[0], components[0])
|
return ReadDirectData(data, getWidth[0], getHeight[0], components[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dataCache: LoadingCache<IStarboundFile, ByteBuffer> = Caffeine.newBuilder()
|
private val dataCache: AsyncLoadingCache<IStarboundFile, ByteBuffer> = Caffeine.newBuilder()
|
||||||
.expireAfterAccess(Duration.ofMinutes(1))
|
.expireAfterAccess(Duration.ofMinutes(1))
|
||||||
.weigher<IStarboundFile, ByteBuffer> { key, value -> value.capacity() }
|
.weigher<IStarboundFile, ByteBuffer> { key, value -> value.capacity() }
|
||||||
.maximumWeight((Runtime.getRuntime().maxMemory() / 4L).coerceIn(1_024L * 1_024L * 32L /* 32 МиБ */, 1_024L * 1_024L * 256L /* 256 МиБ */))
|
.maximumWeight((Runtime.getRuntime().maxMemory() / 4L).coerceIn(1_024L * 1_024L * 32L /* 32 МиБ */, 1_024L * 1_024L * 256L /* 256 МиБ */))
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.EXECUTOR) // SCREENED_EXECUTOR shouldn't be used here
|
.executor(Starbound.IO_EXECUTOR)
|
||||||
.build { readImageDirect(it).data }
|
.buildAsync(CacheLoader { readImageDirect(it).data })
|
||||||
|
|
||||||
private val spaceScanCache = Caffeine.newBuilder()
|
private val spaceScanCache = Caffeine.newBuilder()
|
||||||
.expireAfterAccess(Duration.ofMinutes(30))
|
.expireAfterAccess(Duration.ofMinutes(30))
|
||||||
.softValues()
|
.softValues()
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.SCREENED_EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build<SpaceScanKey, ImmutableSet<Vector2i>>()
|
.build<SpaceScanKey, ImmutableSet<Vector2i>>()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -20,7 +20,6 @@ import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
|||||||
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.util.random.staticRandom64
|
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandomInt
|
import ru.dbotthepony.kstarbound.util.random.staticRandomInt
|
||||||
import ru.dbotthepony.kstarbound.world.Universe
|
import ru.dbotthepony.kstarbound.world.Universe
|
||||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
@ -112,14 +111,12 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findSensiblePlayerStart(): Vector2d? {
|
fun findSensiblePlayerStart(random: RandomGenerator): Vector2d? {
|
||||||
val layout = worldLayout ?: return null
|
val layout = worldLayout ?: return null
|
||||||
|
|
||||||
if (layout.playerStartSearchRegions.isEmpty())
|
if (layout.playerStartSearchRegions.isEmpty())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val random = random()
|
|
||||||
|
|
||||||
for (i in 0 until Globals.worldTemplate.playerStartSearchTries) {
|
for (i in 0 until Globals.worldTemplate.playerStartSearchTries) {
|
||||||
val region = layout.playerStartSearchRegions.random(random)
|
val region = layout.playerStartSearchRegions.random(random)
|
||||||
val x = random.nextInt(region.mins.x, region.maxs.x)
|
val x = random.nextInt(region.mins.x, region.maxs.x)
|
||||||
@ -298,23 +295,30 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
|||||||
var backgroundCave = false
|
var backgroundCave = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private val cellCache = Caffeine.newBuilder()
|
// as said by Ben Manes, if cache is write-heavy, it is up to end users
|
||||||
.maximumSize(1_500_000L) // plentiful of space, and allows for high hit ratio (around 79%) in most situations
|
// to stripe it into multiple distinct caches (so write buffer doesn't get overflown and force
|
||||||
// downside is memory consumption, but why should it matter when we save 80% of cpu time?
|
// to be drained in place)
|
||||||
.expireAfterAccess(Duration.ofSeconds(20))
|
// https://github.com/ben-manes/caffeine/issues/1320#issuecomment-1812884592
|
||||||
.executor(Starbound.SCREENED_EXECUTOR)
|
private val cellCache = Array(256) {
|
||||||
.scheduler(Starbound)
|
Caffeine.newBuilder()
|
||||||
// .recordStats()
|
.maximumSize(50_000L) // plentiful of space, and allows for high hit ratio (around 79%) in most situations
|
||||||
.build<Vector2i, CellInfo> { (x, y) -> cellInfo0(x, y) }
|
// downside is memory consumption, but why should it matter when we save 80% of cpu time?
|
||||||
|
.expireAfterAccess(Duration.ofSeconds(20))
|
||||||
|
.executor(Starbound.EXECUTOR)
|
||||||
|
.scheduler(Starbound)
|
||||||
|
// .recordStats()
|
||||||
|
.build<Vector2i, CellInfo> { (x, y) -> cellInfo0(x, y) }
|
||||||
|
}
|
||||||
|
|
||||||
fun cellInfo(x: Int, y: Int): CellInfo {
|
fun cellInfo(x: Int, y: Int): CellInfo {
|
||||||
worldLayout ?: return CellInfo(x, y)
|
worldLayout ?: return CellInfo(x, y)
|
||||||
return cellCache.get(Vector2i(x, y))
|
val vec = Vector2i(x, y)
|
||||||
|
return cellCache[vec.hashCode() and 255].get(vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cellInfo(pos: Vector2i): CellInfo {
|
fun cellInfo(pos: Vector2i): CellInfo {
|
||||||
worldLayout ?: return CellInfo(pos.x, pos.y)
|
worldLayout ?: return CellInfo(pos.x, pos.y)
|
||||||
return cellCache.get(pos)
|
return cellCache[pos.hashCode() and 255].get(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cellInfo0(x: Int, y: Int): CellInfo {
|
private fun cellInfo0(x: Int, y: Int): CellInfo {
|
||||||
|
@ -128,9 +128,16 @@ fun InputStream.readAABB(): AABB {
|
|||||||
return AABB(readVector2d(), readVector2d())
|
return AABB(readVector2d(), readVector2d())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun InputStream.readAABB(isLegacy: Boolean): AABB {
|
||||||
|
if (isLegacy)
|
||||||
|
return readAABBLegacy()
|
||||||
|
else
|
||||||
|
return readAABB()
|
||||||
|
}
|
||||||
|
|
||||||
fun OutputStream.writeAABBLegacy(value: AABB) {
|
fun OutputStream.writeAABBLegacy(value: AABB) {
|
||||||
writeStruct2f(value.mins.toFloatVector())
|
writeStruct2d(value.mins, true)
|
||||||
writeStruct2f(value.maxs.toFloatVector())
|
writeStruct2d(value.maxs, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun OutputStream.writeAABBLegacyOptional(value: KOptional<AABB>) {
|
fun OutputStream.writeAABBLegacyOptional(value: KOptional<AABB>) {
|
||||||
@ -150,6 +157,11 @@ fun OutputStream.writeAABB(value: AABB) {
|
|||||||
writeStruct2d(value.maxs)
|
writeStruct2d(value.maxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun OutputStream.writeAABB(value: AABB, isLegacy: Boolean) {
|
||||||
|
writeStruct2d(value.mins, isLegacy)
|
||||||
|
writeStruct2d(value.maxs, isLegacy)
|
||||||
|
}
|
||||||
|
|
||||||
private fun InputStream.readBoolean(): Boolean {
|
private fun InputStream.readBoolean(): Boolean {
|
||||||
val read = read()
|
val read = read()
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) {
|
|||||||
val results = newTable()
|
val results = newTable()
|
||||||
|
|
||||||
for (connection in self.inputNodes[index.toInt()].connections) {
|
for (connection in self.inputNodes[index.toInt()].connections) {
|
||||||
val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation) as? WorldObject
|
val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation, WorldObject::class)
|
||||||
|
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
results[entity.entityID] = connection.index
|
results[entity.entityID] = connection.index
|
||||||
@ -163,7 +163,7 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) {
|
|||||||
val results = newTable()
|
val results = newTable()
|
||||||
|
|
||||||
for (connection in self.outputNodes[index.toInt()].connections) {
|
for (connection in self.outputNodes[index.toInt()].connections) {
|
||||||
val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation) as? WorldObject
|
val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation, WorldObject::class)
|
||||||
|
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
results[entity.entityID] = connection.index
|
results[entity.entityID] = connection.index
|
||||||
|
@ -277,6 +277,37 @@ data class AABB(val mins: Vector2d, val maxs: Vector2d) {
|
|||||||
return AABB(pos, pos + Vector2d(width, height))
|
return AABB(pos, pos + Vector2d(width, height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ofPoints(points: Collection<Vector2d>): AABB {
|
||||||
|
if (points.isEmpty())
|
||||||
|
return NEVER
|
||||||
|
|
||||||
|
val minX = points.minOf { it.x }
|
||||||
|
val maxX = points.maxOf { it.x }
|
||||||
|
val minY = points.minOf { it.y }
|
||||||
|
val maxY = points.maxOf { it.y }
|
||||||
|
|
||||||
|
return AABB(
|
||||||
|
Vector2d(minX, minY),
|
||||||
|
Vector2d(maxX, maxY),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("ofPointsI")
|
||||||
|
fun ofPoints(points: Collection<Vector2i>): AABB {
|
||||||
|
if (points.isEmpty())
|
||||||
|
return NEVER
|
||||||
|
|
||||||
|
val minX = points.minOf { it.x }.toDouble()
|
||||||
|
val maxX = points.maxOf { it.x }.toDouble()
|
||||||
|
val minY = points.minOf { it.y }.toDouble()
|
||||||
|
val maxY = points.maxOf { it.y }.toDouble()
|
||||||
|
|
||||||
|
return AABB(
|
||||||
|
Vector2d(minX, minY),
|
||||||
|
Vector2d(maxX, maxY),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
|
@JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
|
||||||
@JvmField val NEVER = AABB(Vector2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
|
@JvmField val NEVER = AABB(Vector2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ class ConnectWirePacket(val target: WireConnection, val source: WireConnection)
|
|||||||
|
|
||||||
override fun play(connection: ServerConnection) {
|
override fun play(connection: ServerConnection) {
|
||||||
connection.enqueue {
|
connection.enqueue {
|
||||||
val target = entityIndex.tileEntityAt(target.entityLocation) as? WorldObject ?: return@enqueue
|
val target = entityIndex.tileEntityAt(target.entityLocation, WorldObject::class) ?: return@enqueue
|
||||||
val source = entityIndex.tileEntityAt(source.entityLocation) as? WorldObject ?: return@enqueue
|
val source = entityIndex.tileEntityAt(source.entityLocation, WorldObject::class) ?: return@enqueue
|
||||||
|
|
||||||
val targetNode = target.outputNodes.getOrNull(this@ConnectWirePacket.target.index) ?: return@enqueue
|
val targetNode = target.outputNodes.getOrNull(this@ConnectWirePacket.target.index) ?: return@enqueue
|
||||||
val sourceNode = source.inputNodes.getOrNull(this@ConnectWirePacket.source.index) ?: return@enqueue
|
val sourceNode = source.inputNodes.getOrNull(this@ConnectWirePacket.source.index) ?: return@enqueue
|
||||||
|
@ -21,7 +21,7 @@ class DisconnectAllWiresPacket(val pos: Vector2i, val node: WireNode) : IServerP
|
|||||||
|
|
||||||
override fun play(connection: ServerConnection) {
|
override fun play(connection: ServerConnection) {
|
||||||
connection.enqueue {
|
connection.enqueue {
|
||||||
val target = entityIndex.tileEntityAt(pos) as? WorldObject ?: return@enqueue
|
val target = entityIndex.tileEntityAt(pos, WorldObject::class) ?: return@enqueue
|
||||||
val node = if (node.isInput) target.inputNodes.getOrNull(node.index) else target.outputNodes.getOrNull(node.index)
|
val node = if (node.isInput) target.inputNodes.getOrNull(node.index) else target.outputNodes.getOrNull(node.index)
|
||||||
node?.removeAllConnections()
|
node?.removeAllConnections()
|
||||||
}
|
}
|
||||||
|
@ -67,9 +67,9 @@ class LegacyWireProcessor(val world: ServerWorld) {
|
|||||||
launch {
|
launch {
|
||||||
ticket.chunk.await()
|
ticket.chunk.await()
|
||||||
|
|
||||||
val findEntity = world.entityIndex.tileEntityAt(pos)
|
val findEntity = world.entityIndex.tileEntityAt(pos, WorldObject::class)
|
||||||
|
|
||||||
if (findEntity is WorldObject) {
|
if (findEntity != null) {
|
||||||
// if entity exists, add it to working entities and find more not loaded entities
|
// if entity exists, add it to working entities and find more not loaded entities
|
||||||
populateWorking(findEntity)
|
populateWorking(findEntity)
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.server.world
|
|||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.future.await
|
import kotlinx.coroutines.future.await
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
@ -70,6 +71,7 @@ import kotlin.concurrent.withLock
|
|||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, ServerChunk>(world, pos) {
|
class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, ServerChunk>(world, pos) {
|
||||||
override var state: ChunkState = ChunkState.FRESH
|
override var state: ChunkState = ChunkState.FRESH
|
||||||
@ -731,7 +733,6 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
pos.tile + Vector2i(width + CHUNK_SIZE_FF, height + CHUNK_SIZE_FF)
|
pos.tile + Vector2i(width + CHUNK_SIZE_FF, height + CHUNK_SIZE_FF)
|
||||||
)
|
)
|
||||||
|
|
||||||
val pacer = ExecutionTimePacer(500_000L, 40L)
|
|
||||||
val random = random(staticRandom64(world.template.seed, pos.x, pos.y, "microdungeon placement"))
|
val random = random(staticRandom64(world.template.seed, pos.x, pos.y, "microdungeon placement"))
|
||||||
|
|
||||||
for (placement in placements) {
|
for (placement in placements) {
|
||||||
@ -758,15 +759,10 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
if (!bounds.isInside(pos) || !bounds.isInside(pos + anchor.reader.size - Vector2i.POSITIVE_XY))
|
if (!bounds.isInside(pos) || !bounds.isInside(pos + anchor.reader.size - Vector2i.POSITIVE_XY))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
val collision = anchor.reader.walkTiles<Boolean> { x, y, tile ->
|
// this is quite ugly code flow, but we should try to avoid double-walking
|
||||||
if (tile.usesPlaces && world.getCell(pos.x + x, pos.y + y).dungeonId != NO_DUNGEON_ID) {
|
// over all dungeon tiles (DungeonTile#canPlace already checks for absence of other dungeons on their place,
|
||||||
return@walkTiles KOptional(true)
|
// so we only need to tell DungeonPart to not force-place)
|
||||||
}
|
if (anchor.canPlace(pos.x, pos.y, world, false)) {
|
||||||
|
|
||||||
return@walkTiles KOptional()
|
|
||||||
}.orElse(false)
|
|
||||||
|
|
||||||
if (!collision && anchor.canPlace(pos.x, pos.y, world)) {
|
|
||||||
try {
|
try {
|
||||||
dungeon.value!!.build(anchor, world, random, pos.x, pos.y, dungeonID = MICRO_DUNGEON_ID).await()
|
dungeon.value!!.build(anchor, world, random, pos.x, pos.y, dungeonID = MICRO_DUNGEON_ID).await()
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
@ -778,7 +774,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
|
|
||||||
// some breathing room for other code, since placement checking is performance intense operation
|
// some breathing room for other code, since placement checking is performance intense operation
|
||||||
if (!world.isInPreparation && world.clients.isNotEmpty())
|
if (!world.isInPreparation && world.clients.isNotEmpty())
|
||||||
pacer.measureAndSuspend()
|
delay(min(60L, anchor.reader.size.x * anchor.reader.size.y / 40L))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package ru.dbotthepony.kstarbound.server.world
|
|||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Cache
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.github.benmanes.caffeine.cache.Scheduler
|
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import kotlinx.coroutines.future.await
|
import kotlinx.coroutines.future.await
|
||||||
@ -221,7 +220,7 @@ class ServerUniverse private constructor(marker: Nothing?) : Universe(), Closeab
|
|||||||
.maximumSize(1024L)
|
.maximumSize(1024L)
|
||||||
.softValues()
|
.softValues()
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.SCREENED_EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun getChunkFuture(pos: Vector2i): CompletableFuture<KOptional<UniverseChunk>> {
|
fun getChunkFuture(pos: Vector2i): CompletableFuture<KOptional<UniverseChunk>> {
|
||||||
|
@ -382,10 +382,11 @@ class ServerWorld private constructor(
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
val tickets = ArrayList<ServerChunk.ITicket>()
|
val tickets = ArrayList<ServerChunk.ITicket>()
|
||||||
|
val random = if (hint == null) random(template.seed) else random()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LOGGER.info("Trying to find player spawn position...")
|
LOGGER.info("Trying to find player spawn position...")
|
||||||
var pos = hint ?: CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart() }, Starbound.EXECUTOR).await() ?: Vector2d(0.0, template.surfaceLevel().toDouble())
|
var pos = hint ?: CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart(random) }, Starbound.EXECUTOR).await() ?: Vector2d(0.0, template.surfaceLevel().toDouble())
|
||||||
var previous = pos
|
var previous = pos
|
||||||
LOGGER.info("Trying to find player spawn position near $pos...")
|
LOGGER.info("Trying to find player spawn position near $pos...")
|
||||||
|
|
||||||
@ -442,7 +443,7 @@ class ServerWorld private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart() }, Starbound.EXECUTOR).await() ?: Vector2d(0.0, template.surfaceLevel().toDouble())
|
pos = CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart(random) }, Starbound.EXECUTOR).await() ?: Vector2d(0.0, template.surfaceLevel().toDouble())
|
||||||
|
|
||||||
if (previous != pos) {
|
if (previous != pos) {
|
||||||
LOGGER.info("Still trying to find player spawn position near $pos...")
|
LOGGER.info("Still trying to find player spawn position near $pos...")
|
||||||
|
@ -82,7 +82,12 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
|||||||
private suspend fun damageTilesLoop() {
|
private suspend fun damageTilesLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
val (positions, isBackground, sourcePosition, damage, source) = damageTilesQueue.receive()
|
val (positions, isBackground, sourcePosition, damage, source) = damageTilesQueue.receive()
|
||||||
world.damageTiles(positions, isBackground, sourcePosition, damage, source, tileModificationBudget)
|
|
||||||
|
try {
|
||||||
|
world.damageTiles(positions, isBackground, sourcePosition, damage, source, tileModificationBudget)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.error("Exception in player damage tiles loop", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +107,9 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
|||||||
}
|
}
|
||||||
} catch (err: CancellationException) {
|
} catch (err: CancellationException) {
|
||||||
client.send(TileModificationFailurePacket(modifications))
|
client.send(TileModificationFailurePacket(modifications))
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
client.send(TileModificationFailurePacket(modifications))
|
||||||
|
LOGGER.error("Exception in player modify tiles loop", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.tile.TileEntity
|
import ru.dbotthepony.kstarbound.world.entities.tile.TileEntity
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.isSuperclassOf
|
||||||
|
|
||||||
// After some thinking, I decided to go with separate spatial index over
|
// After some thinking, I decided to go with separate spatial index over
|
||||||
// using chunk/chunkmap as spatial indexing of entities (just like original engine does).
|
// using chunk/chunkmap as spatial indexing of entities (just like original engine does).
|
||||||
@ -283,10 +285,18 @@ class EntityIndex(val geometry: WorldGeometry) {
|
|||||||
return first(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces }) as TileEntity?
|
return first(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces }) as TileEntity?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T : TileEntity> tileEntityAt(pos: Vector2i, type: KClass<T>): T? {
|
||||||
|
return first(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces && type.isSuperclassOf(it::class) }) as T?
|
||||||
|
}
|
||||||
|
|
||||||
fun tileEntitiesAt(pos: Vector2i): MutableList<TileEntity> {
|
fun tileEntitiesAt(pos: Vector2i): MutableList<TileEntity> {
|
||||||
return query(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces }) as MutableList<TileEntity>
|
return query(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces }) as MutableList<TileEntity>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T : TileEntity> tileEntitiesAt(pos: Vector2i, type: KClass<T>): MutableList<T> {
|
||||||
|
return query(AABB(pos.toDoubleVector(), pos.toDoubleVector() + Vector2d.POSITIVE_XY), Predicate { it is TileEntity && pos in it.occupySpaces && type.isSuperclassOf(it::class) }) as MutableList<T>
|
||||||
|
}
|
||||||
|
|
||||||
fun iterate(rect: AABB, visitor: (AbstractEntity) -> Unit, withEdges: Boolean = true) {
|
fun iterate(rect: AABB, visitor: (AbstractEntity) -> Unit, withEdges: Boolean = true) {
|
||||||
walk<Unit>(rect, { visitor(it); KOptional() }, withEdges)
|
walk<Unit>(rect, { visitor(it); KOptional() }, withEdges)
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,6 @@ import ru.dbotthepony.kstarbound.world.World
|
|||||||
* Entities with dynamics (Player, Drops, Projectiles, NPCs, etc)
|
* Entities with dynamics (Player, Drops, Projectiles, NPCs, etc)
|
||||||
*/
|
*/
|
||||||
abstract class DynamicEntity() : AbstractEntity() {
|
abstract class DynamicEntity() : AbstractEntity() {
|
||||||
private var forceChunkRepos = false
|
|
||||||
|
|
||||||
override var position
|
override var position
|
||||||
get() = movement.position
|
get() = movement.position
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -52,7 +50,6 @@ abstract class DynamicEntity() : AbstractEntity() {
|
|||||||
super.onJoinWorld(world)
|
super.onJoinWorld(world)
|
||||||
world.dynamicEntities.add(this)
|
world.dynamicEntities.add(this)
|
||||||
movement.initialize(world, spatialEntry)
|
movement.initialize(world, spatialEntry)
|
||||||
forceChunkRepos = true
|
|
||||||
metaFixture = spatialEntry!!.Fixture()
|
metaFixture = spatialEntry!!.Fixture()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,7 +553,7 @@ open class MovementController() {
|
|||||||
movement = movement + totalCorrection,
|
movement = movement + totalCorrection,
|
||||||
correction = totalCorrection,
|
correction = totalCorrection,
|
||||||
isStuck = false,
|
isStuck = false,
|
||||||
isOnGround = -totalCorrection.dot(determineGravity()) > SEPARATION_TOLERANCE,
|
isOnGround = totalCorrection.unitVector.dot(determineGravity().unitVector) >= 0.5,
|
||||||
movingCollisionId = movingCollisionId,
|
movingCollisionId = movingCollisionId,
|
||||||
collisionType = maxCollided,
|
collisionType = maxCollided,
|
||||||
// groundSlope = Vector2d.POSITIVE_Y,
|
// groundSlope = Vector2d.POSITIVE_Y,
|
||||||
|
@ -2,9 +2,14 @@ package ru.dbotthepony.kstarbound.world.entities.tile
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
@ -29,7 +34,6 @@ import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
|||||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isMetaTile
|
import ru.dbotthepony.kstarbound.defs.tile.isMetaTile
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isNullTile
|
|
||||||
import ru.dbotthepony.kstarbound.defs.world.BushVariant
|
import ru.dbotthepony.kstarbound.defs.world.BushVariant
|
||||||
import ru.dbotthepony.kstarbound.defs.world.GrassVariant
|
import ru.dbotthepony.kstarbound.defs.world.GrassVariant
|
||||||
import ru.dbotthepony.kstarbound.defs.world.TreeVariant
|
import ru.dbotthepony.kstarbound.defs.world.TreeVariant
|
||||||
@ -64,13 +68,14 @@ import java.io.DataInputStream
|
|||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
class PlantEntity() : TileEntity() {
|
class PlantEntity() : TileEntity() {
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class Piece(
|
data class Piece(
|
||||||
val image: String,
|
val image: String,
|
||||||
val offset: Vector2d,
|
var offset: Vector2d,
|
||||||
var segmentIdx: Int,
|
var segmentIdx: Int,
|
||||||
val isStructuralSegment: Boolean,
|
val isStructuralSegment: Boolean,
|
||||||
val kind: Kind,
|
val kind: Kind,
|
||||||
@ -147,7 +152,7 @@ class PlantEntity() : TileEntity() {
|
|||||||
isCeiling = data.get("ceiling", false)
|
isCeiling = data.get("ceiling", false)
|
||||||
stemDropConfig = data["stemDropConfig"] as? JsonObject ?: JsonObject()
|
stemDropConfig = data["stemDropConfig"] as? JsonObject ?: JsonObject()
|
||||||
foliageDropConfig = data["foliageDropConfig"] as? JsonObject ?: JsonObject()
|
foliageDropConfig = data["foliageDropConfig"] as? JsonObject ?: JsonObject()
|
||||||
saplingDropConfig = data["saplingDropConfig"] as? JsonObject ?: JsonObject()
|
saplingDropConfig = data["saplingDropConfig"] ?: JsonObject()
|
||||||
descriptions = data["descriptions"] as? JsonObject ?: JsonObject()
|
descriptions = data["descriptions"] as? JsonObject ?: JsonObject()
|
||||||
isEphemeral = data.get("ephemeral", false)
|
isEphemeral = data.get("ephemeral", false)
|
||||||
fallsWhenDead = data.get("fallsWhenDead", false)
|
fallsWhenDead = data.get("fallsWhenDead", false)
|
||||||
@ -220,7 +225,7 @@ class PlantEntity() : TileEntity() {
|
|||||||
private set
|
private set
|
||||||
var foliageDropConfig: JsonObject = JsonObject()
|
var foliageDropConfig: JsonObject = JsonObject()
|
||||||
private set
|
private set
|
||||||
var saplingDropConfig: JsonObject = JsonObject()
|
var saplingDropConfig: JsonElement = JsonObject()
|
||||||
private set
|
private set
|
||||||
var descriptions: JsonObject = JsonObject()
|
var descriptions: JsonObject = JsonObject()
|
||||||
private set
|
private set
|
||||||
@ -229,6 +234,7 @@ class PlantEntity() : TileEntity() {
|
|||||||
|
|
||||||
constructor(config: TreeVariant, random: RandomGenerator) : this() {
|
constructor(config: TreeVariant, random: RandomGenerator) : this() {
|
||||||
isCeiling = config.ceiling
|
isCeiling = config.ceiling
|
||||||
|
fallsWhenDead = true
|
||||||
|
|
||||||
stemDropConfig = (config.stemDropConfig as? JsonObject)?.deepCopy() ?: JsonObject()
|
stemDropConfig = (config.stemDropConfig as? JsonObject)?.deepCopy() ?: JsonObject()
|
||||||
foliageDropConfig = (config.foliageDropConfig as? JsonObject)?.deepCopy() ?: JsonObject()
|
foliageDropConfig = (config.foliageDropConfig as? JsonObject)?.deepCopy() ?: JsonObject()
|
||||||
@ -532,7 +538,7 @@ class PlantEntity() : TileEntity() {
|
|||||||
isCeiling = stream.readBoolean()
|
isCeiling = stream.readBoolean()
|
||||||
stemDropConfig = stream.readJsonElement() as JsonObject
|
stemDropConfig = stream.readJsonElement() as JsonObject
|
||||||
foliageDropConfig = stream.readJsonElement() as JsonObject
|
foliageDropConfig = stream.readJsonElement() as JsonObject
|
||||||
saplingDropConfig = stream.readJsonElement() as JsonObject
|
saplingDropConfig = stream.readJsonElement()
|
||||||
descriptions = stream.readJsonElement() as JsonObject
|
descriptions = stream.readJsonElement() as JsonObject
|
||||||
|
|
||||||
isEphemeral = stream.readBoolean()
|
isEphemeral = stream.readBoolean()
|
||||||
@ -640,8 +646,24 @@ class PlantEntity() : TileEntity() {
|
|||||||
override fun tick(delta: Double) {
|
override fun tick(delta: Double) {
|
||||||
super.tick(delta)
|
super.tick(delta)
|
||||||
|
|
||||||
if (world.isServer && piecesInternal.isEmpty()) {
|
if (world.isServer) {
|
||||||
remove(RemovalReason.REMOVED)
|
if (piecesInternal.isEmpty()) {
|
||||||
|
remove(RemovalReason.REMOVED)
|
||||||
|
} else if (roots.isNotEmpty()) {
|
||||||
|
for (root in roots) {
|
||||||
|
if (world.getCell(root).foreground.material.isEmptyTile) {
|
||||||
|
if (fallsWhenDead) {
|
||||||
|
breakAtPosition(tilePosition, position)
|
||||||
|
} else {
|
||||||
|
remove(RemovalReason.DYING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRemote) {
|
||||||
|
health.tick(tileDamageParameters, delta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,10 +682,123 @@ class PlantEntity() : TileEntity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun damage(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean {
|
override fun damage(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean {
|
||||||
// TODO
|
if (damageSpaces.isEmpty())
|
||||||
|
return false
|
||||||
|
|
||||||
|
var baseDamagePosition: Vector2i = damageSpaces.first()
|
||||||
|
|
||||||
|
for (piece in pieces) {
|
||||||
|
if (piece.isStructuralSegment) {
|
||||||
|
for (space in piece.spaces) {
|
||||||
|
for (pos in damageSpaces) {
|
||||||
|
if (world.geometry.wrap(space + tilePosition) == pos && baseDamagePosition.y < pos.y == isCeiling) {
|
||||||
|
// if this space is a "better match" for the root of the plant
|
||||||
|
baseDamagePosition = pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (x, y) = world.geometry.diff(baseDamagePosition, tilePosition)
|
||||||
|
|
||||||
|
// TODO: this is unnatural solution to tree damage,
|
||||||
|
// each tree piece should have its own damage status
|
||||||
|
health.damage(tileDamageParameters, source, damage)
|
||||||
|
tileDamageX = x.toDouble()
|
||||||
|
tileDamageY = y.toDouble()
|
||||||
|
tileDamageEvent.trigger()
|
||||||
|
|
||||||
|
if (health.isDead) {
|
||||||
|
if (fallsWhenDead) {
|
||||||
|
health.reset()
|
||||||
|
breakAtPosition(baseDamagePosition, source)
|
||||||
|
} else {
|
||||||
|
remove(RemovalReason.DYING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun breakAtPosition(position: Vector2i, source: Vector2d) {
|
||||||
|
val internalPos = world.geometry.diff(position, tilePosition)
|
||||||
|
var breakAtPiece = pieces.lastOrNull { it.isStructuralSegment && internalPos in it.spaces }
|
||||||
|
|
||||||
|
// default to highest structural piece
|
||||||
|
if (breakAtPiece == null) {
|
||||||
|
breakAtPiece = pieces.lastOrNull { it.isStructuralSegment }
|
||||||
|
}
|
||||||
|
|
||||||
|
// plant has no structural segments? this is a terrible fallback because it
|
||||||
|
// prevents destruction
|
||||||
|
breakAtPiece ?: return
|
||||||
|
|
||||||
|
var breakPoint = position.toDoubleVector() - tilePosition
|
||||||
|
|
||||||
|
if (breakAtPiece.spaces.isNotEmpty()) {
|
||||||
|
val bounds = AABB.ofPoints(breakAtPiece.spaces)
|
||||||
|
|
||||||
|
breakPoint = Vector2d(
|
||||||
|
bounds.mins.x + bounds.width / 2.0,
|
||||||
|
if (isCeiling) bounds.maxs.y else bounds.mins.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val droppedPieces = ArrayList<Piece>()
|
||||||
|
|
||||||
|
var idx = 0
|
||||||
|
|
||||||
|
while (idx < pieces.size) {
|
||||||
|
if (piecesInternal[idx].segmentIdx >= breakAtPiece.segmentIdx) {
|
||||||
|
droppedPieces.add(piecesInternal.removeAt(idx))
|
||||||
|
} else {
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val breakPointI = Vector2i((breakPoint.x + 0.5).toInt(), (breakPoint.y + 0.5).toInt())
|
||||||
|
|
||||||
|
// Calculate a new origin for the droppedPieces
|
||||||
|
for (piece in droppedPieces) {
|
||||||
|
piece.offset -= breakPoint
|
||||||
|
piece.spaces = piece.spaces.map { it - breakPointI }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
val worldSpaceBreakPoint = breakPoint + tilePosition
|
||||||
|
val segments = Int2ObjectAVLTreeMap<ArrayList<Piece>>()
|
||||||
|
|
||||||
|
for (piece in droppedPieces) {
|
||||||
|
segments.computeIfAbsent(piece.segmentIdx, Int2ObjectFunction { ArrayList() }).add(piece)
|
||||||
|
}
|
||||||
|
|
||||||
|
val angle = world.random.nextDouble(-0.3, 0.3)
|
||||||
|
val itr = segments.keys.iterator(segments.keys.lastInt())
|
||||||
|
val fallVector = (source - worldSpaceBreakPoint).unitVector
|
||||||
|
var first = true
|
||||||
|
|
||||||
|
while (itr.hasPrevious()) {
|
||||||
|
val index = itr.previousInt()
|
||||||
|
val segment = segments[index]!!
|
||||||
|
|
||||||
|
val entity = PlantPieceEntity(
|
||||||
|
segment,
|
||||||
|
worldSpaceBreakPoint,
|
||||||
|
fallVector,
|
||||||
|
description,
|
||||||
|
isCeiling,
|
||||||
|
stemDropConfig,
|
||||||
|
foliageDropConfig,
|
||||||
|
saplingDropConfig,
|
||||||
|
first,
|
||||||
|
angle
|
||||||
|
)
|
||||||
|
|
||||||
|
entity.joinWorld(world)
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "PlantEntity[at=$tilePosition, pieces=${pieces.size}]"
|
return "PlantEntity[at=$tilePosition, pieces=${pieces.size}]"
|
||||||
}
|
}
|
||||||
@ -692,7 +827,7 @@ class PlantEntity() : TileEntity() {
|
|||||||
// First bail out if we can't fit anything we're not adjusting
|
// First bail out if we can't fit anything we're not adjusting
|
||||||
for (space in occupySpaces) {
|
for (space in occupySpaces) {
|
||||||
// TODO: conditions seems to be inverted
|
// TODO: conditions seems to be inverted
|
||||||
if (withinAdjustments(space, position) && world.entityIndex.tileEntitiesAt(space).any { it is PlantEntity }) {
|
if (withinAdjustments(space, position) && world.entityIndex.tileEntityAt(space, PlantEntity::class) != null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,285 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities.tile
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonNull
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
|
import ru.dbotthepony.kommons.gson.get
|
||||||
|
import ru.dbotthepony.kommons.io.readCollection
|
||||||
|
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||||
|
import ru.dbotthepony.kommons.io.writeCollection
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kommons.util.getValue
|
||||||
|
import ru.dbotthepony.kommons.util.setValue
|
||||||
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.image.Image
|
||||||
|
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||||
|
import ru.dbotthepony.kstarbound.io.readAABB
|
||||||
|
import ru.dbotthepony.kstarbound.io.readDouble
|
||||||
|
import ru.dbotthepony.kstarbound.io.readEnumStupid
|
||||||
|
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||||
|
import ru.dbotthepony.kstarbound.io.readVector2d
|
||||||
|
import ru.dbotthepony.kstarbound.io.writeAABB
|
||||||
|
import ru.dbotthepony.kstarbound.io.writeDouble
|
||||||
|
import ru.dbotthepony.kstarbound.io.writeEnumStupid
|
||||||
|
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
||||||
|
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.math.vector.times
|
||||||
|
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
|
||||||
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||||
|
import ru.dbotthepony.kstarbound.world.entities.MovementController
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
|
class PlantPieceEntity() : DynamicEntity() {
|
||||||
|
override val type: EntityType
|
||||||
|
get() = EntityType.PLANT_DROP
|
||||||
|
|
||||||
|
private var calculatedMetaBoundingBox = AABB.ZERO
|
||||||
|
private var calculatedCollisionBox = AABB.ZERO
|
||||||
|
private var calculatedCollisionHull = Poly.EMPTY
|
||||||
|
|
||||||
|
override val metaBoundingBox: AABB
|
||||||
|
get() = calculatedMetaBoundingBox + position
|
||||||
|
|
||||||
|
override val collisionArea: AABB
|
||||||
|
get() = calculatedCollisionBox + position
|
||||||
|
|
||||||
|
override val movement = MovementController().also { networkGroup.upstream.add(it.networkGroup) }
|
||||||
|
var spawnedDrops by networkedBoolean().also { networkGroup.upstream.add(it) }
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val piecesInternal = ArrayList<Piece>()
|
||||||
|
|
||||||
|
val pieces: List<Piece> = Collections.unmodifiableList(piecesInternal)
|
||||||
|
|
||||||
|
var isFirst = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
var stemConfig: JsonObject = JsonObject()
|
||||||
|
private set
|
||||||
|
var foliageConfig: JsonObject = JsonObject()
|
||||||
|
private set
|
||||||
|
var saplingConfig: JsonElement = JsonNull.INSTANCE
|
||||||
|
private set
|
||||||
|
|
||||||
|
var rotationRate = 0.0
|
||||||
|
private set
|
||||||
|
var rotationFallThreshold = 0.0
|
||||||
|
private set
|
||||||
|
var rotationCap = 0.0
|
||||||
|
private set
|
||||||
|
|
||||||
|
var timeToLive = 30.0
|
||||||
|
private set
|
||||||
|
|
||||||
|
data class Piece(
|
||||||
|
val image: String,
|
||||||
|
val offset: Vector2d,
|
||||||
|
val segmentIdx: Int,
|
||||||
|
val flip: Boolean,
|
||||||
|
val kind: PlantEntity.Piece.Kind,
|
||||||
|
) {
|
||||||
|
constructor(piece: PlantEntity.Piece) : this(piece.image, piece.offset, piece.segmentIdx, piece.flip, piece.kind)
|
||||||
|
|
||||||
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||||
|
stream.readInternedString(),
|
||||||
|
stream.readVector2d(isLegacy),
|
||||||
|
0, // stream.readIntStupid(isLegacy),
|
||||||
|
stream.readBoolean(),
|
||||||
|
PlantEntity.Piece.Kind.entries[stream.readEnumStupid(isLegacy)],
|
||||||
|
)
|
||||||
|
|
||||||
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeBinaryString(image)
|
||||||
|
stream.writeStruct2d(offset, isLegacy)
|
||||||
|
// stream.writeIntStupid(segmentIdx, isLegacy)
|
||||||
|
stream.writeBoolean(flip)
|
||||||
|
stream.writeEnumStupid(kind.ordinal, isLegacy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
pieces: List<PlantEntity.Piece>,
|
||||||
|
position: Vector2d,
|
||||||
|
damageSource: Vector2d,
|
||||||
|
description: String,
|
||||||
|
upsideDown: Boolean,
|
||||||
|
stemConfig: JsonObject,
|
||||||
|
foliageConfig: JsonObject,
|
||||||
|
saplingConfig: JsonElement,
|
||||||
|
isFirst: Boolean,
|
||||||
|
angle: Double
|
||||||
|
) : this() {
|
||||||
|
this.stemConfig = stemConfig
|
||||||
|
this.foliageConfig = foliageConfig
|
||||||
|
this.saplingConfig = saplingConfig
|
||||||
|
this.isFirst = isFirst
|
||||||
|
|
||||||
|
this.movement.position = position
|
||||||
|
this.description = description
|
||||||
|
|
||||||
|
if (!upsideDown) {
|
||||||
|
this.rotationRate = 0.00001 * (damageSource.x + angle).sign
|
||||||
|
this.rotationFallThreshold = PI / (3.0 + angle)
|
||||||
|
this.rotationCap = PI - this.rotationFallThreshold
|
||||||
|
}
|
||||||
|
|
||||||
|
val stemSpaces = pieces.stream().filter { it.isStructuralSegment }.flatMap { it.spaces.stream() }.collect(Collectors.toCollection(::ObjectArraySet))
|
||||||
|
val allSpaces = pieces.stream().flatMap { it.spaces.stream() }.collect(Collectors.toCollection(::ObjectArraySet))
|
||||||
|
|
||||||
|
for (piece in pieces) {
|
||||||
|
piecesInternal.add(Piece(piece))
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatedMetaBoundingBox = AABB.ofPoints(allSpaces)
|
||||||
|
|
||||||
|
if (pieces.any { it.isStructuralSegment } && stemSpaces.isNotEmpty()) {
|
||||||
|
calculatedCollisionBox = AABB.ofPoints(stemSpaces)
|
||||||
|
|
||||||
|
if (stemSpaces.size >= 2) {
|
||||||
|
calculatedCollisionHull = Poly.quickhull(stemSpaces.map { it.toDoubleVector() })
|
||||||
|
} else {
|
||||||
|
calculatedCollisionHull = Poly(calculatedCollisionBox)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
calculatedCollisionBox = calculatedMetaBoundingBox
|
||||||
|
calculatedCollisionHull = Poly(calculatedMetaBoundingBox)
|
||||||
|
}
|
||||||
|
|
||||||
|
//calculatedCollisionHull = calculatedCollisionHull * 0.5 + calculatedCollisionHull.aabb.centre * 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this() {
|
||||||
|
timeToLive = stream.readDouble(isLegacy)
|
||||||
|
isFirst = stream.readBoolean()
|
||||||
|
description = stream.readInternedString()
|
||||||
|
calculatedMetaBoundingBox = stream.readAABB(isLegacy)
|
||||||
|
calculatedCollisionBox = stream.readAABB(isLegacy)
|
||||||
|
rotationRate = stream.readDouble(isLegacy)
|
||||||
|
|
||||||
|
piecesInternal.clear()
|
||||||
|
piecesInternal.addAll(stream.readCollection { Piece(this, isLegacy) })
|
||||||
|
|
||||||
|
stemConfig = stream.readJsonElement() as JsonObject
|
||||||
|
foliageConfig = stream.readJsonElement() as JsonObject
|
||||||
|
saplingConfig = stream.readJsonElement()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeDouble(timeToLive, isLegacy)
|
||||||
|
stream.writeBoolean(isFirst)
|
||||||
|
stream.writeBinaryString(description)
|
||||||
|
stream.writeAABB(calculatedMetaBoundingBox, isLegacy)
|
||||||
|
stream.writeAABB(calculatedCollisionBox, isLegacy)
|
||||||
|
stream.writeDouble(rotationRate, isLegacy)
|
||||||
|
stream.writeCollection(piecesInternal) { it.write(this, isLegacy) }
|
||||||
|
stream.writeJsonElement(stemConfig)
|
||||||
|
stream.writeJsonElement(foliageConfig)
|
||||||
|
stream.writeJsonElement(saplingConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onJoinWorld(world: World<*, *>) {
|
||||||
|
val parameters = MovementParameters(
|
||||||
|
collisionPoly = Either.left(calculatedCollisionHull),
|
||||||
|
ignorePlatformCollision = true,
|
||||||
|
gravityMultiplier = 0.2,
|
||||||
|
physicsEffectCategories = ImmutableSet.of("plantdrop")
|
||||||
|
)
|
||||||
|
|
||||||
|
movement.applyParameters(parameters)
|
||||||
|
super.onJoinWorld(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tick(delta: Double) {
|
||||||
|
super.tick(delta)
|
||||||
|
|
||||||
|
timeToLive -= delta
|
||||||
|
|
||||||
|
if (!isRemote) {
|
||||||
|
// TODO: think up a better curve then sin
|
||||||
|
val rotationAcceleration = 0.01 * world.gravityAt(position).length * rotationRate.sign * delta
|
||||||
|
|
||||||
|
if (movement.rotation.absoluteValue > rotationCap)
|
||||||
|
rotationRate -= rotationAcceleration
|
||||||
|
else if (movement.rotation.absoluteValue < rotationFallThreshold)
|
||||||
|
rotationRate += rotationAcceleration
|
||||||
|
|
||||||
|
movement.rotation = rotationRate
|
||||||
|
|
||||||
|
if (timeToLive > 0.0) {
|
||||||
|
movement.applyParameters(MovementParameters(gravityEnabled = rotationRate.absoluteValue >= rotationFallThreshold))
|
||||||
|
|
||||||
|
if (movement.isOnGround) {
|
||||||
|
timeToLive = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((timeToLive <= 0.0 || world.gravityAt(position).lengthSquared == 0.0) && !spawnedDrops) {
|
||||||
|
spawnedDrops = true
|
||||||
|
|
||||||
|
for (piece in piecesInternal) {
|
||||||
|
var dropOptions = JsonArray()
|
||||||
|
|
||||||
|
when (piece.kind) {
|
||||||
|
PlantEntity.Piece.Kind.NONE -> {}
|
||||||
|
|
||||||
|
PlantEntity.Piece.Kind.STEM -> {
|
||||||
|
dropOptions = stemConfig.get("drops", JsonArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
PlantEntity.Piece.Kind.FOLIAGE -> {
|
||||||
|
dropOptions = foliageConfig.get("drops", JsonArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropOptions.size() > 0) {
|
||||||
|
val option = dropOptions.random(world.random).asJsonArray
|
||||||
|
|
||||||
|
for (drop in option) {
|
||||||
|
val img = Image.get(piece.image) ?: continue
|
||||||
|
|
||||||
|
var pos = piece.offset + img.size.toDoubleVector() * 0.5 / PIXELS_IN_STARBOUND_UNIT
|
||||||
|
pos = pos.rotate(movement.rotation)
|
||||||
|
pos += Vector2d(world.random.nextDouble(-0.2, 0.2), world.random.nextDouble(-0.2, 0.2))
|
||||||
|
pos += position
|
||||||
|
|
||||||
|
var descriptor = ItemDescriptor(drop)
|
||||||
|
|
||||||
|
if (descriptor.name == "sapling") {
|
||||||
|
descriptor = descriptor.copy(parameters = saplingConfig as? JsonObject ?: JsonObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
val entity = ItemDropEntity(descriptor)
|
||||||
|
entity.position = pos
|
||||||
|
entity.joinWorld(world)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(RemovalReason.DYING)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world.isServer && timeToLive <= 0.0) {
|
||||||
|
remove(RemovalReason.REMOVED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -269,7 +269,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
if (connectionsInternal.isNotEmpty()) {
|
if (connectionsInternal.isNotEmpty()) {
|
||||||
// ensure that we disconnect both ends
|
// ensure that we disconnect both ends
|
||||||
val any = connectionsInternal.removeIf {
|
val any = connectionsInternal.removeIf {
|
||||||
val otherEntity = world.entityIndex.tileEntityAt(it.entityLocation) as? WorldObject
|
val otherEntity = world.entityIndex.tileEntityAt(it.entityLocation, WorldObject::class)
|
||||||
val otherConnections = if (isInput) otherEntity?.outputNodes else otherEntity?.inputNodes
|
val otherConnections = if (isInput) otherEntity?.outputNodes else otherEntity?.inputNodes
|
||||||
val any = otherConnections?.getOrNull(it.index)?.connectionsInternal?.removeIf { it.entityLocation == tilePosition && it.index == index }
|
val any = otherConnections?.getOrNull(it.index)?.connectionsInternal?.removeIf { it.entityLocation == tilePosition && it.index == index }
|
||||||
|
|
||||||
@ -547,7 +547,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
val itr = node.connectionsInternal.listIterator()
|
val itr = node.connectionsInternal.listIterator()
|
||||||
|
|
||||||
for (connection in itr) {
|
for (connection in itr) {
|
||||||
connection.otherEntity = connection.otherEntity ?: world.entityIndex.tileEntityAt(connection.entityLocation) as? WorldObject
|
connection.otherEntity = connection.otherEntity ?: world.entityIndex.tileEntityAt(connection.entityLocation, WorldObject::class)
|
||||||
|
|
||||||
if (connection.otherEntity?.isInWorld == false) {
|
if (connection.otherEntity?.isInWorld == false) {
|
||||||
// break connection if other entity got removed
|
// break connection if other entity got removed
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.terrain
|
package ru.dbotthepony.kstarbound.world.terrain
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.github.benmanes.caffeine.cache.Scheduler
|
|
||||||
import ru.dbotthepony.kommons.arrays.Double2DArray
|
|
||||||
import ru.dbotthepony.kommons.arrays.Float2DArray
|
import ru.dbotthepony.kommons.arrays.Float2DArray
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
@ -63,7 +61,7 @@ class KarstCaveTerrainSelector(data: Data, parameters: TerrainSelectorParameters
|
|||||||
.softValues()
|
.softValues()
|
||||||
.expireAfterAccess(Duration.ofMinutes(1))
|
.expireAfterAccess(Duration.ofMinutes(1))
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.SCREENED_EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build<Int, Layer>(::Layer)
|
.build<Int, Layer>(::Layer)
|
||||||
|
|
||||||
private inner class Sector(val sector: Vector2i) {
|
private inner class Sector(val sector: Vector2i) {
|
||||||
@ -132,7 +130,7 @@ class KarstCaveTerrainSelector(data: Data, parameters: TerrainSelectorParameters
|
|||||||
.softValues()
|
.softValues()
|
||||||
.expireAfterAccess(Duration.ofMinutes(1))
|
.expireAfterAccess(Duration.ofMinutes(1))
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.SCREENED_EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build<Vector2i, Sector>(::Sector)
|
.build<Vector2i, Sector>(::Sector)
|
||||||
|
|
||||||
override fun get(x: Int, y: Int): Double {
|
override fun get(x: Int, y: Int): Double {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.terrain
|
package ru.dbotthepony.kstarbound.world.terrain
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.github.benmanes.caffeine.cache.Scheduler
|
|
||||||
import ru.dbotthepony.kommons.arrays.Double2DArray
|
|
||||||
import ru.dbotthepony.kommons.arrays.Float2DArray
|
import ru.dbotthepony.kommons.arrays.Float2DArray
|
||||||
import ru.dbotthepony.kommons.math.linearInterpolation
|
import ru.dbotthepony.kommons.math.linearInterpolation
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
@ -186,7 +184,7 @@ class WormCaveTerrainSelector(data: Data, parameters: TerrainSelectorParameters)
|
|||||||
.softValues()
|
.softValues()
|
||||||
.expireAfterAccess(Duration.ofMinutes(1))
|
.expireAfterAccess(Duration.ofMinutes(1))
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.SCREENED_EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build<Vector2i, Sector>(::Sector)
|
.build<Vector2i, Sector>(::Sector)
|
||||||
|
|
||||||
override fun get(x: Int, y: Int): Double {
|
override fun get(x: Int, y: Int): Double {
|
||||||
|
Loading…
Reference in New Issue
Block a user