/* * Copyright 2010, Maarten Billemont * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lyndir.omicron.cli.view; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import com.google.common.collect.*; import com.googlecode.lanterna.input.Key; import com.googlecode.lanterna.screen.Screen; import com.googlecode.lanterna.screen.ScreenCharacterStyle; import com.googlecode.lanterna.terminal.Terminal; import com.lyndir.lanterna.view.*; import com.lyndir.lhunath.opal.math.*; import com.lyndir.omicron.api.*; import com.lyndir.omicron.api.util.Maybe; import com.lyndir.omicron.cli.OmicronCLI; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * @author lhunath, 2013-07-21 */ public class MapView extends View { private static final Map<LevelType, Terminal.Color> levelTypeColors = // ImmutableMap.of( LevelType.GROUND, Terminal.Color.GREEN, LevelType.SKY, Terminal.Color.CYAN, LevelType.SPACE, Terminal.Color.BLUE ); private static final Map<ResourceType, Terminal.Color> resourceTypeColors = // ImmutableMap.of( ResourceType.FUEL, Terminal.Color.RED, ResourceType.METALS, Terminal.Color.WHITE, ResourceType.SILICON, Terminal.Color.YELLOW, ResourceType.RARE_ELEMENTS, Terminal.Color.MAGENTA ); private Vec2 offset = Vec2.create(); private LevelType levelType; @Nullable private Terminal.Color mapColor; @Nullable private String backgroundPattern; private boolean hasUnits; public MapView(@Nonnull final LevelType levelType) { this.levelType = levelType; } @Override protected void onReady() { super.onReady(); OmicronCLI.get().addGameListener( new GameListener() { @Override public void onNewTurn(final Turn currentTurn) { if (!hasUnits) setHomeOffset(); } } ); } @Override protected void drawForeground(final Screen screen) { super.drawForeground( screen ); Optional<IGameController> gameController = OmicronCLI.get().getGameController(); if (!gameController.isPresent()) return; Optional<IPlayer> localPlayerOptional = OmicronCLI.get().getLocalPlayer(); if (!localPlayerOptional.isPresent()) return; IPlayer localPlayer = localPlayerOptional.get(); // Create an empty grid. Size levelSize = gameController.get().getGame().getLevel( getLevelType() ).getSize(); Table<Integer, Integer, ITile> grid = HashBasedTable.create( levelSize.getHeight(), levelSize.getWidth() ); // Iterate observable tiles and populate the grid. localPlayer.observableTiles().forEach( tile -> { Vec2 coordinate = positionToMapCoordinate( tile.getPosition() ); grid.put( coordinate.getY(), coordinate.getX(), tile ); } ); // Draw grid in view. Box contentBox = getContentBoxOnScreen(); for (int screenX = contentBox.getLeft(); screenX <= contentBox.getRight(); ++screenX) for (int screenY = contentBox.getTop(); screenY <= contentBox.getBottom(); ++screenY) { int tileY = screenY - contentBox.getTop() + getOffset().getY(); int tileX = screenX - contentBox.getLeft() + getOffset().getX(); if (!levelSize.isInBounds( Vec2.create( tileX, tileY ) )) continue; ITile tile = grid.get( tileY, tileX ); Maybe<? extends IGameObject> contents; Terminal.Color bgColor = getBackgroundColor(); if (tile == null) contents = Maybe.empty(); else { contents = tile.getContents(); bgColor = levelTypeColors.get( tile.getLevel().getType() ); for (final ResourceType resourceType : ResourceType.values()) { Maybe<Integer> resourceQuantity = tile.getResourceQuantity( resourceType ); if (resourceQuantity.presence() == Maybe.Presence.PRESENT) bgColor = resourceTypeColors.get( resourceType ); } } screen.putString( screenX + (screenY % 2 == 0? 0: 1), screenY, contents.presence() == Maybe.Presence.PRESENT? contents.get().getType().getTypeName().substring( 0, 1 ) : " ", getMapColor(), bgColor, ScreenCharacterStyle.Bold ); // Draw off-screen warning labels. } Inset offScreen = new Inset( Math.max( 0, getOffset().getY() ), Math.max( 0, levelSize.getWidth() - contentBox.getSize().getWidth() - getOffset().getX() + 1 ), Math.max( 0, levelSize.getHeight() - contentBox.getSize().getHeight() - getOffset().getY() - 1 ), Math.max( 0, getOffset().getX() ) ); int centerX = contentBox.getLeft() + (levelSize.getWidth() - offScreen.getHorizontal()) / 2 - getOffset().getX() + offScreen.getLeft(); int centerY = contentBox.getTop() + (levelSize.getHeight() - offScreen.getVertical()) / 2 - getOffset().getY() + offScreen.getTop(); centerX = Math.min( contentBox.getRight() - 3, Math.max( contentBox.getLeft(), centerX ) ); centerY = Math.min( contentBox.getBottom() - 1, Math.max( contentBox.getTop() + 1, centerY ) ); if (offScreen.getTop() > 0) screen.putString( centerX, contentBox.getTop(), // String.format( "%+d", offScreen.getTop() ), getInfoTextColor(), getInfoBackgroundColor() ); if (offScreen.getRight() > 0) { String label = String.format( "%+d", offScreen.getRight() ); screen.putString( contentBox.getRight() - label.length(), centerY, // label, getInfoTextColor(), getInfoBackgroundColor() ); } if (offScreen.getBottom() > 0) screen.putString( centerX, contentBox.getBottom(), // String.format( "%+d", offScreen.getBottom() ), getInfoTextColor(), getInfoBackgroundColor() ); if (offScreen.getLeft() > 0) screen.putString( contentBox.getLeft(), centerY, // String.format( "%+d", offScreen.getLeft() ), getInfoTextColor(), getInfoBackgroundColor() ); } @Override protected boolean onKey(final Key key) { if (key.getKind() == Key.Kind.ArrowUp && key.isCtrlPressed()) { setOffset( getOffset().translate( 0, -1 ) ); return true; } if (key.getKind() == Key.Kind.ArrowDown && key.isCtrlPressed()) { setOffset( getOffset().translate( 0, 1 ) ); return true; } if (key.getKind() == Key.Kind.ArrowLeft && key.isCtrlPressed()) { setOffset( getOffset().translate( -1, 0 ) ); return true; } if (key.getKind() == Key.Kind.ArrowRight && key.isCtrlPressed()) { setOffset( getOffset().translate( 1, 0 ) ); return true; } if (key.getKind() == Key.Kind.Home && key.isCtrlPressed()) { setHomeOffset(); return true; } return false; } private void setHomeOffset() { Optional<IPlayer> localPlayerOptional = OmicronCLI.get().getLocalPlayer(); Stream<? extends IGameObject> gameObjects = Stream.empty(); if (localPlayerOptional.isPresent()) gameObjects = localPlayerOptional.get().getController().playerObjectsObservable(); setOffset( gameObjects.filter( gameObject -> gameObject.getLocation().get().getLevel().getType() == getLevelType() ) .map( gameObject -> { // Transform game objects into their offset from the center of the map. hasUnits = true; Box contentBox = getContentBoxOnScreen(); return positionToMapCoordinate( gameObject.getLocation().get().getPosition() ) // .translate( -contentBox.getSize().getWidth() / 2, -contentBox.getSize().getHeight() / 2 ); } ) .findFirst() .orElseGet( () -> { // If there is no game object in this level, go to the map's center. hasUnits = false; Box contentBox = getContentBoxOnScreen(); return Vec2.create( contentBox.getSize().getWidth() / 2, contentBox.getSize().getHeight() / 2 ); } ) ); } private static Vec2 positionToMapCoordinate(final Vec2 position) { int y = position.getY(); int x = position.getX() + y / 2; return Vec2.create( x, y ); } @Override public String getBackgroundPattern() { return ifNotNullElse( backgroundPattern, getTheme().mapBgPattern() ); } @Override public void setBackgroundPattern(@Nullable final String backgroundPattern) { this.backgroundPattern = backgroundPattern; } public Terminal.Color getMapColor() { return ifNotNullElse( mapColor, getTheme().mapFg() ); } public void setMapColor(@Nullable final Terminal.Color mapColor) { this.mapColor = mapColor; } public LevelType getLevelType() { return levelType; } public void setLevelType(final LevelType levelType) { this.levelType = levelType; } @Nonnull public Vec2 getOffset() { return offset; } public void setOffset(@Nonnull final Vec2 offset) { this.offset = offset; } public Vec2 getCenterTile() { Size contentBox = getContentBoxOnScreen().getSize(); return offset.translate( contentBox.getWidth() / 2, contentBox.getHeight() / 2 ); } }