DEADSOFTWARE

Prettier world + ores
[cavedroid.git] / core / src / ru / deadsoftware / cavedroid / game / world / GameWorldGenerator.kt
index bf37f454c3ad36851a40062e5d74b2ffad7cdfde..e00a0ea4dd2cd3397b632650b69b635cdc1ec24f 100644 (file)
 package ru.deadsoftware.cavedroid.game.world
 
-import com.badlogic.gdx.utils.TimeUtils
-import ru.deadsoftware.cavedroid.game.GameItems
+import com.google.common.primitives.Ints.min
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.world.Biome
+import ru.deadsoftware.cavedroid.game.model.world.generator.WorldGeneratorConfig
 import kotlin.math.abs
+import kotlin.math.max
 import kotlin.random.Random
 
-object GameWorldGenerator {
+class GameWorldGenerator(
+    private val config: WorldGeneratorConfig,
+    private val gameItemsHolder: GameItemsHolder,
+) {
 
-    private const val BIOME_MIN_SIZE = 64
+    private val random = Random(config.seed)
 
-    private enum class Biome {
-        PLAINS,
-        DESERT
-    }
+    private val foreMap by lazy { Array(config.width) { Array(config.height) { gameItemsHolder.fallbackBlock } } }
+    private val backMap by lazy { Array(config.width) { Array(config.height) { gameItemsHolder.fallbackBlock } } }
+
+    private val heights by lazy { generateHeights() }
+    private val biomesMap by lazy { generateBiomes() }
+
+    private val plainsPlants = listOf("dandelion", "rose", "tallgrass")
+    private val mushrooms = listOf("mushroom_brown", "mushroom_red",)
+
+    private fun generateHeights(): IntArray {
+        val surfaceHeightRange = config.minSurfaceHeight .. config.maxSurfaceHeight
+        val result = IntArray(config.width)
+
+        result[0] = surfaceHeightRange.random(random)
 
-    private fun generateHeights(width: Int, min: Int, max: Int, random: Random) = IntArray(width).apply {
-        set(0, (min + max) / 2)
-        for (x in 1 until width) {
-            val previous = get(x - 1)
+        for (x in 1 ..< config.width) {
+            val previous = result[x - 1]
             var d = random.nextInt(-5, 6).let { if (it !in -4..4) it / abs(it) else 0 }
 
-            if (previous + d !in min..max) { d = -d }
-            if (lastIndex - x < abs(get(0) - previous) * 3) {
-                d = get(0).compareTo(previous).let { if (it != 0) it / abs(it) else 0 }
+            if (previous + d !in surfaceHeightRange) { d = -d }
+
+            if (result.lastIndex - x < abs(result[0] - previous) * 3) {
+                d = result[0].compareTo(previous).let { if (it != 0) it / abs(it) else 0 }
             }
 
-            set(x, get(x - 1) + d)
+            result[x] = result[x - 1] + d
         }
+
+        return result
     }
 
-    private fun generateBiomes(width: Int, random: Random) = buildMap<Int, Biome> {
+    private fun generateBiomes(): Map<Int, Biome> {
         val xSequence = sequence {
             var lastX = 0
             var count = 0
 
-            while (lastX < width - BIOME_MIN_SIZE - 1) {
+            while (lastX < config.width - config.minBiomeSize - 1) {
                 yield(lastX)
 
-                lastX = random.nextInt(lastX + BIOME_MIN_SIZE, width)
+                lastX = random.nextInt(lastX + config.minBiomeSize, config.width)
                 count++
             }
         }
 
-        return xSequence.associateWith { Biome.values()[random.nextInt(Biome.values().size)] }
+        return xSequence.associateWith { config.biomes.random(random) }
     }
 
-    private fun plainsBiome(
-        foreMap: Array<IntArray>,
-        backMap: Array<IntArray>,
-        width: Int,
-        height: Int,
-        x: Int,
-        xHeight: Int,
-        random: Random,
-    ) {
-        foreMap[x][xHeight] = GameItems.getBlockId("grass")
-        foreMap[x][height - 1] = GameItems.getBlockId("bedrock")
-        backMap[x][xHeight] = GameItems.getBlockId("grass")
-        backMap[x][height - 1] = GameItems.getBlockId("bedrock")
-
-        for (y in xHeight + 1 until height - 1) {
+    private fun winterBiome(x: Int) {
+        assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+        val surfaceHeight = heights[x]
+
+        val grass = gameItemsHolder.getBlock("grass_snowed")
+        val bedrock = gameItemsHolder.getBlock("bedrock")
+        val dirt = gameItemsHolder.getBlock("dirt")
+        val stone = gameItemsHolder.getBlock("stone")
+
+        foreMap[x][surfaceHeight] = grass
+        foreMap[x][config.height - 1] = bedrock
+        backMap[x][surfaceHeight] = grass
+        backMap[x][config.height - 1] = bedrock
+
+        for (y in min(surfaceHeight + 1, config.seaLevel) ..< config.height - 1) {
+            if (y <= surfaceHeight) {
+                backMap[x][y] = dirt
+                continue
+            }
+
             foreMap[x][y] = when {
-                y < xHeight + random.nextInt(5, 8) -> GameItems.getBlockId("dirt")
-                else -> GameItems.getBlockId("stone")
+                y < surfaceHeight + random.nextInt(5, 8) -> dirt
+                else -> stone
             }
             backMap[x][y] = foreMap[x][y]
         }
     }
 
-    private fun desertBiome(
-        foreMap: Array<IntArray>,
-        backMap: Array<IntArray>,
-        width: Int,
-        height: Int,
-        x: Int,
-        xHeight: Int,
-        random: Random,
-    ) {
-        foreMap[x][xHeight] = GameItems.getBlockId("sand")
-        foreMap[x][height - 1] = GameItems.getBlockId("bedrock")
-        backMap[x][xHeight] = GameItems.getBlockId("sand")
-        backMap[x][height - 1] = GameItems.getBlockId("bedrock")
-
-        for (y in xHeight + 1 until height - 1) {
+    private fun plainsBiome(x: Int) {
+        assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+        val surfaceHeight = heights[x]
+
+        val grass = gameItemsHolder.getBlock("grass")
+        val bedrock = gameItemsHolder.getBlock("bedrock")
+        val dirt = gameItemsHolder.getBlock("dirt")
+        val stone = gameItemsHolder.getBlock("stone")
+
+        foreMap[x][surfaceHeight] = grass
+        foreMap[x][config.height - 1] = bedrock
+        backMap[x][surfaceHeight] = grass
+        backMap[x][config.height - 1] = bedrock
+
+        for (y in min(surfaceHeight + 1, config.seaLevel) ..< config.height - 1) {
+            if (y <= surfaceHeight) {
+                backMap[x][y] = dirt
+                continue
+            }
+
             foreMap[x][y] = when {
-                y < xHeight + random.nextInt(5, 8) -> GameItems.getBlockId("sand")
-                else -> GameItems.getBlockId("stone")
+                y < surfaceHeight + random.nextInt(5, 8) -> dirt
+                else -> stone
             }
             backMap[x][y] = foreMap[x][y]
         }
+
+        val plant = random.nextInt(100)
+        if (surfaceHeight < config.seaLevel) {
+            if (plant < 10) {
+                generateOak(x)
+            } else if (plant < 40) {
+                generateTallGrass(x)
+            }
+        }
     }
 
-    private fun fillWater(foreMap: Array<IntArray>, width: Int, height: Int, waterLevel: Int) {
-        for (x in 0 until width) {
-            for (y in waterLevel until height) {
-                if (foreMap[x][y] != 0) {
+    private fun desertBiome(x: Int) {
+        assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+        val surfaceHeight = heights[x]
+
+        val sand = gameItemsHolder.getBlock("sand")
+        val bedrock = gameItemsHolder.getBlock("bedrock")
+        val sandstone = gameItemsHolder.getBlock("sandstone")
+        val stone = gameItemsHolder.getBlock("stone")
+
+
+        foreMap[x][surfaceHeight] = sand
+        foreMap[x][config.height - 1] = bedrock
+        backMap[x][surfaceHeight] = sand
+        backMap[x][config.height - 1] = bedrock
+
+        for (y in min(surfaceHeight + 1, config.seaLevel) ..< config.height - 1) {
+            if (y <= surfaceHeight) {
+                backMap[x][y] = sand
+                continue
+            }
+
+            foreMap[x][y] = when {
+                y < surfaceHeight + random.nextInt(5, 8) -> sand
+                y < surfaceHeight + random.nextInt(0, 2) -> sandstone
+                else -> stone
+            }
+            backMap[x][y] = foreMap[x][y]
+        }
+
+        val plant = random.nextInt(100)
+        if (surfaceHeight < config.seaLevel) {
+            if (plant < 5) {
+                generateCactus(x)
+            } else if (plant < 10) {
+                generateDeadBush(x)
+            }
+        }
+    }
+
+    private fun fillWater() {
+        val water = gameItemsHolder.getBlock("water")
+
+        for (x in 0 ..< config.width) {
+            for (y in config.seaLevel ..< config.height) {
+                if (foreMap[x][y] != gameItemsHolder.fallbackBlock) {
                     break
                 }
 
-                foreMap[x][y] = GameItems.getBlockId("water")
+                foreMap[x][y] = water
+            }
+        }
+    }
+
+    private fun generateCactus(x: Int) {
+        val cactus = gameItemsHolder.getBlock("cactus")
+        val cactusHeight = random.nextInt(3)
+        val h = heights[x] - 1
+
+        for (y in h downTo max(0, h - cactusHeight)) {
+            foreMap[x][y] = cactus
+        }
+    }
+
+    private fun generateOak(x: Int) {
+        val log = gameItemsHolder.getBlock("log_oak")
+        val leaves = gameItemsHolder.getBlock("leaves_oak")
+        val h = heights[x] - 1
+        val treeH = random.nextInt(5, 7)
+        val height = max(0, h - treeH)
+
+        val top = height - 1
+        if (top >= 0) {
+            foreMap[x][top] = leaves
+            backMap[x][top] = leaves
+        }
+
+        for (x1 in max(0, x - 1) .. min(config.width - 1, x + 1)) {
+            for (y in height .. height + treeH - 4) {
+                foreMap[x1][y] = leaves
+                backMap[x1][y] = leaves
+            }
+            if (random.nextInt(15) < 3) {
+                foreMap[x1][heights[x1] - 1] = gameItemsHolder.getBlock(mushrooms.random(random))
+            }
+        }
+
+        for (y in h downTo height) {
+            backMap[x][y] = log
+        }
+    }
+
+    private fun generateTallGrass(x: Int) {
+        val tallGrass = gameItemsHolder.getBlock(plainsPlants.random(random))
+        val h = heights[x] - 1
+        if (h > 0) {
+            foreMap[x][h] = tallGrass
+        }
+    }
+
+    private fun generateDeadBush(x: Int) {
+        val bush = gameItemsHolder.getBlock("deadbush")
+        val h = heights[x] - 1
+        if (h > 0) {
+            foreMap[x][h] = bush
+        }
+    }
+
+    private fun generateOres(x : Int) {
+        val stone = gameItemsHolder.getBlock("stone")
+        val coal = gameItemsHolder.getBlock("coal_ore")
+        val iron = gameItemsHolder.getBlock("iron_ore")
+        val gold = gameItemsHolder.getBlock("gold_ore")
+        val diamond = gameItemsHolder.getBlock("diamond_ore")
+        val lapis = gameItemsHolder.getBlock("lapis_ore")
+
+        for (y in heights[x] ..< config.height) {
+            val res = random.nextInt(10000)
+
+            val h = config.height - y
+            val block = when {
+                res in 0..<25 && h < 16 -> diamond
+                res in 25 ..< 50 && h < 32 -> gold
+                res in 50 ..< 250 && h < 64 -> iron
+                res in 250 ..< 450 && h < 128 -> coal
+                res in 450 ..< (450 + (25 - (abs(h - 16) * (25 / 16)))) -> lapis
+                else -> null
+            }
+
+            if (block != null && foreMap[x][y] == stone) {
+                foreMap[x][y] = block
             }
         }
     }
 
     /**
-     * Generates world of given width and height with given seed
-     * @param width world width
-     * @param height world height
-     * @param seed seed for random number generator
-     * @return pair of foreground and background layers
+     * Generate world
      */
-    fun generate(width: Int, height: Int, seed: Long = TimeUtils.millis()): Pair<Array<IntArray>, Array<IntArray>> {
-        val random = Random(seed)
-        val foreMap = Array(width) { IntArray(height) }
-        val backMap = Array(width) { IntArray(width) }
-        val heightsMap = generateHeights(width, height / 4, height * 3 / 4, random)
-        val biomesMap = generateBiomes(width, random)
-
+    fun generate(): Pair<Array<Array<Block>>, Array<Array<Block>>> {
         var biome = Biome.PLAINS
 
-        for (x in 0 until width) {
-            val xHeight = heightsMap[x]
+        for (x in 0 until config.width) {
             biome = biomesMap[x] ?: biome
 
             when (biome) {
-                Biome.PLAINS -> plainsBiome(foreMap, backMap, width, height, x, xHeight, random)
-                Biome.DESERT -> desertBiome(foreMap, backMap, width, height, x, xHeight, random)
+                Biome.PLAINS -> plainsBiome(x)
+                Biome.DESERT -> desertBiome(x)
+                Biome.WINTER -> winterBiome(x)
             }
+
+            generateOres(x)
         }
 
-        fillWater(foreMap, width, height, height / 2)
+        fillWater()
 
         return Pair(foreMap, backMap)
     }