feat: Implemented crafting

This commit is contained in:
Jakub Žitník 2025-02-23 22:02:22 +01:00
parent aa5915914e
commit 03a663258b
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
18 changed files with 578 additions and 159 deletions

View File

@ -10,6 +10,9 @@ import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder; import org.jline.terminal.TerminalBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
import static cz.jzitnik.game.ui.InventoryClickHandler.click;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
@ -45,7 +48,7 @@ public class Main {
MouseEvent mouseEvent = terminal.readMouseEvent(); MouseEvent mouseEvent = terminal.readMouseEvent();
switch (game.getWindow()) { switch (game.getWindow()) {
case WORLD -> mouseHandler.handle(mouseEvent); case WORLD -> mouseHandler.handle(mouseEvent);
case INVENTORY -> game.getInventory().click(mouseEvent, terminal, screenRenderer, game); case INVENTORY -> click(mouseEvent, terminal, screenRenderer, game, Optional.empty());
} }
} }
} }

View File

@ -236,7 +236,7 @@ public class Game {
} }
if (!blocks.stream().allMatch(Block::isGhost)) { if (!blocks.stream().allMatch(Block::isGhost)) {
return; RightClickHandler.handle(x, y, this);
} }
if (!(inventory.getItemInHand().isPresent() && inventory.getItemInHand().get().getType() == ItemType.BLOCK)) { if (!(inventory.getItemInHand().isPresent() && inventory.getItemInHand().get().getType() == ItemType.BLOCK)) {

View File

@ -0,0 +1,7 @@
package cz.jzitnik.game;
public class RightClickHandler {
public static void handle(int x, int y, Game game) {
}
}

View File

@ -16,6 +16,7 @@ public class SpriteLoader {
STONE, STONE,
BEDROCK, BEDROCK,
BREAKING, BREAKING,
CRAFTING_TABLE,
OAK_LOG, OAK_LOG,
OAK_LEAF, OAK_LEAF,
@ -24,7 +25,9 @@ public class SpriteLoader {
// Items // Items
ITEM_DIRT, ITEM_DIRT,
ITEM_OAK_LOG, ITEM_OAK_LOG,
ITEM_OAK_PLANKS ITEM_OAK_PLANKS,
ITEM_STICK,
ITEM_CRAFTING_TABLE
} }
public static final HashMap<SPRITES, Sprite> SPRITES_MAP = new HashMap<>(); public static final HashMap<SPRITES, Sprite> SPRITES_MAP = new HashMap<>();
@ -40,10 +43,13 @@ public class SpriteLoader {
SPRITES_MAP.put(SPRITES.OAK_LOG, new OakLog()); SPRITES_MAP.put(SPRITES.OAK_LOG, new OakLog());
SPRITES_MAP.put(SPRITES.OAK_LEAF, new OakLeaf()); SPRITES_MAP.put(SPRITES.OAK_LEAF, new OakLeaf());
SPRITES_MAP.put(SPRITES.OAK_PLANKS, new SimpleSprite("oak_planks.ans")); SPRITES_MAP.put(SPRITES.OAK_PLANKS, new SimpleSprite("oak_planks.ans"));
SPRITES_MAP.put(SPRITES.CRAFTING_TABLE, new SimpleSprite("crafting_table.ans"));
SPRITES_MAP.put(SPRITES.ITEM_DIRT, new SimpleSprite("items/dirt.ans")); SPRITES_MAP.put(SPRITES.ITEM_DIRT, new SimpleSprite("items/dirt.ans"));
SPRITES_MAP.put(SPRITES.ITEM_OAK_LOG, new SimpleSprite("items/oak_log.ans")); SPRITES_MAP.put(SPRITES.ITEM_OAK_LOG, new SimpleSprite("items/oak_log.ans"));
SPRITES_MAP.put(SPRITES.ITEM_OAK_PLANKS, new SimpleSprite("items/oak_planks.ans")); SPRITES_MAP.put(SPRITES.ITEM_OAK_PLANKS, new SimpleSprite("items/oak_planks.ans"));
SPRITES_MAP.put(SPRITES.ITEM_STICK, new SimpleSprite("items/stick.ans"));
SPRITES_MAP.put(SPRITES.ITEM_CRAFTING_TABLE, new SimpleSprite("items/crafting_table.ans"));
} }
public static SpriteList<SPRITES> load() { public static SpriteList<SPRITES> load() {

View File

@ -1,6 +1,6 @@
package cz.jzitnik.game.crafting; package cz.jzitnik.game.crafting;
import cz.jzitnik.game.items.Item; import cz.jzitnik.game.items.InventoryItem;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@ -10,5 +10,5 @@ import java.util.function.Supplier;
@Getter @Getter
public class CraftingRecipe { public class CraftingRecipe {
private String[][] recipe; private String[][] recipe;
private Supplier<Item> itemSupplier; private Supplier<InventoryItem> itemSupplier;
} }

View File

@ -1,18 +1,133 @@
package cz.jzitnik.game.crafting; package cz.jzitnik.game.crafting;
import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.items.ItemBlockSupplier; import cz.jzitnik.game.items.ItemBlockSupplier;
import java.util.ArrayList; import java.util.*;
import java.util.List;
public class CraftingRecipeList { public class CraftingRecipeList {
private static List<CraftingRecipe> recipes = new ArrayList<>(); private static List<CraftingRecipe> recipes = new ArrayList<>();
static { static {
String[][] pattern = new String[9][9]; // Oak planks
for (int i = 0; i < 9; i++) { recipes.add(new CraftingRecipe(new String[][]{
pattern[i][i] = "oak_log"; {"oak_log", null, null},
{null, null, null},
{null, null, null}
}, () -> new InventoryItem(4, ItemBlockSupplier.Items.oakPlanks())));
recipes.add(new CraftingRecipe(new String[][]{
{"oak_planks", "oak_planks", null},
{"oak_planks", "oak_planks", null},
{null, null, null}
}, () -> new InventoryItem(1, ItemBlockSupplier.Items.craftingTable())));
recipes.add(new CraftingRecipe(new String[][]{
{"oak_planks", null, null},
{"oak_planks", null, null},
{null, null, null}
}, () -> new InventoryItem(4, ItemBlockSupplier.Items.stick())));
}
// 2x2 crafting
public static Optional<CraftingRecipe> getRecipeSmall(String[] r) {
for (CraftingRecipe recipe : recipes) {
if (matchesByItemSet(recipe.getRecipe(), r)) {
return Optional.of(recipe);
}
} }
recipes.add(new CraftingRecipe(pattern, ItemBlockSupplier.Items::oakPlanks)); return Optional.empty();
}
// 3x3 crafting
public static Optional<CraftingRecipe> getRecipeFull(String[] r) {
for (CraftingRecipe recipe : recipes) {
if (matchesByItemSet(recipe.getRecipe(), r)) {
return Optional.of(recipe);
}
}
return Optional.empty();
}
public static String[][] trimNullEdges(String[][] array) {
int n = array.length;
int minRow = n, maxRow = -1, minCol = n, maxCol = -1;
// Find the bounding box of non-null values
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (array[i][j] != null) {
minRow = Math.min(minRow, i);
maxRow = Math.max(maxRow, i);
minCol = Math.min(minCol, j);
maxCol = Math.max(maxCol, j);
}
}
}
// If all elements are null, return an empty array
if (maxRow == -1) {
return new String[0][0];
}
int newSizeRow = maxRow - minRow + 1;
int newSizeCol = maxCol - minCol + 1;
String[][] trimmedArray = new String[newSizeRow][newSizeCol];
// Copy the relevant portion of the array
for (int i = 0; i < newSizeRow; i++) {
System.arraycopy(array[minRow + i], minCol, trimmedArray[i], 0, newSizeCol);
}
return trimmedArray;
}
public static boolean are2DArraysIdentical(String[][] array1, String[][] array2) {
if (array1.length != array2.length) {
return false;
}
for (int i = 0; i < array1.length; i++) {
if (array1[i].length != array2[i].length) {
return false;
}
for (int j = 0; j < array1[i].length; j++) {
if (array1[i][j] == null && array2[i][j] != null) {
return false;
}
if (array1[i][j] != null && !array1[i][j].equals(array2[i][j])) {
return false;
}
}
}
return true;
}
private static boolean matchesByItemSet(String[][] recipe, String[] grid) {
String[][] finalGrid = new String[3][3];
if (grid.length == 4) {
finalGrid[0][0] = grid[0];
finalGrid[0][1] = grid[1];
finalGrid[1][0] = grid[2];
finalGrid[1][1] = grid[3];
} else {
finalGrid[0][0] = grid[0];
finalGrid[0][1] = grid[1];
finalGrid[0][2] = grid[2];
finalGrid[1][0] = grid[3];
finalGrid[1][1] = grid[4];
finalGrid[1][2] = grid[5];
finalGrid[2][0] = grid[6];
finalGrid[2][1] = grid[7];
finalGrid[2][2] = grid[8];
}
var croppedRecipe = trimNullEdges(recipe);
var croppedGrid = trimNullEdges(finalGrid);
return are2DArraysIdentical(croppedRecipe, croppedGrid);
} }
} }

View File

@ -29,6 +29,14 @@ public class Item {
this.block = Optional.of(block); this.block = Optional.of(block);
} }
public Item(String id, String name, ItemType type, SpriteLoader.SPRITES sprite) {
this.id = id;
this.name = name;
this.type = type;
this.sprite = sprite;
this.block = Optional.empty();
}
public void use() { public void use() {
durability--; durability--;
} }

