feat: Dragging around items in inventory
This commit is contained in:
@@ -109,6 +109,7 @@ public class FullRoomDrawHandler extends AbstractEventHandler<FullRoomDraw> {
|
||||
// Screen too small to fit the room
|
||||
eventManager.emitEvent(new TerminalTooSmallEvent());
|
||||
renderState.setTerminalTooSmall(true);
|
||||
log.error("Terminal too small", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ public class MouseActionEventHandler extends AbstractEventHandler<MouseAction> {
|
||||
|
||||
object.ifPresent(selectable -> selectable.interact(dm));
|
||||
}
|
||||
default -> uiClickHandlerRepository.handleElse(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,12 @@ package cz.jzitnik.game;
|
||||
|
||||
import cz.jzitnik.game.items.GameItem;
|
||||
import cz.jzitnik.game.utils.RoomCords;
|
||||
import cz.jzitnik.utils.DependencyManager;
|
||||
import cz.jzitnik.utils.events.EventManager;
|
||||
import cz.jzitnik.ui.Inventory;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -21,9 +18,22 @@ import java.util.concurrent.TimeUnit;
|
||||
public class Player {
|
||||
private final RoomCords playerCords;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private final List<GameItem> inventory = new CopyOnWriteArrayList<>();
|
||||
private final GameItem[] inventory = new GameItem[Inventory.ITEMS_X * Inventory.ITEMS_Y];
|
||||
private boolean swinging = false;
|
||||
|
||||
public boolean addItem(GameItem item) {
|
||||
boolean added = false;
|
||||
for (int i = 0; i < inventory.length; i++) {
|
||||
if (inventory[i] == null) {
|
||||
inventory[i] = item;
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
@Setter
|
||||
private PlayerRotation playerRotation = PlayerRotation.FRONT;
|
||||
|
||||
|
||||
@@ -92,7 +92,6 @@ public final class Chest extends GameObject implements UIClickHandler {
|
||||
16, // Item Size
|
||||
1, // Item Padding (Changed to 1 as per your request example)
|
||||
new ColoredPixel(BORDER_COLOR),
|
||||
new ColoredPixel(TRANSPARENT_COLOR),
|
||||
new ColoredPixel(TRANSPARENT_COLOR)
|
||||
);
|
||||
}
|
||||
@@ -228,7 +227,7 @@ public final class Chest extends GameObject implements UIClickHandler {
|
||||
.map(GameItem::getTexture)
|
||||
.toArray(BufferedImage[]::new);
|
||||
|
||||
Pixel[][] uiPixels = grid.render(textures, -1);
|
||||
Pixel[][] uiPixels = grid.render(textures);
|
||||
|
||||
for (int y = 0; y < grid.getHeight(); y++) {
|
||||
for (int x = 0; x < grid.getWidth(); x++) {
|
||||
@@ -269,7 +268,10 @@ public final class Chest extends GameObject implements UIClickHandler {
|
||||
}
|
||||
|
||||
GameItem item = items.get(itemIndex);
|
||||
gameState.getPlayer().getInventory().add(item);
|
||||
boolean added = gameState.getPlayer().addItem(item);
|
||||
if (!added) {
|
||||
return;
|
||||
}
|
||||
eventManager.emitEvent(new InventoryRerender());
|
||||
|
||||
items.remove(item);
|
||||
|
||||
@@ -8,4 +8,8 @@ public interface GlobalUIClickHandler {
|
||||
default boolean handleMove(MouseAction ignoredMouseAction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handleElse(MouseAction ignoredMouseAction) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ public interface UIClickHandler {
|
||||
void handleClick(MouseAction mouseAction);
|
||||
|
||||
default void handleMove(MouseAction ignoredMouseAction) {}
|
||||
default void handleElse(MouseAction ignoredMouseAction) {}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,12 @@ import cz.jzitnik.utils.events.EventManager;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Dependency
|
||||
public class Inventory implements GlobalUIClickHandler {
|
||||
private static final int ITEMS_X = 3;
|
||||
private static final int ITEMS_Y = 5;
|
||||
public static final int ITEMS_X = 3;
|
||||
public static final int ITEMS_Y = 5;
|
||||
private static final int OUTER_BORDER_WIDTH = 2;
|
||||
private static final int INNER_BORDER_WIDTH = 1;
|
||||
private static final int ITEM_SIZE = 16; // Characters
|
||||
@@ -35,8 +36,10 @@ public class Inventory implements GlobalUIClickHandler {
|
||||
new ColoredPixel(new TextColor.RGB(255, 255, 255));
|
||||
private static final Pixel BACKGROUND_COLOR =
|
||||
new ColoredPixel(new TextColor.RGB(166, 166, 166));
|
||||
private static final Pixel BACKGROUND_COLOR_SELECTED =
|
||||
private static final Pixel BACKGROUND_COLOR_HOVERED =
|
||||
new ColoredPixel(new TextColor.RGB(186, 186, 186));
|
||||
private static final Pixel BACKGROUND_COLOR_SELECTED =
|
||||
new ColoredPixel(new TextColor.RGB(216, 216, 216));
|
||||
private static final int ITEM_SLOT_SIZE = ITEM_SIZE + ITEM_PADDING * 2;
|
||||
public static final int INVENTORY_WIDTH =
|
||||
OUTER_BORDER_WIDTH * 2 +
|
||||
@@ -46,7 +49,6 @@ public class Inventory implements GlobalUIClickHandler {
|
||||
(ITEMS_Y * (ITEM_SLOT_SIZE + INNER_BORDER_WIDTH) - INNER_BORDER_WIDTH);
|
||||
private static final int REAL_INVENTORY_HEIGHT =
|
||||
Math.ceilDiv(INVENTORY_HEIGHT, 2); // 2 pixels per terminal row
|
||||
|
||||
private static final Grid grid = new Grid(
|
||||
ITEMS_X,
|
||||
ITEMS_Y,
|
||||
@@ -55,10 +57,9 @@ public class Inventory implements GlobalUIClickHandler {
|
||||
ITEM_SIZE,
|
||||
ITEM_PADDING,
|
||||
BORDER_COLOR,
|
||||
BACKGROUND_COLOR,
|
||||
BACKGROUND_COLOR_SELECTED
|
||||
BACKGROUND_COLOR
|
||||
);
|
||||
int selectedIndex = -1;
|
||||
private final InventoryState inventoryState = new InventoryState();
|
||||
@InjectDependency
|
||||
private ResourceManager resourceManager;
|
||||
@InjectState
|
||||
@@ -86,15 +87,52 @@ public class Inventory implements GlobalUIClickHandler {
|
||||
|
||||
@Override
|
||||
public boolean handleClick(MouseAction mouseAction) {
|
||||
inventoryState.onNextDragTakeItemOnIndex = -1;
|
||||
|
||||
TerminalPosition terminalPosition = calculateActualCords(mouseAction.getPosition());
|
||||
var inventory = gameState.getPlayer().getInventory();
|
||||
|
||||
if (inventoryState.draggingItem != null) {
|
||||
int itemClickedOnIndex = grid.getItemIndexAt(terminalPosition.getColumn(), terminalPosition.getRow(), true);
|
||||
GameItem gameItem = inventoryState.draggingItem;
|
||||
inventoryState.draggingItem = null;
|
||||
inventoryState.draggingItemPosition = null;
|
||||
inventoryState.selectedItem = -1;
|
||||
|
||||
if (itemClickedOnIndex != -1) {
|
||||
inventory[itemClickedOnIndex] = gameItem;
|
||||
inventoryState.hoveredItem = itemClickedOnIndex;
|
||||
} else {
|
||||
gameState.getPlayer().addItem(gameItem);
|
||||
}
|
||||
|
||||
eventManager.emitEvent(new InventoryRerender());
|
||||
return true;
|
||||
}
|
||||
|
||||
int itemClickedOnIndex = grid.getItemIndexAt(terminalPosition.getColumn(), terminalPosition.getRow());
|
||||
|
||||
if (terminalPosition.getColumn() < 0 || terminalPosition.getRow() < 0 || terminalPosition.getRow() >= INVENTORY_HEIGHT || terminalPosition.getColumn() >= INVENTORY_WIDTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int itemClickedOnIndex = grid.getItemIndexAt(terminalPosition.getColumn(), terminalPosition.getRow());
|
||||
if (itemClickedOnIndex == -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Clicking on items
|
||||
if (inventoryState.selectedItem == itemClickedOnIndex) {
|
||||
inventoryState.selectedItem = -1;
|
||||
} else if (inventoryState.selectedItem == -1) {
|
||||
inventoryState.selectedItem = itemClickedOnIndex;
|
||||
} else {
|
||||
// Already have selected item and now clicked on different item so swap items
|
||||
GameItem temp = inventory[inventoryState.selectedItem];
|
||||
inventory[inventoryState.selectedItem] = inventory[itemClickedOnIndex];
|
||||
inventory[itemClickedOnIndex] = temp;
|
||||
inventoryState.selectedItem = -1;
|
||||
}
|
||||
|
||||
eventManager.emitEvent(new InventoryRerender());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -104,8 +142,8 @@ public class Inventory implements GlobalUIClickHandler {
|
||||
TerminalPosition terminalPosition = calculateActualCords(mouseAction.getPosition());
|
||||
|
||||
if (terminalPosition.getColumn() < 0 || terminalPosition.getRow() < 0 || terminalPosition.getRow() >= INVENTORY_HEIGHT || terminalPosition.getColumn() >= INVENTORY_WIDTH) {
|
||||
if (selectedIndex != -1) {
|
||||
selectedIndex = -1;
|
||||
if (inventoryState.hoveredItem != -1) {
|
||||
inventoryState.hoveredItem = -1;
|
||||
eventManager.emitEvent(new InventoryRerender());
|
||||
}
|
||||
return false;
|
||||
@@ -113,14 +151,47 @@ public class Inventory implements GlobalUIClickHandler {
|
||||
|
||||
int newSelectedIndex = grid.getItemIndexAt(terminalPosition.getColumn(), terminalPosition.getRow());
|
||||
|
||||
if (newSelectedIndex != selectedIndex) {
|
||||
selectedIndex = newSelectedIndex;
|
||||
if (newSelectedIndex != inventoryState.hoveredItem) {
|
||||
inventoryState.hoveredItem = newSelectedIndex;
|
||||
eventManager.emitEvent(new InventoryRerender());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleElse(MouseAction mouseAction) {
|
||||
TerminalPosition terminalPosition = calculateActualCords(mouseAction.getPosition());
|
||||
|
||||
if (terminalPosition.getColumn() < 0 || terminalPosition.getRow() < 0 || terminalPosition.getRow() >= INVENTORY_HEIGHT || terminalPosition.getColumn() >= INVENTORY_WIDTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (mouseAction.getActionType()) {
|
||||
case CLICK_DOWN ->
|
||||
inventoryState.onNextDragTakeItemOnIndex = grid.getItemIndexAt(terminalPosition.getColumn(), terminalPosition.getRow());
|
||||
case DRAG -> {
|
||||
boolean render = false;
|
||||
if (inventoryState.onNextDragTakeItemOnIndex != -1) {
|
||||
inventoryState.draggingItem = gameState.getPlayer().getInventory()[inventoryState.onNextDragTakeItemOnIndex];
|
||||
gameState.getPlayer().getInventory()[inventoryState.onNextDragTakeItemOnIndex] = null;
|
||||
inventoryState.onNextDragTakeItemOnIndex = -1;
|
||||
render = true;
|
||||
} else if (inventoryState.draggingItem != null) {
|
||||
render = true;
|
||||
}
|
||||
|
||||
if (render) {
|
||||
inventoryState.selectedItem = -1;
|
||||
inventoryState.draggingItemPosition = terminalPosition;
|
||||
eventManager.emitEvent(new InventoryRerender());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private TerminalPosition calculateActualCords(TerminalPosition position) {
|
||||
return new TerminalPosition(position.getColumn() - offsetX, position.getRow() * 2 - offsetY);
|
||||
}
|
||||
@@ -130,14 +201,55 @@ public class Inventory implements GlobalUIClickHandler {
|
||||
var inventory = gameState.getPlayer().getInventory();
|
||||
var buffer = screenBuffer.getRenderedBuffer();
|
||||
|
||||
BufferedImage[] textures = inventory.stream()
|
||||
.map(GameItem::getTexture)
|
||||
BufferedImage[] textures = Arrays.stream(inventory)
|
||||
.map(item -> item == null ? null : item.getTexture())
|
||||
.toArray(BufferedImage[]::new);
|
||||
|
||||
Pixel[][] internalBuffer = grid.render(textures, selectedIndex);
|
||||
Pixel[] backgrounds = new Pixel[ITEMS_X * ITEMS_Y];
|
||||
if (inventoryState.hoveredItem != -1) {
|
||||
backgrounds[inventoryState.hoveredItem] = BACKGROUND_COLOR_HOVERED;
|
||||
}
|
||||
if (inventoryState.selectedItem != -1) {
|
||||
backgrounds[inventoryState.selectedItem] = BACKGROUND_COLOR_SELECTED;
|
||||
}
|
||||
Pixel[][] internalBuffer = grid.render(textures, backgrounds);
|
||||
|
||||
if (inventoryState.draggingItem != null) {
|
||||
BufferedImage texture = inventoryState.draggingItem.getTexture();
|
||||
int offsetX = Math.min(
|
||||
internalBuffer[0].length - texture.getWidth(),
|
||||
Math.max(0, inventoryState.draggingItemPosition.getColumn() - (texture.getWidth() / 2))
|
||||
);
|
||||
int offsetY = Math.min(
|
||||
internalBuffer.length - texture.getHeight(),
|
||||
Math.max(0, inventoryState.draggingItemPosition.getRow() - (texture.getHeight() / 2))
|
||||
);
|
||||
|
||||
for (int y = 0; y < texture.getHeight(); y++) {
|
||||
for (int x = 0; x < texture.getWidth(); x++) {
|
||||
int pixel = texture.getRGB(x, y);
|
||||
int alpha = (pixel >> 24) & 0xff;
|
||||
int r = (pixel >> 16) & 0xff;
|
||||
int g = (pixel >> 8) & 0xff;
|
||||
int b = pixel & 0xff;
|
||||
if (alpha == 0) {
|
||||
continue;
|
||||
}
|
||||
internalBuffer[y + offsetY][x + offsetX] = new ColoredPixel(new TextColor.RGB(r, g, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < grid.getHeight(); y++) {
|
||||
System.arraycopy(internalBuffer[y], 0, buffer[y + offsetY], offsetX, grid.getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
private static class InventoryState {
|
||||
protected int hoveredItem = -1;
|
||||
protected int selectedItem = -1;
|
||||
protected GameItem draggingItem;
|
||||
protected TerminalPosition draggingItemPosition;
|
||||
protected int onNextDragTakeItemOnIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ public class Grid {
|
||||
|
||||
private final Pixel borderColor;
|
||||
private final Pixel backgroundColor;
|
||||
private final Pixel backgroundColorSelected;
|
||||
|
||||
private final int itemSlotSize;
|
||||
private final int cellSize;
|
||||
@@ -29,8 +28,7 @@ public class Grid {
|
||||
int itemSize,
|
||||
int itemPadding,
|
||||
Pixel borderColor,
|
||||
Pixel backgroundColor,
|
||||
Pixel backgroundColorSelected
|
||||
Pixel backgroundColor
|
||||
) {
|
||||
this.itemsX = itemsX;
|
||||
this.itemsY = itemsY;
|
||||
@@ -39,7 +37,6 @@ public class Grid {
|
||||
this.itemPadding = itemPadding;
|
||||
this.borderColor = borderColor;
|
||||
this.backgroundColor = backgroundColor;
|
||||
this.backgroundColorSelected = backgroundColorSelected;
|
||||
|
||||
this.itemSlotSize = itemSize + itemPadding * 2;
|
||||
this.cellSize = itemSlotSize + innerBorderWidth;
|
||||
@@ -53,7 +50,11 @@ public class Grid {
|
||||
(itemsY * (itemSlotSize + innerBorderWidth) - innerBorderWidth);
|
||||
}
|
||||
|
||||
public Pixel[][] render(BufferedImage[] textures, int selectedIndex) {
|
||||
public Pixel[][] render(BufferedImage[] textures) {
|
||||
return render(textures, new Pixel[textures.length]);
|
||||
}
|
||||
|
||||
public Pixel[][] render(BufferedImage[] textures, Pixel[] customBackgrounds) {
|
||||
Pixel[][] buffer = new Pixel[gridHeight][gridWidth];
|
||||
|
||||
for (int y = 0; y < gridHeight; y++) {
|
||||
@@ -91,11 +92,13 @@ public class Grid {
|
||||
int slotX = outerBorderWidth + itemX * cellSize;
|
||||
int slotY = outerBorderWidth + itemY * cellSize;
|
||||
|
||||
if (index == selectedIndex) {
|
||||
Pixel customBackground = customBackgrounds[index];
|
||||
|
||||
if (customBackground != null) {
|
||||
for (int dy = 0; dy < itemSlotSize; dy++) {
|
||||
for (int dx = 0; dx < itemSlotSize; dx++) {
|
||||
if (slotY + dy < gridHeight && slotX + dx < gridWidth) {
|
||||
buffer[slotY + dy][slotX + dx] = backgroundColorSelected;
|
||||
buffer[slotY + dy][slotX + dx] = customBackgrounds[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,13 +131,17 @@ public class Grid {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public int getItemIndexAt(int x, int y) {
|
||||
return getItemIndexAt(x, y, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates which item index corresponds to the given x/y coordinates relative to the Grid's top-left.
|
||||
* @param x X coordinate relative to the Grid (0 is left edge of the outer border)
|
||||
* @param y Y coordinate relative to the Grid (0 is top edge of the outer border)
|
||||
* @return The index of the item, or -1 if the click was on a border/background/out of bounds.
|
||||
*/
|
||||
public int getItemIndexAt(int x, int y) {
|
||||
public int getItemIndexAt(int x, int y, boolean ignoreInnerBorder) {
|
||||
if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) {
|
||||
return -1;
|
||||
}
|
||||
@@ -150,11 +157,24 @@ public class Grid {
|
||||
int col = contentX / cellSize;
|
||||
int row = contentY / cellSize;
|
||||
|
||||
if (contentX % cellSize >= itemSlotSize) return -1;
|
||||
if (contentY % cellSize >= itemSlotSize) return -1;
|
||||
|
||||
if (col >= itemsX || row >= itemsY) return -1;
|
||||
|
||||
int innerX = contentX % cellSize;
|
||||
int innerY = contentY % cellSize;
|
||||
|
||||
if (!ignoreInnerBorder) {
|
||||
if (innerX >= itemSlotSize) return -1;
|
||||
if (innerY >= itemSlotSize) return -1;
|
||||
} else {
|
||||
// Clamp to nearest valid position inside item slot
|
||||
if (innerX >= itemSlotSize) {
|
||||
innerX = itemSlotSize - 1;
|
||||
}
|
||||
if (innerY >= itemSlotSize) {
|
||||
innerY = itemSlotSize - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return row * itemsX + col;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,31 @@ public class UIClickHandlerRepository {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean handleElse(MouseAction mouseAction) {
|
||||
if (roomSpecificHandlers.containsKey(gameState.getCurrentRoom())) {
|
||||
Map<RerenderScreen.ScreenPart, UIClickHandler> handlers = roomSpecificHandlers.get(gameState.getCurrentRoom());
|
||||
|
||||
for (var entry : handlers.entrySet()) {
|
||||
RerenderScreen.ScreenPart part = entry.getKey();
|
||||
UIClickHandler uiClickHandler = entry.getValue();
|
||||
TerminalPosition position = new TerminalPosition(mouseAction.getPosition().getColumn(), mouseAction.getPosition().getRow() * 2);
|
||||
|
||||
if (part.isWithin(position)) {
|
||||
uiClickHandler.handleElse(mouseAction);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var handler : globalHandlers) {
|
||||
if (handler.handleElse(mouseAction)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean handleMove(MouseAction mouseAction) {
|
||||
if (roomSpecificHandlers.containsKey(gameState.getCurrentRoom())) {
|
||||
Map<RerenderScreen.ScreenPart, UIClickHandler> handlers = roomSpecificHandlers.get(gameState.getCurrentRoom());
|
||||
|
||||
Reference in New Issue
Block a user