/* * 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.ButtonClickedEvent; import de.lessvoid.nifty.controls.DroppableDroppedEvent; import de.lessvoid.nifty.effects.EffectEventId; import de.lessvoid.nifty.elements.Element; import de.lessvoid.nifty.elements.render.ImageRenderer; import de.lessvoid.nifty.input.NiftyMouseInputEvent; 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.graphics.Camera; import illarion.client.graphics.Item; import illarion.client.gui.EntitySlickRenderImage; import illarion.client.gui.GameMapGui; import illarion.client.gui.Tooltip; import illarion.client.gui.controller.game.NumberSelectPopupHandler.Callback; import illarion.client.input.*; import illarion.client.input.PrimaryKeyMapDrag.PrimaryKeyMapDragCallback; import illarion.client.world.CharMovementMode; import illarion.client.world.MapTile; import illarion.client.world.World; import illarion.client.world.interactive.InteractionManager; import illarion.client.world.interactive.InteractiveMapTile; import illarion.client.world.movement.MouseMovementHandler; import illarion.client.world.movement.Movement; import illarion.common.config.ConfigChangedEvent; import illarion.common.types.ItemCount; import illarion.common.types.Rectangle; import illarion.common.types.ServerCoordinate; import org.bushe.swing.event.annotation.AnnotationProcessor; import org.bushe.swing.event.annotation.EventSubscriber; import org.bushe.swing.event.annotation.EventTopicSubscriber; import org.illarion.engine.graphic.SceneEvent; import org.illarion.engine.input.ForwardingTarget; import org.illarion.engine.input.Input; import org.illarion.engine.input.Key; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * This class is used to monitor all dropping operations on the droppable area over the game map and notify the * interaction manager about a drop in case one happens. * * @author Martin Karing <nitram@illarion.org> */ public final class GameMapHandler implements GameMapGui, ScreenController { /** * This class is used as end operation to the dragging that started on the map. It takes care that the elements * used * to perform the dragging and cleaned up properly. * * @author Martin Karing <nitram@illarion.org> */ private static final class GameMapDragEndOperation implements Runnable { /** * The element that is dragged around. */ private final Element drag; /** * The element the dragged element needs to return to. */ private final Element returnTo; /** * Create a new end of drag operation. * * @param draggedElement the dragged element * @param returnToElement the element the dragged element needs to return to */ GameMapDragEndOperation(Element draggedElement, Element returnToElement) { drag = draggedElement; returnTo = returnToElement; } /** * Execute this operation. */ @Override public void run() { drag.setVisible(false); drag.markForMove(returnTo); } } /** * The logging instance that handles the logging output of this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(GameMapHandler.class); @Nonnull private final Input input; /** * The Nifty-GUI instance that is handling the GUI display currently. */ private Nifty activeNifty; /** * The screen that takes care for the display currently. */ private Screen activeScreen; /** * This mouse event instance is used to initiate the dragging event. */ @Nonnull private final NiftyMouseInputEvent mouseEvent; /** * The panel that is located on top of the game map. So this is the lowest located panel and the intended parent * for * the original location of the dragged object. */ @Nullable private Element gamePanel; /** * The element that is dragged around. */ @Nullable private Element draggedGraphic; /** * The element that displays the image that is dragged around. */ private Element draggedImage; /** * This operation is executed once a dragging operation is done. */ private Runnable endOfDragOp; /** * The handler for the number selection popup. */ private final NumberSelectPopupHandler numberSelect; private final TooltipHandler tooltipHandler; private boolean followMouseWithPathFinding; /** * Default constructor that takes care to initialize the variables required for this class to work. */ public GameMapHandler( @Nonnull Input input, NumberSelectPopupHandler numberSelectPopupHandler, TooltipHandler tooltip) { mouseEvent = new NiftyMouseInputEvent(); numberSelect = numberSelectPopupHandler; tooltipHandler = tooltip; this.input = input; AnnotationProcessor.process(this); followMouseWithPathFinding = IllaClient.getCfg().getBoolean("followMousePathFinding"); } /** * Handle a input event that was published. */ @EventSubscriber(eventClass = ClickOnMapEvent.class) public void handleClickEvent(@Nonnull SceneEvent event) { World.getMapDisplay().getGameScene().publishEvent(event); } /** * Handle a input event that was published. */ @EventSubscriber(eventClass = DoubleClickOnMapEvent.class) public void handleDoubleClick(@Nonnull SceneEvent event) { World.getMapDisplay().getGameScene().publishEvent(event); } /** * Handle a input event that was published. */ @EventSubscriber(eventClass = DragOnMapEvent.class) public void handleDragging(@Nonnull DragOnMapEvent data) { if (World.getInteractionManager().isDragging()) { return; } switch (data.getKey()) { case Left: handlePrimaryKeyDrag(data); break; } } /** * Handle dragging events from the primary mouse key. * * @param data the event data */ private void handlePrimaryKeyDrag(@Nonnull DragOnMapEvent data) { Movement movement = World.getPlayer().getMovementHandler(); if (!movement.getFollowMouseHandler().isActive() && !movement.getTargetMouseMovementHandler().isActive()) { SceneEvent newEvent = new PrimaryKeyMapDrag(data, new PrimaryKeyMapDragCallback() { @Override public boolean startDraggingItemFromTile(@Nonnull PrimaryKeyMapDrag event, @Nonnull MapTile tile) { return handleDragOnMap(event, tile); } @Override public void notHandled() { moveTowardsMouse(data); } }); World.getMapDisplay().getGameScene().publishEvent(newEvent); } else { moveTowardsMouse(data); } } private boolean handleDragOnMap(@Nonnull PrimaryKeyMapDrag event, @Nullable MapTile mapTile) { if (mapTile == null) { return false; } InteractiveMapTile targetTile = mapTile.getInteractive(); if (!targetTile.canDrag()) { return false; } event.getInputReceiver().guiTookControl(); if ((activeScreen != null) && (activeNifty != null)) { Item movedItem = targetTile.getTopItem(); assert movedItem != null; int width = movedItem.getTemplate().getGuiTexture().getWidth(); int height = movedItem.getTemplate().getGuiTexture().getHeight(); draggedGraphic.resetLayout(); draggedGraphic.setConstraintWidth(SizeValue.px(width)); draggedGraphic.setConstraintHeight(SizeValue.px(height)); draggedGraphic.setConstraintX(SizeValue.px(event.getOldX() - (width / 2))); draggedGraphic.setConstraintY(SizeValue.px(event.getOldY() - (height / 2))); draggedGraphic.showWithoutEffects(); draggedGraphic.reactivate(); draggedImage.setWidth(width); draggedImage.setHeight(height); draggedImage.showWithoutEffects(); ImageRenderer imgRender = draggedImage.getRenderer(ImageRenderer.class); imgRender.setImage( new NiftyImage(activeNifty.getRenderEngine(), new EntitySlickRenderImage(movedItem.getTemplate()))); gamePanel.layoutElements(); input.disableForwarding(ForwardingTarget.Mouse); mouseEvent.initialize(event.getOldX(), event.getOldY(), 0, true, false, false); mouseEvent.setButton0InitialDown(true); activeScreen.mouseEvent(mouseEvent); mouseEvent.initialize(event.getNewX(), event.getNewY(), 0, true, false, false); activeScreen.mouseEvent(mouseEvent); } World.getInteractionManager().notifyDraggingMap(mapTile, endOfDragOp); return true; } @EventTopicSubscriber(topic = "followMousePathFinding") private void onFollowMouseWithPathFindingCfgChanged(@Nonnull String topic, @Nonnull ConfigChangedEvent event) { followMouseWithPathFinding = event.getConfig().getBoolean("followMousePathFinding"); } /** * Calling this function causes the character to walk towards the mouse. * * @param event the event that contains the data for the move */ private void moveTowardsMouse(@Nonnull DragOnMapEvent event) { MouseMovementHandler handler; if (followMouseWithPathFinding) { handler = World.getPlayer().getMovementHandler().getTargetMouseMovementHandler(); } else { handler = World.getPlayer().getMovementHandler().getFollowMouseHandler(); } handler.handleMouse(event.getNewX(), event.getNewY()); if (event.isStartDragging()) { handler.assumeControl(); } input.enableForwarding(ForwardingTarget.Mouse); } @EventSubscriber(eventClass = MoveOnMapEvent.class) public void handleMouseMove(@Nonnull SceneEvent event) { if (World.getInteractionManager().isDragging()) { return; } if (World.getPlayer().getMovementHandler().getFollowMouseHandler().isActive()) { return; } World.getMapDisplay().getGameScene().publishEvent(event); } @EventSubscriber(eventClass = PointOnMapEvent.class) public void handlePointAt(@Nonnull SceneEvent event) { if (World.getInteractionManager().isDragging()) { return; } if (World.getPlayer().getMovementHandler().getFollowMouseHandler().isActive()) { return; } World.getMapDisplay().getGameScene().publishEvent(event); } /** * Called in case something is dropped on the game map. */ @NiftyEventSubscriber(id = "mapDropTarget") public void dropOnMap(String topic, @Nonnull DroppableDroppedEvent data) { Element droppedElement = data.getDraggable().getElement(); int dropSpotX = droppedElement.getX() + (droppedElement.getWidth() / 2); int dropSpotY = droppedElement.getY() + (droppedElement.getHeight() / 2); ItemCount amount = World.getInteractionManager().getMovedAmount(); InteractionManager iManager = World.getInteractionManager(); if (amount == null) { LOGGER.error("Corrupted drag 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.dropAtMap(dropSpotX, dropSpotY, ItemCount.getInstance(value)); } }); } else { iManager.dropAtMap(dropSpotX, dropSpotY, amount); } } /** * The event subscriber for click events on the run button. * * @param topic the event topic * @param data the event data */ @NiftyEventSubscriber(id = "toggleRunBtn") public void onToggleRunButtonClicked(String topic, ButtonClickedEvent data) { toggleRunMode(); } /** * Toggle the pulsing animation of the run button. */ @Override public void toggleRunMode() { boolean nowWalking = true; if (World.getPlayer().getMovementHandler().getDefaultMovementMode() == CharMovementMode.Run) { World.getPlayer().getMovementHandler().setDefaultMovementMode(CharMovementMode.Walk); } else { World.getPlayer().getMovementHandler().setDefaultMovementMode(CharMovementMode.Run); nowWalking = false; } if (activeScreen == null) { return; } @Nullable Element runBtn = activeScreen.findElementById("toggleRunBtn"); if (runBtn == null) { return; } if (nowWalking) { runBtn.stopEffect(EffectEventId.onCustom); } else { runBtn.startEffect(EffectEventId.onCustom, null, "pulse"); } } private boolean isShiftPressed() { return input.isAnyKeyDown(Key.LeftShift, Key.RightShift); } @Override public void bind(@Nonnull Nifty nifty, @Nonnull Screen screen) { activeNifty = nifty; activeScreen = screen; gamePanel = screen.findElementById("gamePanel"); draggedGraphic = gamePanel.findElementById("mapDragObject"); draggedImage = draggedGraphic.findElementById("mapDragImage"); endOfDragOp = new GameMapDragEndOperation(draggedGraphic, gamePanel); } @Override public void onEndScreen() { activeNifty.unsubscribeAnnotations(this); AnnotationProcessor.unprocess(this); } @Override public void onStartScreen() { activeNifty.subscribeAnnotations(this); AnnotationProcessor.process(this); } @Override public void showItemTooltip(@Nonnull ServerCoordinate location, int stackPosition, @Nonnull Tooltip tooltip) { LOGGER.debug("Now showing tooltip for {} at stack position {}", location, stackPosition); MapTile targetTile = World.getMap().getMapAt(location); if (targetTile == null) { return; } try { Item targetItem = targetTile.getItem(stackPosition); Rectangle originalDisplayRect = targetItem.getInteractionRect(); Rectangle fixedRectangle = new Rectangle(originalDisplayRect); fixedRectangle.move(-Camera.getInstance().getViewportOffsetX(), -Camera.getInstance().getViewportOffsetY()); tooltipHandler.showToolTip(fixedRectangle, tooltip); } catch (IndexOutOfBoundsException e) { LOGGER.warn("Error while showing a tooltip: {}", e.getMessage()); } } }