View File

@ -20,7 +20,10 @@ public class ItemBlockSupplier {
return new Item("oak_log", "Oak log", ItemType.BLOCK, SpriteLoader.SPRITES.ITEM_OAK_LOG, ref); return new Item("oak_log", "Oak log", ItemType.BLOCK, SpriteLoader.SPRITES.ITEM_OAK_LOG, ref);
} }
public static Item oakPlanks(Block ref) { public static Item oakPlanks(Block ref) {
return new Item("oak_log", "Oak planks", ItemType.BLOCK, SpriteLoader.SPRITES.ITEM_OAK_PLANKS, ref); return new Item("oak_planks", "Oak planks", ItemType.BLOCK, SpriteLoader.SPRITES.ITEM_OAK_PLANKS, ref);
}
public static Item craftingTable(Block ref) {
return new Item("crafting_table", "Crafting table", ItemType.BLOCK, SpriteLoader.SPRITES.ITEM_CRAFTING_TABLE, ref);
} }
} }
@ -50,6 +53,11 @@ public class ItemBlockSupplier {
block.setDrops(List.of(Helper.oakPlanks(block))); block.setDrops(List.of(Helper.oakPlanks(block)));
return block; return block;
} }
public static Block craftingTable() {
var block = new Block("crafting_table", SpriteLoader.SPRITES.CRAFTING_TABLE, 3, ItemType.AXE, new ArrayList<>());
block.setDrops(List.of(Helper.craftingTable(block)));
return block;
}
} }
// I hate this but whatever // I hate this but whatever
@ -60,5 +68,11 @@ public class ItemBlockSupplier {
public static Item oakPlanks() { public static Item oakPlanks() {
return Helper.oakPlanks(Blocks.oakPlanks()); return Helper.oakPlanks(Blocks.oakPlanks());
} }
public static Item stick() {
return new Item("stick", "Stick", ItemType.USELESS_ITEM, SpriteLoader.SPRITES.ITEM_STICK);
}
public static Item craftingTable() {
return Helper.craftingTable(Blocks.craftingTable());
}
} }
} }

