/******************************************************************************* * Copyright (c) 2015 - 2017 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package jsettlers.graphics.map; import go.graphics.GLDrawContext; import go.graphics.IllegalBufferException; import go.graphics.UIPoint; import go.graphics.event.GOEvent; import go.graphics.event.GOEventHandler; import go.graphics.event.GOKeyEvent; import go.graphics.event.GOModalEventHandler; import go.graphics.event.command.GOCommandEvent; import go.graphics.event.mouse.GODrawEvent; import go.graphics.event.mouse.GOHoverEvent; import go.graphics.event.mouse.GOPanEvent; import go.graphics.event.mouse.GOZoomEvent; import go.graphics.region.RegionContent; import go.graphics.sound.SoundPlayer; import go.graphics.text.EFontSize; import go.graphics.text.TextDrawer; import jsettlers.common.Color; import jsettlers.common.CommitInfo; import jsettlers.common.CommonConstants; import jsettlers.common.buildings.EBuildingType; import jsettlers.common.images.AnimationSequence; import jsettlers.common.images.EImageLinkType; import jsettlers.common.images.ImageLink; import jsettlers.common.images.OriginalImageLink; import jsettlers.common.map.EDebugColorModes; import jsettlers.common.map.IGraphicsGrid; import jsettlers.common.map.shapes.IMapArea; import jsettlers.common.map.shapes.MapRectangle; import jsettlers.common.mapobject.EMapObjectType; import jsettlers.common.mapobject.IMapObject; import jsettlers.common.menu.IMapInterfaceListener; import jsettlers.common.menu.IStartedGame; import jsettlers.common.menu.UIState; import jsettlers.common.menu.action.EActionType; import jsettlers.common.menu.action.IAction; import jsettlers.common.menu.messages.IMessage; import jsettlers.common.movable.IMovable; import jsettlers.common.position.FloatRectangle; import jsettlers.common.position.ShortPoint2D; import jsettlers.common.selectable.ISelectionSet; import jsettlers.common.statistics.IGameTimeProvider; import jsettlers.graphics.action.Action; import jsettlers.graphics.action.ActionFireable; import jsettlers.graphics.action.ActionHandler; import jsettlers.graphics.action.ActionThreadBlockingListener; import jsettlers.graphics.action.PointAction; import jsettlers.graphics.action.ScreenChangeAction; import jsettlers.graphics.action.SelectAreaAction; import jsettlers.graphics.action.ShowConstructionMarksAction; import jsettlers.graphics.font.FontDrawerFactory; import jsettlers.graphics.localization.Labels; import jsettlers.graphics.map.controls.IControls; import jsettlers.graphics.map.controls.original.OriginalControls; import jsettlers.graphics.map.draw.Background; import jsettlers.graphics.map.draw.ImageProvider; import jsettlers.graphics.map.draw.MapObjectDrawer; import jsettlers.graphics.messages.Messenger; import jsettlers.graphics.sound.BackgroundSound; import jsettlers.graphics.sound.SoundManager; /** * This is the main map content class. It manages the map drawing on the screen region. * <p> * <h1>The drawing process</h1> The map is drawn in three steps. At first, the background is drawn. After that, it is overlayed with the images for * settlers, and other map objects. Then the interface is drawn above everything else. * <p> * The objects and background are drawn with the map draw context. * <p> * UI structure: * <ul> * <li>left minimap decoration</li> * <li>right minimap decoration</li> * <li>main background * <ul> * <li>tabs * <ol> * <li>building tabs</li> * <li>goods tabs</li> * <li>settlers tabs</li> * <li>game tab</li> * </ol> * </li> * </ul> * </li> * </ul> * * @author michael */ public final class MapContent implements RegionContent, IMapInterfaceListener, ActionFireable, ActionThreadBlockingListener { private static final AnimationSequence GOTO_ANIMATION = new AnimationSequence(new OriginalImageLink(EImageLinkType.SETTLER, 3, 1).getName(), 0, 2); private static final float UI_OVERLAY_Z = .95f; private final class ZoomEventHandler implements GOModalEventHandler { float startZoom = context.getScreen().getZoom(); @Override public void phaseChanged(GOEvent event) { } @Override public void finished(GOEvent event) { eventDataChanged(((GOZoomEvent) event).getZoomFactor(), ((GOZoomEvent) event).getPointingPosition()); } @Override public void aborted(GOEvent event) { eventDataChanged(1, null); } @Override public void eventDataChanged(GOEvent event) { eventDataChanged(((GOZoomEvent) event).getZoomFactor(), ((GOZoomEvent) event).getPointingPosition()); } private void eventDataChanged(float zoomFactor, UIPoint p) { float newZoom = startZoom * zoomFactor; setZoom(newZoom, p); } } private static final int SCREEN_PADDING = 50; private static final float OVERDRAW_BOTTOM_PX = 50; private static final float MESSAGE_OFFSET_X = 300; private static final int MESSAGE_OFFSET_Y = 30; private static final int MESSAGE_LINE_HEIGHT = 18; private static final long GOTO_MARK_TIME = 1500; private static final long DOUBLE_CLICK_TIME = 500; private final IGraphicsGrid map; private final Background background = new Background(); private final MapDrawContext context; private final MapObjectDrawer objectDrawer; /** * The current connector that connects the outside world to us. */ private final MapInterfaceConnector connector; private final FramerateComputer framerate = new FramerateComputer(); private final Messenger messenger; private final SoundManager soundmanager; private final BackgroundSound backgroundSound; private final ReplaceableTextDrawer textDrawer; private final IGameTimeProvider gameTimeProvider; private final ETextDrawPosition textDrawPosition; /** * The controls that represent the interface. */ private final IControls controls; private FloatRectangle oldScreen; private UIPoint mousePosition = new UIPoint(0, 0); private int windowWidth = 1; private int windowHeight = 1; private ShortPoint2D scrollMarker; private long scrollMarkerTime; private ShortPoint2D moveToMarker; private long moveToMarkerTime; private String tooltipString = ""; private EDebugColorModes debugColorMode = EDebugColorModes.NONE; private PlacementBuilding placementBuilding; private UIPoint currentSelectionAreaEnd; private boolean actionThreadIsSlow; private long lastSelectPointTime = 0; private ShortPoint2D lastSelectPointPos = null; private UIPoint currentSelectionAreaStart; /** * Creates a new map content for the given map. * * @param game * The map. * @param player * The player */ public MapContent(IStartedGame game, SoundPlayer player, ETextDrawPosition textDrawPosition) { this(game, player, textDrawPosition, null); } /** * * Creates a new map content for the given map. * * @param game * The map. * @param player * The player * @param controls * The controls object to use as user interface. If it is <code>null</code> the original controls are overlayed. */ public MapContent(IStartedGame game, SoundPlayer player, ETextDrawPosition textDrawPosition, IControls controls) { this.map = game.getMap(); this.gameTimeProvider = game.getGameTimeProvider(); this.textDrawPosition = textDrawPosition; this.messenger = new Messenger(this.gameTimeProvider); this.textDrawer = new ReplaceableTextDrawer(); this.context = new MapDrawContext(map); this.soundmanager = new SoundManager(player); objectDrawer = new MapObjectDrawer(context, soundmanager); backgroundSound = new BackgroundSound(context, soundmanager); backgroundSound.start(); if (controls == null) { this.controls = new OriginalControls(this, game.getInGamePlayer()); } else { this.controls = controls; } this.controls.setDrawContext(this, context); this.connector = new MapInterfaceConnector(this); this.connector.addListener(this); map.setBackgroundListener(background); } private void resizeTo(int newWindowWidth, int newWindowHeight) { windowWidth = newWindowWidth; windowHeight = newWindowHeight; this.controls.resizeTo(windowWidth, windowHeight); reapplyContentSizes(); } private void reapplyContentSizes() { this.context.setSize(windowWidth, windowHeight); } @Override public void drawContent(GLDrawContext gl, int newWidth, int newHeight) { try { framerate.nextFrame(); // TODO: Do only check once. if (textDrawer.getTextDrawer(gl, EFontSize.NORMAL).getWidth("a") == 0) { textDrawer.setTextDrawerFactory(new FontDrawerFactory()); } if (newWidth != windowWidth || newHeight != windowHeight) { resizeTo(newWidth, newHeight); } adaptScreenSize(); this.objectDrawer.increaseAnimationStep(); this.context.begin(gl); long start = System.currentTimeMillis(); FloatRectangle screen = this.context.getScreen().getPosition().bigger(SCREEN_PADDING); drawBackground(screen); long backgroundDuration = System.currentTimeMillis() - start; start = System.currentTimeMillis(); drawMain(screen); if (scrollMarker != null) { drawGotoMarker(); } if (moveToMarker != null) { drawMoveToMarker(); } this.context.end(); long foregroundDuration = System.currentTimeMillis() - start; start = System.currentTimeMillis(); gl.glTranslatef(0, 0, UI_OVERLAY_Z); drawSelectionHint(gl); controls.drawAt(gl); drawMessages(gl); drawFramerateTimeAndHash(gl); if (actionThreadIsSlow) { drawActionThreadSlow(gl); } drawTooltip(gl); long uiTime = System.currentTimeMillis() - start; if (CommonConstants.ENABLE_GRAPHICS_TIMES_DEBUG_OUTPUT) { System.out.println("Background: " + backgroundDuration + "ms, Foreground: " + foregroundDuration + "ms, UI: " + uiTime + "ms"); } } catch (Throwable t) { System.err.println("Main draw handler cought throwable:"); t.printStackTrace(System.err); } } private void drawGotoMarker() { long timeDifference = System.currentTimeMillis() - scrollMarkerTime; if (timeDifference > GOTO_MARK_TIME) { scrollMarker = null; } else { ImageLink image = GOTO_ANIMATION.getImage(timeDifference < GOTO_MARK_TIME / 2 ? 0 : 1); objectDrawer.drawGotoMarker(scrollMarker, ImageProvider.getInstance().getImage(image)); } } private void drawMoveToMarker() { long timeDifference = System.currentTimeMillis() - moveToMarkerTime; if (timeDifference >= GOTO_MARK_TIME) { moveToMarker = null; } else { objectDrawer.drawMoveToMarker(moveToMarker, timeDifference / GOTO_MARK_TIME); } } private float messageAlpha(IMessage m) { int age = m.getAge(); return age < 1500 ? Math.min(1, age / 1000f) : Math.max(0, 1f - (float) age / IMessage.MESSAGE_TTL); } private void drawMessages(GLDrawContext gl) { TextDrawer drawer = textDrawer.getTextDrawer(gl, EFontSize.HEADLINE); int messageIndex = 0; messenger.doTick(); for (IMessage m : messenger.getMessages()) { float x = MESSAGE_OFFSET_X; int y = MESSAGE_OFFSET_Y + messageIndex * MESSAGE_LINE_HEIGHT; float a = messageAlpha(m); if (m.getSender() >= 0) { String name = getPlayername(m.getSender()) + ":"; Color color = context.getPlayerColor(m.getSender()); float width = (float) drawer.getWidth(name); float bright = color.getRed() + color.getGreen() + color.getBlue(); if (bright < .9f) { // black drawer.setColor(1, 1, 1, a / 2); } else if (bright < 2f) { // bad visibility drawer.setColor(0, 0, 0, a / 2); } for (int i = -1; i < 3; i++) { drawer.drawString(x + i, y - 1, name); } drawer.setColor(color.getRed(), color.getGreen(), color.getBlue(), a); drawer.drawString(x, y, name); x += width + 10; } drawer.setColor(1, 1, 1, a); drawer.drawString(x, y, m.getMessage()); messageIndex++; } } private String getPlayername(byte sender) { // TODO: Player names return "player " + sender; } private void adaptScreenSize() { FloatRectangle newScreen = context.getScreen().getPosition(); if (!newScreen.equals(oldScreen)) { getInterfaceConnector().fireAction(new ScreenChangeAction(context.getScreenArea())); } oldScreen = newScreen; } private void drawSelectionHint(GLDrawContext gl) { if (this.currentSelectionAreaStart != null && this.currentSelectionAreaEnd != null) { float x1 = (float) this.currentSelectionAreaStart.getX(); float y1 = (float) this.currentSelectionAreaStart.getY(); float x2 = (float) this.currentSelectionAreaEnd.getX(); float y2 = (float) this.currentSelectionAreaEnd.getY(); gl.color(1, 1, 1, 1); gl.drawLine(new float[] { x1, y1, 0, x2, y1, 0, x2, y2, 0, x1, y2, 0 }, true); } } private void drawFramerateTimeAndHash(GLDrawContext gl) { if (textDrawPosition == ETextDrawPosition.NONE) { return; } String fps = Labels.getString("map-fps", framerate.getRate()); long gameTime = gameTimeProvider.getGameTime() / 1000; String timeString = Labels.getString("map-time", gameTime / 60 / 60, (gameTime / 60) % 60, (gameTime) % 60); TextDrawer drawer = textDrawer.getTextDrawer(gl, EFontSize.NORMAL); float letterWidth = getLetterWidth(drawer); float textLineHeight = getTextLineHeight(drawer); float yFirstLine = windowHeight - 1.5f * textLineHeight; float ySecondLine = windowHeight - 3.0f * textLineHeight; float sideXOffset = 2 * letterWidth; drawer.drawString(getConfiguredX(sideXOffset, windowWidth, 7 * letterWidth), yFirstLine, fps); drawer.drawString(getConfiguredX(sideXOffset + 9 * letterWidth, windowWidth, 9 * letterWidth), yFirstLine, timeString); drawer.drawString(getConfiguredX(sideXOffset, windowWidth, 7 * letterWidth), ySecondLine, CommitInfo.COMMIT_HASH_SHORT); } private float getConfiguredX(float borderDistance, int windowWidth, float fixedTextLength) { if (textDrawPosition == ETextDrawPosition.TOP_LEFT) { return borderDistance; } else { return windowWidth - (borderDistance + fixedTextLength); } } private float getLetterWidth(TextDrawer drawer) { return drawer.getWidth("X"); } private float getTextLineHeight(TextDrawer drawer) { return drawer.getHeight("X"); } private void drawTooltip(GLDrawContext gl) { if (!tooltipString.isEmpty()) { TextDrawer drawer = textDrawer.getTextDrawer(gl, EFontSize.NORMAL); drawer.drawString((int) mousePosition.getX(), (int) mousePosition.getY(), tooltipString); } } private void drawActionThreadSlow(GLDrawContext gl) { TextDrawer drawer = textDrawer.getTextDrawer(gl, EFontSize.NORMAL); String string = Labels.getString("action_firerer_slow"); float x = windowWidth - (float) drawer.getWidth(string) - 5; float y = windowHeight - 3 * (float) drawer.getHeight(string); drawer.drawString(x, y, string); } /** * Draws the main content (buildings, settlers, ...), assuming the context is set up. */ private void drawMain(FloatRectangle screen) { short height = map.getHeight(); short width = map.getWidth(); MapRectangle area = this.context.getConverter().getMapForScreen(screen); double bottomDrawY = screen.getMinY() - OVERDRAW_BOTTOM_PX; boolean linePartiallyVisible = true; for (int line = 0; line < area.getLines() + 50 && linePartiallyVisible; line++) { int y = area.getLineY(line); if (y < 0) { continue; } if (y >= height) { break; } linePartiallyVisible = false; int endX = Math.min(area.getLineEndX(line), width - 1); int startX = Math.max(area.getLineStartX(line), 0); for (int x = startX; x <= endX; x = map.nextDrawableX(x, y, endX)) { drawTile(x, y); if (!linePartiallyVisible) { double drawSpaceY = this.context.getConverter().getViewY(x, y, this.context.getHeight(x, y)); if (drawSpaceY > bottomDrawY) { linePartiallyVisible = true; } } } } if (placementBuilding != null) { ShortPoint2D underMouse = this.context.getPositionOnScreen((float) mousePosition.getX(), (float) mousePosition.getY()); if (0 <= underMouse.x && underMouse.x < width && 0 <= underMouse.y && underMouse.y < height) { IMapObject mapObject = map.getMapObjectsAt(underMouse.x, underMouse.y); if (mapObject != null && mapObject.getMapObject(EMapObjectType.CONSTRUCTION_MARK) != null) { // if there is a construction mark this.objectDrawer.drawMapObject(underMouse.x, underMouse.y, placementBuilding); } } } if (debugColorMode != EDebugColorModes.NONE) { drawDebugColors(); } context.getDrawBuffer().flush(); } private void drawTile(int x, int y) { IMapObject object = map.getMapObjectsAt(x, y); if (object != null) { this.objectDrawer.drawMapObject(x, y, object); } IMovable movable = map.getMovableAt(x, y); if (movable != null) { this.objectDrawer.draw(movable); } if (map.isBorder(x, y)) { byte player = map.getPlayerIdAt(x, y); objectDrawer.drawPlayerBorderObject(x, y, player); } } private void drawDebugColors() { GLDrawContext gl = this.context.getGl(); // @formatter:off float[] shape = new float[] { 0, 4, .5f, 0, 0, -3, 2, .5f, 0, 0, -3, -2, .5f, 0, 0, 0, -4, .5f, 0, 0, 0, -4, .5f, 0, 0, 3, -2, .5f, 0, 0, 3, 2, .5f, 0, 0, 0, 4, .5f, 0, 0 }; // @formatter:on context.getScreenArea().stream().filterBounds(map.getWidth(), map.getHeight()).forEach((x, y) -> { try { int argb = map.getDebugColorAt(x, y, debugColorMode); if (argb != 0) { this.context.beginTileContext(x, y); gl.color(((argb >> 16) & 0xff) / 255f, ((argb >> 8) & 0xff) / 255f, ((argb >> 0) & 0xff) / 255f, ((argb >> 24) & 0xff) / 255f); gl.drawQuadWithTexture(null, shape); context.endTileContext(); } } catch (IllegalBufferException e) { // TODO: Create a crash report // This should never happen since we only use texture 0 (no texture) } }); } /** * Draws the background. * * @param screen */ private void drawBackground(FloatRectangle screen) { this.background.drawMapContent(this.context, screen); } @Override public void handleEvent(GOEvent event) { if (event instanceof GOPanEvent) { UIPoint center = ((GOPanEvent) event).getPanCenter(); if (center == null || !controls.containsPoint(center)) { event.setHandler(new PanHandler(this.context.getScreen())); } } else if (event instanceof GOCommandEvent) { GOCommandEvent commandEvent = (GOCommandEvent) event; Action action = getActionForCommand(commandEvent); // also set when action was null, to abort drawing. fireActionEvent(event, action); } else if (event instanceof GOKeyEvent) { Action actionForKeyboard = getActionForKeyboard(((GOKeyEvent) event).getKeyCode()); if (actionForKeyboard != null) { fireActionEvent(event, actionForKeyboard); } } else if (event instanceof GODrawEvent) { GODrawEvent drawEvent = (GODrawEvent) event; if (!controls.handleDrawEvent(drawEvent)) { handleDraw(drawEvent); } } else if (event instanceof GOHoverEvent) { GOHoverEvent hoverEvent = (GOHoverEvent) event; handleHover(hoverEvent); } else if (event instanceof GOZoomEvent) { GOZoomEvent zoomEvent = (GOZoomEvent) event; handleZoom(zoomEvent); } } private void handleZoom(GOZoomEvent zoomEvent) { zoomEvent.setHandler(new ZoomEventHandler()); } /** * Zoom in */ public void zoomIn() { if (context != null) { float zoom = context.getScreen().getZoom(); setZoom(zoom * 1.3f, null); } } /** * Zoom out */ public void zoomOut() { if (context != null) { float zoom = context.getScreen().getZoom(); setZoom(zoom / 1.3f, null); } } /** * Zoom to default value */ public void zoom100() { if (context != null) { setZoom(1.0f, null); } } private void fireActionEvent(GOEvent event, Action action) { event.setHandler(new ActionHandler(action, this)); } /** * Gets a action for a keyboard key. * * @param keyCode * The key name as String. * @return The action that corresponds to the key */ private static Action getActionForKeyboard(String keyCode) { System.out.println(keyCode); if ("F12".equalsIgnoreCase(keyCode)) { return new Action(EActionType.FAST_FORWARD); } else if ("P".equalsIgnoreCase(keyCode) || "PAUSE".equalsIgnoreCase(keyCode)) { return new Action(EActionType.SPEED_TOGGLE_PAUSE); } else if ("BACK".equalsIgnoreCase(keyCode)) { return new Action(EActionType.BACK); } else if ("+".equals(keyCode) || "]".equals(keyCode)) { return new Action(EActionType.SPEED_FASTER); } else if ("-".equals(keyCode) || "/".equals(keyCode)) { return new Action(EActionType.SPEED_SLOWER); } else if (" ".equals(keyCode) || "space".equalsIgnoreCase(keyCode)) { return new Action(EActionType.SHOW_MESSAGE); } else if ("d".equalsIgnoreCase(keyCode)) { return new Action(EActionType.DEBUG_ACTION); } else if ("s".equalsIgnoreCase(keyCode)) { return new Action(EActionType.STOP_WORKING); } else if ("e".equalsIgnoreCase(keyCode)) { return new Action(EActionType.TOGGLE_DEBUG); } else if ("o".equalsIgnoreCase(keyCode)) { return new Action(EActionType.TOGGLE_ORIGINAL_GRAPHICS); } else if ("q".equalsIgnoreCase(keyCode)) { // TODO: Only show the exit menu. return new Action(EActionType.EXIT); } else if ("w".equalsIgnoreCase(keyCode)) { return new Action(EActionType.TOGGLE_FOG_OF_WAR); } else if ("z".equalsIgnoreCase(keyCode)) { return new Action(EActionType.SHOW_SELECTION); } else if ("n".equalsIgnoreCase(keyCode)) { return new Action(EActionType.NEXT_OF_TYPE); } else if ("F5".equalsIgnoreCase(keyCode)) { return new Action(EActionType.ZOOM_IN); } else if ("F6".equalsIgnoreCase(keyCode)) { return new Action(EActionType.ZOOM_OUT); } else if ("F2".equalsIgnoreCase(keyCode)) { return new Action(EActionType.SAVE); } else if ("DELETE".equalsIgnoreCase(keyCode)) { return new Action(EActionType.DESTROY); } else if ("ESCAPE".equalsIgnoreCase(keyCode)) { return new Action(EActionType.ABORT); } else { return null; } } private final GOEventHandler hoverHandler = new GOModalEventHandler() { @Override public void phaseChanged(GOEvent event) { } @Override public void finished(GOEvent event) { changeMousePosition(((GOHoverEvent) event).getHoverPosition()); } @Override public void aborted(GOEvent event) { } @Override public void eventDataChanged(GOEvent event) { changeMousePosition(((GOHoverEvent) event).getHoverPosition()); } }; private void handleHover(GOHoverEvent hoverEvent) { hoverEvent.setHandler(hoverHandler); } /** * This is called whenever the mouse pointer position changed. Used for tooltips. * * @param position * The new mouse position. */ protected void changeMousePosition(UIPoint position) { mousePosition = position; if (controls.containsPoint(position)) { tooltipString = controls.getDescriptionFor(position); } else { float x = (float) position.getX(); float y = (float) position.getY(); ShortPoint2D onMap = this.context.getPositionOnScreen(x, y); tooltipString = controls.getMapTooltip(onMap); } if (tooltipString == null) { tooltipString = ""; } } private Action getActionForCommand(GOCommandEvent commandEvent) { UIPoint position = commandEvent.getCommandPosition(); if (controls.containsPoint(position)) { return controls.getActionFor(position, commandEvent.isSelecting()); } else { // handle map click return handleCommandOnMap(commandEvent, position); } } private void handleDraw(GODrawEvent drawEvent) { handleDrawOnMap(drawEvent); } private void handleDrawOnMap(GODrawEvent drawEvent) { this.currentSelectionAreaStart = drawEvent.getDrawPosition(); drawEvent.setHandler(this.drawSelectionHandler); } private final GOEventHandler drawSelectionHandler = new GOModalEventHandler() { @Override public void phaseChanged(GOEvent event) { } @Override public void finished(GOEvent event) { updateSelectionArea(((GODrawEvent) event).getDrawPosition(), true); } @Override public void aborted(GOEvent event) { abortSelectionArea(); } @Override public void eventDataChanged(GOEvent event) { updateSelectionArea(((GODrawEvent) event).getDrawPosition(), false); } }; private Action handleCommandOnMap(GOCommandEvent commandEvent, UIPoint position) { float x = (float) position.getX(); float y = (float) position.getY(); ShortPoint2D onMap = this.context.getPositionOnScreen(x, y); if (this.context.checkMapCoordinates(onMap.x, onMap.y)) { Action action; if (commandEvent.isSelecting()) { action = handleSelectCommand(onMap); } else { action = new PointAction(EActionType.MOVE_TO, onMap); } return action; } return null; } private Action handleSelectCommand(ShortPoint2D onMap) { long currentTime = System.currentTimeMillis(); if (currentTime - lastSelectPointTime < DOUBLE_CLICK_TIME && onMap.equals(lastSelectPointPos)) { lastSelectPointTime = 0; return new PointAction(EActionType.SELECT_POINT_TYPE, onMap); } else { lastSelectPointTime = currentTime; lastSelectPointPos = onMap; return new PointAction(EActionType.SELECT_POINT, onMap); } } protected void abortSelectionArea() { this.currentSelectionAreaStart = null; this.currentSelectionAreaEnd = null; } private void updateSelectionArea(UIPoint mousePosition, boolean finished) { if (finished && currentSelectionAreaStart != null) { int x1 = (int) mousePosition.getX(); int x2 = (int) this.currentSelectionAreaStart.getX(); int y1 = (int) mousePosition.getY(); int y2 = (int) this.currentSelectionAreaStart.getY(); if (x1 > x2) { int temp = x1; x1 = x2; x2 = temp; } if (y1 > y2) { int temp = y1; y1 = y2; y2 = temp; } IMapArea area = this.context.getRectangleOnScreen(x1, y1, x2, y2); getInterfaceConnector().fireAction(new SelectAreaAction(area)); this.currentSelectionAreaStart = null; this.currentSelectionAreaEnd = null; } else { this.currentSelectionAreaEnd = mousePosition; } } /** * Gets the interface connector for the ui. * * @return The connector to access the interface. */ public MapInterfaceConnector getInterfaceConnector() { return this.connector; } public void setSelection(ISelectionSet selection) { controls.displaySelection(selection); } public void scrollTo(ShortPoint2D point, boolean mark) { if (point != null) { this.context.scrollTo(point); if (mark) { scrollMarker = point; scrollMarkerTime = System.currentTimeMillis(); } } } @Override public void action(IAction action) { controls.action(action); switch (action.getActionType()) { case TOGGLE_DEBUG: debugColorMode = EDebugColorModes.getNextMode(debugColorMode); System.out.println("Current debugColorMode: " + debugColorMode); break; case TOGGLE_ORIGINAL_GRAPHICS: context.ENABLE_ORIGINAL = !context.ENABLE_ORIGINAL; break; case PAN_TO: PointAction panAction = (PointAction) action; scrollTo(panAction.getPosition(), false); break; case SCREEN_CHANGE: ScreenChangeAction screenAction = (ScreenChangeAction) action; controls.setMapViewport(screenAction.getScreenArea()); break; case ZOOM_IN: if (context.getScreen().getZoom() < 1.1) { setZoom(context.getScreen().getZoom() * 2, null); } break; case ZOOM_OUT: if (context.getScreen().getZoom() > 0.6) { setZoom(context.getScreen().getZoom() / 2, null); } break; case MOVE_TO: moveToMarker = ((PointAction) action).getPosition(); moveToMarkerTime = System.currentTimeMillis(); break; case SHOW_MESSAGE: scrollTo(messenger.getPosition(), true); break; case SHOW_CONSTRUCTION_MARK: EBuildingType buildingType = ((ShowConstructionMarksAction) action).getBuildingType(); placementBuilding = buildingType == null ? null : new PlacementBuilding(buildingType); break; default: break; } } private void setZoom(float newZoom, UIPoint pointingPosition) { context.getScreen().setZoom(newZoom, pointingPosition); reapplyContentSizes(); } public void addMessage(IMessage message) { boolean printMsg; synchronized (messenger) { printMsg = messenger.addMessage(message); } if (printMsg) { switch (message.getType()) { case ATTACKED: soundmanager.playSound(SoundManager.NOTIFY_ATTACKED, 1); break; default: break; } } } @Override public void fireAction(IAction action) { IAction fire = controls.replaceAction(action); if (fire != null) { getInterfaceConnector().fireAction(fire); } } @Override public void actionThreadSlow(boolean isBlocking) { actionThreadIsSlow = isBlocking; } @Override public void actionThreadCaughtException(Throwable e) { // This is currently ignored. TODO: Where to catch exceptions? } public void stop() { backgroundSound.stop(); controls.stop(); } protected void loadUIState(UIState state) { if (state == null) { return; } if (state.getStartPoint() != null) { scrollTo(state.getStartPoint(), false); } else { setZoom(state.getZoom(), null); context.getScreen().setScreenCenter(state.getScreenCenterX(), state.getScreenCenterY()); } } protected UIState getUIState() { ScreenPosition screen = context.getScreen(); return new UIState(screen.getScreenCenterX(), screen.getScreenCenterY(), screen.getZoom()); } /** * Gets the color for a given player. * * @param player * The player to get the color for. * @return The color. */ public Color getPlayerColor(byte player) { return context.getPlayerColor(player); } }