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.craft.CraftingRecipe import ru.deadsoftware.cavedroid.game.model.craft.CraftingResult import ru.deadsoftware.cavedroid.game.model.dto.BlockDto import ru.deadsoftware.cavedroid.game.model.dto.CraftingDto import ru.deadsoftware.cavedroid.game.model.dto.GameItemsDto import ru.deadsoftware.cavedroid.game.model.dto.ItemDto import ru.deadsoftware.cavedroid.game.model.item.InventoryItem 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 java.util.LinkedList import javax.inject.Inject @GameScope class GameItemsHolder @Inject constructor( private val assetLoader: AssetLoader, private val blockMapper: BlockMapper, private val itemMapper: ItemMapper, ) { private var _initialized: Boolean = false private val blocksMap = LinkedHashMap() private val itemsMap = LinkedHashMap() private val craftingRecipes = LinkedList() lateinit var fallbackBlock: Block private set lateinit var fallbackItem: Item private set init { initialize() } private fun loadBlocks(dtoMap: Map) { dtoMap.forEach { (key, dto) -> blocksMap[key] = blockMapper.map(key, dto) .apply(Block::initialize) } 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) -> try { itemsMap[key] = itemMapper.map( key = key, dto = dto, block = blocksMap[key], slabTopBlock = blocksMap[dto.topSlabBlock] as? Block.Slab, slabBottomBlock = blocksMap[dto.bottomSlabBlock] as? Block.Slab ) } catch (e: Exception) { Gdx.app.error(TAG, "Failed to map item $key. Reason: ${e.message}") e.printStackTrace() } } fallbackItem = itemsMap[FALLBACK_ITEM_KEY] ?: throw IllegalArgumentException("Fallback item key '$FALLBACK_ITEM_KEY' not found") } private fun loadCraftingRecipes() { val jsonString = assetLoader.getAssetHandle("json/crafting.json").readString() val jsonMap = JsonFormat.decodeFromString>(jsonString) if (jsonMap.isNotEmpty() && itemsMap.isEmpty()) { throw IllegalStateException("items should be loaded before crafting") } jsonMap.forEach { (key, value) -> craftingRecipes += CraftingRecipe( input = value.input.map(::getItem), output = CraftingResult(getItem(key), value.count) ) } } fun initialize() { 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(jsonString) loadBlocks(gameItemsDto.blocks) loadItems(gameItemsDto.items) _initialized = true loadCraftingRecipes() } private fun Map.getOrFallback(key: String, fallback: T, lazyErrorMessage: () -> String): T { if (!_initialized) { throw IllegalStateException("GameItemsHolder was not initialized before use") } 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" } } fun craftItem(input: List): InventoryItem? { return try { craftingRecipes.first { rec -> rec.input == input}.output.toInventoryItem() } catch (e: NoSuchElementException) { null } } fun getAllItems(): Collection { return itemsMap.values } fun getItemFromCreativeInventory(position: Int): Item { return if (position in itemsMap.values.indices) { itemsMap.values.elementAt(position) } else { fallbackItem } } fun getMaxCreativeScrollAmount(): Int = itemsMap.size / 8 fun getBlocksByType(type: Class): List { return blocksMap.values.filterIsInstance(type) } 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" } }