Immediate dungeon placement

This commit is contained in:
DBotThePony 2024-05-06 02:12:01 +07:00
parent 161d19f263
commit 566751b77b
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 219 additions and 36 deletions

View File

@ -259,7 +259,7 @@ public class DirectCallExecutor {
*/ */
public Object[] resume(Continuation continuation) public Object[] resume(Continuation continuation)
throws CallException, CallPausedException, InterruptedException { throws CallException, CallPausedException, InterruptedException {
return execute(continuation, schedulingContextFactory.newInstance(), performJavaConversions); return execute(continuation, schedulingContextFactory.newInstance(), false);
} }
private static class Result implements CallEventHandler { private static class Result implements CallEventHandler {

View File

@ -29,6 +29,8 @@ import ru.dbotthepony.kstarbound.json.builder.JsonIgnore
import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState
import ru.dbotthepony.kstarbound.world.api.AbstractTileState
import ru.dbotthepony.kstarbound.world.api.ImmutableTileState
import ru.dbotthepony.kstarbound.world.api.MutableLiquidState import ru.dbotthepony.kstarbound.world.api.MutableLiquidState
import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.api.TileColor
@ -90,13 +92,7 @@ abstract class DungeonBrush {
return return
// TODO: delete objects too? // TODO: delete objects too?
world.setLiquid(x, y, AbstractLiquidState.EMPTY) world.clearTile(x, y)
world.setForeground(x, y, BuiltinMetaMaterials.EMPTY)
world.setForeground(x, y, BuiltinMetaMaterials.EMPTY_MOD)
world.setBackground(x, y, BuiltinMetaMaterials.EMPTY)
world.setBackground(x, y, BuiltinMetaMaterials.EMPTY_MOD)
world.setDungeonID(x, y)
} }
override fun toString(): String { override fun toString(): String {
@ -145,24 +141,19 @@ abstract class DungeonBrush {
val colorVariant: String = "0", // HOLY FUCKING SHIT val colorVariant: String = "0", // HOLY FUCKING SHIT
) )
private val state by lazy {
ImmutableTileState(material.orEmptyTile, modifier.orEmptyModifier, color, hueShift, modHueShift)
}
override fun execute(x: Int, y: Int, phase: Phase, world: DungeonWorld) { override fun execute(x: Int, y: Int, phase: Phase, world: DungeonWorld) {
if (phase !== Phase.PLACE_WALLS) if (phase !== Phase.PLACE_WALLS)
return return
if (isBackground) if (isBackground) {
world.setBackground(x, y, material.orEmptyTile, hueShift, color) world.setCombined(x, y, background = state, foreground = null, liquid = null, setBackgroundModifier = modifier.isRealModifier)
else } else {
world.setForeground(x, y, material.orEmptyTile, hueShift, color) val liquid = if (material.orEmptyTile.value.collisionKind.isSolidCollision) AbstractLiquidState.EMPTY else null
world.setCombined(x, y, background = null, foreground = state, liquid = liquid, setForegroundModifier = modifier.isRealModifier)
if (!isBackground && material.orEmptyTile.value.collisionKind.isSolidCollision) {
world.setLiquid(x, y, AbstractLiquidState.EMPTY)
}
if (modifier.isRealModifier) {
if (isBackground)
world.setBackground(x, y, modifier.orEmptyModifier, modHueShift)
else
world.setForeground(x, y, modifier.orEmptyModifier, modHueShift)
} }
} }
} }

View File

@ -220,10 +220,11 @@ data class DungeonDefinition(
dungeonID: Int = 0, dungeonID: Int = 0,
terrainSurfaceSpaceExtends: Int = 0, terrainSurfaceSpaceExtends: Int = 0,
commit: Boolean = true, commit: Boolean = true,
scope: CoroutineScope = Starbound.GLOBAL_SCOPE scope: CoroutineScope = Starbound.GLOBAL_SCOPE,
immediate: Boolean = false,
): CompletableFuture<DungeonWorld> { ): CompletableFuture<DungeonWorld> {
require(dungeonID in 0 .. NO_DUNGEON_ID) { "Dungeon ID out of range: $dungeonID" } require(dungeonID in 0 .. NO_DUNGEON_ID) { "Dungeon ID out of range: $dungeonID" }
val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize) val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize, immediate = immediate)
val validAnchors = anchorParts.filter { world.template.threatLevel in it.minimumThreatLevel .. it.maximumThreatLevel } val validAnchors = anchorParts.filter { world.template.threatLevel in it.minimumThreatLevel .. it.maximumThreatLevel }
@ -247,9 +248,21 @@ data class DungeonDefinition(
}.asCompletableFuture() }.asCompletableFuture()
} }
fun build(anchor: DungeonPart, world: ServerWorld, random: RandomGenerator, x: Int, y: Int, dungeonID: Int = NO_DUNGEON_ID, markSurfaceAndTerrain: Boolean = false, forcePlacement: Boolean = false, terrainSurfaceSpaceExtends: Int = 0, commit: Boolean = true, scope: CoroutineScope = Starbound.GLOBAL_SCOPE): CompletableFuture<DungeonWorld> { fun build(
anchor: DungeonPart,
world: ServerWorld,
random: RandomGenerator,
x: Int, y: Int,
dungeonID: Int = NO_DUNGEON_ID,
markSurfaceAndTerrain: Boolean = false,
forcePlacement: Boolean = false,
terrainSurfaceSpaceExtends: Int = 0,
commit: Boolean = true,
scope: CoroutineScope = Starbound.GLOBAL_SCOPE,
immediate: Boolean = false
): CompletableFuture<DungeonWorld> {
require(anchor in anchorParts) { "$anchor does not belong to $name" } require(anchor in anchorParts) { "$anchor does not belong to $name" }
val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize) val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends, expectedSize = expectedSize, immediate = immediate)
return scope.async { return scope.async {
generate0(anchor, dungeonWorld, x, y, forcePlacement, dungeonID) generate0(anchor, dungeonWorld, x, y, forcePlacement, dungeonID)

View File

@ -27,7 +27,9 @@ import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.ChunkState import ru.dbotthepony.kstarbound.world.ChunkState
import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState
import ru.dbotthepony.kstarbound.world.api.AbstractTileState
import ru.dbotthepony.kstarbound.world.api.MutableTileState import ru.dbotthepony.kstarbound.world.api.MutableTileState
import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kstarbound.world.entities.tile.TileEntity import ru.dbotthepony.kstarbound.world.entities.tile.TileEntity
@ -45,7 +47,20 @@ import kotlin.math.max
// Facade world for generating dungeons, so generation can be performed without affecting world state, // Facade world for generating dungeons, so generation can be performed without affecting world state,
// and if error occurs, won't require world's rollback, as well allowing dungeon to be generated mostly // and if error occurs, won't require world's rollback, as well allowing dungeon to be generated mostly
// off world's thread. // off world's thread.
class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val markSurfaceLevel: Int? = null, val terrainSurfaceSpaceExtends: Int = 0, expectedSize: Int = 0) { class DungeonWorld(
val parent: ServerWorld,
val random: RandomGenerator,
val markSurfaceLevel: Int? = null,
val terrainSurfaceSpaceExtends: Int = 0,
expectedSize: Int = 0,
/**
* Whenever tile changes should be passthrough, if possible
* (improved generation speed and memory usage, but expects world chunks to be already
* loaded into memory AND no concurrent chunkmap operations are performed)
*/
val immediate: Boolean = false,
) {
val geometry = parent.geometry val geometry = parent.geometry
data class Material( data class Material(
@ -81,11 +96,11 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar
val targetChunkState = if (parent.template.worldParameters is FloatingDungeonWorldParameters) ChunkState.FULL else ChunkState.TERRAIN val targetChunkState = if (parent.template.worldParameters is FloatingDungeonWorldParameters) ChunkState.FULL else ChunkState.TERRAIN
private val liquid = HashMap<Vector2i, AbstractLiquidState>(max(8192, expectedSize), 0.5f) private val liquid = HashMap<Vector2i, AbstractLiquidState>(if (immediate) 2048 else max(8192, expectedSize), 0.5f)
private val foregroundMaterial = HashMap<Vector2i, Material>(max(8192, expectedSize), 0.5f) private val foregroundMaterial = HashMap<Vector2i, Material>(if (immediate) 2048 else max(8192, expectedSize), 0.5f)
private val foregroundModifier = HashMap<Vector2i, Modifier>(max(8192, expectedSize), 0.5f) private val foregroundModifier = HashMap<Vector2i, Modifier>(if (immediate) 2048 else max(8192, expectedSize), 0.5f)
private val backgroundMaterial = HashMap<Vector2i, Material>(max(8192, expectedSize), 0.5f) private val backgroundMaterial = HashMap<Vector2i, Material>(if (immediate) 2048 else max(8192, expectedSize), 0.5f)
private val backgroundModifier = HashMap<Vector2i, Modifier>(max(8192, expectedSize), 0.5f) private val backgroundModifier = HashMap<Vector2i, Modifier>(if (immediate) 2048 else max(8192, expectedSize), 0.5f)
// for entity spaces which should be considered empty if they // for entity spaces which should be considered empty if they
// are occupied by tile entity // are occupied by tile entity
@ -117,8 +132,6 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar
private var currentBoundingBox: AABBi? = null private var currentBoundingBox: AABBi? = null
fun touched(): Set<Vector2i> = Collections.unmodifiableSet(touchedTiles)
fun touch(x: Int, y: Int) { fun touch(x: Int, y: Int) {
val wrapped = geometry.wrap(Vector2i(x, y)) val wrapped = geometry.wrap(Vector2i(x, y))
touchedTiles.add(wrapped) touchedTiles.add(wrapped)
@ -260,6 +273,131 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar
setBackground(x, y, Modifier(modifier, hueShift)) setBackground(x, y, Modifier(modifier, hueShift))
} }
private val emptyMaterial = Material(AbstractTileState.EMPTY.material, AbstractTileState.EMPTY.hueShift, AbstractTileState.EMPTY.color)
private val emptyModifier = Modifier(AbstractTileState.EMPTY.modifier, AbstractTileState.EMPTY.modifierHueShift)
private fun setCombinedFallback(
x: Int, y: Int,
foreground: AbstractTileState?,
background: AbstractTileState?,
liquid: AbstractLiquidState?,
setForegroundMaterial: Boolean,
setForegroundModifier: Boolean,
setBackgroundMaterial: Boolean,
setBackgroundModifier: Boolean,
) {
val pos = geometry.wrap(Vector2i(x, y))
if (foreground != null) {
if (setForegroundMaterial)
this.foregroundMaterial[pos] = Material(foreground.material, foreground.hueShift, foreground.color)
if (setForegroundModifier)
this.foregroundModifier[pos] = Modifier(foreground.modifier, foreground.modifierHueShift)
}
if (background != null) {
if (setBackgroundMaterial)
this.backgroundMaterial[pos] = Material(background.material, background.hueShift, background.color)
if (setBackgroundModifier)
this.backgroundModifier[pos] = Modifier(background.modifier, background.modifierHueShift)
}
if (liquid != null) this.liquid[pos] = liquid
}
fun setCombined(
x: Int, y: Int,
foreground: AbstractTileState?,
background: AbstractTileState?,
liquid: AbstractLiquidState?,
dungeonId: Int? = null,
setForegroundMaterial: Boolean = true,
setForegroundModifier: Boolean = true,
setBackgroundMaterial: Boolean = true,
setBackgroundModifier: Boolean = true,
) {
if (immediate) {
val cell = parent.getCell(x, y).mutable()
if (foreground != null) cell.foreground.from(foreground, setForegroundMaterial, setForegroundModifier)
if (background != null) cell.background.from(background, setBackgroundMaterial, setBackgroundModifier)
if (liquid != null) cell.liquid.from(liquid)
cell.dungeonId = dungeonId ?: cell.dungeonId
if (!parent.setCell(x, y, cell.immutable())) {
LOGGER.debug("Cell at $x, $y is out of preloaded bounds for immediate dungeon generator... While this won't affect dungeon generation, it will degrade performance and signify there is a bug somewhere.")
setCombinedFallback(
x = x,
y = y,
foreground = foreground,
background = background,
liquid = liquid,
setForegroundMaterial = setForegroundMaterial,
setForegroundModifier = setForegroundModifier,
setBackgroundMaterial = setBackgroundMaterial,
setBackgroundModifier = setBackgroundModifier,
)
} else {
val pos = geometry.wrap(Vector2i(x, y))
if (foreground != null) {
if (setForegroundMaterial) this.foregroundMaterial.remove(pos)
if (setForegroundModifier) this.foregroundModifier.remove(pos)
}
if (background != null) {
if (setBackgroundMaterial) this.backgroundMaterial.remove(pos)
if (setBackgroundModifier) this.backgroundModifier.remove(pos)
}
if (liquid != null) this.liquid.remove(pos)
}
} else {
setCombinedFallback(
x = x,
y = y,
foreground = foreground,
background = background,
liquid = liquid,
setForegroundMaterial = setForegroundMaterial,
setForegroundModifier = setForegroundModifier,
setBackgroundMaterial = setBackgroundMaterial,
setBackgroundModifier = setBackgroundModifier,
)
}
}
fun clearTile(x: Int, y: Int) {
val pos = geometry.wrap(Vector2i(x, y))
if (immediate) {
if (parent.setCell(x, y, AbstractCell.EMPTY)) {
this.foregroundMaterial.remove(pos)
this.foregroundModifier.remove(pos)
this.backgroundMaterial.remove(pos)
this.backgroundModifier.remove(pos)
this.liquid.remove(pos)
} else {
this.foregroundMaterial[pos] = emptyMaterial
this.foregroundModifier[pos] = emptyModifier
this.backgroundMaterial[pos] = emptyMaterial
this.backgroundModifier[pos] = emptyModifier
this.liquid[pos] = AbstractLiquidState.EMPTY
}
} else {
this.foregroundMaterial[pos] = emptyMaterial
this.foregroundModifier[pos] = emptyModifier
this.backgroundMaterial[pos] = emptyMaterial
this.backgroundModifier[pos] = emptyModifier
this.liquid[pos] = AbstractLiquidState.EMPTY
}
dungeonIDs.remove(pos)
}
fun needsForegroundBiomeMod(x: Int, y: Int): Boolean { fun needsForegroundBiomeMod(x: Int, y: Int): Boolean {
val pos = geometry.wrap(Vector2i(x, y)) val pos = geometry.wrap(Vector2i(x, y))
val material = foregroundMaterial[pos] ?: return false val material = foregroundMaterial[pos] ?: return false

View File

@ -142,6 +142,14 @@ data class AABBi(val mins: Vector2i, val maxs: Vector2i) {
) )
} }
/**
* Rectangle with given [width] * 2 and [height] * 2
*/
fun withSide(pos: IStruct2i, size: IStruct2i): AABBi {
val (width, height) = size
return withSide(pos, width, height)
}
@JvmField val ZERO = AABBi(Vector2i.ZERO, Vector2i.ZERO) @JvmField val ZERO = AABBi(Vector2i.ZERO, Vector2i.ZERO)
} }
} }

