dynamic chunk generation

This commit is contained in:
minjaesong
2024-01-18 22:41:30 +09:00
parent b2ea61aa4d
commit c8329b36c5
12 changed files with 176 additions and 87 deletions

View File

@@ -28,6 +28,7 @@ import net.torvald.terrarum.gameitems.mouseInInteractableRange
import net.torvald.terrarum.gameparticles.ParticleBase import net.torvald.terrarum.gameparticles.ParticleBase
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.WorldSimulator import net.torvald.terrarum.gameworld.WorldSimulator
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.gameactors.* import net.torvald.terrarum.modulebasegame.gameactors.*
import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver
@@ -43,6 +44,8 @@ import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser
import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen
import net.torvald.terrarum.modulebasegame.worldgenerator.WorldgenParams import net.torvald.terrarum.modulebasegame.worldgenerator.WorldgenParams
import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.realestate.LandUtil.CHUNK_H
import net.torvald.terrarum.realestate.LandUtil.CHUNK_W
import net.torvald.terrarum.savegame.VDUtil import net.torvald.terrarum.savegame.VDUtil
import net.torvald.terrarum.savegame.VirtualDisk import net.torvald.terrarum.savegame.VirtualDisk
import net.torvald.terrarum.serialise.Common import net.torvald.terrarum.serialise.Common
@@ -62,6 +65,7 @@ import org.khelekore.prtree.PRTree
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.logging.Level import java.util.logging.Level
import kotlin.experimental.and
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.min import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
@@ -363,6 +367,9 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
// Terrarum.itemCodex.loadFromSave(codices.item) // Terrarum.itemCodex.loadFromSave(codices.item)
// Terrarum.apocryphas = HashMap(codices.apocryphas) // Terrarum.apocryphas = HashMap(codices.apocryphas)
// feed info to the worldgen
Worldgen.attachMap(world, WorldgenParams(world.generatorSeed))
} }
} }
@@ -898,6 +905,8 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
App.audioMixer.convolveBusCave.volume = 0.0 App.audioMixer.convolveBusCave.volume = 0.0
} }
actorNowPlaying?.let { if (WORLD_UPDATE_TIMER % 4 == 1) updateWorldGenerator(actorNowPlaying!!) }
WORLD_UPDATE_TIMER += 1 WORLD_UPDATE_TIMER += 1
@@ -1062,6 +1071,24 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
} }
} }
private fun Point2iMod(x: Int, y: Int) = Point2i(x fmod world.width, y)
private fun updateWorldGenerator(actor: ActorWithBody) {
val pcx = (actor.intTilewiseHitbox.canonicalX.toInt() fmod world.width) / CHUNK_W
val pcy = (actor.intTilewiseHitbox.canonicalY.toInt() fmod world.width) / CHUNK_H
listOf(
Point2iMod(pcx - 1, pcy - 2), Point2iMod(pcx, pcy - 2), Point2iMod(pcx + 1, pcy - 2),
Point2iMod(pcx - 2, pcy - 1), Point2iMod(pcx - 1, pcy - 1), Point2iMod(pcx, pcy - 1), Point2iMod(pcx + 1, pcy - 1), Point2iMod(pcx + 2, pcy - 1),
Point2iMod(pcx - 2, pcy), Point2iMod(pcx - 1, pcy), Point2iMod(pcx + 1, pcy), Point2iMod(pcx + 2, pcy),
Point2iMod(pcx - 2, pcy + 1), Point2iMod(pcx - 1, pcy + 1), Point2iMod(pcx, pcy + 1), Point2iMod(pcx + 1, pcy + 1), Point2iMod(pcx + 2, pcy + 1),
Point2iMod(pcx - 1, pcy + 2), Point2iMod(pcx, pcy + 2), Point2iMod(pcx + 1, pcy + 2),
).filter { it.y in 0 until world.height }.filter { (cx, cy) ->
world.chunkFlags[cy][cx].and(0x7F) == 0.toByte()
}.forEach { (cx, cy) ->
Worldgen.generateChunkIngame(cx, cy) { cx, cy -> }
}
}
private var worldTransitionOngoing = false private var worldTransitionOngoing = false
private var worldTransitionPauseRequested = -1 private var worldTransitionPauseRequested = -1
private var saveRequested2 = false private var saveRequested2 = false

View File

