Expose almost all World Lua bindings
This commit is contained in:
parent
be6f637d9b
commit
7f16e643f3
29
ADDITIONS.md
29
ADDITIONS.md
@ -137,6 +137,27 @@ val color: TileColor = TileColor.DEFAULT
|
||||
* Added `world.unsetUniverseFlag(flag: String): Boolean`
|
||||
* Added `world.placeDungeonAsync(name: String, position: Vector2d, dungeonID: Int?, seed: Long?): RpcPromise<Boolean>`
|
||||
* Added `world.tryPlaceDungeonAsync(name: String, position: Vector2d, dungeonID: Int?, seed: Long?): RpcPromise<Boolean>`
|
||||
* Added `world.materialColorName(pos: Vector2i, layer: String): String`. Returns one of next strings:
|
||||
* `"default"`
|
||||
* `"red"`
|
||||
* `"blue"`
|
||||
* `"green"`
|
||||
* `"yellow"`
|
||||
* `"brown"`
|
||||
* `"purple"`
|
||||
* `"black"`
|
||||
* `"white"`
|
||||
* Added `world.spawnLiquidPromise(pos: Vector2i, liquid: Any, quantity: Float): RpcPromise<Boolean>`
|
||||
* However, correct clientside result will be returned _only_ when using native protocol
|
||||
* Added `world.damageTilesPromise(positions: List<Vector2i>, layer: String, damageSource: Vector2d, damageType: String, damageAmount: Double, harvestLevel: Int = 999, sourceEntity: EntityID = 0): RpcPromise<String>`, which return one of next values on promise completion:
|
||||
* `"none"`
|
||||
* `"protected"`
|
||||
* `"normal"`
|
||||
* Keep in mind that it returns top-most damage status, which means:
|
||||
* If some tiles were protected, and others were not, it will return `"normal"`.
|
||||
* If all tiles were protected, it will return `"protected"`.
|
||||
* If none tiles were damaged, it will return `"none"`.
|
||||
* Added `world.damageTileAreaPromise(radius: Double, position: Vector2i, layer: String, damageSource: Vector2d, damageType: String, damageAmount: Double, harvestLevel: Int = 999, sourceEntity: EntityID = 0): RpcPromise<String>`, with same notes as `world.damageTilesPromise()` apply
|
||||
|
||||
#### Changes
|
||||
|
||||
@ -155,6 +176,14 @@ val color: TileColor = TileColor.DEFAULT
|
||||
* `world.tryPlaceDungeon(name: String, position: Vector2d, dungeonID: Int?, seed: Long?): Boolean` now accepts optional `seed`. If not specified, engine will determine one (like original engine does).
|
||||
* Please update code to use `world.tryPlaceDungeonAsync`, because there are absolutely no guarantees dungeon will be generated the moment `world.tryPlaceDungeon` call returns
|
||||
* `world.setDungeonGravity(id: Int, gravity: Either<Double, Vector2d>)` now accept directional vector. **Attention:** Directional gravity is WIP.
|
||||
* `world.setMaterialColor(pos: Vector2i, layer: String, color: Any): Boolean`
|
||||
* Now returns boolean whenever cell was set (returns `false` when called with position outside of loaded chunks)
|
||||
* Now accepts string name along integer index
|
||||
* `world.placeMaterial(pos: Vector2i, layer: String, material: String, hueShift: Number?, allowOverlap: Boolean): RpcPromise<Vector2i>` now returns `RpcPromise<Vector2i>` of unapplied tile modifications instead of `Boolean`, which wasn't representative anyway, and outright wrong if this function was called on client
|
||||
* However, correct clientside results will be returned _only_ when using native protocol
|
||||
* `world.placeMod(pos: Vector2i, layer: String, mod: String, hueShift: Number?, allowOverlap: Boolean): RpcPromise<Vector2i>` now returns `RpcPromise<Vector2i>` of unapplied tile modifications instead of `Boolean`, which wasn't representative anyway, and outright wrong if this function was called on client
|
||||
* However, correct clientside results will be returned _only_ when using native protocol
|
||||
* `world.spawnLiquid(pos: Vector2i, liquid: Any, quantity: Float): Boolean` now accepts both liquid ID and liquid name. However, you should be using `world.spawnLiquidPromise(pos: Vector2i, liquid: Any, quantity: Float): RpcPromise<Boolean>` instead
|
||||
|
||||
#### Fixes
|
||||
|
||||
|
@ -47,44 +47,7 @@ operator fun <T> ThreadLocal<T>.setValue(thisRef: Any, property: KProperty<*>, v
|
||||
|
||||
operator fun <K : Any, V : Any> ImmutableMap.Builder<K, V>.set(key: K, value: V): ImmutableMap.Builder<K, V> = put(key, value)
|
||||
|
||||
fun String.sintern(): String = Starbound.STRINGS.intern(this)
|
||||
|
||||
inline fun <reified T> Gson.fromJson(reader: JsonReader): T? = fromJson<T>(reader, T::class.java)
|
||||
inline fun <reified T> Gson.fromJson(reader: JsonElement): T? = getAdapter(T::class.java).read(FastJsonTreeReader(reader))
|
||||
|
||||
fun <T> Gson.fromJsonFast(reader: JsonElement, type: Class<T>): T = getAdapter(type).read(FastJsonTreeReader(reader))
|
||||
|
||||
/**
|
||||
* guarantees even distribution of tasks while also preserving encountered order of elements
|
||||
*/
|
||||
fun <T> Collection<IStarboundFile>.batch(executor: ForkJoinPool, batchSize: Int = 16, mapper: (IStarboundFile) -> KOptional<T>): Stream<T> {
|
||||
require(batchSize >= 1) { "Invalid batch size: $batchSize" }
|
||||
|
||||
if (batchSize == 1 || size <= batchSize) {
|
||||
val tasks = ArrayList<ForkJoinTask<KOptional<T>>>()
|
||||
|
||||
for (listedFile in this) {
|
||||
tasks.add(executor.submit(Callable { mapper.invoke(listedFile) }))
|
||||
}
|
||||
|
||||
return tasks.stream().map { it.join() }.filter { it.isPresent }.map { it.value }
|
||||
}
|
||||
|
||||
val batches = ArrayList<ForkJoinTask<List<KOptional<T>>>>()
|
||||
var batch = ArrayList<IStarboundFile>(batchSize)
|
||||
|
||||
for (listedFile in this) {
|
||||
batch.add(listedFile)
|
||||
|
||||
if (batch.size >= batchSize) {
|
||||
val mbatch = batch
|
||||
batches.add(executor.submit(Callable { mbatch.map { mapper.invoke(it) } }))
|
||||
batch = ArrayList(batchSize)
|
||||
}
|
||||
}
|
||||
|
||||
if (batch.isNotEmpty())
|
||||
batches.add(executor.submit(Callable { batch.map { mapper.invoke(it) } }))
|
||||
|
||||
return batches.stream().flatMap { it.join().stream() }.filter { it.isPresent }.map { it.value }
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.client.render.Mesh
|
||||
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||
@ -43,6 +45,7 @@ import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.TileView
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.positiveModulo
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
@ -332,7 +335,7 @@ class ClientWorld(
|
||||
modifications: Collection<Pair<Vector2i, TileModification>>,
|
||||
allowEntityOverlap: Boolean,
|
||||
ignoreTileProtection: Boolean
|
||||
): List<Pair<Vector2i, TileModification>> {
|
||||
): CompletableFuture<List<Pair<Vector2i, TileModification>>> {
|
||||
// send packets to server here
|
||||
// this is required because Lua scripts call this method
|
||||
// and Lua scripts want these changes to be applied serverside (good game security, i approve)
|
||||
@ -400,6 +403,16 @@ class ClientWorld(
|
||||
return future
|
||||
}
|
||||
|
||||
override fun damageTiles(
|
||||
positions: Collection<IStruct2i>,
|
||||
isBackground: Boolean,
|
||||
sourcePosition: Vector2d,
|
||||
damage: TileDamage,
|
||||
source: AbstractEntity?
|
||||
): CompletableFuture<TileDamageResult> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ring = listOf(
|
||||
Vector2i(0, 0),
|
||||
|
@ -2,7 +2,6 @@ package ru.dbotthepony.kstarbound.defs.dungeon
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
@ -29,7 +28,6 @@ import ru.dbotthepony.kstarbound.json.builder.JsonIgnore
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.world.Direction
|
||||
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.TileColor
|
||||
@ -114,14 +112,14 @@ abstract class DungeonBrush {
|
||||
isBackground,
|
||||
data.material, data.modifier,
|
||||
data.modHueShift, data.hueShift,
|
||||
TileColor.entries.firstOrNull { it.lowercase == data.colorVariant.lowercase() } ?: TileColor.entries[data.colorVariant.toIntOrNull() ?: throw JsonSyntaxException("Invalid color variant: ${data.colorVariant}")]
|
||||
TileColor.entries.firstOrNull { it.jsonName == data.colorVariant.lowercase() } ?: TileColor.entries[data.colorVariant.toIntOrNull() ?: throw JsonSyntaxException("Invalid color variant: ${data.colorVariant}")]
|
||||
)
|
||||
|
||||
constructor(isBackground: Boolean, material: Registry.Ref<TileDefinition>, data: TiledData) : this(
|
||||
isBackground, material,
|
||||
data.modifier, data.hueshift,
|
||||
data.modhueshift,
|
||||
TileColor.entries.firstOrNull { it.lowercase == data.colorVariant.lowercase() } ?: TileColor.entries[data.colorVariant.toIntOrNull() ?: throw JsonSyntaxException("Invalid color variant: ${data.colorVariant}")]
|
||||
TileColor.entries.firstOrNull { it.jsonName == data.colorVariant.lowercase() } ?: TileColor.entries[data.colorVariant.toIntOrNull() ?: throw JsonSyntaxException("Invalid color variant: ${data.colorVariant}")]
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
|
@ -1,7 +1,11 @@
|
||||
package ru.dbotthepony.kstarbound.defs.tile
|
||||
|
||||
enum class TileDamageResult {
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
enum class TileDamageResult : IStringSerializable {
|
||||
NONE,
|
||||
PROTECTED,
|
||||
NORMAL;
|
||||
|
||||
override val jsonName: String = name.lowercase()
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
||||
return true
|
||||
}
|
||||
|
||||
fun surfaceLevel(): Int {
|
||||
val surfaceLevel: Int get() {
|
||||
val parameters = worldParameters
|
||||
|
||||
if (parameters is TerrestrialWorldParameters) {
|
||||
@ -169,6 +169,18 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
||||
return geometry.size.y / 2
|
||||
}
|
||||
|
||||
val undergroundLevel: Int get() {
|
||||
val parameters = worldParameters
|
||||
|
||||
if (parameters is TerrestrialWorldParameters) {
|
||||
return parameters.surfaceLayer.layerMinHeight
|
||||
} else if (parameters is FloatingDungeonWorldParameters) {
|
||||
return parameters.dungeonUndergroundLevel
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
class PotentialBiomeItems(
|
||||
// Potential items that would spawn at the given block assuming it is at
|
||||
val surfaceBiomeItems: List<BiomePlaceables.Placement> = listOf(),
|
||||
|
@ -359,16 +359,6 @@ fun TableFactory.from(value: Poly?): Table? {
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.fromCollection(value: Collection<JsonElement?>): Table {
|
||||
val table = newTable(value.size, 0)
|
||||
|
||||
for ((k, v) in value.withIndex()) {
|
||||
table.rawset(Conversions.normaliseKey(k + 1), from(v))
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: IStruct2i?): Table? {
|
||||
value ?: return null
|
||||
|
||||
@ -428,3 +418,12 @@ fun TableFactory.from(value: AABB?): Table? {
|
||||
it.rawset(4L, value.maxs.y)
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.tableFrom(collection: Collection<Any?>): Table {
|
||||
val alloc = newTable(collection.size, 0)
|
||||
|
||||
for ((i, v) in collection.withIndex())
|
||||
alloc[i + 1L] = v
|
||||
|
||||
return alloc
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ import ru.dbotthepony.kstarbound.lua.toPoly
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
||||
import ru.dbotthepony.kstarbound.lua.unpackAsArray
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
||||
import ru.dbotthepony.kstarbound.math.AABB
|
||||
import ru.dbotthepony.kstarbound.math.Line2d
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
@ -518,9 +519,21 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
returnBuffer.setTo(from(self.gravityAt(toVector2d(pos))))
|
||||
}
|
||||
|
||||
callbacks["spawnLiquidPromise"] = luaFunction { pos: Table, liquid: Any, quantity: Number ->
|
||||
val action = TileModification.Pour(if (liquid is ByteString) Registries.liquid.ref(liquid.decode()) else Registries.liquid.ref((liquid as Number).toInt()), quantity.toFloat())
|
||||
|
||||
returnBuffer.setTo(
|
||||
LuaFuture(
|
||||
future = self.applyTileModifications(listOf(toVector2i(pos) to action), false)
|
||||
.thenApply { it.isEmpty() },
|
||||
isLocal = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
callbacks["spawnLiquid"] = luaFunction { pos: Table, liquid: Any, quantity: Number ->
|
||||
val action = TileModification.Pour(if (liquid is ByteString) Registries.liquid.ref(liquid.decode()) else Registries.liquid.ref((liquid as Number).toInt()), quantity.toFloat())
|
||||
returnBuffer.setTo(self.applyTileModifications(listOf(toVector2i(pos) to action), false).isEmpty())
|
||||
returnBuffer.setTo(self.applyTileModifications(listOf(toVector2i(pos) to action), false).thenApply { it.isEmpty() }.getNow(true))
|
||||
}
|
||||
|
||||
callbacks["destroyLiquid"] = luaFunction { pos: Table ->
|
||||
@ -549,7 +562,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
callbacks["type"] = luaFunction { returnBuffer.setTo(self.template.worldParameters?.typeName ?: "unknown") }
|
||||
callbacks["size"] = luaFunction { returnBuffer.setTo(from(self.geometry.size)) }
|
||||
callbacks["inSurfaceLayer"] = luaFunction { pos: Table -> returnBuffer.setTo(self.template.isSurfaceLayer(toVector2i(pos))) }
|
||||
callbacks["surfaceLevel"] = luaFunction { returnBuffer.setTo(self.template.surfaceLevel()) }
|
||||
callbacks["surfaceLevel"] = luaFunction { returnBuffer.setTo(self.template.surfaceLevel) }
|
||||
callbacks["terrestrial"] = luaFunction { returnBuffer.setTo(self.template.worldParameters is TerrestrialWorldParameters) }
|
||||
|
||||
callbacks["itemDropItem"] = luaFunction { id: Number ->
|
||||
@ -589,6 +602,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
callbacks["dungeonId"] = luaFunction { pos: Table -> returnBuffer.setTo(self.getCell(toVector2i(pos)).dungeonId) }
|
||||
|
||||
provideWorldEntitiesBindings(self, callbacks, lua)
|
||||
provideWorldEnvironmentalBindings(self, callbacks, lua)
|
||||
|
||||
if (self is ServerWorld) {
|
||||
provideServerWorldBindings(self, callbacks, lua)
|
||||
|
@ -0,0 +1,223 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.LuaRuntimeException
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.lib.ArgumentIterator
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import ru.dbotthepony.kommons.collect.map
|
||||
import ru.dbotthepony.kommons.collect.toList
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyModifier
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyModifier
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNullTile
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.iterator
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunctionN
|
||||
import ru.dbotthepony.kstarbound.lua.luaStub
|
||||
import ru.dbotthepony.kstarbound.lua.nextOptionalInteger
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.tableFrom
|
||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
||||
import ru.dbotthepony.kstarbound.util.valueOf
|
||||
import ru.dbotthepony.kstarbound.world.TileModification
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||
import ru.dbotthepony.kstarbound.world.tileAreaBrush
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
private val foregroundStr = ByteString.of("foreground")
|
||||
private val backgroundStr = ByteString.of("background")
|
||||
|
||||
private fun isBackground(layer: ByteString): Boolean {
|
||||
return if (layer == backgroundStr)
|
||||
true
|
||||
else if (layer == foregroundStr)
|
||||
false
|
||||
else
|
||||
throw LuaRuntimeException("Invalid tile layer $layer")
|
||||
}
|
||||
|
||||
private fun ExecutionContext.damageTilesImpl(self: World<*, *>, it: ArgumentIterator): CompletableFuture<TileDamageResult> {
|
||||
val positions = it.nextTable().iterator().map { toVector2i(it.value) }.toList()
|
||||
val isBackground = isBackground(it.nextString())
|
||||
|
||||
val sourcePosition = toVector2d(it.nextTable())
|
||||
val damageType = TileDamageType.entries.valueOf(it.nextString().decode())
|
||||
val damage = it.nextFloat()
|
||||
val harvestLevel = it.nextOptionalInteger()?.toInt() ?: 999
|
||||
val sourceEntity = self.entities[it.nextOptionalInteger()?.toInt() ?: 0]
|
||||
|
||||
return self.damageTiles(positions, isBackground, sourcePosition, TileDamage(damageType, damage, harvestLevel), sourceEntity)
|
||||
}
|
||||
|
||||
private fun ExecutionContext.damageTileAreaImpl(self: World<*, *>, it: ArgumentIterator): CompletableFuture<TileDamageResult> {
|
||||
val center = toVector2i(it.nextTable())
|
||||
val radius = it.nextFloat()
|
||||
val isBackground = isBackground(it.nextString())
|
||||
|
||||
val sourcePosition = toVector2d(it.nextTable())
|
||||
val damageType = TileDamageType.entries.valueOf(it.nextString().decode())
|
||||
val damage = it.nextFloat()
|
||||
val harvestLevel = it.nextOptionalInteger()?.toInt() ?: 999
|
||||
val sourceEntity = self.entities[it.nextOptionalInteger()?.toInt() ?: 0]
|
||||
|
||||
return self.damageTiles(tileAreaBrush(center, radius), isBackground, sourcePosition, TileDamage(damageType, damage, harvestLevel), sourceEntity)
|
||||
}
|
||||
|
||||
fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua: LuaEnvironment) {
|
||||
callbacks["lightLevel"] = luaStub("lightLevel")
|
||||
callbacks["windLevel"] = luaStub("windLevel")
|
||||
|
||||
callbacks["breathable"] = luaFunction { pos: Table ->
|
||||
returnBuffer.setTo(self.isBreathable(toVector2i(pos)))
|
||||
}
|
||||
|
||||
callbacks["underground"] = luaFunction { pos: Table ->
|
||||
returnBuffer.setTo(self.template.undergroundLevel >= toVector2d(pos).y)
|
||||
}
|
||||
|
||||
callbacks["material"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
|
||||
if (tile.material.isNullTile) {
|
||||
returnBuffer.setTo()
|
||||
} else if (tile.material.isEmptyTile) {
|
||||
returnBuffer.setTo(false)
|
||||
} else {
|
||||
returnBuffer.setTo(tile.material.key)
|
||||
}
|
||||
|
||||
}
|
||||
callbacks["mod"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
|
||||
if (tile.modifier.isNotEmptyModifier) {
|
||||
returnBuffer.setTo(tile.modifier.key)
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["materialHueShift"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
returnBuffer.setTo(tile.hueShift)
|
||||
}
|
||||
|
||||
callbacks["modHueShift"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
returnBuffer.setTo(tile.modifierHueShift)
|
||||
}
|
||||
|
||||
callbacks["materialColor"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
returnBuffer.setTo(tile.color.ordinal)
|
||||
}
|
||||
|
||||
callbacks["materialColorName"] = luaFunction { pos: Table, layer: ByteString ->
|
||||
val isBackground = isBackground(layer)
|
||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
||||
returnBuffer.setTo(tile.color.jsonName)
|
||||
}
|
||||
|
||||
callbacks["setMaterialColor"] = luaFunction { pos: Table, layer: ByteString, color: Any ->
|
||||
val isBackground = isBackground(layer)
|
||||
|
||||
val actualColor = if (color is Number)
|
||||
TileColor.entries[color.toInt()]
|
||||
else if (color is ByteString)
|
||||
TileColor.entries.valueOf(color.decode())
|
||||
else
|
||||
throw LuaRuntimeException("Unknown tile color $color")
|
||||
|
||||
val actualPos = toVector2i(pos)
|
||||
val cell = self.getCell(actualPos).mutable()
|
||||
cell.tile(isBackground).color = actualColor
|
||||
returnBuffer.setTo(self.setCell(actualPos, cell))
|
||||
}
|
||||
|
||||
callbacks["oceanLevel"] = luaFunction { pos: Table ->
|
||||
returnBuffer.setTo(self.template.cellInfo(toVector2i(pos)).oceanLiquidLevel)
|
||||
}
|
||||
|
||||
callbacks["environmentStatusEffects"] = luaFunction { pos: Table ->
|
||||
returnBuffer.setTo(tableFrom(self.environmentStatusEffects(toVector2i(pos))))
|
||||
}
|
||||
|
||||
callbacks["damageTiles"] = luaFunctionN("damageTiles") {
|
||||
returnBuffer.setTo(damageTilesImpl(self, it).getNow(TileDamageResult.NONE) != TileDamageResult.NONE)
|
||||
}
|
||||
|
||||
callbacks["damageTilesPromise"] = luaFunctionN("damageTilesPromise") {
|
||||
returnBuffer.setTo(
|
||||
LuaFuture(
|
||||
future = damageTilesImpl(self, it).thenApply { it.jsonName },
|
||||
isLocal = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
callbacks["damageTileArea"] = luaFunctionN("damageTileArea") {
|
||||
returnBuffer.setTo(damageTileAreaImpl(self, it).getNow(TileDamageResult.NONE) != TileDamageResult.NONE)
|
||||
}
|
||||
|
||||
callbacks["damageTileAreaPromise"] = luaFunctionN("damageTileAreaPromise") {
|
||||
returnBuffer.setTo(
|
||||
LuaFuture(
|
||||
future = damageTileAreaImpl(self, it).thenApply { it.jsonName },
|
||||
isLocal = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
callbacks["placeMaterial"] = luaFunctionN("placeMaterial") {
|
||||
val pos = toVector2i(it.nextTable())
|
||||
val isBackground = isBackground(it.nextString())
|
||||
val material = Registries.tiles.getOrThrow(it.nextString().decode())
|
||||
val hueShift: Float? = (it.nextAny() as? Number)?.let { it.toFloat() / 255f * 360f }
|
||||
val allowOverlap = it.nextBoolean()
|
||||
|
||||
val action = TileModification.PlaceMaterial(isBackground, material.ref, hueShift)
|
||||
|
||||
// original engine return here doesn't make any sense whatsoever
|
||||
returnBuffer.setTo(
|
||||
LuaFuture(
|
||||
future = self.applyTileModifications(listOf(pos to action), allowOverlap, false)
|
||||
.thenApply { tableFrom(it.map { from(it.first) }) },
|
||||
isLocal = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
callbacks["placeMod"] = luaFunctionN("placeMod") {
|
||||
val pos = toVector2i(it.nextTable())
|
||||
val isBackground = isBackground(it.nextString())
|
||||
val modifier = Registries.tileModifiers.getOrThrow(it.nextString().decode())
|
||||
val hueShift: Float? = (it.nextAny() as? Number)?.let { it.toFloat() / 255f * 360f }
|
||||
val allowOverlap = it.nextBoolean()
|
||||
|
||||
val action = TileModification.PlaceModifier(isBackground, modifier.ref, hueShift)
|
||||
|
||||
// original engine return here doesn't make any sense whatsoever
|
||||
returnBuffer.setTo(
|
||||
LuaFuture(
|
||||
future = self.applyTileModifications(listOf(pos to action), allowOverlap, false)
|
||||
.thenApply { tableFrom(it.map { from(it.first) }) },
|
||||
isLocal = false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -337,6 +337,18 @@ class ServerWorld private constructor(
|
||||
return updated
|
||||
}
|
||||
|
||||
override fun damageTiles(
|
||||
positions: Collection<IStruct2i>,
|
||||
isBackground: Boolean,
|
||||
sourcePosition: Vector2d,
|
||||
damage: TileDamage,
|
||||
source: AbstractEntity?
|
||||
): CompletableFuture<TileDamageResult> {
|
||||
return runBlocking {
|
||||
CompletableFuture.completedFuture(damageTiles(positions, isBackground, sourcePosition, damage, source, null))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this method does not block if pacer is null (safe to use with runBlocking {})
|
||||
*/
|
||||
@ -460,8 +472,10 @@ class ServerWorld private constructor(
|
||||
return topMost
|
||||
}
|
||||
|
||||
override fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean): List<Pair<Vector2i, TileModification>> {
|
||||
return runBlocking { applyTileModifications(modifications, allowEntityOverlap, ignoreTileProtection, null) }
|
||||
override fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean): CompletableFuture<List<Pair<Vector2i, TileModification>>> {
|
||||
return runBlocking {
|
||||
CompletableFuture.completedFuture(applyTileModifications(modifications, allowEntityOverlap, ignoreTileProtection, null))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false, pacer: ActionPacer?): List<Pair<Vector2i, TileModification>> {
|
||||
@ -629,7 +643,7 @@ class ServerWorld private constructor(
|
||||
|
||||
try {
|
||||
LOGGER.debug("Trying to find player spawn position...")
|
||||
var pos = hint ?: CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart(random) }, 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
|
||||
LOGGER.debug("Trying to find player spawn position near {}...", pos)
|
||||
|
||||
@ -686,7 +700,7 @@ class ServerWorld private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
pos = CompletableFuture.supplyAsync(Supplier { template.findSensiblePlayerStart(random) }, 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) {
|
||||
LOGGER.debug("Still trying to find player spawn position near {}...", pos)
|
||||
|
@ -99,7 +99,7 @@ fun ICellAccess.castRay(
|
||||
val hitTiles = ArrayList<RayCastResult.HitCell>()
|
||||
var cellPosX = roundTowardsNegativeInfinity(start.x)
|
||||
var cellPosY = roundTowardsNegativeInfinity(start.y)
|
||||
var cell = getCell(cellPosX, cellPosY) ?: return RayCastResult(start, Vector2d.ZERO)
|
||||
var cell = getCell(cellPosX, cellPosY)
|
||||
|
||||
val direction = (end - start).unitVector
|
||||
|
||||
|
28
src/main/kotlin/ru/dbotthepony/kstarbound/world/Utils.kt
Normal file
28
src/main/kotlin/ru/dbotthepony/kstarbound/world/Utils.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun tileAreaBrush(pos: Vector2i, radius: Double, square: Boolean = false): List<Vector2i> {
|
||||
if (radius <= 0.0)
|
||||
return emptyList()
|
||||
|
||||
val result = ObjectArrayList<Vector2i>()
|
||||
val squareRadius = (if (square) Double.POSITIVE_INFINITY else radius).pow(2.0)
|
||||
val intRadius = radius.roundToInt()
|
||||
|
||||
for (x in -intRadius .. intRadius) {
|
||||
for (y in -intRadius .. intRadius) {
|
||||
val length = x * x + y * y
|
||||
|
||||
if (length <= squareRadius) {
|
||||
result.add(Vector2i(x + pos.x, y + pos.y))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.sortWith { o1, o2 -> o1.distanceSquared(pos).compareTo(o2.distanceSquared(pos)) }
|
||||
return result
|
||||
}
|
@ -30,6 +30,8 @@ import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.tile.ARTIFICIAL_DUNGEON_ID
|
||||
import ru.dbotthepony.kstarbound.defs.tile.DESTROYED_DUNGEON_ID
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||
@ -42,6 +44,7 @@ import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.EntityMessagePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
||||
import ru.dbotthepony.kstarbound.util.ActionPacer
|
||||
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||
@ -587,11 +590,23 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
}
|
||||
|
||||
fun gravityAt(pos: IStruct2i): Vector2d {
|
||||
return template.worldParameters?.gravity ?: Vector2d.ZERO
|
||||
val cell = getCell(pos)
|
||||
return dungeonGravityInternal[cell.dungeonId] ?: template.worldParameters?.gravity ?: Vector2d.ZERO
|
||||
}
|
||||
|
||||
fun gravityAt(pos: IStruct2d): Vector2d {
|
||||
return template.worldParameters?.gravity ?: Vector2d.ZERO
|
||||
val cell = getCell(pos.component1().toInt(), pos.component2().toInt())
|
||||
return dungeonGravityInternal[cell.dungeonId] ?: template.worldParameters?.gravity ?: Vector2d.ZERO
|
||||
}
|
||||
|
||||
fun isBreathable(pos: IStruct2i): Boolean {
|
||||
val cell = getCell(pos)
|
||||
return dungeonBreathableInternal.getOrDefault(cell.dungeonId, template.worldParameters?.airless != true)
|
||||
}
|
||||
|
||||
fun isBreathable(pos: IStruct2d): Boolean {
|
||||
val cell = getCell(pos.component1().toInt(), pos.component2().toInt())
|
||||
return dungeonBreathableInternal.getOrDefault(cell.dungeonId, template.worldParameters?.airless != true)
|
||||
}
|
||||
|
||||
data class LiquidLevel(val type: Registry.Entry<LiquidDefinition>, val average: Double)
|
||||
@ -622,7 +637,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
/**
|
||||
* returns unapplied tile modifications
|
||||
*/
|
||||
abstract fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false): List<Pair<Vector2i, TileModification>>
|
||||
abstract fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false): CompletableFuture<List<Pair<Vector2i, TileModification>>>
|
||||
|
||||
fun addDamageNotification(notification: DamageNotificationPacket) {
|
||||
pushRemoteDamageNotification(notification)
|
||||
@ -719,6 +734,15 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
}
|
||||
}
|
||||
|
||||
fun environmentStatusEffects(pos: Vector2i): Set<String> {
|
||||
// TODO: judging by original code, they wanted to allow definition
|
||||
// of custom environmental effects per dungeon id
|
||||
// But it never happened
|
||||
return template.worldParameters?.environmentStatusEffects ?: setOf()
|
||||
}
|
||||
|
||||
abstract fun damageTiles(positions: Collection<IStruct2i>, isBackground: Boolean, sourcePosition: Vector2d, damage: TileDamage, source: AbstractEntity? = null): CompletableFuture<TileDamageResult>
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
// uint8_t
|
||||
enum class TileColor {
|
||||
enum class TileColor : IStringSerializable {
|
||||
DEFAULT,
|
||||
RED,
|
||||
BLUE,
|
||||
@ -14,7 +15,7 @@ enum class TileColor {
|
||||
BLACK,
|
||||
WHITE;
|
||||
|
||||
val lowercase = name.lowercase()
|
||||
override val jsonName = name.lowercase()
|
||||
|
||||
companion object {
|
||||
private val map: ImmutableMap<String, TileColor>
|
||||
|
Loading…
Reference in New Issue
Block a user