diff --git a/src/net/torvald/terrarum/gameworld/WorldSimulator.kt b/src/net/torvald/terrarum/gameworld/WorldSimulator.kt index 90d69dda9..e7b80a8b2 100644 --- a/src/net/torvald/terrarum/gameworld/WorldSimulator.kt +++ b/src/net/torvald/terrarum/gameworld/WorldSimulator.kt @@ -108,7 +108,7 @@ object WorldSimulator { fun buryGrassImmediately() { - ingame.terrainChangeQueue.forEach { + ingame.terrainChangeQueue.toList().forEach { val blockProp = BlockCodex[it.new] if (blockProp.isSolid && !blockProp.isActorBlock) { if (world.getTileFromTerrain(it.posX, it.posY + 1) == Block.GRASS) { diff --git a/src/net/torvald/terrarum/modulebasegame/ExplosionManager.kt b/src/net/torvald/terrarum/modulebasegame/ExplosionManager.kt index 060f152a9..a90cfe03f 100644 --- a/src/net/torvald/terrarum/modulebasegame/ExplosionManager.kt +++ b/src/net/torvald/terrarum/modulebasegame/ExplosionManager.kt @@ -1,10 +1,13 @@ package net.torvald.terrarum.modulebasegame +import net.torvald.terrarum.BlockCodex import net.torvald.terrarum.gameworld.BlockLayerI16 import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.getOffset import net.torvald.terrarum.tryDispose import net.torvald.unsafe.UnsafeHelper +import kotlin.math.max +import kotlin.math.min /** * Created by minjaesong on 2024-02-13. @@ -23,7 +26,7 @@ object ExplosionManager { memcpyFromWorld(world, tx - CALC_RADIUS, ty - CALC_RADIUS, line, tilemap) } - createExplosionWorker(tilemap, tx, ty, power, world, callback).start() + createExplosionWorker(world, tilemap, tx, ty, power, world, callback).start() } private fun memcpyFromWorld(world: GameWorld, xStart: Int, yStart: Int, yOff: Int, out: BlockLayerI16) { @@ -60,23 +63,163 @@ object ExplosionManager { /** * @param tilemap a portion copy of the tilemap from the world, centred to the explosive - * @param tx tilewise centre-x of the explosive - * @param ty tilewise centre-y of the explosive + * @param tx tilewise centre-x of the explosive from the world + * @param ty tilewise centre-y of the explosive from the world * @param outWorld world object to write the result to */ - private fun createExplosionWorker(tilemap: BlockLayerI16, tx: Int, ty: Int, power: Float, outWorld: GameWorld, callback: () -> Unit): Thread { - return Thread { - // simulate explosion like lightmaprenderer + private fun createExplosionWorker(world: GameWorld, tilemap: BlockLayerI16, tx: Int, ty: Int, power: Float, outWorld: GameWorld, callback: () -> Unit): Thread { return Thread { + val lightmap = UnsafeFloatArray(CALC_WIDTH, CALC_WIDTH) // explosion power map + val _mapThisTileOpacity = UnsafeFloatArray(CALC_WIDTH, CALC_WIDTH) // the tile strengths + val _mapThisTileOpacity2 = UnsafeFloatArray(CALC_WIDTH, CALC_WIDTH) // the tile strengths + val _mapLightLevelThis = UnsafeFloatArray(CALC_WIDTH, CALC_WIDTH) // the explosion powers + var _thisTileLuminosity = 0f // ??? + var _ambientAccumulator = 0f // ambient explosion power + var _thisTileOpacity = 0f + var _thisTileOpacity2 = 0f - // write to the world + fun _swipeTask(x: Int, y: Int, x2: Int, y2: Int, swipeDiag: Boolean) {//, distFromLightSrc: Ivec4) { + if (x2 < 0 || y2 < 0 || x2 >= CALC_WIDTH || y2 >= CALC_WIDTH) return + _ambientAccumulator = _mapLightLevelThis[x, y] - // dispose of the tilemap copy - tilemap.tryDispose() + if (!swipeDiag) { + _thisTileOpacity = _mapThisTileOpacity[x, y] + _ambientAccumulator = maxOf(_ambientAccumulator, lightmap[x2, y2] - _thisTileOpacity) + } + else { + _thisTileOpacity2 = _mapThisTileOpacity2[x, y] + _ambientAccumulator = maxOf(_ambientAccumulator, lightmap[x2, y2] - _thisTileOpacity2) + } - callback() + _mapLightLevelThis[x, y] = _ambientAccumulator + lightmap[x, y] = _ambientAccumulator } + fun swipeBoomPower(sx: Int, sy: Int, ex: Int, ey: Int, dx: Int, dy: Int, strengthmap: UnsafeFloatArray, swipeDiag: Boolean) { + var swipeX = sx + var swipeY = sy + while (swipeX*dx <= ex*dx && swipeY*dy <= ey*dy) { + // conduct the task #1 + // spread towards the end + _swipeTask(swipeX, swipeY, swipeX-dx, swipeY-dy, swipeDiag) + + swipeX += dx + swipeY += dy + } + + swipeX = ex; swipeY = ey + while (swipeX*dx >= sx*dx && swipeY*dy >= sy*dy) { + // conduct the task #2 + // spread towards the start + _swipeTask(swipeX, swipeY, swipeX+dx, swipeY+dy, swipeDiag) + + swipeX -= dx + swipeY -= dy + } + } + + fun r1(strengthmap: UnsafeFloatArray) { + for (line in 1 until CALC_WIDTH - 1) { + swipeBoomPower(1, line, CALC_WIDTH - 2, line, 1, 0, strengthmap, false) + } + } + fun r2(strengthmap: UnsafeFloatArray) { + for (line in 1 until CALC_WIDTH - 1) { + swipeBoomPower(line, 1, line, CALC_WIDTH - 2, 0, 1, strengthmap, false) + } + } + fun r3(strengthmap: UnsafeFloatArray) { + for (i in 0 until CALC_WIDTH + CALC_WIDTH - 5) { + swipeBoomPower( + max(1, i - CALC_WIDTH + 4), max(1, CALC_WIDTH - 2 - i), + min(CALC_WIDTH - 2, i + 1), min(CALC_WIDTH - 2, (CALC_WIDTH + CALC_WIDTH - 5) - i), + 1, 1, strengthmap, true + ) + } + } + fun r4(strengthmap: UnsafeFloatArray) { + for (i in 0 until CALC_WIDTH + CALC_WIDTH - 5) { + swipeBoomPower( + max(1, i - CALC_WIDTH + 4), min(CALC_WIDTH - 2, i + 1), + min(CALC_WIDTH - 2, i + 1), max(1, (CALC_WIDTH - 2) - (CALC_WIDTH + CALC_WIDTH - 6) + i), + 1, -1, strengthmap, true + ) + } + } + + val worldXstart = tx - CALC_RADIUS - 1 + val worldYstart = ty - CALC_RADIUS - 1 + + // precalculate + for (rawy in worldYstart until worldYstart + CALC_WIDTH) { + for (rawx in worldXstart until worldXstart + CALC_WIDTH) { + val lx = rawx - (tx - CALC_RADIUS - 1) + val ly = rawy - (ty - CALC_RADIUS - 1) + val (worldX, worldY) = world.coerceXY(rawx, rawy) + + val _thisTerrain = world.getTileFromTerrainRaw(worldX, worldY) + val _thisTerrainProp = BlockCodex[world.tileNumberToNameMap[_thisTerrain.toLong()]] + + // create tile strength map + _mapThisTileOpacity.set(lx, ly, _thisTerrainProp.strength.toFloat()) + _mapThisTileOpacity2.set(lx, ly, _thisTerrainProp.strength.toFloat() * 1.41421356f) + + // initialise explosion power map with the explosive + if (tx == worldX && ty == worldY) { + _thisTileLuminosity = maxOf(_thisTileLuminosity, power) + _mapLightLevelThis.max(lx, ly, _thisTileLuminosity) + } + } + } + + // simulate explosion like strengthmaprenderer + r1(lightmap);r2(lightmap);r3(lightmap);r4(lightmap) + r1(lightmap);r2(lightmap);r3(lightmap);r4(lightmap) + + // write lightmap to the tilemap + for (rawy in worldYstart until worldYstart + CALC_WIDTH) { + for (rawx in worldXstart until worldXstart + CALC_WIDTH) { + val lx = rawx - (tx - CALC_RADIUS - 1) + val ly = rawy - (ty - CALC_RADIUS - 1) + world.inflictTerrainDamage(rawx, rawy, lightmap[lx, ly].toDouble()) + } + } + + // memcpy the tilemap to the world + + // dispose of the tilemap copy + lightmap.destroy() + _mapThisTileOpacity.destroy() + _mapThisTileOpacity2.destroy() + _mapLightLevelThis.destroy() + tilemap.tryDispose() + + callback() + } } + + + + + private class UnsafeFloatArray(val width: Int, val height: Int) { + private val SIZE_IN_BYTES = 4L * width * height + val array = UnsafeHelper.allocate(SIZE_IN_BYTES) + + init { + array.fillWith(0) + } + + private inline fun toAddr(x: Int, y: Int) = (width * y + x).toLong() + + operator fun set(x: Int, y: Int, value: Float) { + array.setFloat(toAddr(x, y), value) + } + + operator fun get(x: Int, y: Int) = array.getFloat(toAddr(x, y)) + fun max(x: Int, y: Int, value: Float) { + set(x, y, maxOf(get(x, y), value)) + } + + fun destroy() = this.array.destroy() } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt index c0245574e..6a05d599d 100644 --- a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt @@ -1356,21 +1356,21 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) { } if (it is CuedByTerrainChange) { - terrainChangeQueue.forEach { cue -> + terrainChangeQueue.toList().forEach { cue -> // printdbg(this, "Ingame actors terrainChangeCue: ${cue}") it.updateForTerrainChange(cue) } } if (it is CuedByWallChange) { - wallChangeQueue.forEach { cue -> + wallChangeQueue.toList().forEach { cue -> // printdbg(this, "Ingame actors wallChangeCue: ${cue}") it.updateForWallChange(cue) } } if (it is CuedByWireChange) { - wireChangeQueue.forEach { cue -> + wireChangeQueue.toList().forEach { cue -> // printdbg(this, "Ingame actors wireChangeCue: ${cue}") it.updateForWireChange(cue) } diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt index a62a05277..2a953a626 100644 --- a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt @@ -194,7 +194,7 @@ class TerrarumMusicGovernor : MusicGovernor() { protected var ambState = 0 protected var ambFired = false - fun getRandomMusicInterval() = 25f + Math.random().toFloat() * 10f + fun getRandomMusicInterval() = 3.6f + Math.random().toFloat() * 1.2f // use shorter gap a la mixtape var stopCaller: Any? = null; private set var playCaller: Any? = null; private set diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorPrimedBomb.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorPrimedBomb.kt index 6d2294ad4..472f056dd 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorPrimedBomb.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorPrimedBomb.kt @@ -65,7 +65,7 @@ open class ActorPrimedBomb( /** * Created by minjaesong on 2024-02-14. */ -class ActorCherryBomb : ActorPrimedBomb(500f, 4.5f) { +class ActorCherryBomb : ActorPrimedBomb(100f, 4.5f) { init { val itemImage = CommonResourcePool.getAsItemSheet("basegame.items").get(0,13) diff --git a/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt b/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt index 40feff063..cfb8aed6f 100644 --- a/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt +++ b/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt @@ -213,29 +213,26 @@ object LightmapRenderer { // 'NEWLIGHT2' LIGHT SWIPER // O((8*2)n) where n is a size of the map. /* - */fun r1(lightmap: UnsafeCvecArray) { - swipeDiag = false for (line in 1 until LIGHTMAP_HEIGHT - 1) { swipeLight( 1, line, LIGHTMAP_WIDTH - 2, line, 1, 0, - lightmap + lightmap, false ) } } /* | */fun r2(lightmap: UnsafeCvecArray) { - swipeDiag = false for (line in 1 until LIGHTMAP_WIDTH - 1) { swipeLight( line, 1, line, LIGHTMAP_HEIGHT - 2, 0, 1, - lightmap + lightmap, false ) } } /* \ */fun r3(lightmap: UnsafeCvecArray) { - swipeDiag = true /* construct indices such that: 56789ABC 4 1 w-2 @@ -268,12 +265,11 @@ object LightmapRenderer { max(1, i - LIGHTMAP_HEIGHT + 4), max(1, LIGHTMAP_HEIGHT - 2 - i), min(LIGHTMAP_WIDTH - 2, i + 1), min(LIGHTMAP_HEIGHT - 2, (LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 5) - i), 1, 1, - lightmap + lightmap, true ) } } /* / */fun r4(lightmap: UnsafeCvecArray) { - swipeDiag = true /* 1 w-2 /////---/ @@ -302,7 +298,7 @@ object LightmapRenderer { max(1, i - LIGHTMAP_HEIGHT + 4), min(LIGHTMAP_HEIGHT - 2, i + 1), min(LIGHTMAP_WIDTH - 2, i + 1), max(1, (LIGHTMAP_HEIGHT - 2) - (LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 6) + i), 1, -1, - lightmap + lightmap, true ) } } @@ -324,11 +320,7 @@ object LightmapRenderer { // why dark spots appear in the first place) // - Multithreading? I have absolutely no idea. // - If you naively slice the screen (job area) to multithread, the seam will appear. - r1(lightmap);r2(lightmap); - -// if (!App.getConfigBoolean("fx_newlight")) { - r3(lightmap);r4(lightmap) -// } + r1(lightmap);r2(lightmap);r3(lightmap);r4(lightmap) } App.measureDebugTime("Renderer.Precalculate2") { @@ -341,11 +333,8 @@ object LightmapRenderer { } App.measureDebugTime("Renderer.LightRuns2") { - r1(lightmap);r2(lightmap); -// if (!App.getConfigBoolean("fx_newlight")) { - r3(lightmap);r4(lightmap) // two looks better than one -// } // no rendering trickery will eliminate the need of 2nd pass, even the "decay out" + r1(lightmap);r2(lightmap);r3(lightmap);r4(lightmap) } App.getConfigInt("lightpasses").let { passcnt -> @@ -617,68 +606,43 @@ object LightmapRenderer { } private const val giScale = 0.35f - private var swipeX = -1 - private var swipeY = -1 - private var swipeDiag = false - private val distFromLightSrc = Ivec4() - private fun _swipeTask(x: Int, y: Int, x2: Int, y2: Int, lightmap: UnsafeCvecArray) {//, distFromLightSrc: Ivec4) { + private fun _swipeTask(x: Int, y: Int, x2: Int, y2: Int, lightmap: UnsafeCvecArray, swipeDiag: Boolean) {//, distFromLightSrc: Ivec4) { if (x2 < 0 || y2 < 0 || x2 >= LIGHTMAP_WIDTH || y2 >= LIGHTMAP_HEIGHT) return -// _ambientAccumulator.set(_mapLightLevelThis.getVec(x, y)) _mapLightLevelThis.getAndSet(_ambientAccumulator, x, y) if (!swipeDiag) { _mapThisTileOpacity.getAndSet(_thisTileOpacity, x, y) - _ambientAccumulator.maxAndAssign(darkenColoured(x2, y2, _thisTileOpacity, lightmap))//, distFromLightSrc)) + _ambientAccumulator.maxAndAssign(darkenColoured(x2, y2, _thisTileOpacity, lightmap)) } else { _mapThisTileOpacity2.getAndSet(_thisTileOpacity2, x, y) - _ambientAccumulator.maxAndAssign(darkenColoured(x2, y2, _thisTileOpacity2, lightmap))//, distFromLightSrc)) + _ambientAccumulator.maxAndAssign(darkenColoured(x2, y2, _thisTileOpacity2, lightmap)) } _mapLightLevelThis.setVec(x, y, _ambientAccumulator) lightmap.setVec(x, y, _ambientAccumulator) } - private fun swipeLight(sx: Int, sy: Int, ex: Int, ey: Int, dx: Int, dy: Int, lightmap: UnsafeCvecArray) { - swipeX = sx; swipeY = sy -// if (App.getConfigBoolean("fx_newlight")) distFromLightSrc.broadcast(0) + private fun swipeLight(sx: Int, sy: Int, ex: Int, ey: Int, dx: Int, dy: Int, lightmap: UnsafeCvecArray, swipeDiag: Boolean) { + var swipeX = sx + var swipeY = sy while (swipeX*dx <= ex*dx && swipeY*dy <= ey*dy) { // conduct the task #1 // spread towards the end - - _swipeTask(swipeX, swipeY, swipeX-dx, swipeY-dy, lightmap)//, distFromLightSrc) + _swipeTask(swipeX, swipeY, swipeX-dx, swipeY-dy, lightmap, swipeDiag) swipeX += dx swipeY += dy - -// if (App.getConfigBoolean("fx_newlight")) { -// distFromLightSrc.add(1) -// -// if (_mapLightLevelThis.getR(swipeX - dx, swipeY - dy) <= _mapLightLevelThis.getR(swipeX, swipeY)) distFromLightSrc.r = 0 -// if (_mapLightLevelThis.getG(swipeX - dx, swipeY - dy) <= _mapLightLevelThis.getG(swipeX, swipeY)) distFromLightSrc.g = 0 -// if (_mapLightLevelThis.getB(swipeX - dx, swipeY - dy) <= _mapLightLevelThis.getB(swipeX, swipeY)) distFromLightSrc.b = 0 -// if (_mapLightLevelThis.getA(swipeX - dx, swipeY - dy) <= _mapLightLevelThis.getA(swipeX, swipeY)) distFromLightSrc.a = 0 -// } } swipeX = ex; swipeY = ey -// if (App.getConfigBoolean("fx_newlight")) distFromLightSrc.broadcast(0) while (swipeX*dx >= sx*dx && swipeY*dy >= sy*dy) { // conduct the task #2 // spread towards the start - _swipeTask(swipeX, swipeY, swipeX+dx, swipeY+dy, lightmap)//, distFromLightSrc) + _swipeTask(swipeX, swipeY, swipeX+dx, swipeY+dy, lightmap, swipeDiag) swipeX -= dx swipeY -= dy - -// if (App.getConfigBoolean("fx_newlight")) { -// distFromLightSrc.add(1) -// -// if (_mapLightLevelThis.getR(swipeX + dx, swipeY + dy) <= _mapLightLevelThis.getR(swipeX, swipeY)) distFromLightSrc.r = 0 -// if (_mapLightLevelThis.getG(swipeX + dx, swipeY + dy) <= _mapLightLevelThis.getG(swipeX, swipeY)) distFromLightSrc.g = 0 -// if (_mapLightLevelThis.getB(swipeX + dx, swipeY + dy) <= _mapLightLevelThis.getB(swipeX, swipeY)) distFromLightSrc.b = 0 -// if (_mapLightLevelThis.getA(swipeX + dx, swipeY + dy) <= _mapLightLevelThis.getA(swipeX, swipeY)) distFromLightSrc.a = 0 -// } } }