From: fredboy Date: Sat, 20 Apr 2024 13:02:13 +0000 (+0700) Subject: Create new game items holder X-Git-Tag: alpha0.6.0~18 X-Git-Url: https://deadsoftware.ru/gitweb?p=cavedroid.git;a=commitdiff_plain;h=63ffd8af5e9788f36fc75b6d5c29ae525eb74692 Create new game items holder --- diff --git a/build.gradle b/build.gradle index 10228bd..821b6e3 100644 --- a/build.gradle +++ b/build.gradle @@ -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 diff --git a/core/build.gradle b/core/build.gradle index d968841..cdf2ee7 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -2,6 +2,7 @@ plugins { id "org.jetbrains.kotlin.jvm" id "kotlin" id "idea" + id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion" } sourceCompatibility = 17 diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameItems.java b/core/src/ru/deadsoftware/cavedroid/game/GameItems.java index 7cf5252..4024c36 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/GameItems.java +++ b/core/src/ru/deadsoftware/cavedroid/game/GameItems.java @@ -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 index 0000000..73da0c2 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt @@ -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() + private val itemsMap = LinkedHashMap() + + private lateinit var fallbackBlock: Block + private lateinit var fallbackItem: Item + + + + private fun loadBlocks(dtoMap: Map) { + 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) { + 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 Map.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 diff --git a/core/src/ru/deadsoftware/cavedroid/game/GameProc.java b/core/src/ru/deadsoftware/cavedroid/game/GameProc.java index 139a31b..0c3947b 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/GameProc.java +++ b/core/src/ru/deadsoftware/cavedroid/game/GameProc.java @@ -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); diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt b/core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt index 62c6f49..4536344 100644 --- a/core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt +++ b/core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt @@ -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 index 0000000..697a042 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/dto/BlockDto.kt @@ -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 index 0000000..63229b3 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/dto/GameItemsDto.kt @@ -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, + @SerialName("items") val items: Map, +) { + object GameItemsDtoJsonSerializer : JsonTransformingSerializer(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 index 0000000..d46eddd --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/dto/ItemDto.kt @@ -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 index 0000000..0838491 --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt @@ -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 index 0000000..796fc8b --- /dev/null +++ b/core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt @@ -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 diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt index 5b89719..7fb5df1 100644 --- a/core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt +++ b/core/src/ru/deadsoftware/cavedroid/misc/utils/AssetLoader.kt @@ -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, ) {