From ec7ff3199dc493e1f21ea0791f30f6b990245b90 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 2 Mar 2024 17:16:02 +0900 Subject: [PATCH] fix: techtree button not working if craftingUI is loaded independently --- .../gameactors/FixtureFurnaceAndAnvil.kt | 5 - .../gameactors/FixtureWorkbench.kt | 5 - .../terrarum/modulebasegame/ui/UICrafting.kt | 604 ++---------------- .../modulebasegame/ui/UICraftingWorkbench.kt | 593 +++++++++++++++++ .../modulebasegame/ui/UIInventoryFull.kt | 4 +- .../ui/UIItemCraftingCandidateGrid.kt | 9 +- .../terrarum/modulebasegame/ui/UITechView.kt | 15 +- 7 files changed, 652 insertions(+), 583 deletions(-) create mode 100644 src/net/torvald/terrarum/modulebasegame/ui/UICraftingWorkbench.kt diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureFurnaceAndAnvil.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureFurnaceAndAnvil.kt index a5984b767..4bc9d0fef 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureFurnaceAndAnvil.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureFurnaceAndAnvil.kt @@ -1,12 +1,9 @@ package net.torvald.terrarum.modulebasegame.gameactors -import com.badlogic.gdx.Gdx import net.torvald.gdx.graphics.Cvec import net.torvald.spriteanimation.SheetSpriteAnimation import net.torvald.terrarum.* -import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.audio.MusicContainer -import net.torvald.terrarum.audio.decibelsToFullscale import net.torvald.terrarum.audio.dsp.Gain import net.torvald.terrarum.audio.dsp.NullFilter import net.torvald.terrarum.blockproperties.Block @@ -18,9 +15,7 @@ import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase import net.torvald.terrarum.modulebasegame.ui.UICrafting -import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack -import net.torvald.unsafe.UnsafeHelper /** * Created by minjaesong on 2023-12-05. diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureWorkbench.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureWorkbench.kt index 09e157413..93f1aa75e 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureWorkbench.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/FixtureWorkbench.kt @@ -1,16 +1,11 @@ package net.torvald.terrarum.modulebasegame.gameactors import net.torvald.terrarum.BlockCodex -import net.torvald.terrarum.INGAME -import net.torvald.terrarum.Terrarum import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.gameactors.AVKey import net.torvald.terrarum.langpack.Lang -import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase import net.torvald.terrarum.modulebasegame.ui.UICrafting -import net.torvald.terrarum.modulebasegame.ui.UIInventoryFull -import net.torvald.terrarum.modulebasegame.ui.UIWallCalendar import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack /** diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UICrafting.kt b/src/net/torvald/terrarum/modulebasegame/ui/UICrafting.kt index ddf79f8c7..7fae24766 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UICrafting.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UICrafting.kt @@ -1,594 +1,84 @@ package net.torvald.terrarum.modulebasegame.ui -import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.g2d.SpriteBatch -import net.torvald.terrarum.* -import net.torvald.terrarum.App.* -import net.torvald.terrarum.gameactors.AVKey -import net.torvald.terrarum.gameitems.GameItem -import net.torvald.terrarum.gameitems.ItemID -import net.torvald.terrarum.gameitems.isWall -import net.torvald.terrarum.itemproperties.CraftingCodex -import net.torvald.terrarum.langpack.Lang -import net.torvald.terrarum.modulebasegame.gameactors.ActorInventory -import net.torvald.terrarum.modulebasegame.gameactors.CraftingStation -import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory -import net.torvald.terrarum.modulebasegame.gameactors.InventoryPair -import net.torvald.terrarum.modulebasegame.ui.UIItemInventoryCellCommonRes.tooltipShowing -import net.torvald.terrarum.modulebasegame.ui.UIItemInventoryItemGrid.Companion.listGap -import net.torvald.terrarum.modulebasegame.ui.UITemplateHalfInventory.Companion.INVENTORY_NAME_TEXT_GAP -import net.torvald.terrarum.ui.* -import net.torvald.terrarum.ui.UIItemCatBar.Companion.FILTER_CAT_ALL -import net.torvald.unicode.getKeycapPC +import net.torvald.terrarum.App +import net.torvald.terrarum.gamecontroller.TerrarumKeyboardEvent +import net.torvald.terrarum.ui.Toolkit +import net.torvald.terrarum.ui.UICanvas +import net.torvald.terrarum.ui.UIItemHorizontalFadeSlide /** - * This UI has inventory, but it's just there to display all craftable items and should not be serialised. - * - * Created by minjaesong on 2022-03-10. + * Created by minjaesong on 2024-03-02. */ class UICrafting(val full: UIInventoryFull?) : UICanvas( toggleKeyLiteral = if (full == null) "control_key_inventory" else null, toggleButtonLiteral = if (full == null) "control_gamepad_start" else null -), HasInventory { +) { override var width = Toolkit.drawWidth override var height = App.scr.height - private val playerThings = UITemplateHalfInventory(this, false).also { pt -> - pt.itemListTouchDownFun = { gameItem, _, _, _, theButton -> if (gameItem != null) { - val recipe = recipeClicked - val itemID = gameItem.dynamicID + internal val transitionalCraftingUI = UICraftingWorkbench(full, this) + internal val transitionalTechtreePanel = UITechView(full, this) + val transitionPanel = UIItemHorizontalFadeSlide( + this, + (width - UIInventoryFull.internalWidth) / 2, + UIInventoryFull.INVENTORY_CELLS_OFFSET_Y(), + width, + App.scr.height, + 0f, + listOf(transitionalCraftingUI, transitionalTechtreePanel), + listOf() + ) + + private val uis = listOf(transitionalCraftingUI, transitionalTechtreePanel) - // change ingredient used - if (recipe != null) { - // don't rely on highlightedness of the button to determine the item on the button is the selected - // ingredient (because I don't fully trust my code lol) - val targetItemToAlter = - recipe.ingredients.filter { (key, mode) -> // altering recipe doesn't make sense if player selected a recipe that requires no tag-ingredients - val tags = key.split(',') - val wantsWall = tags.contains("WALL") - (mode == CraftingCodex.CraftingItemKeyMode.TAG && gameItem.hasAllTags(tags) && (wantsWall == gameItem.originalID.isWall())) // true if (wants wall and is wall) or (wants no wall and is not wall) - }.let { - if (it.size > 1) - println( - "[UICrafting] Your recipe seems to have two similar ingredients defined\n" + - "affected ingredients: ${it.joinToString()}\n" + - "the recipe: ${recipe}" - ) - it.firstOrNull() - } - - targetItemToAlter?.let { (key, mode) -> - val oldItem = _getItemListIngredients().getInventory().first { (itm, qty) -> - val tags = key.split(',') - val wantsWall = tags.contains("WALL") - (mode == CraftingCodex.CraftingItemKeyMode.TAG && ItemCodex[itm]!!.hasAllTags(tags) && (wantsWall == itm.isWall())) // true if (wants wall and is wall) or (wants no wall and is not wall) - } - changeIngredient(recipe, oldItem, itemID) - refreshCraftButtonStatus() - } - } - // show all the items that can be made using this ingredient - else { - itemListCraftable.rebuild(arrayOf(itemID)) - pt.itemList.clearForceHighlightList() - pt.itemList.addToForceHighlightList(listOf(itemID)) - - if (itemListCraftable.craftingRecipes.isEmpty()) { - pt.itemList.clearForceHighlightList() - itemListCraftable.rebuild(FILTER_CAT_ALL) - } - } - } - else { - pt.itemList.clearForceHighlightList() - recipeClicked = null - filterPlayerListUsing(recipeClicked) - highlightCraftingCandidateButton(null) - ingredients.clear() - itemListIngredients.rebuild(FILTER_CAT_ALL) - }} - } - - private val catIcons = CommonResourcePool.getAsTextureRegionPack("inventory_category") - - - internal val itemListCraftable: UIItemCraftingCandidateGrid // might be changed to something else - internal val itemListIngredients: UIItemInventoryItemGrid // this one is definitely not to be changed - private val buttonCraft: UIItemTextButton - private val spinnerCraftCount: UIItemSpinner - - private val ingredients = FixtureInventory() // this one is definitely not to be changed - - private val negotiator = object : InventoryTransactionNegotiator() { - override fun accept(player: FixtureInventory, fixture: FixtureInventory, item: GameItem, amount: Long) { -// TODO() - } - - override fun refund(fixture: FixtureInventory, player: FixtureInventory, item: GameItem, amount: Long) { -// TODO() - } - } - - override fun getNegotiator() = negotiator - override fun getFixtureInventory(): FixtureInventory = TODO() - override fun getPlayerInventory(): ActorInventory = INGAME.actorNowPlaying!!.inventory - - private val halfSlotOffset = (UIItemInventoryElemSimple.height + listGap) / 2 - - private val thisOffsetX = UIInventoryFull.INVENTORY_CELLS_OFFSET_X() + UIItemInventoryElemSimple.height + listGap - halfSlotOffset - private val thisOffsetX2 = thisOffsetX + (listGap + UIItemInventoryElemWide.height) * 7 - private val thisXend = thisOffsetX + (listGap + UIItemInventoryElemWide.height) * 13 - listGap - private val thisOffsetY = UIInventoryFull.INVENTORY_CELLS_OFFSET_Y() - private val cellsWidth = (listGap + UIItemInventoryElemWide.height) * 6 - listGap - - private val LAST_LINE_IN_GRID = ((UIItemInventoryElemWide.height + listGap) * (UIInventoryFull.CELLS_VRT - 2)) + 22//359 // TEMPORARY VALUE! - - private var recipeClicked: CraftingCodex.CraftingRecipe? = null - - private val controlHelp: String - get() = if (App.environment == RunningEnvironment.PC) - "${getKeycapPC(ControlPresets.getKey("control_key_inventory"))} ${Lang["GAME_ACTION_CLOSE"]}" - else - "$gamepadLabelStart ${Lang["GAME_ACTION_CLOSE"]}\u3000 " + - "$gamepadLabelLEFTRIGHT ${Lang["GAME_OBJECTIVE_MULTIPLIER"]}\u3000 " + - "${App.gamepadLabelWest} ${Lang["GAME_ACTION_CRAFT"]}" - - private val oldSelectedItems = ArrayList() - - private val craftMult - get() = spinnerCraftCount.value.toLong() - - private fun _getItemListPlayer() = playerThings.itemList - private fun _getItemListIngredients() = itemListIngredients - private fun _getItemListCraftables() = itemListCraftable - - init { - val craftButtonsY = thisOffsetY + 23 + (UIItemInventoryElemWide.height + listGap) * (UIInventoryFull.CELLS_VRT - 1) - val buttonWidth = (UIItemInventoryElemWide.height + listGap) * 3 - listGap - 2 - - // ingredient list - itemListIngredients = UIItemInventoryItemGrid( - this, - { ingredients }, - thisOffsetX, - thisOffsetY + LAST_LINE_IN_GRID, - 6, 1, - drawScrollOnRightside = false, - drawWallet = false, - hideSidebar = true, - colourTheme = UIItemInventoryCellCommonRes.defaultInventoryCellTheme.copy( - cellHighlightSubCol = Toolkit.Theme.COL_INACTIVE - ), - keyDownFun = { _, _, _, _, _ -> }, - wheelFun = { _, _, _, _, _, _ -> }, - touchDownFun = { gameItem, amount, _, _, _ -> gameItem?.let { gameItem -> - // if the clicked item is craftable one, present its recipe to the player // - - CraftingRecipeCodex.getRecipesFor(gameItem.originalID)?.let { recipes -> - // select most viable recipe (completely greedy search) - val player = getPlayerInventory() - // list of [Score, Ingredients, Recipe] - recipes.map { recipe -> - // list of (Item, How many player has, How many the recipe requires) - val items = recipeToIngredientRecord(player, recipe, nearbyCraftingStations) - - val score = items.fold(1L) { acc, item -> - (item.howManyPlayerHas).times(16L) + 1L - } - - listOf(score, items, recipe) - }.maxByOrNull { it[0] as Long }?.let { (_, items, recipe) -> - val items = items as List - val recipe = recipe as CraftingCodex.CraftingRecipe - - // change selected recipe to mostViableRecipe then update the UIs accordingly - val selectedItems = ArrayList() - - resetSpinner() - - ingredients.clear() - recipeClicked = recipe - - items.forEach { - val itm = it.selectedItem - val qty = it.howManyRecipeWants - - selectedItems.add(itm) - ingredients.add(itm, qty) - } - - _getItemListPlayer().let { - it.removeFromForceHighlightList(oldSelectedItems) - //filterPlayerListUsing(recipeClicked) // ??? - it.addToForceHighlightList(selectedItems) - filterPlayerListUsing(recipeClicked) - } - - _getItemListIngredients().rebuild(FILTER_CAT_ALL) - - _getItemListCraftables().highlightRecipe(recipeClicked, true) - - oldSelectedItems.clear() - oldSelectedItems.addAll(selectedItems) - - refreshCraftButtonStatus() - } - } - } } - ) - - // make sure grid buttons for ingredients do nothing (even if they are hidden!) - itemListIngredients.navRemoCon.listButtonListener = { _,_, -> } - itemListIngredients.navRemoCon.gridButtonListener = { _,_, -> } - itemListIngredients.isCompactMode = true - itemListIngredients.setCustomHighlightRuleSub { - it.item?.let { ingredient -> - return@setCustomHighlightRuleSub getPlayerInventory().searchByID(ingredient.dynamicID)?.let { itemOnPlayer -> - itemOnPlayer.qty * craftMult >= it.amount * craftMult - } == true - } - false - } - - - - // crafting list to the left - itemListCraftable = UIItemCraftingCandidateGrid( - this, - thisOffsetX, - thisOffsetY, - 6, UIInventoryFull.CELLS_VRT - 2, // decrease the internal height so that craft/cancel button would fit in - keyDownFun = { _, _, _, _, _ -> }, - touchDownFun = { gameItem, amount, _, recipe0, button -> - (recipe0 as? CraftingCodex.CraftingRecipe).let { recipe -> - val selectedItems = ArrayList() - - val playerInventory = getPlayerInventory() - ingredients.clear() - recipeClicked = recipe -// printdbg(this, "Recipe selected: $recipe") - recipe?.ingredients?.forEach { ingredient -> - val selectedItem = resolveIngredientKey(playerInventory, ingredient, recipe.product) - selectedItems.add(selectedItem) - ingredients.add(selectedItem, ingredient.qty) - } - - _getItemListPlayer().removeFromForceHighlightList(oldSelectedItems) - _getItemListPlayer().addToForceHighlightList(selectedItems) - if (recipe != null) _getItemListPlayer().itemPage = 0 - filterPlayerListUsing(recipeClicked) - _getItemListIngredients().rebuild(FILTER_CAT_ALL) - - highlightCraftingCandidateButton(recipe) - - oldSelectedItems.clear() - oldSelectedItems.addAll(selectedItems) - - if (recipe == null) { - playerThings.itemList.clearForceHighlightList() - } - - refreshCraftButtonStatus() - } - } - ) - buttonCraft = UIItemTextButton(this, - { Lang["GAME_ACTION_CRAFT"] }, thisOffsetX + 3 + buttonWidth + listGap, craftButtonsY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true) - spinnerCraftCount = UIItemSpinner(this, thisOffsetX + 1, craftButtonsY, 1, 1, App.getConfigInt("basegame:gameplay_max_crafting"), 1, buttonWidth, numberToTextFunction = {"×\u200A${it.toInt()}"}) - spinnerCraftCount.selectionChangeListener = { - itemListIngredients.numberMultiplier = it.toLong() - itemListIngredients.rebuild(FILTER_CAT_ALL) - itemListCraftable.numberMultiplier = it.toLong() - itemListCraftable.rebuild() - refreshCraftButtonStatus() - } - - - buttonCraft.clickOnceListener = { _,_ -> - getPlayerInventory().let { player -> recipeClicked?.let { recipe -> - // check if player has enough amount of ingredients - val itemCraftable = itemListIngredients.getInventory().all { (itm, qty) -> - (player.searchByID(itm)?.qty ?: -1) >= qty * craftMult - } - - - if (itemCraftable) { - itemListIngredients.getInventory().forEach { (itm, qty) -> - player.remove(itm, qty * craftMult) - } - player.add(recipe.product, recipe.moq * craftMult) - - // reset selection status after a crafting to hide the possible artefact where no-longer-craftable items are still displayed due to ingredient depletion - resetUI() // also clears forcehighlightlist - playerThings.rebuild(FILTER_CAT_ALL) - itemListCraftable.rebuild(FILTER_CAT_ALL) - } - } } - refreshCraftButtonStatus() - } - // make grid mode buttons work together -// itemListCraftable.gridModeButtons[0].clickOnceListener = { _,_ -> setCompact(false) } -// itemListCraftable.gridModeButtons[1].clickOnceListener = { _,_ -> setCompact(true) } - - handler.allowESCtoClose = true - - - val menuButtonTechView = UIItemImageButton( - this, catIcons.get(20, 1), - initialX = itemListCraftable.navRemoCon.posX + 12, - initialY = itemListCraftable.navRemoCon.getIconPosY(-2) - 8, - highlightable = true - ).also { - it.clickOnceListener = { _, _ -> - full?.transitionPanel?.setLeftUIto(1) - full?.transitionPanel?.uis?.get(0)?.show() - it.highlighted = false - } - } - - val menuButtonCraft = UIItemImageButton( - this, catIcons.get(19, 1), - initialX = itemListCraftable.navRemoCon.posX + 12, - initialY = itemListCraftable.navRemoCon.getIconPosY(-1) - 8, - activeCol = Toolkit.Theme.COL_SELECTED, - inactiveCol = Toolkit.Theme.COL_SELECTED, - highlightable = true - ) - - - addUIitem(itemListCraftable) - addUIitem(itemListIngredients) - addUIitem(playerThings) - addUIitem(spinnerCraftCount) - addUIitem(buttonCraft) - // temporarily disabled for 0.4 release - if (TerrarumAppConfiguration.VERSION_RAW >= 0x0000_000004_000001) { - addUIitem(menuButtonCraft) - addUIitem(menuButtonTechView) - } - } - - private fun filterPlayerListUsing(recipe: CraftingCodex.CraftingRecipe?) { - if (recipe == null) - playerThings.rebuild(FILTER_CAT_ALL) - else { - val items = recipe.ingredients.flatMap { - getItemCandidatesForIngredient(getPlayerInventory(), it).map { it.itm } - }.filter { it != recipe.product }.sorted() // filter out the product itself from the ingredient - - val filterFun = { pair: InventoryPair -> - items.binarySearch(pair.itm) >= 0 - } - playerThings.rebuild(filterFun, recipe.product) - } - } - - var nearbyCraftingStations = emptyList(); protected set - - fun getCraftingStationsWithinReach(): List { - val reach = 2 * INGAME.actorNowPlaying!!.actorValue.getAsDouble(AVKey.REACH)!! * (INGAME.actorNowPlaying!!.actorValue.getAsDouble(AVKey.REACHBUFF) ?: 1.0) * INGAME.actorNowPlaying!!.scale - val nearbyCraftingStations = INGAME.findKNearestActors(INGAME.actorNowPlaying!!, 256) { - it is CraftingStation && (distBetweenActors(it, INGAME.actorNowPlaying!!) < reach) - } - return nearbyCraftingStations.flatMap { (it.get() as CraftingStation).tags } - } - - private fun changeIngredient(recipe: CraftingCodex.CraftingRecipe?, old: InventoryPair, new: ItemID) { - playerThings.itemList.removeFromForceHighlightList(oldSelectedItems) - - oldSelectedItems.remove(old.itm) - oldSelectedItems.add(new) - - playerThings.itemList.addToForceHighlightList(oldSelectedItems) - playerThings.itemList.itemPage = 0 - filterPlayerListUsing(recipe) - - // change highlight status of itemListIngredients - itemListIngredients.getInventory().let { - val amount = old.qty - it.remove(old.itm, amount) - it.add(new, amount) - } - itemListIngredients.rebuild(FILTER_CAT_ALL) - } - - private fun highlightCraftingCandidateButton(recipe: CraftingCodex.CraftingRecipe?) { // a proxy function - itemListCraftable.highlightRecipe(recipe) - itemListCraftable.rebuild(FILTER_CAT_ALL) - } - - /** - * Updates Craft! button so that the button is correctly highlighted - */ - fun refreshCraftButtonStatus() { - val itemCraftable = if (itemListIngredients.getInventory().totalUniqueCount < 1) false - else getPlayerInventory().let { player -> - // check if player has enough amount of ingredients - itemListIngredients.getInventory().all { (itm, qty) -> - (player.searchByID(itm)?.qty ?: -1) >= qty * craftMult - } - } - - buttonCraft.isEnabled = itemCraftable - } - - // reset whatever player has selected to null and bring UI to its initial state fun resetUI() { - // reset spinner - resetSpinner() - - // reset selected recipe status - recipeClicked = null - filterPlayerListUsing(recipeClicked) - highlightCraftingCandidateButton(null) - ingredients.clear() - playerThings.itemList.clearForceHighlightList() - itemListIngredients.rebuild(FILTER_CAT_ALL) - - // reset scroll - itemListCraftable.itemPage = 0 - playerThings.itemList.itemPage = 0 - - refreshCraftButtonStatus() + transitionalCraftingUI.resetUI() + transitionalTechtreePanel.resetUI() } - private fun resetSpinner() { - spinnerCraftCount.resetToSmallest() - itemListIngredients.numberMultiplier = 1L - itemListCraftable.numberMultiplier = 1L + fun showCraftingUI() { + transitionPanel.setLeftUIto(0) + transitionalCraftingUI.show() } - private var openingClickLatched = false - - override fun show() { - nearbyCraftingStations = getCraftingStationsWithinReach() -// printdbg(this, "Nearby crafting stations: $nearbyCraftingStations") - - playerThings.setGetInventoryFun { INGAME.actorNowPlaying!!.inventory } - itemListUpdate() - - openingClickLatched = Terrarum.mouseDown - - tooltipShowing.clear() - INGAME.setTooltipMessage(null) - - resetUI() - } - - private var encumbrancePerc = 0f - - private fun itemListUpdate() { - // let itemlists be sorted - itemListCraftable.rebuild() - playerThings.rebuild(FILTER_CAT_ALL) - encumbrancePerc = getPlayerInventory().encumberment.toFloat() - } - - override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { - if (!openingClickLatched) { - return super.touchDown(screenX, screenY, pointer, button) - } - return false + fun showTechViewUI() { + transitionPanel.setLeftUIto(1) + transitionalTechtreePanel.show() } override fun updateImpl(delta: Float) { - // NO super.update due to an infinite recursion - this.uiItems.forEach { it.update(delta) } + uiItems.forEach { it.update(delta) } - if (openingClickLatched && !Terrarum.mouseDown) openingClickLatched = false + // copy transition of parent (UIItemHorizontalFadeSlide) to this (also UIItemHorizontalFadeSlide) + full?.let { full -> + uis.forEach { + it.posX = full.transitionPanel.getOffX(0) + it.opacity = full.transitionPanel.getOpacity(0) + } + } } override fun renderImpl(frameDelta: Float, batch: SpriteBatch, camera: OrthographicCamera) { - // NO super.render due to an infinite recursion - this.uiItems.forEach { it.render(frameDelta, batch, camera) } - - batch.color = Color.WHITE - - // text label for two inventory grids - val craftingLabel = Lang["GAME_CRAFTING"] - val ingredientsLabel = Lang["GAME_INVENTORY_INGREDIENTS"] - - App.fontGame.draw(batch, craftingLabel, thisOffsetX + (cellsWidth - App.fontGame.getWidth(craftingLabel)) / 2, thisOffsetY - INVENTORY_NAME_TEXT_GAP) - App.fontGame.draw(batch, ingredientsLabel, thisOffsetX + (cellsWidth - App.fontGame.getWidth(ingredientsLabel)) / 2, thisOffsetY + LAST_LINE_IN_GRID - INVENTORY_NAME_TEXT_GAP) - - - // control hints - val controlHintXPos = thisOffsetX + 2f - blendNormalStraightAlpha(batch) - App.fontGame.draw(batch, controlHelp, controlHintXPos, UIInventoryFull.yEnd - 20) - - - if (INGAME.actorNowPlaying != null) { - //draw player encumb - val encumbBarXPos = thisXend - UIInventoryCells.weightBarWidth + 36 - val encumbBarYPos = UIInventoryFull.yEnd - 20 + 3f - UIInventoryCells.drawEncumbranceBar(batch, encumbBarXPos, encumbBarYPos, encumbrancePerc, INGAME.actorNowPlaying!!.inventory) + if (full == null) { + UIInventoryFull.drawBackground(batch, opacity) } - - blendNormalStraightAlpha(batch) + uiItems.forEach { it.render(frameDelta, batch, camera) } } - override fun doOpening(delta: Float) { - super.doOpening(delta) - INGAME.setTooltipMessage(null) - } - - override fun doClosing(delta: Float) { - super.doClosing(delta) - INGAME.setTooltipMessage(null) - } - - override fun endOpening(delta: Float) { - super.endOpening(delta) - tooltipShowing.clear() - INGAME.setTooltipMessage(null) // required! - } - - override fun endClosing(delta: Float) { - super.endClosing(delta) - resetUI() - tooltipShowing.clear() - INGAME.setTooltipMessage(null) // required! - } - - override fun dispose() { + transitionPanel.dispose() } - companion object { - data class RecipeIngredientRecord( - val selectedItem: ItemID, - val howManyPlayerHas: Long, - val howManyRecipeWants: Long, - val craftingStationAvailable: Boolean, - ) + init { + addUIitem(transitionPanel) + } - fun getItemCandidatesForIngredient(inventory: FixtureInventory, ingredient: CraftingCodex.CraftingIngredients): List { - return if (ingredient.keyMode == CraftingCodex.CraftingItemKeyMode.TAG) { - val tags = ingredient.key.split(',') - val wantsWall = tags.contains("WALL") - // If the player has the required item, use it; otherwise, will take an item from the ItemCodex - inventory.filter { (itm, qty) -> - ItemCodex[itm]?.hasAllTags(tags) == true && qty >= ingredient.qty && (wantsWall == itm.isWall()) // true if (wants wall and is wall) or (wants no wall and is not wall) - } - } - else { - listOf(InventoryPair(ingredient.key, -1)) - } - } - - fun resolveIngredientKey(inventory: FixtureInventory, ingredient: CraftingCodex.CraftingIngredients, product: ItemID): ItemID { - val candidate = getItemCandidatesForIngredient(inventory, ingredient).filter { it.itm != product } - -// printdbg(this, "resolveIngredientKey product=$product, candidate=$candidate") - - return if (ingredient.keyMode == CraftingCodex.CraftingItemKeyMode.TAG) { - // filter out the product itself from the ingredient - candidate.maxByOrNull { it.qty }?.itm ?: ( - (ItemCodex.itemCodex.firstNotNullOfOrNull { if (it.value.hasTag(ingredient.key)) it.key else null }) ?: - throw NullPointerException("Item with tag '${ingredient.key}' not found. Possible cause: game or a module not updated or installed (ingredient: $ingredient)") - ) - } - else { - ingredient.key - } - } - - /** - * For each ingredient of the recipe, returns list of (ingredient, how many the player has the ingredient, how many the recipe wants) - */ - fun recipeToIngredientRecord(inventory: FixtureInventory, recipe: CraftingCodex.CraftingRecipe, nearbyCraftingStations: List): List { - val hasStation = if (recipe.workbench.isBlank()) true else nearbyCraftingStations.containsAll(recipe.workbench.split(',')) - return recipe.ingredients.map { ingredient -> - val selectedItem = resolveIngredientKey(inventory, ingredient, recipe.product) - val howManyPlayerHas = inventory.searchByID(selectedItem)?.qty ?: 0L - val howManyTheRecipeWants = ingredient.qty - - RecipeIngredientRecord(selectedItem, howManyPlayerHas, howManyTheRecipeWants, hasStation) - } - } + override fun setPosition(x: Int, y: Int) { + transitionalCraftingUI.setPosition(x, y) + transitionalTechtreePanel.setPosition(x, y) } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UICraftingWorkbench.kt b/src/net/torvald/terrarum/modulebasegame/ui/UICraftingWorkbench.kt new file mode 100644 index 000000000..3cb21e550 --- /dev/null +++ b/src/net/torvald/terrarum/modulebasegame/ui/UICraftingWorkbench.kt @@ -0,0 +1,593 @@ +package net.torvald.terrarum.modulebasegame.ui + +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.OrthographicCamera +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import net.torvald.terrarum.* +import net.torvald.terrarum.App.* +import net.torvald.terrarum.gameactors.AVKey +import net.torvald.terrarum.gameitems.GameItem +import net.torvald.terrarum.gameitems.ItemID +import net.torvald.terrarum.gameitems.isWall +import net.torvald.terrarum.itemproperties.CraftingCodex +import net.torvald.terrarum.langpack.Lang +import net.torvald.terrarum.modulebasegame.gameactors.ActorInventory +import net.torvald.terrarum.modulebasegame.gameactors.CraftingStation +import net.torvald.terrarum.modulebasegame.gameactors.FixtureInventory +import net.torvald.terrarum.modulebasegame.gameactors.InventoryPair +import net.torvald.terrarum.modulebasegame.ui.UIItemInventoryCellCommonRes.tooltipShowing +import net.torvald.terrarum.modulebasegame.ui.UIItemInventoryItemGrid.Companion.listGap +import net.torvald.terrarum.modulebasegame.ui.UITemplateHalfInventory.Companion.INVENTORY_NAME_TEXT_GAP +import net.torvald.terrarum.ui.* +import net.torvald.terrarum.ui.UIItemCatBar.Companion.FILTER_CAT_ALL +import net.torvald.unicode.getKeycapPC + +/** + * This UI has inventory, but it's just there to display all craftable items and should not be serialised. + * + * Created by minjaesong on 2022-03-10. + */ +class UICraftingWorkbench(val inventoryUI: UIInventoryFull?, val parentContainer: UICrafting) : UICanvas( + toggleKeyLiteral = if (inventoryUI == null) "control_key_inventory" else null, + toggleButtonLiteral = if (inventoryUI == null) "control_gamepad_start" else null +), HasInventory { + + override var width = Toolkit.drawWidth + override var height = App.scr.height + + private val playerThings = UITemplateHalfInventory(this, false).also { pt -> + pt.itemListTouchDownFun = { gameItem, _, _, _, theButton -> if (gameItem != null) { + val recipe = recipeClicked + val itemID = gameItem.dynamicID + + // change ingredient used + if (recipe != null) { + // don't rely on highlightedness of the button to determine the item on the button is the selected + // ingredient (because I don't fully trust my code lol) + val targetItemToAlter = + recipe.ingredients.filter { (key, mode) -> // altering recipe doesn't make sense if player selected a recipe that requires no tag-ingredients + val tags = key.split(',') + val wantsWall = tags.contains("WALL") + (mode == CraftingCodex.CraftingItemKeyMode.TAG && gameItem.hasAllTags(tags) && (wantsWall == gameItem.originalID.isWall())) // true if (wants wall and is wall) or (wants no wall and is not wall) + }.let { + if (it.size > 1) + println( + "[UICrafting] Your recipe seems to have two similar ingredients defined\n" + + "affected ingredients: ${it.joinToString()}\n" + + "the recipe: ${recipe}" + ) + it.firstOrNull() + } + + targetItemToAlter?.let { (key, mode) -> + val oldItem = _getItemListIngredients().getInventory().first { (itm, qty) -> + val tags = key.split(',') + val wantsWall = tags.contains("WALL") + (mode == CraftingCodex.CraftingItemKeyMode.TAG && ItemCodex[itm]!!.hasAllTags(tags) && (wantsWall == itm.isWall())) // true if (wants wall and is wall) or (wants no wall and is not wall) + } + changeIngredient(recipe, oldItem, itemID) + refreshCraftButtonStatus() + } + } + // show all the items that can be made using this ingredient + else { + itemListCraftable.rebuild(arrayOf(itemID)) + pt.itemList.clearForceHighlightList() + pt.itemList.addToForceHighlightList(listOf(itemID)) + + if (itemListCraftable.craftingRecipes.isEmpty()) { + pt.itemList.clearForceHighlightList() + itemListCraftable.rebuild(FILTER_CAT_ALL) + } + } + } + else { + pt.itemList.clearForceHighlightList() + recipeClicked = null + filterPlayerListUsing(recipeClicked) + highlightCraftingCandidateButton(null) + ingredients.clear() + itemListIngredients.rebuild(FILTER_CAT_ALL) + }} + } + + private val catIcons = CommonResourcePool.getAsTextureRegionPack("inventory_category") + + + internal val itemListCraftable: UIItemCraftingCandidateGrid // might be changed to something else + internal val itemListIngredients: UIItemInventoryItemGrid // this one is definitely not to be changed + private val buttonCraft: UIItemTextButton + private val spinnerCraftCount: UIItemSpinner + + private val ingredients = FixtureInventory() // this one is definitely not to be changed + + private val negotiator = object : InventoryTransactionNegotiator() { + override fun accept(player: FixtureInventory, fixture: FixtureInventory, item: GameItem, amount: Long) { +// TODO() + } + + override fun refund(fixture: FixtureInventory, player: FixtureInventory, item: GameItem, amount: Long) { +// TODO() + } + } + + override fun getNegotiator() = negotiator + override fun getFixtureInventory(): FixtureInventory = TODO() + override fun getPlayerInventory(): ActorInventory = INGAME.actorNowPlaying!!.inventory + + private val halfSlotOffset = (UIItemInventoryElemSimple.height + listGap) / 2 + + private val thisOffsetX = UIInventoryFull.INVENTORY_CELLS_OFFSET_X() + UIItemInventoryElemSimple.height + listGap - halfSlotOffset + private val thisOffsetX2 = thisOffsetX + (listGap + UIItemInventoryElemWide.height) * 7 + private val thisXend = thisOffsetX + (listGap + UIItemInventoryElemWide.height) * 13 - listGap + private val thisOffsetY = UIInventoryFull.INVENTORY_CELLS_OFFSET_Y() + private val cellsWidth = (listGap + UIItemInventoryElemWide.height) * 6 - listGap + + private val LAST_LINE_IN_GRID = ((UIItemInventoryElemWide.height + listGap) * (UIInventoryFull.CELLS_VRT - 2)) + 22//359 // TEMPORARY VALUE! + + private var recipeClicked: CraftingCodex.CraftingRecipe? = null + + private val controlHelp: String + get() = if (App.environment == RunningEnvironment.PC) + "${getKeycapPC(ControlPresets.getKey("control_key_inventory"))} ${Lang["GAME_ACTION_CLOSE"]}" + else + "$gamepadLabelStart ${Lang["GAME_ACTION_CLOSE"]}\u3000 " + + "$gamepadLabelLEFTRIGHT ${Lang["GAME_OBJECTIVE_MULTIPLIER"]}\u3000 " + + "${App.gamepadLabelWest} ${Lang["GAME_ACTION_CRAFT"]}" + + private val oldSelectedItems = ArrayList() + + private val craftMult + get() = spinnerCraftCount.value.toLong() + + private fun _getItemListPlayer() = playerThings.itemList + private fun _getItemListIngredients() = itemListIngredients + private fun _getItemListCraftables() = itemListCraftable + + init { + val craftButtonsY = thisOffsetY + 23 + (UIItemInventoryElemWide.height + listGap) * (UIInventoryFull.CELLS_VRT - 1) + val buttonWidth = (UIItemInventoryElemWide.height + listGap) * 3 - listGap - 2 + + // ingredient list + itemListIngredients = UIItemInventoryItemGrid( + this, + { ingredients }, + thisOffsetX, + thisOffsetY + LAST_LINE_IN_GRID, + 6, 1, + drawScrollOnRightside = false, + drawWallet = false, + hideSidebar = true, + colourTheme = UIItemInventoryCellCommonRes.defaultInventoryCellTheme.copy( + cellHighlightSubCol = Toolkit.Theme.COL_INACTIVE + ), + keyDownFun = { _, _, _, _, _ -> }, + wheelFun = { _, _, _, _, _, _ -> }, + touchDownFun = { gameItem, amount, _, _, _ -> gameItem?.let { gameItem -> + // if the clicked item is craftable one, present its recipe to the player // + + CraftingRecipeCodex.getRecipesFor(gameItem.originalID)?.let { recipes -> + // select most viable recipe (completely greedy search) + val player = getPlayerInventory() + // list of [Score, Ingredients, Recipe] + recipes.map { recipe -> + // list of (Item, How many player has, How many the recipe requires) + val items = recipeToIngredientRecord(player, recipe, nearbyCraftingStations) + + val score = items.fold(1L) { acc, item -> + (item.howManyPlayerHas).times(16L) + 1L + } + + listOf(score, items, recipe) + }.maxByOrNull { it[0] as Long }?.let { (_, items, recipe) -> + val items = items as List + val recipe = recipe as CraftingCodex.CraftingRecipe + + // change selected recipe to mostViableRecipe then update the UIs accordingly + val selectedItems = ArrayList() + + resetSpinner() + + ingredients.clear() + recipeClicked = recipe + + items.forEach { + val itm = it.selectedItem + val qty = it.howManyRecipeWants + + selectedItems.add(itm) + ingredients.add(itm, qty) + } + + _getItemListPlayer().let { + it.removeFromForceHighlightList(oldSelectedItems) + //filterPlayerListUsing(recipeClicked) // ??? + it.addToForceHighlightList(selectedItems) + filterPlayerListUsing(recipeClicked) + } + + _getItemListIngredients().rebuild(FILTER_CAT_ALL) + + _getItemListCraftables().highlightRecipe(recipeClicked, true) + + oldSelectedItems.clear() + oldSelectedItems.addAll(selectedItems) + + refreshCraftButtonStatus() + } + } + } } + ) + + // make sure grid buttons for ingredients do nothing (even if they are hidden!) + itemListIngredients.navRemoCon.listButtonListener = { _,_, -> } + itemListIngredients.navRemoCon.gridButtonListener = { _,_, -> } + itemListIngredients.isCompactMode = true + itemListIngredients.setCustomHighlightRuleSub { + it.item?.let { ingredient -> + return@setCustomHighlightRuleSub getPlayerInventory().searchByID(ingredient.dynamicID)?.let { itemOnPlayer -> + itemOnPlayer.qty * craftMult >= it.amount * craftMult + } == true + } + false + } + + + + // crafting list to the left + itemListCraftable = UIItemCraftingCandidateGrid( + this, + thisOffsetX, + thisOffsetY, + 6, UIInventoryFull.CELLS_VRT - 2, // decrease the internal height so that craft/cancel button would fit in + keyDownFun = { _, _, _, _, _ -> }, + touchDownFun = { gameItem, amount, _, recipe0, button -> + (recipe0 as? CraftingCodex.CraftingRecipe).let { recipe -> + val selectedItems = ArrayList() + + val playerInventory = getPlayerInventory() + ingredients.clear() + recipeClicked = recipe +// printdbg(this, "Recipe selected: $recipe") + recipe?.ingredients?.forEach { ingredient -> + val selectedItem = resolveIngredientKey(playerInventory, ingredient, recipe.product) + selectedItems.add(selectedItem) + ingredients.add(selectedItem, ingredient.qty) + } + + _getItemListPlayer().removeFromForceHighlightList(oldSelectedItems) + _getItemListPlayer().addToForceHighlightList(selectedItems) + if (recipe != null) _getItemListPlayer().itemPage = 0 + filterPlayerListUsing(recipeClicked) + _getItemListIngredients().rebuild(FILTER_CAT_ALL) + + highlightCraftingCandidateButton(recipe) + + oldSelectedItems.clear() + oldSelectedItems.addAll(selectedItems) + + if (recipe == null) { + playerThings.itemList.clearForceHighlightList() + } + + refreshCraftButtonStatus() + } + } + ) + buttonCraft = UIItemTextButton(this, + { Lang["GAME_ACTION_CRAFT"] }, thisOffsetX + 3 + buttonWidth + listGap, craftButtonsY, buttonWidth, alignment = UIItemTextButton.Companion.Alignment.CENTRE, hasBorder = true) + spinnerCraftCount = UIItemSpinner(this, thisOffsetX + 1, craftButtonsY, 1, 1, App.getConfigInt("basegame:gameplay_max_crafting"), 1, buttonWidth, numberToTextFunction = {"×\u200A${it.toInt()}"}) + spinnerCraftCount.selectionChangeListener = { + itemListIngredients.numberMultiplier = it.toLong() + itemListIngredients.rebuild(FILTER_CAT_ALL) + itemListCraftable.numberMultiplier = it.toLong() + itemListCraftable.rebuild() + refreshCraftButtonStatus() + } + + + buttonCraft.clickOnceListener = { _,_ -> + getPlayerInventory().let { player -> recipeClicked?.let { recipe -> + // check if player has enough amount of ingredients + val itemCraftable = itemListIngredients.getInventory().all { (itm, qty) -> + (player.searchByID(itm)?.qty ?: -1) >= qty * craftMult + } + + + if (itemCraftable) { + itemListIngredients.getInventory().forEach { (itm, qty) -> + player.remove(itm, qty * craftMult) + } + player.add(recipe.product, recipe.moq * craftMult) + + // reset selection status after a crafting to hide the possible artefact where no-longer-craftable items are still displayed due to ingredient depletion + resetUI() // also clears forcehighlightlist + playerThings.rebuild(FILTER_CAT_ALL) + itemListCraftable.rebuild(FILTER_CAT_ALL) + } + } } + refreshCraftButtonStatus() + } + // make grid mode buttons work together +// itemListCraftable.gridModeButtons[0].clickOnceListener = { _,_ -> setCompact(false) } +// itemListCraftable.gridModeButtons[1].clickOnceListener = { _,_ -> setCompact(true) } + + handler.allowESCtoClose = true + + + val menuButtonTechView = UIItemImageButton( + this, catIcons.get(20, 1), + initialX = itemListCraftable.navRemoCon.posX + 12, + initialY = itemListCraftable.navRemoCon.getIconPosY(-2) - 8, + highlightable = true + ).also { + it.clickOnceListener = { _, _ -> + parentContainer.showTechViewUI() + it.highlighted = false + } + } + + val menuButtonCraft = UIItemImageButton( + this, catIcons.get(19, 1), + initialX = itemListCraftable.navRemoCon.posX + 12, + initialY = itemListCraftable.navRemoCon.getIconPosY(-1) - 8, + activeCol = Toolkit.Theme.COL_SELECTED, + inactiveCol = Toolkit.Theme.COL_SELECTED, + highlightable = true + ) + + + addUIitem(itemListCraftable) + addUIitem(itemListIngredients) + addUIitem(playerThings) + addUIitem(spinnerCraftCount) + addUIitem(buttonCraft) + // temporarily disabled for 0.4 release + if (TerrarumAppConfiguration.VERSION_RAW >= 0x0000_000004_000001) { + addUIitem(menuButtonCraft) + addUIitem(menuButtonTechView) + } + } + + private fun filterPlayerListUsing(recipe: CraftingCodex.CraftingRecipe?) { + if (recipe == null) + playerThings.rebuild(FILTER_CAT_ALL) + else { + val items = recipe.ingredients.flatMap { + getItemCandidatesForIngredient(getPlayerInventory(), it).map { it.itm } + }.filter { it != recipe.product }.sorted() // filter out the product itself from the ingredient + + val filterFun = { pair: InventoryPair -> + items.binarySearch(pair.itm) >= 0 + } + playerThings.rebuild(filterFun, recipe.product) + } + } + + var nearbyCraftingStations = emptyList(); protected set + + fun getCraftingStationsWithinReach(): List { + val reach = 2 * INGAME.actorNowPlaying!!.actorValue.getAsDouble(AVKey.REACH)!! * (INGAME.actorNowPlaying!!.actorValue.getAsDouble(AVKey.REACHBUFF) ?: 1.0) * INGAME.actorNowPlaying!!.scale + val nearbyCraftingStations = INGAME.findKNearestActors(INGAME.actorNowPlaying!!, 256) { + it is CraftingStation && (distBetweenActors(it, INGAME.actorNowPlaying!!) < reach) + } + return nearbyCraftingStations.flatMap { (it.get() as CraftingStation).tags } + } + + private fun changeIngredient(recipe: CraftingCodex.CraftingRecipe?, old: InventoryPair, new: ItemID) { + playerThings.itemList.removeFromForceHighlightList(oldSelectedItems) + + oldSelectedItems.remove(old.itm) + oldSelectedItems.add(new) + + playerThings.itemList.addToForceHighlightList(oldSelectedItems) + playerThings.itemList.itemPage = 0 + filterPlayerListUsing(recipe) + + // change highlight status of itemListIngredients + itemListIngredients.getInventory().let { + val amount = old.qty + it.remove(old.itm, amount) + it.add(new, amount) + } + itemListIngredients.rebuild(FILTER_CAT_ALL) + } + + private fun highlightCraftingCandidateButton(recipe: CraftingCodex.CraftingRecipe?) { // a proxy function + itemListCraftable.highlightRecipe(recipe) + itemListCraftable.rebuild(FILTER_CAT_ALL) + } + + /** + * Updates Craft! button so that the button is correctly highlighted + */ + fun refreshCraftButtonStatus() { + val itemCraftable = if (itemListIngredients.getInventory().totalUniqueCount < 1) false + else getPlayerInventory().let { player -> + // check if player has enough amount of ingredients + itemListIngredients.getInventory().all { (itm, qty) -> + (player.searchByID(itm)?.qty ?: -1) >= qty * craftMult + } + } + + buttonCraft.isEnabled = itemCraftable + } + + // reset whatever player has selected to null and bring UI to its initial state + fun resetUI() { + // reset spinner + resetSpinner() + + // reset selected recipe status + recipeClicked = null + filterPlayerListUsing(recipeClicked) + highlightCraftingCandidateButton(null) + ingredients.clear() + playerThings.itemList.clearForceHighlightList() + itemListIngredients.rebuild(FILTER_CAT_ALL) + + // reset scroll + itemListCraftable.itemPage = 0 + playerThings.itemList.itemPage = 0 + + refreshCraftButtonStatus() + } + + private fun resetSpinner() { + spinnerCraftCount.resetToSmallest() + itemListIngredients.numberMultiplier = 1L + itemListCraftable.numberMultiplier = 1L + } + + private var openingClickLatched = false + + override fun show() { + nearbyCraftingStations = getCraftingStationsWithinReach() +// printdbg(this, "Nearby crafting stations: $nearbyCraftingStations") + + playerThings.setGetInventoryFun { INGAME.actorNowPlaying!!.inventory } + itemListUpdate() + + openingClickLatched = Terrarum.mouseDown + + tooltipShowing.clear() + INGAME.setTooltipMessage(null) + + resetUI() + } + + private var encumbrancePerc = 0f + + private fun itemListUpdate() { + // let itemlists be sorted + itemListCraftable.rebuild() + playerThings.rebuild(FILTER_CAT_ALL) + encumbrancePerc = getPlayerInventory().encumberment.toFloat() + } + + override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { + if (!openingClickLatched) { + return super.touchDown(screenX, screenY, pointer, button) + } + return false + } + + override fun updateImpl(delta: Float) { + // NO super.update due to an infinite recursion + this.uiItems.forEach { it.update(delta) } + + if (openingClickLatched && !Terrarum.mouseDown) openingClickLatched = false + } + + override fun renderImpl(frameDelta: Float, batch: SpriteBatch, camera: OrthographicCamera) { + // NO super.render due to an infinite recursion + this.uiItems.forEach { it.render(frameDelta, batch, camera) } + + batch.color = Color.WHITE + + // text label for two inventory grids + val craftingLabel = Lang["GAME_CRAFTING"] + val ingredientsLabel = Lang["GAME_INVENTORY_INGREDIENTS"] + + App.fontGame.draw(batch, craftingLabel, thisOffsetX + (cellsWidth - App.fontGame.getWidth(craftingLabel)) / 2, thisOffsetY - INVENTORY_NAME_TEXT_GAP) + App.fontGame.draw(batch, ingredientsLabel, thisOffsetX + (cellsWidth - App.fontGame.getWidth(ingredientsLabel)) / 2, thisOffsetY + LAST_LINE_IN_GRID - INVENTORY_NAME_TEXT_GAP) + + + // control hints + val controlHintXPos = thisOffsetX + 2f + blendNormalStraightAlpha(batch) + App.fontGame.draw(batch, controlHelp, controlHintXPos, UIInventoryFull.yEnd - 20) + + + if (INGAME.actorNowPlaying != null) { + //draw player encumb + val encumbBarXPos = thisXend - UIInventoryCells.weightBarWidth + 36 + val encumbBarYPos = UIInventoryFull.yEnd - 20 + 3f + UIInventoryCells.drawEncumbranceBar(batch, encumbBarXPos, encumbBarYPos, encumbrancePerc, INGAME.actorNowPlaying!!.inventory) + } + + + blendNormalStraightAlpha(batch) + } + + override fun doOpening(delta: Float) { + super.doOpening(delta) + INGAME.setTooltipMessage(null) + } + + override fun doClosing(delta: Float) { + super.doClosing(delta) + INGAME.setTooltipMessage(null) + } + + override fun endOpening(delta: Float) { + super.endOpening(delta) + tooltipShowing.clear() + INGAME.setTooltipMessage(null) // required! + } + + override fun endClosing(delta: Float) { + super.endClosing(delta) + resetUI() + tooltipShowing.clear() + INGAME.setTooltipMessage(null) // required! + } + + + override fun dispose() { + } + + companion object { + data class RecipeIngredientRecord( + val selectedItem: ItemID, + val howManyPlayerHas: Long, + val howManyRecipeWants: Long, + val craftingStationAvailable: Boolean, + ) + + fun getItemCandidatesForIngredient(inventory: FixtureInventory, ingredient: CraftingCodex.CraftingIngredients): List { + return if (ingredient.keyMode == CraftingCodex.CraftingItemKeyMode.TAG) { + val tags = ingredient.key.split(',') + val wantsWall = tags.contains("WALL") + // If the player has the required item, use it; otherwise, will take an item from the ItemCodex + inventory.filter { (itm, qty) -> + ItemCodex[itm]?.hasAllTags(tags) == true && qty >= ingredient.qty && (wantsWall == itm.isWall()) // true if (wants wall and is wall) or (wants no wall and is not wall) + } + } + else { + listOf(InventoryPair(ingredient.key, -1)) + } + } + + fun resolveIngredientKey(inventory: FixtureInventory, ingredient: CraftingCodex.CraftingIngredients, product: ItemID): ItemID { + val candidate = getItemCandidatesForIngredient(inventory, ingredient).filter { it.itm != product } + +// printdbg(this, "resolveIngredientKey product=$product, candidate=$candidate") + + return if (ingredient.keyMode == CraftingCodex.CraftingItemKeyMode.TAG) { + // filter out the product itself from the ingredient + candidate.maxByOrNull { it.qty }?.itm ?: ( + (ItemCodex.itemCodex.firstNotNullOfOrNull { if (it.value.hasTag(ingredient.key)) it.key else null }) ?: + throw NullPointerException("Item with tag '${ingredient.key}' not found. Possible cause: game or a module not updated or installed (ingredient: $ingredient)") + ) + } + else { + ingredient.key + } + } + + /** + * For each ingredient of the recipe, returns list of (ingredient, how many the player has the ingredient, how many the recipe wants) + */ + fun recipeToIngredientRecord(inventory: FixtureInventory, recipe: CraftingCodex.CraftingRecipe, nearbyCraftingStations: List): List { + val hasStation = if (recipe.workbench.isBlank()) true else nearbyCraftingStations.containsAll(recipe.workbench.split(',')) + return recipe.ingredients.map { ingredient -> + val selectedItem = resolveIngredientKey(inventory, ingredient, recipe.product) + val howManyPlayerHas = inventory.searchByID(selectedItem)?.qty ?: 0L + val howManyTheRecipeWants = ingredient.qty + + RecipeIngredientRecord(selectedItem, howManyPlayerHas, howManyTheRecipeWants, hasStation) + } + } + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt index 2e179efa8..46571da64 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIInventoryFull.kt @@ -247,7 +247,6 @@ class UIInventoryFull( // private val transitionalMinimap = UIInventoryMinimap(this) internal val transitionalCraftingUI = UICrafting(this) - internal val transitionalTechTreeViewUI = UITechView(this) internal val transitionalItemCells = UIInventoryCells(this) internal val transitionalEscMenu = UIInventoryEscMenu(this) val transitionPanel = UIItemHorizontalFadeSlide( @@ -257,7 +256,7 @@ class UIInventoryFull( width, App.scr.height, 1f, - listOf(transitionalCraftingUI, transitionalTechTreeViewUI), + listOf(transitionalCraftingUI), listOf(transitionalItemCells), listOf(transitionalEscMenu) ) @@ -320,7 +319,6 @@ class UIInventoryFull( transitionPanel.forcePosition(0) catBar.setSelectedPanel(0) transitionalCraftingUI.resetUI() - transitionalTechTreeViewUI.resetUI() it.setAsOpen() } diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIItemCraftingCandidateGrid.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIItemCraftingCandidateGrid.kt index 14220a574..615608164 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIItemCraftingCandidateGrid.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIItemCraftingCandidateGrid.kt @@ -2,7 +2,6 @@ package net.torvald.terrarum.modulebasegame.ui import net.torvald.terrarum.CraftingRecipeCodex import net.torvald.terrarum.ItemCodex -import net.torvald.terrarum.ui.UIItemCatBar import net.torvald.terrarum.ceilToInt import net.torvald.terrarum.gameitems.GameItem import net.torvald.terrarum.gameitems.ItemID @@ -15,7 +14,7 @@ import net.torvald.terrarum.ui.UIItemCatBar.Companion.CAT_ALL * Created by minjaesong on 2022-06-28. */ class UIItemCraftingCandidateGrid( - parentUI: UICrafting, + parentUI: UICraftingWorkbench, initialX: Int, initialY: Int, horizontalCells: Int, verticalCells: Int, drawScrollOnRightside: Boolean = false, @@ -83,7 +82,7 @@ class UIItemCraftingCandidateGrid( private fun isCraftable(player: FixtureInventory, recipe: CraftingCodex.CraftingRecipe, nearbyCraftingStations: List): Boolean { // printdbg(this, "Is this recipe craftable? $recipe") - return UICrafting.recipeToIngredientRecord(player, recipe, nearbyCraftingStations).none { + return UICraftingWorkbench.recipeToIngredientRecord(player, recipe, nearbyCraftingStations).none { // printdbg(this, " considering ingredient ${it.selectedItem}, ${it.howManyRecipeWants} is required and got ${it.howManyPlayerHas}; crafting station available? ${it.craftingStationAvailable}") it.howManyPlayerHas <= 0L || !it.craftingStationAvailable } @@ -114,14 +113,14 @@ class UIItemCraftingCandidateGrid( if (currentFilter1 == CAT_ALL) CraftingRecipeCodex.props.forEach { (_, recipes) -> recipes.forEach { - if (isCraftable((parentUI as UICrafting).getPlayerInventory(), it, (parentUI as UICrafting).nearbyCraftingStations)) { + if (isCraftable((parentUI as UICraftingWorkbench).getPlayerInventory(), it, (parentUI as UICraftingWorkbench).nearbyCraftingStations)) { craftingRecipes.add(it) } } } else CraftingRecipeCodex.getCraftableRecipesUsingTheseItems(currentFilter1).forEach { - if (isCraftable((parentUI as UICrafting).getPlayerInventory(), it, (parentUI as UICrafting).nearbyCraftingStations)) { + if (isCraftable((parentUI as UICraftingWorkbench).getPlayerInventory(), it, (parentUI as UICraftingWorkbench).nearbyCraftingStations)) { craftingRecipes.add(it) } } diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UITechView.kt b/src/net/torvald/terrarum/modulebasegame/ui/UITechView.kt index f617432da..49f600ccd 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UITechView.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UITechView.kt @@ -14,10 +14,10 @@ import net.torvald.unicode.getKeycapPC /** * Created by minjaesong on 2024-02-18. */ -class UITechView(val full: UIInventoryFull?, private val colourTheme: InventoryCellColourTheme = UIItemInventoryCellCommonRes.defaultInventoryCellTheme, +class UITechView(val inventoryUI: UIInventoryFull?, val parentContainer: UICrafting, private val colourTheme: InventoryCellColourTheme = UIItemInventoryCellCommonRes.defaultInventoryCellTheme, ) : UICanvas( - toggleKeyLiteral = if (full == null) "control_key_inventory" else null, - toggleButtonLiteral = if (full == null) "control_gamepad_start" else null + toggleKeyLiteral = if (inventoryUI == null) "control_key_inventory" else null, + toggleButtonLiteral = if (inventoryUI == null) "control_gamepad_start" else null ) { override var width = Toolkit.drawWidth @@ -34,10 +34,10 @@ class UITechView(val full: UIInventoryFull?, private val colourTheme: InventoryC // private val navbarX = posX1 + UIItemListNavBarVertical.LIST_TO_CONTROL_GAP // private val navbarY = posY1 - private val navbarX = full!!.transitionalCraftingUI.itemListCraftable.navRemoCon.posX + 12 - private val navbarY = full!!.transitionalCraftingUI.itemListCraftable.navRemoCon.posY - 8 + private val navbarX = parentContainer.transitionalCraftingUI.itemListCraftable.navRemoCon.posX + 12 + private val navbarY = parentContainer.transitionalCraftingUI.itemListCraftable.navRemoCon.posY - 8 private val navbarWidth = UIItemListNavBarVertical.WIDTH - private val navbarHeight = full!!.transitionalCraftingUI.itemListCraftable.height + private val navbarHeight = parentContainer.transitionalCraftingUI.itemListCraftable.height private val panelX = 32 + navbarX private val panelY = navbarY @@ -61,8 +61,7 @@ class UITechView(val full: UIInventoryFull?, private val colourTheme: InventoryC highlightable = true ).also { it.clickOnceListener = { _, _ -> - full?.transitionPanel?.setLeftUIto(0) - full?.transitionPanel?.uis?.get(0)?.show() + parentContainer.showCraftingUI() it.highlighted = false } }