@@ -3,6 +3,7 @@ package net.torvald.terrarum.modulebasegame.serialise
import net.torvald.gdx.graphics.PixmapIO2 import net.torvald.gdx.graphics.PixmapIO2
import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.TerrarumAppConfiguration import net.torvald.terrarum.TerrarumAppConfiguration
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
@@ -15,6 +16,7 @@ import net.torvald.terrarum.toInt
import net.torvald.terrarum.utils.PlayerLastStatus import net.torvald.terrarum.utils.PlayerLastStatus
import java.io.File import java.io.File
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import kotlin.experimental.and
/** /**
* Created by minjaesong on 2021-09-29. * Created by minjaesong on 2021-09-29.
@@ -111,17 +113,20 @@ class QuickSingleplayerWorldSavingThread(
printdbg(this, "Writing chunks... $chunksWrote/$chunkCount (chunk# $chunkNumber at layer# $layerNum)") printdbg(this, "Writing chunks... $chunksWrote/$chunkCount (chunk# $chunkNumber at layer# $layerNum)")
val chunkXY = LandUtil.chunkNumToChunkXY(ingame.world, chunkNumber) val (cx, cy) = LandUtil.chunkNumToChunkXY(ingame.world, chunkNumber)
val chunkFlag = ingame.world.chunkFlags[cy][cx]
// println("Chunk xy from number $chunkNumber -> (${chunkXY.x}, ${chunkXY.y})") // println("Chunk xy from number $chunkNumber -> (${chunkXY.x}, ${chunkXY.y})")
val chunkBytes = WriteWorld.encodeChunk(layer, chunkXY.x, chunkXY.y) if (chunkFlag and 0x7F == GameWorld.CHUNK_LOADED) {
val entryID = 0x1_0000_0000L or layerNum.toLong().shl(24) or chunkNumber.toLong() val chunkBytes = WriteWorld.encodeChunk(layer, cx, cy)
val entryID = 0x1_0000_0000L or layerNum.toLong().shl(24) or chunkNumber.toLong()
val entryContent = EntryFile(chunkBytes) val entryContent = EntryFile(chunkBytes)
val entry = DiskEntry(entryID, ROOT, creation_t, time_t, entryContent) val entry = DiskEntry(entryID, ROOT, creation_t, time_t, entryContent)
// "W1L0-92,15" // "W1L0-92,15"
addFile(disk, entry); skimmer.appendEntry(entry) addFile(disk, entry); skimmer.appendEntry(entry)
}
WriteSavegame.saveProgress += chunkProgressMultiplier WriteSavegame.saveProgress += chunkProgressMultiplier
chunksWrote += 1 chunksWrote += 1

View File

@@ -2,6 +2,7 @@ package net.torvald.terrarum.modulebasegame.serialise
import net.torvald.gdx.graphics.PixmapIO2 import net.torvald.gdx.graphics.PixmapIO2
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.gameworld.GameWorld.Companion.CHUNK_LOADED
import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase
@@ -12,6 +13,7 @@ import net.torvald.terrarum.savegame.*
import net.torvald.terrarum.serialise.Common import net.torvald.terrarum.serialise.Common
import java.io.File import java.io.File
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import kotlin.experimental.and
/** /**
* Created by minjaesong on 2021-09-14. * Created by minjaesong on 2021-09-14.
@@ -129,17 +131,20 @@ class WorldSavingThread(
for (layer in layers.indices) { for (layer in layers.indices) {
for (cx in 0 until cw) { for (cx in 0 until cw) {
for (cy in 0 until ch) { for (cy in 0 until ch) {
val chunkFlag = ingame.world.chunkFlags[cy][cx]
val chunkNumber = LandUtil.chunkXYtoChunkNum(ingame.world, cx, cy).toLong() val chunkNumber = LandUtil.chunkXYtoChunkNum(ingame.world, cx, cy).toLong()
if (chunkFlag and 0x7F == CHUNK_LOADED) {
// Echo("Writing chunks... ${(cw*ch*layer) + chunkNumber + 1}/${cw*ch*layers.size}") // Echo("Writing chunks... ${(cw*ch*layer) + chunkNumber + 1}/${cw*ch*layers.size}")
val chunkBytes = WriteWorld.encodeChunk(layers[layer]!!, cx, cy) val chunkBytes = WriteWorld.encodeChunk(layers[layer]!!, cx, cy)
val entryID = 0x1_0000_0000L or layer.toLong().shl(24) or chunkNumber val entryID = 0x1_0000_0000L or layer.toLong().shl(24) or chunkNumber
val entryContent = EntryFile(chunkBytes) val entryContent = EntryFile(chunkBytes)
val entry = DiskEntry(entryID, VDFileID.ROOT, creation_t, time_t, entryContent) val entry = DiskEntry(entryID, VDFileID.ROOT, creation_t, time_t, entryContent)
// "W1L0-92,15" // "W1L0-92,15"
addFile(disk, entry) addFile(disk, entry)
}
WriteSavegame.saveProgress += 1 WriteSavegame.saveProgress += 1
} }

View File

@@ -193,19 +193,21 @@ object LoadSavegame {
val cw = LandUtil.CHUNK_W val cw = LandUtil.CHUNK_W
val ch = LandUtil.CHUNK_H val ch = LandUtil.CHUNK_H
val chunkCount = world.width * world.height / (cw * ch) val chunkCount = world.width * world.height / (cw * ch)
val hasOreLayer = (newIngame.worldDisk.getFile(0x1_0000_0000L or 2L.shl(24)) != null) val worldLayer = intArrayOf(0,1,2).map { world.getLayer(it) }
val worldLayer = (if (hasOreLayer) intArrayOf(0,1,2) else intArrayOf(0,1)).map { world.getLayer(it) } for (chunk in 0L until chunkCount) {
val layerCount = worldLayer.size
for (chunk in 0L until (world.width * world.height) / (cw * ch)) {
for (layer in worldLayer.indices) { for (layer in worldLayer.indices) {
loadscreen.addMessage(Lang["MENU_IO_LOADING"]) loadscreen.addMessage(Lang["MENU_IO_LOADING"])
val chunkFile = newIngame.worldDisk.getFile(0x1_0000_0000L or layer.toLong().shl(24) or chunk)!! newIngame.worldDisk.getFile(0x1_0000_0000L or layer.toLong().shl(24) or chunk)?.let { chunkFile ->
val (cx, cy) = LandUtil.chunkNumToChunkXY(world, chunk.toInt()) val (cx, cy) = LandUtil.chunkNumToChunkXY(world, chunk.toInt())
ReadWorld.decodeChunkToLayer(chunkFile.getContent(), worldLayer[layer]!!, cx, cy) if (layer == 2) {
printdbg(this, "Loading ore layer chunk ($cx, $cy) size: ${chunkFile.getSizePure()}")
}
world.chunkFlags[cy][cx] = world.chunkFlags[cy][cx] or CHUNK_LOADED ReadWorld.decodeChunkToLayer(chunkFile.getContent(), worldLayer[layer]!!, cx, cy)
world.chunkFlags[cy][cx] = world.chunkFlags[cy][cx] or CHUNK_LOADED
}
} }
loadscreen.progress.getAndAdd(1) loadscreen.progress.getAndAdd(1)
} }

View File

@@ -5,7 +5,6 @@ import com.sudoplay.joise.module.*
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.LoadScreenBase import net.torvald.terrarum.LoadScreenBase
import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarum.gameworld.BlockAddress import net.torvald.terrarum.gameworld.BlockAddress
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
@@ -28,9 +27,11 @@ class Biomegen(world: GameWorld, isFinal: Boolean, seed: Long, params: Any, val
private lateinit var THISWORLD_SANDSTONE: ItemID private lateinit var THISWORLD_SANDSTONE: ItemID
override fun getDone(loadscreen: LoadScreenBase) { override fun getDone(loadscreen: LoadScreenBase?) {
loadscreen.stageValue += 1 loadscreen?.let {
loadscreen.progress.set(0L) it.stageValue += 1
it.progress.set(0L)
}
val SAND_RND = (seed shake "SANDYCOLOURS").ushr(7).xor(seed and 255L).and(255L).toInt() val SAND_RND = (seed shake "SANDYCOLOURS").ushr(7).xor(seed and 255L).and(255L).toInt()

View File

@@ -2,11 +2,9 @@ package net.torvald.terrarum.modulebasegame.worldgenerator
import com.sudoplay.joise.Joise import com.sudoplay.joise.Joise
import com.sudoplay.joise.module.* import com.sudoplay.joise.module.*
import net.torvald.random.XXHash32
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.LoadScreenBase import net.torvald.terrarum.LoadScreenBase
import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.realestate.LandUtil.CHUNK_H import net.torvald.terrarum.realestate.LandUtil.CHUNK_H
import net.torvald.terrarum.realestate.LandUtil.CHUNK_W import net.torvald.terrarum.realestate.LandUtil.CHUNK_W
@@ -23,9 +21,11 @@ class Cavegen(world: GameWorld, isFinal: Boolean, val highlandLowlandSelectCache
const val YHEIGHT_DIVISOR = 2.0 / 7.0 const val YHEIGHT_DIVISOR = 2.0 / 7.0
} }
override fun getDone(loadscreen: LoadScreenBase) { override fun getDone(loadscreen: LoadScreenBase?) {
loadscreen.stageValue += 1 loadscreen?.let {
loadscreen.progress.set(0L) it.stageValue += 1
it.progress.set(0L)
}
Worldgen.threadExecutor.renew() Worldgen.threadExecutor.renew()
submitJob(loadscreen) submitJob(loadscreen)

View File

@@ -3,11 +3,9 @@ package net.torvald.terrarum.modulebasegame.worldgenerator
import com.sudoplay.joise.Joise import com.sudoplay.joise.Joise
import com.sudoplay.joise.module.* import com.sudoplay.joise.module.*
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.worldgenerator.Terragen.Companion.YHEIGHT_DIVISOR import net.torvald.terrarum.modulebasegame.worldgenerator.Terragen.Companion.YHEIGHT_DIVISOR
import net.torvald.terrarum.modulebasegame.worldgenerator.Terragen.Companion.YHEIGHT_MAGIC import net.torvald.terrarum.modulebasegame.worldgenerator.Terragen.Companion.YHEIGHT_MAGIC
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.realestate.LandUtil.CHUNK_H import net.torvald.terrarum.realestate.LandUtil.CHUNK_H
import net.torvald.terrarum.realestate.LandUtil.CHUNK_W import net.torvald.terrarum.realestate.LandUtil.CHUNK_W
import kotlin.math.cos import kotlin.math.cos
@@ -18,9 +16,11 @@ import kotlin.math.sqrt
* Created by minjaesong on 2023-10-25. * Created by minjaesong on 2023-10-25.
*/ */
class Oregen(world: GameWorld, isFinal: Boolean, private val caveAttenuateBiasScaledCache: ModuleCache, seed: Long, private val ores: List<OregenParams>) : Gen(world, isFinal, seed) { class Oregen(world: GameWorld, isFinal: Boolean, private val caveAttenuateBiasScaledCache: ModuleCache, seed: Long, private val ores: List<OregenParams>) : Gen(world, isFinal, seed) {
override fun getDone(loadscreen: LoadScreenBase) { override fun getDone(loadscreen: LoadScreenBase?) {
loadscreen.stageValue += 1 loadscreen?.let {
loadscreen.progress.set(0L) it.stageValue += 1
it.progress.set(0L)
}
Worldgen.threadExecutor.renew() Worldgen.threadExecutor.renew()
submitJob(loadscreen) submitJob(loadscreen)

View File

@@ -4,12 +4,10 @@ import com.sudoplay.joise.Joise
import net.torvald.random.XXHash64 import net.torvald.random.XXHash64
import net.torvald.terrarum.LoadScreenBase import net.torvald.terrarum.LoadScreenBase
import net.torvald.terrarum.Point2i import net.torvald.terrarum.Point2i
import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarum.gameitems.isOre import net.torvald.terrarum.gameitems.isOre
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.fmod import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.realestate.LandUtil.CHUNK_H import net.torvald.terrarum.realestate.LandUtil.CHUNK_H
import net.torvald.terrarum.realestate.LandUtil.CHUNK_W import net.torvald.terrarum.realestate.LandUtil.CHUNK_W
@@ -24,7 +22,7 @@ import kotlin.math.max
*/ */
class OregenAutotiling(world: GameWorld, isFinal: Boolean, seed: Long, val tilingModes: HashMap<ItemID, String>) : Gen(world, isFinal, seed) { class OregenAutotiling(world: GameWorld, isFinal: Boolean, seed: Long, val tilingModes: HashMap<ItemID, String>) : Gen(world, isFinal, seed) {
override fun getDone(loadscreen: LoadScreenBase) { override fun getDone(loadscreen: LoadScreenBase?) {
Worldgen.threadExecutor.renew() Worldgen.threadExecutor.renew()
submitJob(loadscreen) submitJob(loadscreen)
Worldgen.threadExecutor.join() Worldgen.threadExecutor.join()

View File

@@ -29,9 +29,11 @@ class Terragen(world: GameWorld, isFinal: Boolean , val highlandLowlandSelectCac
private val dirtStoneDitherSize = 3 // actual dither size will be double of this value private val dirtStoneDitherSize = 3 // actual dither size will be double of this value
private val stoneSlateDitherSize = 4 private val stoneSlateDitherSize = 4
override fun getDone(loadscreen: LoadScreenBase) { override fun getDone(loadscreen: LoadScreenBase?) {
loadscreen.stageValue += 1 loadscreen?.let {
loadscreen.progress.set(0L) it.stageValue += 1
it.progress.set(0L)
}
Worldgen.threadExecutor.renew() Worldgen.threadExecutor.renew()
submitJob(loadscreen) submitJob(loadscreen)
@@ -98,7 +100,7 @@ class Terragen(world: GameWorld, isFinal: Boolean , val highlandLowlandSelectCac
% %
* - where the stone layer actually begins * - where the stone layer actually begins
*/ */
if (dirtStoneTransition > 0) { /*if (dirtStoneTransition > 0) {
for (pos in 0 until dirtStoneDitherSize * 2) { for (pos in 0 until dirtStoneDitherSize * 2) {
val y = pos + dirtStoneTransition - (dirtStoneDitherSize * 2) + 1 val y = pos + dirtStoneTransition - (dirtStoneDitherSize * 2) + 1
if (y >= world.height) break if (y >= world.height) break
@@ -116,14 +118,14 @@ class Terragen(world: GameWorld, isFinal: Boolean , val highlandLowlandSelectCac
world.setTileWall(x, y, newTile, true) world.setTileWall(x, y, newTile, true)
} }
} }
} }*/
/* /*
# #
# - stone-to-slate transition, height = stoneSlateDitherSize # - stone-to-slate transition, height = stoneSlateDitherSize
# #
*/ */
if (stoneSlateTransition > 0) { /*if (stoneSlateTransition > 0) {
for (pos in 0 until stoneSlateDitherSize) { for (pos in 0 until stoneSlateDitherSize) {
val y = pos + stoneSlateTransition - stoneSlateDitherSize + 1 val y = pos + stoneSlateTransition - stoneSlateDitherSize + 1
if (y >= world.height) break if (y >= world.height) break
@@ -138,7 +140,7 @@ class Terragen(world: GameWorld, isFinal: Boolean , val highlandLowlandSelectCac
world.setTileWall(x, y, newTile, true) world.setTileWall(x, y, newTile, true)
} }
} }
} }*/
} }
} }

View File

@@ -2,32 +2,28 @@ package net.torvald.terrarum.modulebasegame.worldgenerator
import com.sudoplay.joise.Joise import com.sudoplay.joise.Joise
import net.torvald.random.HQRNG import net.torvald.random.HQRNG
import net.torvald.random.XXHash32
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.concurrent.sliceEvenly
import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarum.gameworld.BlockAddress import net.torvald.terrarum.gameworld.BlockAddress
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.worldgenerator.Biomegen.Companion.BIOME_KEY_PLAINS
import net.torvald.terrarum.modulebasegame.worldgenerator.Biomegen.Companion.BIOME_KEY_SPARSE_WOODS
import net.torvald.terrarum.modulebasegame.worldgenerator.Biomegen.Companion.BIOME_KEY_WOODLANDS
import net.torvald.terrarum.modulebasegame.worldgenerator.Terragen.Companion.YHEIGHT_DIVISOR import net.torvald.terrarum.modulebasegame.worldgenerator.Terragen.Companion.YHEIGHT_DIVISOR
import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.realestate.LandUtil.CHUNK_H
import net.torvald.terrarum.realestate.LandUtil.CHUNK_W
import net.torvald.terrarum.serialise.toUint import net.torvald.terrarum.serialise.toUint
import kotlin.math.absoluteValue
/** /**
* Created by minjaesong on 2023-11-10. * Created by minjaesong on 2023-11-10.
*/ */
class Treegen(world: GameWorld, isFinal: Boolean, seed: Long, val terragenParams: TerragenParams, params: TreegenParams, val biomeMap: HashMap<BlockAddress, Byte>) : Gen(world, isFinal, seed, params) { class Treegen(world: GameWorld, isFinal: Boolean, seed: Long, val terragenParams: TerragenParams, params: TreegenParams, val biomeMap: HashMap<BlockAddress, Byte>) : Gen(world, isFinal, seed, params) {
override fun getDone(loadscreen: LoadScreenBase) { override fun getDone(loadscreen: LoadScreenBase?) {
loadscreen.stageValue += 1 loadscreen?.let {
loadscreen.progress.set(0L) it.stageValue += 1
it.progress.set(0L)
}
Worldgen.threadExecutor.renew() Worldgen.threadExecutor.renew()
submitJob(loadscreen) submitJob(loadscreen)
@@ -40,7 +36,7 @@ class Treegen(world: GameWorld, isFinal: Boolean, seed: Long, val terragenParams
override fun draw(xStart: Int, yStart: Int, noises: List<Joise>, soff: Double) { override fun draw(xStart: Int, yStart: Int, noises: List<Joise>, soff: Double) {
for (i in 0 until 10) { for (i in 0 until 10) {
val xs = (xStart + 9*i) until (xStart + 9*i) + 9 val xs = (xStart + 9*i) until (xStart + 9*i) + 9
tryToPlant(xs, makeGrassMap(xs), HQRNG(seed shake xs.last.toLong())) tryToPlant(xs, 986578287, makeGrassMap(xs), HQRNG(seed shake xs.last.toLong()))
} }
} }
@@ -74,9 +70,19 @@ class Treegen(world: GameWorld, isFinal: Boolean, seed: Long, val terragenParams
return r return r
} }
private val treePlot1 = arrayOf(2, 3) private val treePlot1 = intArrayOf(2, 3)
private val treePlot2 = arrayOf(6, 7) private val treePlot2 = intArrayOf(6, 7)
private val treePlotM = arrayOf(4, 5) private val treePlotM = intArrayOf(4, 5)
private fun IntArray.takeRand(x: Int, y: Int, h: Int): Int {
val r = ((XXHash32.hashGeoCoord(x, y) * 31 + h) and 0xFFFFFF) / 16777216f
return this[(r * this.size).toInt()]
}
private fun List<Int>.takeRand(x: Int, y: Int, h: Int): Int {
val r = ((XXHash32.hashGeoCoord(x, y) * 31 + h) and 0xFFFFFF) / 16777216f
return this[(r * this.size).toInt()]
}
private fun Double.toDitherredInt(rng: HQRNG): Int { private fun Double.toDitherredInt(rng: HQRNG): Int {
val ibase = this.floorToInt() val ibase = this.floorToInt()
@@ -84,7 +90,7 @@ class Treegen(world: GameWorld, isFinal: Boolean, seed: Long, val terragenParams
return if (rng.nextDouble() < 1.0 - thre) ibase else ibase + 1 return if (rng.nextDouble() < 1.0 - thre) ibase else ibase + 1
} }
private fun tryToPlant(xs: IntProgression, grassMap: Array<List<Int>>, rng: HQRNG) { private fun tryToPlant(xs: IntProgression, ys: Int, grassMap: Array<List<Int>>, rng: HQRNG) {
val treeSpecies = 0 val treeSpecies = 0
@@ -130,28 +136,28 @@ class Treegen(world: GameWorld, isFinal: Boolean, seed: Long, val terragenParams
when (treeToSpawn.size) { when (treeToSpawn.size) {
2 -> { 2 -> {
val plot1 = if (treeToSpawn[0] < 3) treePlot1.random() else treePlot1[0] val plot1 = if (treeToSpawn[0] < 3) treePlot1.takeRand(xs.first, ys, 123) else treePlot1[0]
val plot2 = if (treeToSpawn[1] < 3) treePlot2.random() else treePlot2[0] val plot2 = if (treeToSpawn[1] < 3) treePlot2.takeRand(xs.first, ys, 456) else treePlot2[0]
// if there is no grass, grassMap[x] is an empty list // if there is no grass, grassMap[x] is an empty list
if (treeToSpawn[0] != 0) { if (treeToSpawn[0] != 0) {
grassMap[plot1].let { if (it.isEmpty()) null else it.random() }?.let { grassMap[plot1].let { if (it.isEmpty()) null else it.takeRand(xs.first + plot1, ys, 1234) }?.let {
plantTree(xs.first + plot1, it, treeSpecies, 1, rng) // TODO use treeSize from the treeToSpawn plantTree(xs.first + plot1, it, treeSpecies, 1, rng) // TODO use treeSize from the treeToSpawn
} }
} }
if (treeToSpawn[1] != 0) { if (treeToSpawn[1] != 0) {
grassMap[plot2].let { if (it.isEmpty()) null else it.random() }?.let { grassMap[plot2].let { if (it.isEmpty()) null else it.takeRand(xs.first + plot2, ys, 2345) }?.let {
plantTree(xs.first + plot2, it, treeSpecies, 1, rng) // TODO use treeSize from the treeToSpawn plantTree(xs.first + plot2, it, treeSpecies, 1, rng) // TODO use treeSize from the treeToSpawn
} }
} }
} }
1 -> { 1 -> {
val plot1 = if (treeToSpawn[0] < 3) treePlotM.random() else treePlotM[0] val plot1 = if (treeToSpawn[0] < 3) treePlotM.takeRand(xs.first, ys, 3456) else treePlotM[0]
// if there is no grass, grassMap[x] is an empty list // if there is no grass, grassMap[x] is an empty list
if (treeToSpawn[0] != 0) { if (treeToSpawn[0] != 0) {
val treeSize = arrayOf(null, 0, 1, 2)[treeToSpawn[0]] val treeSize = arrayOf(null, 0, 1, 2)[treeToSpawn[0]]
grassMap[plot1].let { if (it.isEmpty()) null else it.random() }?.let { grassMap[plot1].let { if (it.isEmpty()) null else it.takeRand(xs.first + plot1, ys, 4567) }?.let {
plantTree(xs.first + plot1, it, treeSpecies, treeSize!!, rng) plantTree(xs.first + plot1, it, treeSpecies, treeSize!!, rng)
} }
} }
@@ -367,7 +373,9 @@ class Treegen(world: GameWorld, isFinal: Boolean, seed: Long, val terragenParams
val xEnd = xStart + width val xEnd = xStart + width
var xStart2 = xStart var xStart2 = xStart
var xEnd2 = xEnd var xEnd2 = xEnd
Math.random().let {
val r = (XXHash32.hashGeoCoord(x, y) * width * height + growCnt).and(0xffffff) / 16777216f
r.let {
if (it < 0.25) xStart2 += 1 if (it < 0.25) xStart2 += 1
else if (it < 0.5) xEnd2 -= 1 else if (it < 0.5) xEnd2 -= 1
} }

View File

@@ -34,6 +34,10 @@ object Worldgen {
fun attachMap(world: GameWorld, genParams: WorldgenParams) { fun attachMap(world: GameWorld, genParams: WorldgenParams) {
this.world = world this.world = world
params = genParams params = genParams
highlandLowlandSelectCache = getHighlandLowlandSelectCache(params.terragenParams, params.seed)
caveAttenuateBiasScaledCache = getCaveAttenuateBiasScaled(highlandLowlandSelectCache, params.terragenParams)
biomeMap = HashMap()
} }
internal lateinit var highlandLowlandSelectCache: ModuleCache internal lateinit var highlandLowlandSelectCache: ModuleCache
@@ -74,11 +78,6 @@ object Worldgen {
} }
fun generateMap(loadscreen: LoadScreenBase) { fun generateMap(loadscreen: LoadScreenBase) {
highlandLowlandSelectCache = getHighlandLowlandSelectCache(params.terragenParams, params.seed)
caveAttenuateBiasScaledCache = getCaveAttenuateBiasScaled(highlandLowlandSelectCache, params.terragenParams)
biomeMap = HashMap()
val jobs = getJobs() val jobs = getJobs()
@@ -101,6 +100,28 @@ object Worldgen {
} }
/**
* Chunk flags will be set automatically
*/
fun generateChunkIngame(cx: Int, cy: Int, callback: (Int, Int) -> Unit) {
val jobs = getJobs()
printdbg(this, "Generating chunk on ($cx, $cy)")
Thread {
world.chunkFlags[cy][cx] = GameWorld.CHUNK_GENERATING
for (i in jobs.indices) {
val it = jobs[i]
it.theWork.getChunkDone(cx, cy)
}
world.chunkFlags[cy][cx] = GameWorld.CHUNK_LOADED
callback(cx, cy)
}.let {
it.priority = 2
it.start()
}
}
data class Work(val loadingScreenName: String, val theWork: Gen, val tags: List<String>) data class Work(val loadingScreenName: String, val theWork: Gen, val tags: List<String>)
fun getEstimationSec(width: Int, height: Int): Long { fun getEstimationSec(width: Int, height: Int): Long {
@@ -122,6 +143,7 @@ object Worldgen {
val yInit = getChunkGenStrip(world).first val yInit = getChunkGenStrip(world).first
val tallies = ArrayList<Pair<Point2i, Vector2f>>() // xypos, score (0..1+) val tallies = ArrayList<Pair<Point2i, Vector2f>>() // xypos, score (0..1+)
var tries = 0 var tries = 0
var found = false
while (tries < 99) { while (tries < 99) {
val posX = (Math.random() * world.width).toInt() val posX = (Math.random() * world.width).toInt()
var posY = yInit * CHUNK_H var posY = yInit * CHUNK_H
@@ -166,12 +188,23 @@ object Worldgen {
printdbg(this, "...Survey says: $rocks/$rockScoreMin rocks, $trees/$treeScoreMin trees") printdbg(this, "...Survey says: $rocks/$rockScoreMin rocks, $trees/$treeScoreMin trees")
if (score.x >= 1f && score.y >= 1f) break if (score.x >= 1f && score.y >= 1f) {
found = true
break
}
tries += 1 tries += 1
} }
if (found)
return tallies.last().first
return tallies.toTypedArray().also { return tallies.toTypedArray().also {
it.map { it.second.let {
it.x = (it.x).coerceAtMost(1f)
it.y = (it.y).coerceAtMost(1f)
} }
it.shuffle() it.shuffle()
it.sortByDescending { it.second.lengthSquared() } it.sortByDescending { it.second.lengthSquared() }
@@ -342,30 +375,36 @@ object Worldgen {
abstract class Gen(val world: GameWorld, val isFinal: Boolean, val seed: Long, val params: Any? = null) { abstract class Gen(val world: GameWorld, val isFinal: Boolean, val seed: Long, val params: Any? = null) {
open fun getDone(loadscreen: LoadScreenBase) { } // trying to use different name so that it won't be confused with Runnable or Callable open fun getDone(loadscreen: LoadScreenBase?) { } // trying to use different name so that it won't be confused with Runnable or Callable
protected abstract fun getGenerator(seed: Long, params: Any?): List<Joise> protected abstract fun getGenerator(seed: Long, params: Any?): List<Joise>
protected abstract fun draw(xStart: Int, yStart: Int, noises: List<Joise>, soff: Double) protected abstract fun draw(xStart: Int, yStart: Int, noises: List<Joise>, soff: Double)
protected open fun getChunksRange(): List<Int> { private fun getChunksRange(): List<Int> {
val (yStart, yEnd) = Worldgen.getChunkGenStrip(world) val (yStart, yEnd) = Worldgen.getChunkGenStrip(world)
return (0 until world.width / CHUNK_W).flatMap { cx -> return (0 until world.width / CHUNK_W).flatMap { cx ->
(LandUtil.chunkXYtoChunkNum(world, cx, yStart)..LandUtil.chunkXYtoChunkNum(world, cx, yEnd)).toList() (LandUtil.chunkXYtoChunkNum(world, cx, yStart)..LandUtil.chunkXYtoChunkNum(world, cx, yEnd)).toList()
} }
} }
open fun submitJob(loadscreen: LoadScreenBase) { fun submitJob(loadscreen: LoadScreenBase?) {
getChunksRange().forEach { chunkNum -> getChunksRange().forEach { chunkNum ->
val (chunkX, chunkY) = LandUtil.chunkNumToChunkXY(world, chunkNum) val (chunkX, chunkY) = LandUtil.chunkNumToChunkXY(world, chunkNum)
Worldgen.threadExecutor.submit { Worldgen.threadExecutor.submit {
val localJoise = getGenerator(seed, params) val localJoise = getGenerator(seed, params)
val sampleOffset = world.width / 8.0 val sampleOffset = world.width / 8.0
draw(chunkX * LandUtil.CHUNK_W, chunkY * CHUNK_H, localJoise, sampleOffset) draw(chunkX * LandUtil.CHUNK_W, chunkY * CHUNK_H, localJoise, sampleOffset)
loadscreen.progress.addAndGet(1L) loadscreen?.progress?.addAndGet(1L)
world.chunkFlags[chunkY][chunkX] = if (isFinal) GameWorld.CHUNK_LOADED else GameWorld.CHUNK_GENERATING world.chunkFlags[chunkY][chunkX] = if (isFinal) GameWorld.CHUNK_LOADED else GameWorld.CHUNK_GENERATING
} }
} }
} }
fun getChunkDone(chunkX: Int, chunkY: Int) {
val localJoise = getGenerator(seed, params)
val sampleOffset = world.width / 8.0
draw(chunkX * LandUtil.CHUNK_W, chunkY * CHUNK_H, localJoise, sampleOffset)
}
} }
data class WorldgenParams( data class WorldgenParams(

View File

@@ -162,20 +162,22 @@ class BasicDebugInfoWindow : UICanvas() {
val xo = 224 val xo = 224
val yo = 78 val yo = 78
try {
world?.let { world ->
val ppos = ingame?.actorNowPlaying?.centrePosVector
val pcx = (ppos?.x?.div(TILE_SIZED)?.fmod(world.width.toDouble())?.div(CHUNK_W)?.toInt() ?: -999)
val pcy = (ppos?.y?.div(TILE_SIZED)?.fmod(world.height.toDouble())?.div(CHUNK_H)?.toInt() ?: -999)
world?.let { world -> for (y in 0 until world.height / CHUNK_H) {
val ppos = ingame?.actorNowPlaying?.centrePosVector for (x in 0 until world.width / CHUNK_W) {
val pcx = (ppos?.x?.div(TILE_SIZED)?.fmod(world.width.toDouble())?.div(CHUNK_W)?.toInt() ?: -999) val chunkStat = world.chunkFlags[y][x].toUint()
val pcy = (ppos?.y?.div(TILE_SIZED)?.fmod(world.height.toDouble())?.div(CHUNK_H)?.toInt() ?: -999) batch.color = if (pcx == x && pcy == y) chunkStatCurrentChunk else chunkStatColours[chunkStat]
Toolkit.fillArea(batch, xo + 3 * x, yo + 3 * y, 2, 2)
for (y in 0 until world.height / CHUNK_H) { }
for (x in 0 until world.width / CHUNK_W) {
val chunkStat = world.chunkFlags[y][x].toUint()
batch.color = if (pcx == x && pcy == y) chunkStatCurrentChunk else chunkStatColours[chunkStat]
Toolkit.fillArea(batch, xo + 3*x, yo + 3*y, 2, 2)
} }
} }
} }
catch (_: UninitializedPropertyAccessException) {}
} }