View File

@ -5,5 +5,6 @@ public enum ItemType {
SHOVEL, SHOVEL,
AXE, AXE,
SHEARS, SHEARS,
BLOCK BLOCK,
USELESS_ITEM
} }

View File

@ -1,15 +1,12 @@
package cz.jzitnik.game.ui; package cz.jzitnik.game.ui;
import cz.jzitnik.game.Game;
import cz.jzitnik.game.items.InventoryItem; import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.items.Item; import cz.jzitnik.game.items.Item;
import cz.jzitnik.tui.ScreenRenderer;
import cz.jzitnik.tui.SpriteCombiner; import cz.jzitnik.tui.SpriteCombiner;
import cz.jzitnik.game.sprites.ui.Number;
import cz.jzitnik.tui.SpriteList; import cz.jzitnik.tui.SpriteList;
import cz.jzitnik.utils.Numbers;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal; import org.jline.terminal.Terminal;
import java.util.ArrayList; import java.util.ArrayList;
@ -18,9 +15,9 @@ import java.util.Optional;
@Getter @Getter
public class Inventory { public class Inventory {
private static final int INVENTORY_SIZE_PX = 470; public static final int INVENTORY_SIZE_PX = 470;
private static int COLUMN_AMOUNT = 5; public static final int COLUMN_AMOUNT = 5;
private static int ROW_AMOUNT = 4; public static final int ROW_AMOUNT = 4;
private InventoryItem[] items = new InventoryItem[20]; private InventoryItem[] items = new InventoryItem[20];
private InventoryItem[] hotbar = new InventoryItem[9]; private InventoryItem[] hotbar = new InventoryItem[9];
@ -30,6 +27,8 @@ public class Inventory {
private int itemInhHandIndex = 0; private int itemInhHandIndex = 0;
@Setter @Setter
private int selectedItemInv = -1; private int selectedItemInv = -1;
@Setter
private boolean rightClick = false;
public Optional<Item> getItemInHand() { public Optional<Item> getItemInHand() {
if (hotbar[itemInhHandIndex] == null) { if (hotbar[itemInhHandIndex] == null) {
@ -73,6 +72,12 @@ public class Inventory {
// If inventory is full the item is lost // If inventory is full the item is lost
} }
public void addItem(InventoryItem item) {
for (int i = 0; i < item.getAmount(); i++) {
addItem(item.getItem());
}
}
public void addItem(Item item) { public void addItem(Item item) {
if (!item.isStackable()) { if (!item.isStackable()) {
placeItem(item); placeItem(item);
@ -99,42 +104,6 @@ public class Inventory {
placeItem(item); placeItem(item);
} }
private Number.NumberState getNumberState(int digit) {
return switch (digit) {
case 0 -> Number.NumberState.ZERO;
case 1 -> Number.NumberState.ONE;
case 2 -> Number.NumberState.TWO;
case 3 -> Number.NumberState.THREE;
case 4 -> Number.NumberState.FOUR;
case 5 -> Number.NumberState.FIVE;
case 6 -> Number.NumberState.SIX;
case 7 -> Number.NumberState.SEVEN;
case 8 -> Number.NumberState.EIGHT;
case 9 -> Number.NumberState.NINE;
default -> throw new IllegalArgumentException("Unexpected number: " + digit);
};
}
private String getNumberSprite(int number) {
Number nm = new Number();
if (number <= 9) {
Number.NumberState numberState = getNumberState(number);
return Number.fixForHotbar(nm.getSprite(numberState), 1);
}
String numStr = Integer.toString(number); // Convert to string
char firstChar = numStr.charAt(0); // Get first digit
char secondChar = numStr.charAt(1); // Get second digit
int firstDigit = Character.getNumericValue(firstChar);
int secondDigit = Character.getNumericValue(secondChar);
Number.NumberState numberState1 = getNumberState(firstDigit);
Number.NumberState numberState2 = getNumberState(secondDigit);
return SpriteCombiner.combineTwoSprites( Number.fixForHotbar(nm.getSprite(numberState1), 2), Number.fixForHotbar(nm.getSprite(numberState2), 1));
}
private String getHotbarBackground() { private String getHotbarBackground() {
StringBuilder sprite = new StringBuilder(); StringBuilder sprite = new StringBuilder();
@ -174,12 +143,12 @@ public class Inventory {
} }
} }
public void renderFull(StringBuilder buffer, Terminal terminal, SpriteList spriteList) { public void renderFull(StringBuilder buffer, Terminal terminal, SpriteList spriteList, boolean includeCrafting, Optional<Integer> moveTopCustom) {
int widthPixels = COLUMN_AMOUNT * (50 + 4) + 2; int widthPixels = COLUMN_AMOUNT * (50 + 4) + 2;
int heightPixels = ROW_AMOUNT * (25 + 1); int heightPixels = ROW_AMOUNT * (25 + 1);
int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2); int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2);
int moveTop = (terminal.getHeight() / 2) - (heightPixels / 2); int moveTop = moveTopCustom.orElse((terminal.getHeight() / 2) - (heightPixels / 2));
List<String> sprites = getSprites(items, spriteList, selectedItemInv); List<String> sprites = getSprites(items, spriteList, selectedItemInv);
@ -218,7 +187,7 @@ public class Inventory {
} }
} }
if (i + 1 > spacesFromTop) { if (i + 1 > spacesFromTop && includeCrafting) {
int craftingIndex = ((i - spacesFromTop) * 26) + j; int craftingIndex = ((i - spacesFromTop) * 26) + j;
if (craftingIndex >= 0 && craftingIndex < craftingTable.length) { if (craftingIndex >= 0 && craftingIndex < craftingTable.length) {
@ -236,69 +205,6 @@ public class Inventory {
renderHotbar(buffer, spriteList, terminal, true); renderHotbar(buffer, spriteList, terminal, true);
} }
public void click(MouseEvent mouseEvent, Terminal terminal, ScreenRenderer screenRenderer, Game game) {
if (mouseEvent.getType() != MouseEvent.Type.Pressed) {
return;
}
int x = mouseEvent.getX();
int y = mouseEvent.getY();
int widthPixels = COLUMN_AMOUNT * (50 + 4) + 2;
int heightPixels = ROW_AMOUNT * (25 + 1);
int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2);
int moveTop = (terminal.getHeight() / 2) - (heightPixels / 2);
int fx = x - moveLeft;
int fy = y - moveTop;
int startLeftHotbar = (terminal.getWidth() / 2) - (INVENTORY_SIZE_PX / 2);
int startTopHotbar = moveTop + heightPixels + 10;
int startLeftCrafting = moveLeft + widthPixels + 20;
int startTopCrafting = moveTop + 26;
if (x >= startLeftCrafting && y >= startTopCrafting && y <= startTopCrafting + 52 && x <= startLeftCrafting + 106 + 54 + 7) {
smallCraftingTable.click(mouseEvent.getX() - startLeftCrafting, mouseEvent.getY() - startTopCrafting);
screenRenderer.render(game);
return;
}
if (x >= startLeftHotbar && y >= startTopHotbar && x <= startLeftHotbar + INVENTORY_SIZE_PX && y <= startTopHotbar + 26) {
int index = (x - startLeftHotbar) / 51;
if (hotbar[index] == null && selectedItemInv != -1) {
hotbar[index] = getSelectedItem();
} else if (hotbar[index] != null) {
selectedItemInv = 20 + index;
}
screenRenderer.render(game);
return;
}
if (!(fx >= 0 && fx <= widthPixels && fy >=0 && fy <= heightPixels)) {
return;
}
switch (mouseEvent.getButton()) {
case Button1 -> {
int blockX = fx / 54;
int blockY = fy / 26;
InventoryItem inventoryItem = items[blockY * COLUMN_AMOUNT + blockX];
if (inventoryItem != null) {
selectedItemInv = blockY * COLUMN_AMOUNT + blockX;
}
if (inventoryItem == null && selectedItemInv != -1) {
items[blockY * COLUMN_AMOUNT + blockX] = getSelectedItem();
}
}
}
screenRenderer.render(game);
}
public List<String> getSprites(InventoryItem[] items, SpriteList spriteList, int selectedItem) { public List<String> getSprites(InventoryItem[] items, SpriteList spriteList, int selectedItem) {
List<String> sprites = new ArrayList<>(); List<String> sprites = new ArrayList<>();
@ -318,37 +224,127 @@ public class Inventory {
if (i == selectedItem) { if (i == selectedItem) {
sprites.add(SpriteCombiner.combineTwoSprites(getHotbarBackground(), SpriteCombiner.combineTwoSprites( sprites.add(SpriteCombiner.combineTwoSprites(getHotbarBackground(), SpriteCombiner.combineTwoSprites(
spriteList.getSprite(item.getItem().getSprite()).getSprite(), spriteList.getSprite(item.getItem().getSprite()).getSprite(),
getNumberSprite(item.getAmount()) Numbers.getNumberSprite(item.getAmount())
))); )));
continue; continue;
} }
sprites.add(SpriteCombiner.combineTwoSprites( sprites.add(SpriteCombiner.combineTwoSprites(
spriteList.getSprite(item.getItem().getSprite()).getSprite(), spriteList.getSprite(item.getItem().getSprite()).getSprite(),
getNumberSprite(item.getAmount()) Numbers.getNumberSprite(item.getAmount())
)); ));
} }
return sprites; return sprites;
} }
public InventoryDTO getItem(int index) {
InventoryItem temp;
if (index < 20) {
// Normal inventory
return new InventoryDTO(items, index);
} else if (index >= 29) {
// Small crafting table
return new InventoryDTO(smallCraftingTable.getItems(), index - 29);
} else {
// Hotbar
return new InventoryDTO(hotbar, index - 20);
}
}
public InventoryItem getSelectedItem() { public InventoryItem getSelectedItem() {
InventoryItem temp; InventoryItem temp;
if (selectedItemInv < 20) { InventoryDTO data = getItem(selectedItemInv);
temp = items[selectedItemInv]; temp = data.getObj()[data.getIndex()];
items[selectedItemInv] = null; data.getObj()[data.getIndex()] = null;
} else if (selectedItemInv >= 29) {
int index = selectedItemInv - 29;
temp = smallCraftingTable.getItems()[index];
smallCraftingTable.getItems()[index] = null;
} else {
int index = selectedItemInv - 20;
temp = hotbar[index];
hotbar[index] = null;
}
selectedItemInv = -1; selectedItemInv = -1;
rightClick = false;
return temp; return temp;
} }
public InventoryItem getOne() {
InventoryDTO inventoryItem = getItem(selectedItemInv);
InventoryItem item = inventoryItem.getObj()[inventoryItem.getIndex()];
if (item.getAmount() == 1) {
return getSelectedItem();
}
item.setAmount(item.getAmount() - 1);
rightClick = false;
return new InventoryItem(1, item.getItem());
}
public InventoryItem getHalf() {
InventoryDTO inventoryItem = getItem(selectedItemInv);
InventoryItem item = inventoryItem.getObj()[inventoryItem.getIndex()];
if (item.getAmount() == 1) {
return getSelectedItem();
}
int half = item.getAmount() / 2;
item.setAmount(item.getAmount() - half);
selectedItemInv = -1;
rightClick = false;
return new InventoryItem(half, item.getItem());
}
public boolean hasSelectedItem() {
return selectedItemInv != -1;
}
public void mergeItems(int indexFrom, int indexTo) {
InventoryDTO fromData = getItem(indexFrom);
InventoryDTO toData = getItem(indexTo);
InventoryItem fromItem = fromData.getObj()[fromData.getIndex()];
InventoryItem toItem = toData.getObj()[toData.getIndex()];
if (fromItem == null || toItem == null) {
return; // Nothing to merge
}
if (!fromItem.getItem().equals(toItem.getItem())) {
return; // Different items cannot be merged
}
int totalAmount = fromItem.getAmount() + toItem.getAmount();
if (totalAmount > 64) {
toItem.setAmount(64);
fromItem.setAmount(totalAmount - 64);
} else {
toItem.setAmount(totalAmount);
fromData.getObj()[fromData.getIndex()] = null; // Remove the source item after merging
}
selectedItemInv = -1;
}
public void mergeOne(int fromIndex, int toIndex) {
InventoryDTO fromSlot = getItem(fromIndex);
InventoryDTO toSlot = getItem(toIndex);
InventoryItem fromItem = fromSlot.getObj()[fromSlot.getIndex()];
InventoryItem toItem = toSlot.getObj()[toSlot.getIndex()];
if (fromItem == null || !fromItem.getItem().equals(toItem.getItem()) || toItem.getAmount() >= fromItem.getItem().getStackAmount()) {
return; // Can't merge
}
// Move only one item
toItem.setAmount(toItem.getAmount() + 1);
fromItem.setAmount(fromItem.getAmount() - 1);
// Remove source item if empty
if (fromItem.getAmount() <= 0) {
fromSlot.getObj()[fromSlot.getIndex()] = null;
selectedItemInv = -1;
}
}
} }

View File

@ -0,0 +1,120 @@
package cz.jzitnik.game.ui;
import cz.jzitnik.game.Game;
import cz.jzitnik.tui.ScreenRenderer;
import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;
import java.util.Optional;
import static cz.jzitnik.game.ui.Inventory.*;
public class InventoryClickHandler {
public static void click(MouseEvent mouseEvent, Terminal terminal, ScreenRenderer screenRenderer, Game game, Optional<Integer> moveTopCustom) {
if (mouseEvent.getType() != MouseEvent.Type.Pressed) return;
var inventory = game.getInventory();
int x = mouseEvent.getX();
int y = mouseEvent.getY();
int moveLeft = calculateMoveLeft(terminal);
int moveTop = moveTopCustom.orElse(calculateMoveTop(terminal));
if (handleCraftingTableClick(mouseEvent, x, y, inventory, screenRenderer, game, moveLeft, moveTop)) return;
if (handleHotbarClick(mouseEvent, x, y, inventory, screenRenderer, game, terminal, moveTop)) return;
handleInventoryClick(mouseEvent, x, y, inventory, screenRenderer, game, moveLeft, moveTop);
}
private static int calculateMoveLeft(Terminal terminal) {
return (terminal.getWidth() / 2) - ((COLUMN_AMOUNT * (50 + 4) + 2) / 2);
}
private static int calculateMoveTop(Terminal terminal) {
return (terminal.getHeight() / 2) - ((ROW_AMOUNT * (25 + 1)) / 2);
}
private static boolean handleCraftingTableClick(MouseEvent mouseEvent, int x, int y, Inventory inventory, ScreenRenderer screenRenderer, Game game, int moveLeft, int moveTop) {
int startLeftCrafting = moveLeft + (COLUMN_AMOUNT * (50 + 4) + 2) + 20;
int startTopCrafting = moveTop + 26;
if (x < startLeftCrafting || y < startTopCrafting || x > startLeftCrafting + 167 || y > startTopCrafting + 52) return false;
int craftX = x - startLeftCrafting;
int craftY = y - startTopCrafting;
if (craftX > 106 && craftY > 10 && craftY < 37) {
inventory.getSmallCraftingTable().pickup();
screenRenderer.render(game);
return true;
}
int blockIndex = (craftY / 26) * 2 + (craftX / 53);
handleItemClick(mouseEvent, inventory, inventory.getSmallCraftingTable().getItems(), blockIndex, 29);
screenRenderer.render(game);
return true;
}
private static boolean handleHotbarClick(MouseEvent mouseEvent, int x, int y, Inventory inventory, ScreenRenderer screenRenderer, Game game, Terminal terminal, int moveTop) {
int startLeftHotbar = (terminal.getWidth() / 2) - (INVENTORY_SIZE_PX / 2) + 2;
int startTopHotbar = moveTop + (ROW_AMOUNT * (25 + 1)) + 10;
if (x < startLeftHotbar || y < startTopHotbar || x > startLeftHotbar + INVENTORY_SIZE_PX || y > startTopHotbar + 26) return false;
int index = (x - startLeftHotbar) / 52;
handleItemClick(mouseEvent, inventory, inventory.getHotbar(), index, 20);
screenRenderer.render(game);
return true;
}
private static void handleInventoryClick(MouseEvent mouseEvent, int x, int y, Inventory inventory, ScreenRenderer screenRenderer, Game game, int moveLeft, int moveTop) {
int fx = x - moveLeft;
int fy = y - moveTop;
int widthPixels = COLUMN_AMOUNT * (50 + 4) + 2;
int heightPixels = ROW_AMOUNT * (25 + 1);
if (fx < 0 || fx > widthPixels || fy < 0 || fy > heightPixels) return;
int blockIndex = (fy / 26) * COLUMN_AMOUNT + (fx / 54);
handleItemClick(mouseEvent, inventory, inventory.getItems(), blockIndex, 0);
screenRenderer.render(game);
}
private static void handleItemClick(MouseEvent mouseEvent, Inventory inventory, Object[] items, int index, int offset) {
int actualIndex = index + offset;
if (inventory.getSelectedItemInv() == actualIndex) {
return;
}
switch (mouseEvent.getButton()) {
case Button1 -> {
if (items[index] == null && inventory.hasSelectedItem()) {
items[index] = inventory.isRightClick() ? inventory.getHalf() : inventory.getSelectedItem();
} else if (items[index] != null) {
if (inventory.hasSelectedItem()) {
// Merge items
inventory.mergeItems(inventory.getSelectedItemInv(), actualIndex);
} else {
inventory.setSelectedItemInv(index + offset);
}
}
}
case Button3 -> {
if (items[index] != null) {
if (inventory.hasSelectedItem()) {
// Merge just one item into the stack
inventory.mergeOne(inventory.getSelectedItemInv(), actualIndex);
} else {
// Pick up one item
inventory.setSelectedItemInv(actualIndex);
inventory.setRightClick(true);
}
} else {
// Place one item
items[index] = inventory.getOne();
}
}
}
}
}

View File

@ -0,0 +1,12 @@
package cz.jzitnik.game.ui;
import cz.jzitnik.game.items.InventoryItem;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class InventoryDTO {
private InventoryItem[] obj;
private int index;
}

View File

@ -1,11 +1,14 @@
package cz.jzitnik.game.ui; package cz.jzitnik.game.ui;
import cz.jzitnik.game.crafting.CraftingRecipe;
import cz.jzitnik.game.crafting.CraftingRecipeList;
import cz.jzitnik.game.items.InventoryItem; import cz.jzitnik.game.items.InventoryItem;
import cz.jzitnik.game.items.Item; import cz.jzitnik.tui.SpriteCombiner;
import cz.jzitnik.game.items.ItemBlockSupplier;
import cz.jzitnik.tui.SpriteList; import cz.jzitnik.tui.SpriteList;
import cz.jzitnik.utils.Numbers;
import lombok.Getter; import lombok.Getter;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -17,26 +20,25 @@ public class SmallCraftingTable {
@Getter @Getter
private InventoryItem[] items = new InventoryItem[4]; private InventoryItem[] items = new InventoryItem[4];
public void click(int x, int y) { public void pickup() {
if (x > 106) { Optional<CraftingRecipe> recipe = CraftingRecipeList.getRecipeSmall(Arrays.stream(items).map(item -> item == null ? null: item.getItem().getId()).toArray(String[]::new));
if (y > 10 && y < 37) {
// Pickup item from crafting table
return; if (recipe.isPresent()) {
InventoryItem item = recipe.get().getItemSupplier().get();
inventory.addItem(item);
for (int i = 0; i < items.length; i++) {
InventoryItem it = items[i];
if (it == null) {
continue;
}
if (it.getAmount() == 1) {
items[i] = null;
} else {
it.setAmount(it.getAmount() - 1);
}
} }
return;
}
int blockX = x / 53;
int blockY = y / 26;
int blockIndex = blockY * COLUMN_AMOUNT + blockX;
if (items[blockIndex] == null && inventory.getSelectedItemInv() != -1) {
items[blockIndex] = inventory.getSelectedItem();
} else if (items[blockIndex] != null) {
inventory.setSelectedItemInv(blockIndex + 29);
} }
} }
@ -45,9 +47,14 @@ public class SmallCraftingTable {
List<String> sprites = inventory.getSprites(items, spriteList, inventory.getSelectedItemInv() - 29); List<String> sprites = inventory.getSprites(items, spriteList, inventory.getSelectedItemInv() - 29);
Optional<Item> craftedItem = Optional.of(ItemBlockSupplier.Items.dirt()); Optional<CraftingRecipe> recipe = CraftingRecipeList.getRecipeSmall(Arrays.stream(items).map(item -> item == null ? null: item.getItem().getId()).toArray(String[]::new));
String[] craftedSprite = craftedItem.isPresent() ? spriteList.getSprite(craftedItem.get().getSprite()).getSprite().split("\n") : null; Optional<InventoryItem> craftedItem = recipe.map(craftingRecipe -> craftingRecipe.getItemSupplier().get());
String[] craftedSprite = craftedItem.map(inventoryItem -> SpriteCombiner.combineTwoSprites(
spriteList.getSprite(inventoryItem.getItem().getSprite()).getSprite(),
Numbers.getNumberSprite(inventoryItem.getAmount())
).split("\n")).orElse(null);
int counter = 0; int counter = 0;
for (int i = 0; i < ROW_AMOUNT; i++) { for (int i = 0; i < ROW_AMOUNT; i++) {

View File

@ -42,7 +42,7 @@ public class ScreenRenderer {
main.append("\033[H\033[2J"); main.append("\033[H\033[2J");
switch (game.getWindow()) { switch (game.getWindow()) {
case INVENTORY -> game.getInventory().renderFull(main, terminal, spriteList); case INVENTORY -> game.getInventory().renderFull(main, terminal, spriteList, true, Optional.empty());
case WORLD -> { case WORLD -> {
// World // World

View File

@ -0,0 +1,52 @@
package cz.jzitnik.utils;
import cz.jzitnik.game.sprites.ui.Number;
import cz.jzitnik.tui.SpriteCombiner;
public class Numbers {
private static Number.NumberState getNumberState(int digit) {
return switch (digit) {
case 0 -> Number.NumberState.ZERO;
case 1 -> Number.NumberState.ONE;
case 2 -> Number.NumberState.TWO;
case 3 -> Number.NumberState.THREE;
case 4 -> Number.NumberState.FOUR;
case 5 -> Number.NumberState.FIVE;
case 6 -> Number.NumberState.SIX;
case 7 -> Number.NumberState.SEVEN;
case 8 -> Number.NumberState.EIGHT;
case 9 -> Number.NumberState.NINE;
default -> throw new IllegalArgumentException("Unexpected number: " + digit);
};
}
public static String getNumberSprite(int number) {
if (number == 1) {
StringBuilder sprite = new StringBuilder();
for (int i = 0; i < 25; i++) {
sprite.append("\033[0m ".repeat(50));
sprite.append("\n");
}
return sprite.toString();
}
Number nm = new Number();
if (number <= 9) {
Number.NumberState numberState = getNumberState(number);
return Number.fixForHotbar(nm.getSprite(numberState), 1);
}
String numStr = Integer.toString(number); // Convert to string
char firstChar = numStr.charAt(0); // Get first digit
char secondChar = numStr.charAt(1); // Get second digit
int firstDigit = Character.getNumericValue(firstChar);
int secondDigit = Character.getNumericValue(secondChar);
Number.NumberState numberState1 = getNumberState(firstDigit);
Number.NumberState numberState2 = getNumberState(secondDigit);
return SpriteCombiner.combineTwoSprites(Number.fixForHotbar(nm.getSprite(numberState1), 2), Number.fixForHotbar(nm.getSprite(numberState2), 1));
}
}

View File

@ -0,0 +1,26 @@
   ▒▒▒▒▒▒▒▒▒▒▒▒▒   ░░░░░░░░░░░░░   ▒▒▒▒▒▒▒▒▒▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒   ░░░░░░░░░░░░░   ▒▒▒▒▒▒▒▒▒▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   ░░░░░░   ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒         ▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒         ▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒▒   ▒▒▒   ▒▒▒   
   ▒▒▒▒▒▒▒   ▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒         ▒▒▒   
   ▒▒▒▒▒▒▒   ▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒         ▒▒▒   
   ▒▒▒▒▒▒▒   ▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▒▒▒   
   ░▒▒▒▒▒▒   ▒▒▒▒▒▒▒▒▒      ░▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▒▒▒   
   ░▒▒▒▒▒▒   ▒▒▒▒▒▒▒▒▒      ░▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▒▒▒   
   ▒▒▒▒▒▒▒   ▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▒▒▒   
   ▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒   
   ▒▒▒▒▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒      ▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒   
   ▒▒▒▒▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒      ▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒   
   ▒▒▒▒▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒      ▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒   
   ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   
   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒      ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   


View File

@ -0,0 +1,26 @@
                                                  
                      ▓▓▒▒▓▓▓                     
                  ░ ░░ ▒▒ ▒▒ ░  ░                 
              ░░▒▒▒▒▒▒▒▒▒░░░▒▒▒▒▒▒░░░             
           ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒░▒▒▒▒▒░░          
      ▓▓▒  ▒▒▒▒░░░░░▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒  ▓▓▓     
   ▓▓  ▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓  
   ▓▓▒▒▒▒  ░▒▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒   ▒▒▒▒▓▓  
   ▓▓▒▒▒▒▒ ░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░   ▒▒▒▒▒▓▓  
   ▓▓▒▒▒▒▒▒     ░░░▒░░▒▒▒▒▒▒▒░░░░░     ▒▒▒▒▒░░▓▓  
   ▓▓▒▒▒▒▒▒▒▒     ▒▒▒ ▓▒▒▒▒▓▓ ▒▒      ▒▒░░░░▒▒▓▓  
   ▓▓▒▒ ▒▒▒▒▒▓  ▒▒▒▒▒▒▒▒▓ ▓▒▒▒▒▒▒▒▒ ▓▓░░▒▒▒▒▒▒▓▓  
   ▓▓▒▒  ▒▒▒▒  ▓▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒░░░▓  ▒▒▒▒▒▒▒▒▓▓  
   ▓▓▒▒  ▒▒▒▒   ▒▒    ▒▒▓▓▓▒░░▒▒▒▒▒   ▒▒▒▒░░▒░▓▓  
   ▓▓▒▒  ▒▒▒▒   ▒▒  ▒  ▒▓▓▓░▒▒▒▒  ▒   ░░░░▒▒▒▒▓▓  
   ▓▓▒▓▓▓▓▒▒▒▓▓▓▒▒▓▓   ▒▓▓▓▒▒▒ ▒  ░▓▓▓▒▒▒▒▒▒▒▒▓▓  
   ▓▓▒▒▓▓▓▒▒▒   ▒▒▓▓▓▓▓▒▓▓▓▒▒▒░▓░░▒   ▒▒▒▒▒▒░░▓▓  
   ▓▓▒▒▒▒▒▒▒▒   ▒▒▓▓▓▓▓▒▓▓▓░░░▓▒▓▓▒   ▒▒▒░░▒▒▒▓▓  
   ▓▓▒▒▒▒▒▒▒▒▓▓ ▒▒▒▒▓▓▓▒▓▓▓▒▒▒▓▒▒▒▒ ▓▓░▒▒▒▒▒▒▒▓▓  
   ▓▓▒▒▒▒▒▒▒▒   ▒▒▒▒▓▓▓▒▓▓▓▒▒▒▒▒░░░   ▒▒▒▒▒▒▒░▓▓  
      ▒▒▒▒▒▒▒   ▒▒▒▒▒▓▓▒▓▓▓▒░░▒▒▒▒▒   ▒▒▒░░░░     
          ▒▒▒   ▒▒▒▒▒▓▓▒▓▓▓▒▒▒▒▒▒▒▒  ▓░░░         
              ▓▓▒▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒░▒▓▓             
                  ▒▒▒▒▒▒▓▓▓▒▒░░░░                 
                      ▒▒▓▓▓░░                     


View File

@ -0,0 +1,26 @@
                                                  
                                                  
                                                  
                                         ░░░░░░   
                                                  
                                         ░░░      
                                         ░░░      
                                      ░░░         
                               ░   ░░░            
                               ░   ░░░            
                            ▓  ░░░░               
                            ░░░░                  
                            ░░░░                  
                         ░░░   ▒                  
                   ▓▓▓░░░    ░░░                  
                      ░░░   ░                     
                   ░░░                            
                   ░░░                            
                ░░░                               
             ░░░                                  
             ░░░                                  
      ░   ░░░                                     
      ░                                           
      ░