DEADSOFTWARE

Create new game items holder
authorfredboy <fredboy@protonmail.com>
Sat, 20 Apr 2024 13:02:13 +0000 (20:02 +0700)
committerfredboy <fredboy@protonmail.com>
Sat, 20 Apr 2024 13:02:13 +0000 (20:02 +0700)
12 files changed:
build.gradle
core/build.gradle
core/src/ru/deadsoftware/cavedroid/game/GameItems.java
core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/GameProc.java
core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt
core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt

index 10228bd66a6bed664013db7868a828f298737fec..821b6e351e96248da3692ba798cbe6debba26370 100644 (file)
@@ -1,5 +1,14 @@
 buildscript {
 
+    ext {
+        appName = "CaveDroid"
+        gdxVersion = '1.12.0'
+        guavaVersion = '28.1'
+        daggerVersion = '2.51.1'
+        kotlinVersion = '1.9.23'
+        kotlinSerializationVersion = '1.6.3'
+    }
+
     repositories {
         mavenLocal()
         mavenCentral()
@@ -10,19 +19,13 @@ buildscript {
 
     dependencies {
         classpath 'com.android.tools.build:gradle:8.2.2'
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
     }
 }
 
 allprojects {
 
     version = 'alpha0.5.2'
-    ext {
-        appName = "CaveDroid"
-        gdxVersion = '1.12.0'
-        guavaVersion = '28.1'
-        daggerVersion = '2.51.1'
-    }
 
     repositories {
         mavenLocal()
@@ -67,12 +70,14 @@ project(":android") {
 project(":core") {
     apply plugin: "java-library"
 
+
     dependencies {
         api "com.badlogicgames.gdx:gdx:$gdxVersion"
         api "com.google.guava:guava:$guavaVersion-android"
         api "com.google.dagger:dagger:$daggerVersion"
         implementation 'org.jetbrains:annotations:23.1.0'
-        implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.23"
+        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+        implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion"
         annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
     }
 }
\ No newline at end of file
index d968841dbff78228ef471c0889b4b0a190d0530d..cdf2ee7e4708ffec3ab49447cc63298a1f77caab 100644 (file)
@@ -2,6 +2,7 @@ plugins {
     id "org.jetbrains.kotlin.jvm"
     id "kotlin"
     id "idea"
+    id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion"
 }
 
 sourceCompatibility = 17
index 7cf525263a52dec4c252f87d6e91129b45ada223..4024c3614c111a17c64c689ad8fc80b5a500a05e 100644 (file)
@@ -14,6 +14,7 @@ import ru.deadsoftware.cavedroid.misc.Assets;
 import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
 import ru.deadsoftware.cavedroid.misc.utils.SpriteOrigin;
 
+import java.io.FileInputStream;
 import java.util.*;
 
 public class GameItems {
@@ -177,8 +178,8 @@ public class GameItems {
                 );
 
                 Block newBlock = switch (meta) {
-                    case "water" -> new Block.Water(params, 5);
-                    case "lava" -> new Block.Lava(params, 5);
+                    case "water" -> new Block.Water(params);
+                    case "lava" -> new Block.Lava(params);
                     case "slab" -> new Block.Slab(params, fullBlock);
                     default -> new Block.Normal(params);
                 };
diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt b/core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt
new file mode 100644 (file)
index 0000000..73da0c2
--- /dev/null
@@ -0,0 +1,96 @@
+package ru.deadsoftware.cavedroid.game
+
+import com.badlogic.gdx.Gdx
+import kotlinx.serialization.json.Json
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.dto.BlockDto
+import ru.deadsoftware.cavedroid.game.model.dto.GameItemsDto
+import ru.deadsoftware.cavedroid.game.model.dto.ItemDto
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.model.mapper.BlockMapper
+import ru.deadsoftware.cavedroid.game.model.mapper.ItemMapper
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader
+import javax.inject.Inject
+
+@GameScope
+class GameItemsHolder @Inject constructor(
+    private val blockMapper: BlockMapper,
+    private val itemMapper: ItemMapper,
+) {
+
+    private var _initialized: Boolean = false
+
+    private val blocksMap = LinkedHashMap<String, Block>()
+    private val itemsMap = LinkedHashMap<String, Item>()
+
+    private lateinit var fallbackBlock: Block
+    private lateinit var fallbackItem: Item
+
+
+
+    private fun loadBlocks(dtoMap: Map<String, BlockDto>) {
+        dtoMap.forEach { (key, dto) ->
+            blocksMap[key] = blockMapper.map(key, dto)
+        }
+
+        fallbackBlock = blocksMap[FALLBACK_BLOCK_KEY]
+            ?: throw IllegalArgumentException("Fallback block key '$FALLBACK_BLOCK_KEY' not found")
+    }
+
+    private fun loadItems(dtoMap: Map<String, ItemDto>) {
+        if (dtoMap.isNotEmpty() && blocksMap.isEmpty()) {
+            throw IllegalStateException("items should be loaded after blocks")
+        }
+
+        dtoMap.forEach { (key, dto) ->
+            itemsMap[key] = itemMapper.map(key, dto, blocksMap[key])
+        }
+
+        fallbackItem = itemsMap[FALLBACK_ITEM_KEY]
+            ?: throw IllegalArgumentException("Fallback item key '$FALLBACK_ITEM_KEY' not found")
+    }
+
+    fun initialize(assetLoader: AssetLoader) {
+        if (_initialized) {
+            Gdx.app.debug(TAG, "Attempted to init when already initialized")
+            return
+        }
+
+        val jsonString = assetLoader.getAssetHandle("json/game_items.json").readString()
+        val gameItemsDto = JsonFormat.decodeFromString(GameItemsDto.GameItemsDtoJsonSerializer, jsonString)
+
+        loadBlocks(gameItemsDto.blocks)
+        loadItems(gameItemsDto.items)
+
+        _initialized = true
+    }
+
+    private fun <T> Map<String, T>.getOrFallback(key: String, fallback: T, lazyErrorMessage: () -> String): T {
+        val t = this[key] ?: run {
+            Gdx.app.error(TAG, lazyErrorMessage.invoke())
+            return fallback
+        }
+        return t
+    }
+
+    fun getBlock(key: String): Block {
+        return blocksMap.getOrFallback(key, fallbackBlock) {
+            "No block with key '$key' found. Returning $FALLBACK_BLOCK_KEY"
+        }
+    }
+
+    fun getItem(key: String): Item {
+        return itemsMap.getOrFallback(key, fallbackItem) {
+            "No item with key '$key' found. Returning $FALLBACK_BLOCK_KEY"
+        }
+    }
+
+    companion object {
+        private const val TAG = "GameItemsHolder"
+
+        private val JsonFormat = Json { ignoreUnknownKeys = true }
+
+        const val FALLBACK_BLOCK_KEY = "none"
+        const val FALLBACK_ITEM_KEY = "none"
+    }
+}
\ No newline at end of file
index 139a31ba61e8dbe98d54fb458944de45450a35f2..0c3947b499e789a144cad218de8bf294b90e0e9c 100644 (file)
@@ -5,6 +5,7 @@ import com.badlogic.gdx.utils.Timer;
 import ru.deadsoftware.cavedroid.game.mobs.MobsController;
 import ru.deadsoftware.cavedroid.game.world.GameWorldBlocksLogicControllerTask;
 import ru.deadsoftware.cavedroid.game.world.GameWorldFluidsLogicControllerTask;
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
 
 import javax.inject.Inject;
 
@@ -19,6 +20,7 @@ public class GameProc implements Disposable {
     private final MobsController mMobsController;
     private final GameWorldFluidsLogicControllerTask mGameWorldFluidsLogicControllerTask;
     private final GameWorldBlocksLogicControllerTask mGameWorldBlocksLogicControllerTask;
+    private final GameItemsHolder mGameItemsHolder;
 
     private final Timer mWorldLogicTimer = new Timer();
 
@@ -28,7 +30,9 @@ public class GameProc implements Disposable {
                     GameRenderer gameRenderer,
                     MobsController mobsController,
                     GameWorldFluidsLogicControllerTask gameWorldFluidsLogicControllerTask,
-                    GameWorldBlocksLogicControllerTask gameWorldBlocksLogicControllerTask
+                    GameWorldBlocksLogicControllerTask gameWorldBlocksLogicControllerTask,
+                    GameItemsHolder gameItemsHolder,
+                    AssetLoader assetLoader
     ) {
         mGamePhysics = gamePhysics;
         mGameInput = gameInput;
@@ -36,6 +40,9 @@ public class GameProc implements Disposable {
         mMobsController = mobsController;
         mGameWorldFluidsLogicControllerTask = gameWorldFluidsLogicControllerTask;
         mGameWorldBlocksLogicControllerTask = gameWorldBlocksLogicControllerTask;
+        mGameItemsHolder = gameItemsHolder;
+
+        mGameItemsHolder.initialize(assetLoader);
 
         mWorldLogicTimer.scheduleTask(gameWorldFluidsLogicControllerTask, 0,
                 GameWorldFluidsLogicControllerTask.FLUID_UPDATE_INTERVAL_SEC);
index 62c6f493e0f8d66221bc54d1a297dc506a28f688..45363440e290fad732955cddb78424b7c2e6c01f 100644 (file)
@@ -115,18 +115,14 @@ sealed class Block {
         val fullBlockKey: String,
     ): Block()
 
-    sealed class Fluid: Block() {
-        abstract val statesCount: Int
-    }
+    sealed class Fluid: Block()
     
     data class Water(
         override val params: CommonBlockParams,
-        override val statesCount: Int
     ) : Fluid()
 
     data class Lava(
         override val params: CommonBlockParams,
-        override val statesCount: Int
     ) : Fluid()
 
     /* Legacy accessors below */
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt
new file mode 100644 (file)
index 0000000..697a042
--- /dev/null
@@ -0,0 +1,29 @@
+package ru.deadsoftware.cavedroid.game.model.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class BlockDto(
+    @Deprecated("numeric ids will be removed") @SerialName("id") val id: Int,
+    @SerialName("left") val left: Int = 0,
+    @SerialName("top") val top: Int = 0,
+    @SerialName("right") val right: Int = 0,
+    @SerialName("bottom") val bottom: Int = 0,
+    @SerialName("sprite_left") val spriteLeft: Int = 0,
+    @SerialName("sprite_top") val spriteTop: Int = 0,
+    @SerialName("sprite_right") val spriteRight: Int = 0,
+    @SerialName("sprite_bottom") val spriteBottom: Int = 0,
+    @SerialName("hp") val hp: Int = -1,
+    @SerialName("collision") val collision: Boolean = true,
+    @SerialName("background") val background: Boolean = false,
+    @SerialName("transparent") val transparent: Boolean = false,
+    @SerialName("block_required") val blockRequired: Boolean = false,
+    @SerialName("drop") val drop: String,
+    @SerialName("meta") val meta: String? = null,
+    @SerialName("texture") val texture: String,
+    @SerialName("animated") val animated: Boolean = false,
+    @SerialName("frames") val frames: Int = 0,
+    @SerialName("drop_count") val dropCount: Int = 1,
+    @SerialName("full_block") val fullBlock: String? = null,
+)
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt
new file mode 100644 (file)
index 0000000..63229b3
--- /dev/null
@@ -0,0 +1,50 @@
+package ru.deadsoftware.cavedroid.game.model.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.*
+
+@Serializable
+data class GameItemsDto(
+    @SerialName("blocks") val blocks: Map<String, BlockDto>,
+    @SerialName("items") val items: Map<String, ItemDto>,
+) {
+    object GameItemsDtoJsonSerializer : JsonTransformingSerializer<GameItemsDto>(GameItemsDto.serializer()) {
+        private val defaultBlockValuesEqualKeyFieldNames = listOf("drop", "texture")
+        private val defaultItemValuesEqualKeyFieldNames = listOf("name", "texture")
+
+        override fun transformDeserialize(element: JsonElement): JsonElement {
+            val root = element.jsonObject
+            val blocks = root["blocks"]!!.jsonObject
+            val items = root["items"]!!.jsonObject
+
+            return buildJsonObject {
+                putJsonObject("blocks") {
+                    blocks.forEach { (key, blockObj) ->
+                        putJsonObject(key) {
+                            defaultBlockValuesEqualKeyFieldNames.forEach { fieldName ->
+                                put(fieldName, key)
+                            }
+                            blockObj.jsonObject.forEach { (prop, propValue) ->
+                                put(prop, propValue)
+                            }
+                        }
+                    }
+                }
+
+                putJsonObject("items") {
+                    items.forEach { (key, itemObj) ->
+                        putJsonObject(key) {
+                            defaultItemValuesEqualKeyFieldNames.forEach { fieldName ->
+                                put(fieldName, key)
+                            }
+                            itemObj.jsonObject.forEach { (prop, propValue) ->
+                                put(prop, propValue)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt b/core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt
new file mode 100644 (file)
index 0000000..d46eddd
--- /dev/null
@@ -0,0 +1,17 @@
+package ru.deadsoftware.cavedroid.game.model.dto
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ItemDto(
+    @Deprecated("numeric ids will be removed") @SerialName("id") val id: Int,
+    @SerialName("name") val name: String,
+    @SerialName("type") val type: String,
+    @SerialName("texture") val texture: String,
+    @SerialName("origin_x") val originX: Float = 0f,
+    @SerialName("origin_y") val origin_y: Float = 1f,
+    @SerialName("action_key") val actionKey: String? = null,
+    @SerialName("mob_damage_multiplier") val mobDamageMultiplier: Float = 1f,
+    @SerialName("block_damage_multiplier") val blockDamageMultiplier: Float = 1f,
+)
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt
new file mode 100644 (file)
index 0000000..0838491
--- /dev/null
@@ -0,0 +1,87 @@
+package ru.deadsoftware.cavedroid.game.model.mapper
+
+import com.badlogic.gdx.graphics.Texture
+import dagger.Reusable
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.block.*
+import ru.deadsoftware.cavedroid.game.model.block.Block.*
+import ru.deadsoftware.cavedroid.game.model.dto.BlockDto
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader
+import javax.inject.Inject
+
+@Reusable
+class BlockMapper @Inject constructor(
+    private val assetLoader: AssetLoader,
+) {
+
+    fun map(key: String, dto: BlockDto): Block {
+        val commonBlockParams = mapCommonParams(key, dto)
+
+        return when (dto.meta) {
+            "water" -> Water(commonBlockParams)
+            "lava" -> Lava(commonBlockParams)
+            "slab" -> Slab(commonBlockParams, requireNotNull(dto.fullBlock))
+            else -> Normal(commonBlockParams)
+        }
+    }
+
+    private fun mapCommonParams(key: String, dto: BlockDto): CommonBlockParams {
+        return CommonBlockParams(
+            id = dto.id,
+            key = key,
+            collisionMargins = BlockMargins(
+                left = dto.left,
+                top = dto.top,
+                right = dto.right,
+                bottom = dto.bottom
+            ),
+            hitPoints = dto.hp,
+            dropInfo = mapBlockDropInfo(dto),
+            hasCollision = dto.collision,
+            isBackground = dto.background,
+            isTransparent = dto.transparent,
+            requiresBlock = dto.blockRequired,
+            animationInfo = mapBlockAnimationInfo(dto),
+            texture = loadTexture(dto.texture),
+            spriteMargins = BlockMargins(
+                left = dto.spriteLeft,
+                top = dto.spriteTop,
+                right = dto.spriteRight,
+                bottom = dto.spriteBottom,
+            )
+        )
+    }
+
+    private fun mapBlockDropInfo(dto: BlockDto): BlockDropInfo? {
+        val drop = dto.drop
+        val dropCount = dto.dropCount
+
+        if (drop == GameItemsHolder.FALLBACK_ITEM_KEY || dropCount == 0) {
+            return null
+        }
+
+        return BlockDropInfo(
+            itemKey = drop,
+            count = dropCount,
+        )
+    }
+
+    private fun mapBlockAnimationInfo(dto: BlockDto): BlockAnimationInfo? {
+        if (!dto.animated) {
+            return null
+        }
+
+        return BlockAnimationInfo(
+            framesCount = dto.frames,
+        )
+    }
+
+    private fun loadTexture(textureName: String): Texture? {
+        if (textureName == GameItemsHolder.FALLBACK_BLOCK_KEY) {
+            return null
+        }
+
+        return Texture(assetLoader.getAssetHandle("textures/blocks/$textureName.png"))
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt
new file mode 100644 (file)
index 0000000..796fc8b
--- /dev/null
@@ -0,0 +1,53 @@
+package ru.deadsoftware.cavedroid.game.model.mapper
+
+import com.badlogic.gdx.graphics.Texture
+import com.badlogic.gdx.graphics.g2d.Sprite
+import dagger.Reusable
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.model.dto.ItemDto
+import ru.deadsoftware.cavedroid.game.model.item.CommonItemParams
+import ru.deadsoftware.cavedroid.game.model.item.Item
+import ru.deadsoftware.cavedroid.game.model.item.Item.*
+import ru.deadsoftware.cavedroid.misc.utils.AssetLoader
+import ru.deadsoftware.cavedroid.misc.utils.SpriteOrigin
+import javax.inject.Inject
+
+@Reusable
+class ItemMapper @Inject constructor(
+    private val assetLoader: AssetLoader,
+) {
+
+    fun map(key: String, dto: ItemDto, block: Block?): Item {
+        val params = mapCommonParams(key, dto)
+
+        return when (dto.type) {
+            "bucket" -> Bucket(params, requireNotNull(loadSprite(dto)), requireNotNull(dto.actionKey))
+            "shovel" -> Shovel(params, requireNotNull(loadSprite(dto)), dto.mobDamageMultiplier, dto.blockDamageMultiplier)
+            "sword" -> Sword(params, requireNotNull(loadSprite(dto)), dto.mobDamageMultiplier, dto.blockDamageMultiplier)
+            "block" -> Placeable(params, requireNotNull(block))
+            else -> throw IllegalArgumentException("Unknown item type ${dto.type}")
+        }
+    }
+
+    private fun mapCommonParams(key: String, dto: ItemDto): CommonItemParams {
+        return CommonItemParams(
+            id = dto.id,
+            key = key,
+            name = dto.name,
+            inHandSpriteOrigin = SpriteOrigin(
+                x = dto.originX,
+                y = dto.origin_y,
+            )
+        )
+    }
+
+    private fun loadSprite(dto: ItemDto): Sprite? {
+        if (dto.type == "block" || dto.texture == GameItemsHolder.FALLBACK_ITEM_KEY) {
+            return null
+        }
+
+        return Sprite(Texture(assetLoader.getAssetHandle("textures/items/${dto.texture}.png")))
+    }
+
+}
\ No newline at end of file
index 5b897199a19c6d21e28727d58efac86923ef4fd8..7fb5df176396b8b0c28675477fd4f09e61a5e6dc 100644 (file)
@@ -3,9 +3,12 @@ package ru.deadsoftware.cavedroid.misc.utils
 import com.badlogic.gdx.Gdx
 import com.badlogic.gdx.files.FileHandle
 import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
 import java.io.File
 import javax.inject.Inject
+import javax.inject.Singleton
 
+@Singleton
 class AssetLoader @Inject constructor(
     private val mainConfig: MainConfig,
 ) {