diff --git a/ADDITIONS.md b/ADDITIONS.md index 555b550c..e45c2471 100644 --- a/ADDITIONS.md +++ b/ADDITIONS.md @@ -158,6 +158,10 @@ val color: TileColor = TileColor.DEFAULT * 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`, with same notes as `world.damageTilesPromise()` apply + * Added `world.placeMaterialPromise(pos: Vector2i, layer: String, material: String, hueShift: Number?, allowOverlap: Boolean): RpcPromise`, returning promise of unapplied tile modifications + * However, correct clientside result will be returned _only_ when using native protocol + * Added `world.placeModPromise(pos: Vector2i, layer: String, modifier: String, hueShift: Number?, allowOverlap: Boolean): RpcPromise`, returning promise of unapplied tile modifications + * However, correct clientside result will be returned _only_ when using native protocol #### Changes @@ -166,23 +170,17 @@ val color: TileColor = TileColor.DEFAULT * `world.flyingType(): String` has been made shared (previously was server world only) * `world.warpPhase(): String` has been made shared (previously was server world only) * `world.skyTime(): Double` has been made shared (previously was server world only) - * `world.loadRegion(region: AABB): RpcPromise` now returns promise for region load + * `world.loadRegion(region: AABB): RpcPromise` **now returns promise for region load** * Due to how engine handles world loading and unloading (it is completely async), mods which expect `loadRegion` to instantaneously load required regions **will break**. This will not be changed, mods must be adapted to new behavior * `world.breakObject(id: EntityID, smash: Boolean = false): Boolean` argument `smash` is now optional - * `world.loadUniqueEntity(id: String): EntityID` **is busted in new engine** and will cause major issues if used (because engine is incapable for synchronous loading of world chunks, everything is async) - * If your mod is using it **PLEASE** switch to `world.loadUniqueEntityAsync(id: String): RpcPromise` * `world.placeDungeon(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.placeDungeonAsync`, because there are absolutely no guarantees dungeon will be generated the moment `world.placeDungeon` call returns + * Please update code to use `world.placeDungeonAsync()`, because there are absolutely no guarantees dungeon will be generated the moment `world.placeDungeon()` call returns * `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)` 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` now returns `RpcPromise` 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` now returns `RpcPromise` 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` instead #### Fixes @@ -196,6 +194,18 @@ val color: TileColor = TileColor.DEFAULT * **ONLY** for local entities, or when using native protocol (but why would you ever mutate containers over network in first place) * Remote entities on legacy protocol will try to simulate new behavior locally using item checks and remote `putItems` message +#### Deprecations + + * `world.loadUniqueEntity()` **is busted in new engine** and will cause major issues if used (because engine is incapable for synchronous loading of world chunks, everything is async) + * If your mod is using it **PLEASE** switch to `world.loadUniqueEntityAsync(id: String): RpcPromise` + * `world.spawnLiquid()` is deprecated, use `world.spawnLiquidPromise()` instead + * `world.damageTiles()` is deprecated, use `world.damageTilesPromise()` instead + * `world.damageTileArea()` is deprecated, use `world.damageTileAreaPromise()` instead + * `world.placeMaterial()` is deprecated, use `world.placeMaterialPromise()` instead + * `world.placeMod()` is deprecated, use `world.placeModPromise()` instead + * `world.placeDungeon()` is deprecated, use `world.placeDungeonAsync()` instead + * `world.tryPlaceDungeon()` is deprecated, use `world.tryPlaceDungeonAsync()` instead + --------------- # Deterministic world generation diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldEnvironmentalBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldEnvironmentalBindings.kt index 062076b3..4781d6bf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldEnvironmentalBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldEnvironmentalBindings.kt @@ -29,6 +29,7 @@ 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.math.vector.Vector2i import ru.dbotthepony.kstarbound.util.valueOf import ru.dbotthepony.kstarbound.world.TileModification import ru.dbotthepony.kstarbound.world.World @@ -75,6 +76,32 @@ private fun ExecutionContext.damageTileAreaImpl(self: World<*, *>, it: ArgumentI return self.damageTiles(tileAreaBrush(center, radius), isBackground, sourcePosition, TileDamage(damageType, damage, harvestLevel), sourceEntity) } +private fun ExecutionContext.placeMaterialImpl(self: World<*, *>, it: ArgumentIterator): CompletableFuture> { + 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 + return self.applyTileModifications(listOf(pos to action), allowOverlap, false).thenApply { it.map { it.first } } +} + +private fun ExecutionContext.placeModImpl(self: World<*, *>, it: ArgumentIterator): CompletableFuture> { + val pos = toVector2i(it.nextTable()) + val isBackground = isBackground(it.nextString()) + val material = 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, material.ref, hueShift) + + // original engine return here doesn't make any sense whatsoever + return self.applyTileModifications(listOf(pos to action), allowOverlap, false).thenApply { it.map { it.first } } +} + fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua: LuaEnvironment) { callbacks["lightLevel"] = luaStub("lightLevel") callbacks["windLevel"] = luaStub("windLevel") @@ -184,38 +211,26 @@ fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua: } 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() + returnBuffer.setTo(placeMaterialImpl(self, it).getNow(listOf()).isEmpty()) + } - val action = TileModification.PlaceMaterial(isBackground, material.ref, hueShift) - - // original engine return here doesn't make any sense whatsoever + callbacks["placeMaterialPromise"] = luaFunctionN("placeMaterialPromise") { returnBuffer.setTo( LuaFuture( - future = self.applyTileModifications(listOf(pos to action), allowOverlap, false) - .thenApply { tableFrom(it.map { from(it.first) }) }, + future = placeMaterialImpl(self, it).thenApply { tableFrom(it.map { from(it) }) }, 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() + returnBuffer.setTo(placeModImpl(self, it).getNow(listOf()).isEmpty()) + } - val action = TileModification.PlaceModifier(isBackground, modifier.ref, hueShift) - - // original engine return here doesn't make any sense whatsoever + callbacks["placeModPromise"] = luaFunctionN("placeModPromise") { returnBuffer.setTo( LuaFuture( - future = self.applyTileModifications(listOf(pos to action), allowOverlap, false) - .thenApply { tableFrom(it.map { from(it.first) }) }, + future = placeModImpl(self, it).thenApply { tableFrom(it.map { from(it) }) }, isLocal = false ) )