package cz.jzitnik.game.ui; import cz.jzitnik.game.entities.items.InventoryItem; import cz.jzitnik.game.entities.items.Item; import cz.jzitnik.tui.utils.SpriteCombiner; import cz.jzitnik.tui.SpriteList; import cz.jzitnik.tui.utils.Numbers; import lombok.Getter; import lombok.Setter; import org.jline.terminal.Terminal; import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Optional; @Getter public class Inventory implements Serializable { public static final int INVENTORY_SIZE_PX = 470; public static final int COLUMN_AMOUNT = 5; public static final int ROW_AMOUNT = 4; private final InventoryItem[] items = new InventoryItem[20]; private final InventoryItem[] hotbar = new InventoryItem[9]; private transient SmallCraftingTable smallCraftingTable = new SmallCraftingTable(this); @Serial private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { in.defaultReadObject(); smallCraftingTable = new SmallCraftingTable(this); } @Setter private int itemInhHandIndex = 0; @Setter private int selectedItemInv = -1; @Setter private boolean rightClick = false; public Optional getItemInHand() { if (hotbar[itemInhHandIndex] == null) { return Optional.empty(); } return Optional.of(hotbar[itemInhHandIndex].getItem().getLast()); } public void decreaseItemInHand() { if (hotbar[itemInhHandIndex] == null) { return; } if (hotbar[itemInhHandIndex].getAmount() == 1) { hotbar[itemInhHandIndex] = null; return; } hotbar[itemInhHandIndex].decrease(); } private void placeItem(Item item) { var inventoryItem = new InventoryItem(item); // Try to place to hotbar for (int i = 0; i < hotbar.length; i++) { if (hotbar[i] == null) { hotbar[i] = inventoryItem; return; } } // Try to place to inventory for (int i = 0; i < items.length; i++) { if (items[i] == null) { items[i] = inventoryItem; break; } } // If inventory is full the item is lost } public void addItem(InventoryItem item) { for (int i = 0; i < item.getAmount(); i++) { addItem(item.getItem().get(i)); } } public void addItem(List item) { for (Item i : item) { addItem(i); } } public void addItem(Item item) { if (!item.isStackable()) { placeItem(item); return; } // Try to stack in hotbar for (InventoryItem value : hotbar) { if (value != null && value.getItem().getFirst().equals(item) && value.getAmount() < item.getStackAmount()) { value.add(item); return; } } // Try to stack in inventory for (InventoryItem inventoryItem : items) { if (inventoryItem != null && inventoryItem.getItem().getFirst().equals(item) && inventoryItem.getAmount() < item.getStackAmount()) { inventoryItem.add(item); return; } } // If stacking didn't work try to add it to inventory placeItem(item); } private String getHotbarBackground() { StringBuilder sprite = new StringBuilder(); for (int i = 0; i < 25; i++) { sprite.append("\033[38;5;245;48;5;245m▓".repeat(50)); sprite.append("\n"); } return sprite.toString(); } public void renderHotbar(StringBuilder buffer, SpriteList spriteList, Terminal terminal, boolean isFull) { int termWidth = terminal.getWidth(); int startLeft = (termWidth / 2) - (INVENTORY_SIZE_PX / 2); List sprites = getSprites(hotbar, spriteList, (isFull ? selectedItemInv - 20 : itemInhHandIndex)); for (int i = 0; i < 26; i++) { // Empty left space buffer.append("\033[0m ".repeat(Math.max(0, startLeft))); if (i == 0 || i == 25) { buffer.append("\033[38;5;231;48;5;231m▓".repeat(INVENTORY_SIZE_PX - 2)); } else { for (int j = 0; j < 9; j++) { buffer.append("\033[38;5;231;48;5;231m▓".repeat(2)); if (sprites.get(j) == null) { buffer.append("\033[0m ".repeat(50)); continue; } String x = sprites.get(j).split("\n")[i]; buffer.append(x); } } buffer.append("\033[38;5;231;48;5;231m▓".repeat(2)); buffer.append("\033[0m\n"); } } public void renderFull(StringBuilder buffer, Terminal terminal, SpriteList spriteList, boolean includeCrafting, Optional moveTopCustom) { int widthPixels = COLUMN_AMOUNT * (50 + 4) + 2; int heightPixels = ROW_AMOUNT * (25 + 1); int moveLeft = Math.max(0, (terminal.getWidth() / 2) - (widthPixels / 2)); int moveTop = moveTopCustom.orElse((terminal.getHeight() / 2) - (heightPixels / 2)); List sprites = getSprites(items, spriteList, selectedItemInv); // Top center buffer.append("\n".repeat(Math.max(0, moveTopCustom.isPresent() ? 0 : moveTop))); buffer.append("\033[0m ".repeat(moveLeft)); buffer.append("\033[38;5;231;48;5;231m▓".repeat(widthPixels)).append("\033[0m\n"); String[] craftingTable = smallCraftingTable.render(spriteList).toString().split("\n"); int spacesFromTop = (ROW_AMOUNT - SmallCraftingTable.ROW_AMOUNT) / 2; for (int i = 0; i < ROW_AMOUNT; i++) { for (int j = 0; j < 26; j++) { buffer.append("\033[0m ".repeat(moveLeft)); if (j < 25) { buffer.append("\033[38;5;231;48;5;231m▓"); for (int k = 0; k < COLUMN_AMOUNT; k++) { buffer.append("\033[38;5;231;48;5;231m▓".repeat(2)); var item = items[i * COLUMN_AMOUNT + k]; if (item == null) { buffer.append("\033[0m ".repeat(50)); buffer.append("\033[38;5;231;48;5;231m▓".repeat(2)); continue; } var sprite = sprites.get(i * COLUMN_AMOUNT + k).split("\n"); buffer.append(sprite[j]); buffer.append("\033[38;5;231;48;5;231m▓".repeat(2)); } buffer.append("\033[38;5;231;48;5;231m▓"); } else { buffer.append("\033[38;5;231;48;5;231m▓".repeat(widthPixels)); if (i == 0 && includeCrafting) { buffer.append("\033[0m").append(" ".repeat(20)); buffer.append("\033[38;5;231;48;5;231m▓".repeat(106)); } } if (i + 1 > spacesFromTop && includeCrafting) { int craftingIndex = ((i - spacesFromTop) * 26) + j; if (craftingIndex < craftingTable.length) { buffer.append("\033[0m").append(" ".repeat(20)); buffer.append(craftingTable[craftingIndex]); } } buffer.append("\n"); } } buffer.append("\n".repeat(10)); renderHotbar(buffer, spriteList, terminal, true); } public List getSprites(InventoryItem[] items, SpriteList spriteList, int selectedItem) { List sprites = new ArrayList<>(); for (int i = 0; i < items.length; i++) { var item = items[i]; if (item == null) { if (i == selectedItem) { sprites.add(getHotbarBackground()); continue; } sprites.add(null); continue; } StringBuilder stringBuilder = new StringBuilder(); if (item.getItem().getFirst().getDurability() != 0 || item.getItem().getFirst().getDurability() == item.getItem().getFirst().getMaxDurability()) { stringBuilder.append(("\033[0m ".repeat(50) + "\n").repeat(21)); stringBuilder.append("\033[0m ".repeat(4)); int width = (int) (((double) item.getItem().getFirst().getDurability() / item.getItem().getFirst().getMaxDurability()) * 42); stringBuilder.append("\033[38;5;231;48;5;231m▓".repeat(width)); stringBuilder.append("\033[40m ".repeat(42 - width)); stringBuilder.append("\033[0m ".repeat(4)); stringBuilder.append("\n"); stringBuilder.append(("\033[0m ".repeat(50) + "\n").repeat(3)); } String sprite; if (i == selectedItem) { sprite = SpriteCombiner .combineTwoSprites(getHotbarBackground(), SpriteCombiner.combineTwoSprites( item.getItem().getFirst().getSpriteState().isPresent() ? spriteList.getSprite(item.getItem().getFirst().getSprite()) .getSprite(item.getItem().getFirst().getSpriteState() .get()) : spriteList.getSprite(item.getItem().getFirst().getSprite()) .getSprite(), Numbers.getNumberSprite(item.getAmount()))); } else { sprite = SpriteCombiner.combineTwoSprites( item.getItem().getFirst().getSpriteState().isPresent() ? spriteList.getSprite(item.getItem().getFirst().getSprite()) .getSprite(item.getItem().getFirst().getSpriteState().get()) : spriteList.getSprite(item.getItem().getFirst().getSprite()).getSprite(), Numbers.getNumberSprite(item.getAmount())); } if (item.getItem().getFirst().getDurability() == 0 || item.getItem().getFirst().getDurability() == item.getItem().getFirst().getMaxDurability()) { sprites.add(sprite); } else { sprites.add(SpriteCombiner.combineTwoSprites(sprite, stringBuilder.toString())); } } return sprites; } public InventoryDTO getItem(int index, Optional i) { if (index < 20) { // Normal inventory return new InventoryDTO(items, index); } else if (index >= 50) { return new InventoryDTO(i.get(), index - 50); } else if (index >= 29) { // Small crafting table return new InventoryDTO(smallCraftingTable.getItems(), index - 29); } else { // Hotbar return new InventoryDTO(hotbar, index - 20); } } public InventoryItem getSelectedItemNo(Optional i) { InventoryDTO data = getItem(selectedItemInv, i); if (selectedItemInv == -1) { return null; } return data.getObj()[data.getIndex()]; } public InventoryItem getSelectedItem(Optional i) { InventoryItem temp; InventoryDTO data = getItem(selectedItemInv, i); temp = data.getObj()[data.getIndex()]; data.getObj()[data.getIndex()] = null; selectedItemInv = -1; rightClick = false; return temp; } public InventoryItem getOne(Optional i) { InventoryDTO inventoryItem = getItem(selectedItemInv, i); if (inventoryItem.getIndex() == -1) { return null; } InventoryItem item = inventoryItem.getObj()[inventoryItem.getIndex()]; if (item.getAmount() == 1) { return getSelectedItem(i); } rightClick = false; var ij = new InventoryItem(1, item.getItem().getLast()); item.decrease(); return ij; } public InventoryItem getHalf(Optional i) { InventoryDTO inventoryItem = getItem(selectedItemInv, i); InventoryItem item = inventoryItem.getObj()[inventoryItem.getIndex()]; if (item.getAmount() == 1) { return getSelectedItem(i); } int half = item.getAmount() / 2; var inv = new InventoryItem(); for (int j = 0; j < half; j++) { inv.add(item.getItem().getLast()); item.decrease(); } selectedItemInv = -1; rightClick = false; return inv; } public boolean hasSelectedItem() { return selectedItemInv != -1; } public boolean mergeItems(int indexFrom, int indexTo, Optional i) { InventoryDTO fromData = getItem(indexFrom, i); InventoryDTO toData = getItem(indexTo, i); InventoryItem fromItem = fromData.getObj()[fromData.getIndex()]; InventoryItem toItem = toData.getObj()[toData.getIndex()]; if (fromItem == null || toItem == null) { return false; // Nothing to merge } if (!fromItem.getItem().getFirst().isStackable() || !toItem.getItem().getFirst().isStackable()) { return false; } if (!fromItem.getItem().getFirst().equals(toItem.getItem().getFirst())) { return false; // Different items cannot be merged } while (true) { if (toItem.getAmount() == 64) { break; } if (fromItem.getAmount() == 0) { fromData.getObj()[fromData.getIndex()] = null; break; } toItem.add(fromItem.getItem().getLast()); fromItem.decrease(); } selectedItemInv = -1; return true; } public void mergeOne(int fromIndex, int toIndex, Optional i) { InventoryDTO fromSlot = getItem(fromIndex, i); InventoryDTO toSlot = getItem(toIndex, i); InventoryItem fromItem = fromSlot.getObj()[fromSlot.getIndex()]; InventoryItem toItem = toSlot.getObj()[toSlot.getIndex()]; if (fromItem == null || !fromItem.getItem().getFirst().equals(toItem.getItem().getFirst()) || toItem.getAmount() >= fromItem.getItem().getFirst().getStackAmount()) { return; // Can't merge } // Move only one item toItem.add(fromItem.getItem().getLast()); fromItem.decrease(); // Remove source item if empty if (fromItem.getAmount() <= 0) { fromSlot.getObj()[fromSlot.getIndex()] = null; selectedItemInv = -1; } } public void exit() { // Put all items from crafting to inv for (int i = 0; i < smallCraftingTable.getItems().length; i++) { InventoryItem item = smallCraftingTable.getItems()[i]; if (item == null) { continue; } addItem(item); smallCraftingTable.getItems()[i] = null; } } }