View File

@ -565,13 +565,33 @@ class ServerWorld private constructor(
x = geometry.x.cell(x) x = geometry.x.cell(x)
LOGGER.info("Trying to place dungeon ${dungeon.dungeon.key} at $x, ${dungeon.baseHeight}...") val tickets = ArrayList<ServerChunk.ITicket>()
val dungeonWorld = try { val dungeonWorld = try {
dungeon.dungeon.value.generate(this, random(random.nextLong()), x, dungeon.baseHeight, dungeon.blendWithTerrain, dungeon.forcePlacement, dungeonID = currentDungeonID).await() // if immediate placement will cause issues it can be safely toggled off
var immediate = false
if (dungeon.dungeon.value.anchorParts.size == 1 && dungeon.dungeon.value.partMap.size == 1) {
immediate = true
LOGGER.info("Trying to immediately place dungeon ${dungeon.dungeon.key} at $x, ${dungeon.baseHeight}...")
tickets.addAll(permanentChunkTicket(AABBi.withSide(Vector2i(x, dungeon.baseHeight), dungeon.dungeon.value.anchorParts.first().reader.size), ChunkState.TERRAIN).await())
tickets.forEach { it.chunk.await() }
} else {
LOGGER.info("Trying to place dungeon ${dungeon.dungeon.key} at $x, ${dungeon.baseHeight}...")
}
dungeon.dungeon.value.generate(
this,
random(random.nextLong()),
x, dungeon.baseHeight,
dungeon.blendWithTerrain,
dungeon.forcePlacement,
dungeonID = currentDungeonID,
immediate = immediate).await()
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Exception while placing dungeon ${dungeon.dungeon.key} at $x, ${dungeon.baseHeight}", err) LOGGER.error("Exception while placing dungeon ${dungeon.dungeon.key} at $x, ${dungeon.baseHeight}", err)
// continue // continue
tickets.forEach { it.cancel() }
break break
} }

View File

@ -39,6 +39,19 @@ data class MutableTileState(
modifierHueShift = 0f modifierHueShift = 0f
} }
fun from(other: AbstractTileState, setMaterial: Boolean = true, setModifer: Boolean = true) {
if (setMaterial) {
material = other.material
modifier = other.modifier
color = other.color
}
if (setModifer) {
hueShift = other.hueShift
modifierHueShift = other.modifierHueShift
}
}
fun setHueShift(value: Int): MutableTileState { fun setHueShift(value: Int): MutableTileState {
if (value < 0) { if (value < 0) {
hueShift = 0f hueShift = 0f