mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-13 23:26:07 +09:00
347 lines
13 KiB
Kotlin
347 lines
13 KiB
Kotlin
package net.torvald.terrarum.modulebasegame
|
|
|
|
import com.badlogic.gdx.utils.Queue
|
|
import net.torvald.terrarum.BlockCodex
|
|
import net.torvald.terrarum.ItemCodex
|
|
import net.torvald.terrarum.OreCodex
|
|
import net.torvald.terrarum.blockproperties.Block
|
|
import net.torvald.terrarum.ceilToInt
|
|
import net.torvald.terrarum.gameworld.BlockLayerI16
|
|
import net.torvald.terrarum.gameworld.GameWorld
|
|
import net.torvald.terrarum.gameworld.fmod
|
|
import net.torvald.terrarum.gameworld.getOffset
|
|
import net.torvald.terrarum.modulebasegame.gameitems.PickaxeCore
|
|
import net.torvald.unsafe.UnsafeHelper
|
|
import java.util.concurrent.*
|
|
import kotlin.math.*
|
|
|
|
/**
|
|
* Created by minjaesong on 2024-02-13.
|
|
*/
|
|
object ExplosionManager {
|
|
|
|
private val executor = Executors.newSingleThreadExecutor()
|
|
private val futures = ArrayList<Future<*>>()
|
|
private val runners = ArrayList<CallableWithState>()
|
|
|
|
data class CallableWithState(
|
|
val runner: Callable<Unit>,
|
|
var executed: Boolean = false
|
|
)
|
|
|
|
fun goBoom(world: GameWorld, tx: Int, ty: Int, power: Float, dropProbNonOre: Float, dropProbOre: Float, callback: () -> Unit) {
|
|
val CALC_RADIUS = power.ceilToInt() + 2
|
|
val CALC_WIDTH = CALC_RADIUS * 2 + 1
|
|
|
|
// create a copy of the tilemap
|
|
val tilemap = BlockLayerI16(CALC_WIDTH, CALC_WIDTH)
|
|
val breakmap = UnsafeFloatArray(CALC_WIDTH, CALC_WIDTH)
|
|
|
|
// fill in the tilemap copy
|
|
for (line in 0 until CALC_WIDTH) {
|
|
memcpyFromWorldTiles(CALC_RADIUS, CALC_WIDTH, world, tx - CALC_RADIUS, ty - CALC_RADIUS, line, tilemap)
|
|
memcpyFromWorldBreakage(CALC_WIDTH, world, tx - CALC_RADIUS, ty - CALC_RADIUS, line, breakmap)
|
|
}
|
|
|
|
val runner = createExplosionWorker(CALC_RADIUS, CALC_WIDTH, world, breakmap, tilemap, tx, ty, power, dropProbNonOre, dropProbOre, callback)
|
|
// futures.add(executor.submit(runner))
|
|
|
|
runners.removeIf { it.executed }
|
|
runners.add(CallableWithState(runner))
|
|
}
|
|
|
|
init {
|
|
Thread {
|
|
while (true) {
|
|
try {
|
|
runners.toList().firstOrNull { !it.executed }?.let { job ->
|
|
val executor = Executors.newSingleThreadExecutor()
|
|
executor.submit(job.runner).get(500L, TimeUnit.MILLISECONDS)
|
|
executor.shutdownNow()
|
|
job.executed = true
|
|
}
|
|
}
|
|
catch (_: TimeoutException) { }
|
|
|
|
Thread.sleep(50L)
|
|
}
|
|
}.start()
|
|
}
|
|
|
|
private fun memcpyFromWorldTiles(CALC_RADIUS: Int, CALC_WIDTH: Int, world: GameWorld, xStart: Int, yStart: Int, yOff: Int, out: BlockLayerI16) {
|
|
// if the bounding box must wrap around
|
|
if (xStart > world.width - CALC_RADIUS) {
|
|
val lenLeft = world.width - xStart
|
|
val lenRight = CALC_WIDTH - lenLeft
|
|
|
|
UnsafeHelper.memcpy(
|
|
world.layerTerrain.ptr,
|
|
world.layerTerrain.getOffset(xStart, yStart + yOff),
|
|
out.ptr,
|
|
out.getOffset(0, yOff),
|
|
world.layerTerrain.bytesPerBlock * lenLeft
|
|
)
|
|
UnsafeHelper.memcpy(
|
|
world.layerTerrain.ptr,
|
|
world.layerTerrain.getOffset(0, yStart + yOff),
|
|
out.ptr,
|
|
out.getOffset(lenLeft, yOff),
|
|
world.layerTerrain.bytesPerBlock * lenRight
|
|
)
|
|
}
|
|
else {
|
|
UnsafeHelper.memcpy(
|
|
world.layerTerrain.ptr,
|
|
world.layerTerrain.getOffset(xStart, yStart + yOff),
|
|
out.ptr,
|
|
out.getOffset(0, yOff),
|
|
world.layerTerrain.bytesPerBlock * CALC_WIDTH
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun memcpyToWorldTiles(CALC_RADIUS: Int, CALC_WIDTH: Int, world: GameWorld, xStart: Int, yStart: Int, yOff: Int, out: BlockLayerI16) {
|
|
// if the bounding box must wrap around
|
|
if (xStart > world.width - CALC_RADIUS) {
|
|
val lenLeft = world.width - xStart
|
|
val lenRight = CALC_WIDTH - lenLeft
|
|
|
|
UnsafeHelper.memcpy(
|
|
out.ptr,
|
|
out.getOffset(0, yOff),
|
|
world.layerTerrain.ptr,
|
|
world.layerTerrain.getOffset(xStart, yStart + yOff),
|
|
world.layerTerrain.bytesPerBlock * lenLeft
|
|
)
|
|
UnsafeHelper.memcpy(
|
|
out.ptr,
|
|
out.getOffset(lenLeft, yOff),
|
|
world.layerTerrain.ptr,
|
|
world.layerTerrain.getOffset(0, yStart + yOff),
|
|
world.layerTerrain.bytesPerBlock * lenRight
|
|
)
|
|
}
|
|
else {
|
|
UnsafeHelper.memcpy(
|
|
out.ptr,
|
|
out.getOffset(0, yOff),
|
|
world.layerTerrain.ptr,
|
|
world.layerTerrain.getOffset(xStart, yStart + yOff),
|
|
world.layerTerrain.bytesPerBlock * CALC_WIDTH
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun memcpyFromWorldBreakage(CALC_WIDTH: Int, world: GameWorld, xStart: Int, yStart: Int, yOff: Int, out: UnsafeFloatArray) {
|
|
for (x in xStart until xStart + CALC_WIDTH) {
|
|
out[x - xStart, yOff] = world.getTerrainDamage(x, yStart + yOff)
|
|
}
|
|
}
|
|
|
|
private fun memcpyToWorldBreakage(CALC_WIDTH: Int, world: GameWorld, xStart: Int, yStart: Int, yOff: Int, out: UnsafeFloatArray) {
|
|
for (x in xStart until xStart + CALC_WIDTH) {
|
|
world.inflictTerrainDamage(x, yStart + yOff, out[x - xStart, yOff].toDouble(), false)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param tilemap a portion copy of the tilemap from the world, centred to 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(
|
|
CALC_RADIUS: Int, CALC_WIDTH: Int,
|
|
world: GameWorld,
|
|
breakmap: UnsafeFloatArray,
|
|
tilemap: BlockLayerI16,
|
|
tx: Int, ty: Int,
|
|
power: Float,
|
|
dropProbNonOre: Float,
|
|
dropProbOre: Float,
|
|
callback: () -> Unit): Callable<Unit>
|
|
{ return Callable {
|
|
val mapBoomPow = UnsafeFloatArray(CALC_WIDTH, CALC_WIDTH) // explosion power map
|
|
val mapTileStr = UnsafeFloatArray(CALC_WIDTH, CALC_WIDTH) // the tile strengths
|
|
val mapTileStr2 = UnsafeFloatArray(CALC_WIDTH, CALC_WIDTH) // the tile strengths
|
|
var boomPow = 0f // ???
|
|
var ambientAccumulator = 0f // ambient explosion power
|
|
var tileStr = 0f
|
|
var tileStr2 = 0f
|
|
|
|
|
|
fun _swipeTask(x: Int, y: Int, xOld: Int, yOld: Int, swipeDiag: Boolean) {//, distFromLightSrc: Ivec4) {
|
|
if (xOld < 0 || yOld < 0 || xOld >= CALC_WIDTH || yOld >= CALC_WIDTH) return
|
|
|
|
ambientAccumulator = mapBoomPow[x, y]
|
|
|
|
if (!swipeDiag) {
|
|
tileStr = mapTileStr[x, y]
|
|
ambientAccumulator = maxOf(ambientAccumulator, mapBoomPow[xOld, yOld] - tileStr)
|
|
}
|
|
else {
|
|
tileStr2 = mapTileStr2[x, y]
|
|
ambientAccumulator = maxOf(ambientAccumulator, mapBoomPow[xOld, yOld] - tileStr2)
|
|
}
|
|
|
|
mapBoomPow[x, y] = ambientAccumulator
|
|
}
|
|
fun swipeBoomPower(sx: Int, sy: Int, ex: Int, ey: Int, dx: Int, dy: Int, 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() {
|
|
for (line in 1 until CALC_WIDTH - 1) {
|
|
swipeBoomPower(1, line, CALC_WIDTH - 2, line, 1, 0, false)
|
|
}
|
|
}
|
|
fun r2() {
|
|
for (line in 1 until CALC_WIDTH - 1) {
|
|
swipeBoomPower(line, 1, line, CALC_WIDTH - 2, 0, 1, false)
|
|
}
|
|
}
|
|
fun r3() {
|
|
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, true
|
|
)
|
|
}
|
|
}
|
|
fun r4() {
|
|
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, 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()]]
|
|
val thisTerrainDmg = breakmap[lx, ly]//world.getTerrainDamage(worldX, worldY)
|
|
|
|
// create tile strength map
|
|
mapTileStr[lx, ly] = thisTerrainProp.strength.toFloat().minus(thisTerrainDmg).toBlastResistance()
|
|
mapTileStr2[lx, ly] = thisTerrainProp.strength.toFloat().minus(thisTerrainDmg).times(1.4142135f.blastToDmg()).toBlastResistance()
|
|
|
|
// initialise explosion power map with the explosive
|
|
if (tx == worldX && ty == worldY) {
|
|
boomPow = maxOf(boomPow, power)
|
|
mapBoomPow.max(lx, ly, boomPow)
|
|
}
|
|
}
|
|
}
|
|
|
|
// simulate explosion like strengthmaprenderer
|
|
r1();r2();r3();r4()
|
|
r1();r2();r3();r4()
|
|
|
|
//// just write to the damagemap lol
|
|
for (wy in worldYstart until worldYstart + CALC_WIDTH) {
|
|
for (wx in worldXstart until worldXstart + CALC_WIDTH) {
|
|
val lx = wx - (tx - CALC_RADIUS - 1)
|
|
val ly = wy - (ty - CALC_RADIUS - 1)
|
|
world.inflictTerrainDamage(wx, wy, mapBoomPow[lx, ly].blastToDmg().toDouble(), false).let { (tile, ore) ->
|
|
if (ore != null || tile != null) {
|
|
// drop item
|
|
val prob = if (ore != null) dropProbOre else dropProbNonOre
|
|
if (Math.random() < prob) {
|
|
val drop = if (ore != null) OreCodex[ore].item else BlockCodex[tile].drop
|
|
PickaxeCore.dropItem(drop, wx, wy)
|
|
}
|
|
}
|
|
|
|
if (tile != null) {
|
|
PickaxeCore.makeDust(tile, wx, wy, 8 + (5 * Math.random()).toInt())
|
|
|
|
// drop random disc
|
|
val itemprop = ItemCodex[tile]
|
|
if (Math.random() < (1.0 / 4096.0) * (dropProbNonOre) && // prob: 1/16384
|
|
(itemprop?.hasTag("CULTIVABLE") == true ||
|
|
itemprop?.hasTag("SAND") == true ||
|
|
itemprop?.hasTag("GRAVEL") == true)
|
|
) {
|
|
PickaxeCore.dropItem(PickaxeCore.getRandomDisc(), wx, wy)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// dispose of the tilemap copy
|
|
mapBoomPow.destroy()
|
|
breakmap.destroy()
|
|
mapTileStr.destroy()
|
|
mapTileStr2.destroy()
|
|
tilemap.ptr.destroy()
|
|
breakmap.destroy()
|
|
|
|
callback()
|
|
} }
|
|
|
|
|
|
private val q = 2.828427f
|
|
private fun Float.toBlastResistance() = if (this <= 0f) 0f else this.pow(1f / q)
|
|
private fun Float.blastToDmg() = if (this <= 0f) 0f else this.pow(q)
|
|
|
|
|
|
private class UnsafeFloatArray(val width: Int, val height: Int) {
|
|
val bytesPerBlock: Long = 4L
|
|
private val SIZE_IN_BYTES = bytesPerBlock * width * height
|
|
val array = UnsafeHelper.allocate(SIZE_IN_BYTES)
|
|
val ptr get() = array
|
|
|
|
init {
|
|
array.fillWith(0)
|
|
}
|
|
|
|
private inline fun toAddr(x: Int, y: Int) = (width * y + x).toLong()
|
|
inline fun getOffset(x: Int, y: Int) = bytesPerBlock * toAddr(x, y)
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
} |