/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package illarion.client.gui.controller.game;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.NiftyEventSubscriber;
import de.lessvoid.nifty.controls.*;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.elements.events.ElementShowEvent;
import de.lessvoid.nifty.elements.events.NiftyMouseMovedEvent;
import de.lessvoid.nifty.elements.events.NiftyMousePrimaryMultiClickedEvent;
import de.lessvoid.nifty.render.NiftyImage;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import de.lessvoid.nifty.tools.SizeValue;
import illarion.client.IllaClient;
import illarion.client.gui.EntitySlickRenderImage;
import illarion.client.gui.InventoryGui;
import illarion.client.gui.Tooltip;
import illarion.client.gui.controller.game.NumberSelectPopupHandler.Callback;
import illarion.client.net.client.PickUpAllItemsCmd;
import illarion.client.resources.ItemFactory;
import illarion.client.resources.data.ItemTemplate;
import illarion.client.util.Lang;
import illarion.client.util.LookAtTracker;
import illarion.client.util.UpdateTask;
import illarion.client.world.World;
import illarion.client.world.interactive.InteractionManager;
import illarion.client.world.items.CarryLoad;
import illarion.client.world.items.Inventory;
import illarion.client.world.items.MerchantItem;
import illarion.client.world.items.MerchantList;
import illarion.common.types.ItemCount;
import illarion.common.types.ItemId;
import illarion.common.types.Rectangle;
import org.illarion.engine.GameContainer;
import org.illarion.engine.input.Button;
import org.illarion.engine.input.Input;
import org.illarion.engine.input.Key;
import org.illarion.nifty.controls.InventorySlot;
import org.illarion.nifty.controls.InventorySlot.MerchantBuyLevel;
import org.illarion.nifty.controls.Progress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
/**
* This handler takes care for showing and hiding objects in the inventory. Also it monitors all dropping operations on
* the slots of the inventory.
*
* @author Martin Karing <nitram@illarion.org>
*/
public final class GUIInventoryHandler implements InventoryGui, ScreenController {
/**
* This class is used as drag end operation and used to move a object that was dragged out of the inventory back in
* so the server can send the commands to clean everything up.
*
* @author Martin Karing <nitram@illarion.org>
*/
private static class EndOfDragOperation implements Runnable {
/**
* The inventory slot that requires the reset.
*/
private final InventorySlot invSlot;
/**
* Create a new instance of this class and set the effected elements.
*
* @param slot the inventory slot to reset
*/
EndOfDragOperation(InventorySlot slot) {
invSlot = slot;
}
/**
* Execute this operation.
*/
@Override
public void run() {
invSlot.retrieveDraggable();
}
}
private final class InventorySlotUpdate implements UpdateTask {
private final int slotId;
@Nullable
private final ItemId itemId;
@Nullable
private final ItemCount itemCount;
InventorySlotUpdate(int slotId, @Nullable ItemId itemId, @Nullable ItemCount itemCount) {
this.slotId = slotId;
this.itemId = itemId;
this.itemCount = itemCount;
}
@Override
public void onUpdateGame(@Nonnull GameContainer container, int delta) {
setSlotItem(slotId, itemId, itemCount);
}
}
/**
* The logger that takes care for the logging output of this class.
*/
@Nonnull
private static final Logger log = LoggerFactory.getLogger(GUIInventoryHandler.class);
@Nonnull
private final String[] slots;
@Nonnull
private final Element[] invSlots;
@Nonnull
private final boolean[] slotLabelVisibility;
@Nullable
private Element inventoryWindow;
private Nifty activeNifty;
private Screen activeScreen;
private final NumberSelectPopupHandler numberSelect;
private final TooltipHandler tooltipHandler;
@Nonnull
private final Input input;
@Nonnull
private final UpdateTask updateMerchantOverlays = (container, delta) -> {
Inventory inventory = World.getPlayer().getInventory();
for (int i = 0; i < Inventory.SLOT_COUNT; i++) {
updateMerchantOverlay(i, inventory.getItem(i).getItemID());
}
};
public GUIInventoryHandler(
@Nonnull Input input, NumberSelectPopupHandler numberSelectPopupHandler, TooltipHandler tooltipHandler) {
slots = new String[Inventory.SLOT_COUNT];
slots[0] = "invslot_bag";
slots[1] = "invslot_head";
slots[2] = "invslot_neck";
slots[3] = "invslot_chest";
slots[4] = "invslot_hands";
slots[5] = "invslot_lhand";
slots[6] = "invslot_rhand";
slots[7] = "invslot_lfinger";
slots[8] = "invslot_rfinger";
slots[9] = "invslot_legs";
slots[10] = "invslot_feet";
slots[11] = "invslot_cloak";
slots[12] = "invslot_belt1";
slots[13] = "invslot_belt2";
slots[14] = "invslot_belt3";
slots[15] = "invslot_belt4";
slots[16] = "invslot_belt5";
slots[17] = "invslot_belt6";
invSlots = new Element[Inventory.SLOT_COUNT];
slotLabelVisibility = new boolean[Inventory.SLOT_COUNT];
Arrays.fill(slotLabelVisibility, false);
numberSelect = numberSelectPopupHandler;
this.tooltipHandler = tooltipHandler;
this.input = input;
}
@NiftyEventSubscriber(id = "pickUpItemsBtn")
public void onPickUpItemsBtnClick(String topic, @Nonnull ButtonClickedEvent event) {
World.getNet().sendCommand(new PickUpAllItemsCmd());
}
@Override
public void updateCarryLoad() {
World.getUpdateTaskManager().addTask((container, delta) -> updateCarryLoadImpl());
}
private void updateCarryLoadImpl() {
if ((inventoryWindow != null) && inventoryWindow.isVisible()) {
CarryLoad load = World.getPlayer().getCarryLoad();
Element carryLoadDisplay = inventoryWindow.findElementById("carryLoad");
if (carryLoadDisplay != null) {
Element fillElement = carryLoadDisplay.findElementById("#fill");
if (fillElement != null) {
if (load.isRunningPossible()) {
carryLoadDisplay.setStyle("illarion-progress");
fillElement.setStyle("illarion-progress#fill");
} else if (load.isWalkingPossible()) {
carryLoadDisplay.setStyle("illarion-progress-yellow");
fillElement.setStyle("illarion-progress-yellow#fill");
} else {
carryLoadDisplay.setStyle("illarion-progress-red");
fillElement.setStyle("illarion-progress-red#fill");
}
}
Progress carryLoadControl = carryLoadDisplay.getNiftyControl(Progress.class);
if (carryLoadControl != null) {
carryLoadControl.setProgress(0.0);
carryLoadControl.setProgress(load.getLoadFactor());
}
carryLoadDisplay.layoutElements();
}
}
}
@Override
public void updateMerchantOverlay() {
World.getUpdateTaskManager().addTask(updateMerchantOverlays);
}
@Override
public void toggleInventory() {
World.getUpdateTaskManager().addTask((container, delta) -> {
if (inventoryWindow != null) {
if (inventoryWindow.isVisible()) {
hideInventory();
} else {
showInventory();
}
}
});
}
/**
* Hide the inventory window from view
* Hides the current ToolTip
*/
@Override
public void hideInventory() {
World.getUpdateTaskManager().addTask((container, delta) -> {
if (inventoryWindow != null) {
tooltipHandler.hideToolTip();
inventoryWindow.hide();
}
});
}
@Override
public void showInventory() {
World.getUpdateTaskManager().addTask((container, delta) -> {
if (inventoryWindow != null) {
inventoryWindow.show(() -> {
World.getUpdateTaskManager().addTaskForLater((container1, delta1) -> updateCarryLoad());
inventoryWindow.getNiftyControl(Window.class).moveToFront();
});
}
});
}
@NiftyEventSubscriber(id = "inventory")
public void onChangeWindowVisibility(String topic, ElementShowEvent event) {
restoreSlotLabelVisibility();
}
void restoreSlotLabelVisibility() {
for (int i = 0; i < Inventory.SLOT_COUNT; i++) {
InventorySlot invSlot = invSlots[i].getNiftyControl(InventorySlot.class);
if (slotLabelVisibility[i]) {
invSlot.showLabel();
} else {
invSlot.hideLabel();
}
}
inventoryWindow.layoutElements();
}
@NiftyEventSubscriber(id = "openInventoryBtn")
public void onInventoryButtonClicked(String topic, ButtonClickedEvent data) {
toggleInventory();
}
@NiftyEventSubscriber(pattern = "invslot_.*")
public void cancelDragging(String topic, DraggableDragCanceledEvent data) {
World.getInteractionManager().cancelDragging();
}
@NiftyEventSubscriber(pattern = "invslot_.*")
public void onDoubleClickInventory(@Nonnull String topic, @Nonnull NiftyMousePrimaryMultiClickedEvent data) {
int slotId = getSlotNumber(topic);
log.debug("Clicking {} times in inventory slot {}", data.getClickCount(), slotId);
if (data.getClickCount() == 2) {
illarion.client.world.items.InventorySlot slot = World.getPlayer().getInventory().getItem(slotId);
if (!slot.containsItem()) {
return;
}
if (World.getPlayer().hasMerchantList()) {
slot.getInteractive().sell();
} else {
if (slot.getItemTemplate().getItemInfo().isContainer()) {
slot.getInteractive().openContainer();
} else {
slot.getInteractive().use();
}
}
}
}
/**
* Get the number of a slot based on the name.
*
* @param name the name of the slot
* @return the number of the slot fitting the name
*/
private int getSlotNumber(@Nonnull String name) {
for (int i = 0; i < Inventory.SLOT_COUNT; i++) {
if (name.startsWith(slots[i])) {
return i;
}
}
return -1;
}
@NiftyEventSubscriber(pattern = "invslot_.*")
public void dragFromInventory(@Nonnull String topic, DraggableDragStartedEvent data) {
int slotId = getSlotNumber(topic);
World.getInteractionManager().notifyDraggingInventory(slotId, new EndOfDragOperation(
invSlots[slotId].getNiftyControl(InventorySlot.class)));
tooltipHandler.hideToolTip();
}
@NiftyEventSubscriber(pattern = "invslot_.*")
public void dropInInventory(@Nonnull String topic, DroppableDroppedEvent data) {
int slotId = getSlotNumber(topic);
InteractionManager iManager = World.getInteractionManager();
ItemCount amount = iManager.getMovedAmount();
if (amount == null) {
log.error("Corrupted dragging detected.");
iManager.cancelDragging();
return;
}
if (ItemCount.isGreaterOne(amount) && isShiftPressed()) {
numberSelect.requestNewPopup(1, amount.getValue(), new Callback() {
@Override
public void popupCanceled() {
// nothing
}
@Override
public void popupConfirmed(int value) {
iManager.dropAtInventory(slotId, ItemCount.getInstance(value));
}
});
} else {
iManager.dropAtInventory(slotId, amount);
}
inventoryWindow.setFocus();
}
private boolean isShiftPressed() {
return input.isAnyKeyDown(Key.LeftShift, Key.RightShift);
}
@NiftyEventSubscriber(pattern = "invslot_.*")
public void onMouseMoveOverInventory(@Nonnull String topic, NiftyMouseMovedEvent event) {
int slotId = getSlotNumber(topic);
if (input.isAnyButtonDown(Button.Left, Button.Right)) {
return;
}
illarion.client.world.items.InventorySlot slot = World.getPlayer().getInventory().getItem(slotId);
if (!LookAtTracker.isLookAtObject(slot)) {
LookAtTracker.setLookAtObject(slot);
fetchLookAt(slot);
}
}
/**
* Fetch a look at for a slot. This function does not perform any checks or something. It just requests the look
* at. Use with care.
*
* @param slot the slot to fetch
*/
private static void fetchLookAt(@Nonnull illarion.client.world.items.InventorySlot slot) {
slot.getInteractive().lookAt();
}
@Override
public void bind(@Nonnull Nifty nifty, @Nonnull Screen screen) {
activeNifty = nifty;
activeScreen = screen;
inventoryWindow = screen.findElementById("inventory");
for (int i = 0; i < Inventory.SLOT_COUNT; i++) {
invSlots[i] = inventoryWindow.findElementById(slots[i]);
}
inventoryWindow.setConstraintX(new SizeValue(IllaClient.getCfg().getString("inventoryPosX")));
inventoryWindow.setConstraintY(new SizeValue(IllaClient.getCfg().getString("inventoryPosY")));
//inventoryWindow.getParent().layoutElements();
/* Workaround to fix a internal Nifty-GUI problem with changing the styles. */
if (inventoryWindow != null) {
Element carryLoadDisplay = inventoryWindow.findElementById("carryLoad");
if (carryLoadDisplay != null) {
Element fillElement = carryLoadDisplay.findElementById("#fill");
if (fillElement != null) {
fillElement.setStyle("illarion-progress#fill");
}
}
}
}
@Override
public boolean isVisible() {
return inventoryWindow.isVisible();
}
@Override
public void onEndScreen() {
activeNifty.unsubscribeAnnotations(this);
IllaClient.getCfg().set("inventoryPosX", Integer.toString(inventoryWindow.getX()) + "px");
IllaClient.getCfg().set("inventoryPosY", Integer.toString(inventoryWindow.getY()) + "px");
}
@Override
public void onStartScreen() {
activeNifty.subscribeAnnotations(this);
Inventory inventory = World.getPlayer().getInventory();
illarion.client.world.items.InventorySlot invSlot;
for (int i = 0; i < Inventory.SLOT_COUNT; i++) {
invSlot = inventory.getItem(i);
setSlotItem(invSlot.getSlot(), invSlot.getItemID(), invSlot.getCount());
}
}
/**
* Set a new item to a slot.
*
* @param slotId the ID of the slot to change
* @param itemId the ID of the item that shall be displayed in the slot
* @param count the amount of items displayed in this slot
*/
private void setSlotItem(int slotId, @Nullable ItemId itemId, @Nullable ItemCount count) {
if ((slotId < 0) || (slotId >= Inventory.SLOT_COUNT)) {
throw new IllegalArgumentException("Slot ID out of valid range.");
}
InventorySlot invSlot = invSlots[slotId].getNiftyControl(InventorySlot.class);
if (ItemId.isValidItem(itemId)) {
assert itemId != null;
ItemTemplate displayedItem = ItemFactory.getInstance().getTemplate(itemId.getValue());
NiftyImage niftyImage = new NiftyImage(activeNifty.getRenderEngine(),
new EntitySlickRenderImage(displayedItem));
invSlot.setImage(niftyImage);
if (ItemCount.isGreaterOne(count)) {
assert count != null;
invSlot.setLabelText(count.getShortText(Lang.getInstance().getLocale()));
slotLabelVisibility[slotId] = true;
invSlot.showLabel();
} else {
slotLabelVisibility[slotId] = false;
invSlot.hideLabel();
}
updateMerchantOverlay(slotId, itemId);
Element slot = invSlot.getElement();
Rectangle rect = new Rectangle();
rect.set(slot.getX(), slot.getY(), slot.getWidth(), slot.getHeight());
if (rect.isInside(input.getMouseX(), input.getMouseY())) {
fetchLookAt(World.getPlayer().getInventory().getItem(slotId));
}
} else {
slotLabelVisibility[slotId] = false;
invSlot.setImage(null);
invSlot.hideLabel();
updateMerchantOverlay(slotId, itemId);
}
invSlots[slotId].getParent().layoutElements();
}
private void updateMerchantOverlay(int slot, @Nullable ItemId itemId) {
InventorySlot control = invSlots[slot].getNiftyControl(InventorySlot.class);
if (!ItemId.isValidItem(itemId)) {
control.hideMerchantOverlay();
return;
}
MerchantList merchantList = World.getPlayer().getMerchantList();
if (merchantList != null) {
for (int i = 0; i < merchantList.getItemCount(); i++) {
MerchantItem item = merchantList.getItem(i);
if (item.getItemId().equals(itemId)) {
switch (item.getType()) {
case BuyingPrimaryItem:
control.showMerchantOverlay(MerchantBuyLevel.Gold);
return;
case BuyingSecondaryItem:
control.showMerchantOverlay(MerchantBuyLevel.Silver);
return;
case SellingItem:
break;
}
}
}
}
control.hideMerchantOverlay();
}
@Override
public void setItemSlot(int slotId, @Nullable ItemId itemId, @Nullable ItemCount count) {
if ((slotId < 0) || (slotId >= Inventory.SLOT_COUNT)) {
throw new IllegalArgumentException("Slot ID out of valid range.");
}
World.getUpdateTaskManager().addTask(new InventorySlotUpdate(slotId, itemId, count));
}
@Override
public void showTooltip(int slotId, @Nonnull Tooltip tooltip) {
Element slot = invSlots[slotId];
Rectangle rect = new Rectangle();
rect.set(slot.getX(), slot.getY(), slot.getWidth(), slot.getHeight());
tooltipHandler.showToolTip(rect, tooltip);
}
}