public class Actor
{
    private Grid<Actor> grid;
    private Location location;
    private int direction;
    private Color color;

    /**
     * Constructs a blue actor that is facing north.
     */
    public Actor()
    {
        color = Color.BLUE;
        direction = Location.NORTH;
        grid = null;
        location = null;
    }

    /**
     * Gets the color of this actor.
     * @return the color of this actor
     */
    public Color getColor()
    {
        return color;
    }

    /**
     * Sets the color of this actor.
     * @param newColor the new color
     */
    public void setColor(Color newColor)
    {
        color = newColor;
    }

    /**
     * Gets the current direction of this actor.
     * @return the direction of this actor, an angle between 0 and 359 degrees
     */
    public int getDirection()
    {
        return direction;
    }

    /**
     * Sets the current direction of this actor.
     * @param newDirection the new direction. The direction of this actor is set
     * to the angle between 0 and 359 degrees that is equivalent to
     * <code>newDirection</code>.
     */
    public void setDirection(int newDirection)
    {
        direction = newDirection % Location.FULL_CIRCLE;
        if (direction < 0)
            direction += Location.FULL_CIRCLE;
    }

    /**
     * Gets the grid in which this actor is located.
     * @return the grid of this actor, or <code>null</code> if this actor is
     * not contained in a grid
     */
    public Grid<Actor> getGrid()
    {
        return grid;
    }

    /**
     * Gets the location of this actor.
     * @return the location of this actor, or <code>null</code> if this actor is
     * not contained in a grid
     */
    public Location getLocation()
    {
        return location;
    }

    /**
     * Puts this actor into a grid. If there is another actor at the given
     * location, it is removed. <br />
     * Precondition: (1) This actor is not contained in a grid (2)
     * <code>loc</code> is valid in <code>gr</code>
     * @param gr the grid into which this actor should be placed
     * @param loc the location into which the actor should be placed
     */
    public void putSelfInGrid(Grid<Actor> gr, Location loc)
    {
        if (grid != null)
            throw new IllegalStateException(
                    "This actor is already contained in a grid.");

        Actor actor = gr.get(loc);
        if (actor != null)
            actor.removeSelfFromGrid();
        gr.put(loc, this);
        grid = gr;
        location = loc;
    }

    /**
     * Removes this actor from its grid. <br />
     * Precondition: This actor is contained in a grid
     */
    public void removeSelfFromGrid()
    {
        if (grid == null)
            throw new IllegalStateException(
                    "This actor is not contained in a grid.");
        if (grid.get(location) != this)
            throw new IllegalStateException(
                    "The grid contains a different actor at location "
                            + location + ".");

        grid.remove(location);
        grid = null;
        location = null;
    }

    /**
     * Moves this actor to a new location. If there is another actor at the
     * given location, it is removed. <br />
     * Precondition: (1) This actor is contained in a grid (2)
     * <code>newLocation</code> is valid in the grid of this actor
     * @param newLocation the new location
     */
    public void moveTo(Location newLocation)
    {
        if (grid == null)
            throw new IllegalStateException("This actor is not in a grid.");
        if (grid.get(location) != this)
            throw new IllegalStateException(
                    "The grid contains a different actor at location "
                            + location + ".");
        if (!grid.isValid(newLocation))
            throw new IllegalArgumentException("Location " + newLocation
                    + " is not valid.");

        if (newLocation.equals(location))
            return;
        grid.remove(location);
        Actor other = grid.get(newLocation);
        if (other != null)
            other.removeSelfFromGrid();
        location = newLocation;
        grid.put(location, this);
    }

    /**
     * Reverses the direction of this actor. Override this method in subclasses
     * of <code>Actor</code> to define types of actors with different behavior
     * 
     */
    public void act()
    {
        setDirection(getDirection() + Location.HALF_CIRCLE);
    }

    /**
     * Creates a string that describes this actor.
     * @return a string with the location, direction, and color of this actor
     */
    public String toString()
    {
        return getClass().getName() + "[location=" + location + ",direction="
                + direction + ",color=" + color + "]";
    }
} public class GridPanel extends JPanel implements Scrollable,
        PseudoInfiniteViewport.Pannable {
    private static final int MIN_CELL_SIZE = 12;
    private static final int DEFAULT_CELL_SIZE = 48;
    private static final int DEFAULT_CELL_COUNT = 10;
    private static final int TIP_DELAY = 1000;

    public AVWorld avw;

    private Grid<?> grid;
    private int numRows, numCols, originRow, originCol;
    private int cellSize; // the size of each cell, EXCLUDING the gridlines
    private boolean toolTipsEnabled;
    private Color backgroundColor = Color.WHITE;
    private ResourceBundle resources;
    private DisplayMap displayMap;
    //	public Location originalLocation;
    public Location currentLocation;
    private Timer tipTimer;
    private JToolTip tip;
    private JPanel glassPane;

    /**
     * Construct a new GridPanel object with no grid. The view will be
     * empty.
     */
    public GridPanel(DisplayMap map, ResourceBundle res) {
        displayMap = map;
        resources = res;
        setToolTipsEnabled(true);
    }

    public Set<Terrain> shouldBeHighlighted = new HashSet<>();
    public Graphics2D pointerToGraphics2D;

    /**
     * Paint this component.
     *
     * @param g the Graphics object to use to render this component
     */
    public void paintComponent(Graphics g) {

        Graphics2D g2 = (Graphics2D) g;
        pointerToGraphics2D = g2;
        super.paintComponent(g2);
        if (grid == null)
            return;

        Insets insets = getInsets();
        g2.setColor(backgroundColor);
        g2.fillRect(insets.left, insets.top, numCols * (cellSize + 1) + 1, numRows
                * (cellSize + 1) + 1);

        drawBG(g2, insets);

        drawWatermark(g2);

        //comment out following line to hide grid lines
        //		drawGridlines(g2);
        drawOccupants(g2);
        drawCurrentLocation(g2);
        //i think this is okay
        drawBoxesOnSetOfLocations(g2, shouldBeHighlighted);
    }

    //XXX made by me
    public void drawBoxesFromLocation(Graphics2D g2, Location loc) {
        try {
            Unit u = (Unit) avw.getGrid().get(loc);
            if (null == u || u.canMove() == false) {
                return;
            }
            //System.out.println("invoking getValidMoveSpaces() from GridPanel line 142");
            Set<Terrain> validMoveSpaces = u.getValidMoveSpaces();
            drawBoxesOnSetOfLocations(g2, validMoveSpaces);

        } catch (Exception e) {
            System.out.println("some error, see drawBoxesFromLocation() in GridPanel");
            e.printStackTrace();
        }
    }

    public void drawBoxesOnSetOfLocations(Graphics2D g2, Set<Terrain> validMoveSpaces) {
        for (Terrain t : validMoveSpaces) {
            Location l = t;
            Point p = pointForLocation(l);
            //			g2 = (Graphics2D) getGraphics(); //This should work but problem is already solved
            g2.drawRect(p.x - cellSize / 2 - 2, p.y - cellSize / 2 - 2,
                    cellSize + 3, cellSize + 3);
        }
    }

    public void showIconsOnSetOfLocations(Image pic, Set<Terrain> where) {
        Graphics2D g2 = (Graphics2D) getGraphics();
        for (Terrain t : where) {
            Location l = t;
            Point p = pointForLocation(l);
            g2.drawImage(pic, p.x, p.y, null);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    private void drawBG(Graphics2D g2, Insets insets) {
        BufferedImage img = null;
        try {
            img = ImageIO.read(GridPanel.class.getClassLoader().getResourceAsStream(Runner.pathToMapPic));

        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("file not found");
        }
        g2.drawImage(img, insets.left, insets.top, numCols * (cellSize + 1) + 1, numRows
                * (cellSize + 1) + 1, 0, 0, img.getWidth(), img.getHeight(), null);
    }


    /**
     * Draw one occupant object. First verify that the object is actually
     * visible before any drawing, set up the clip appropriately and use the
     * DisplayMap to determine which object to call upon to render this
     * particular Locatable. Note that we save and restore the graphics
     * transform to restore back to normalcy no matter what the renderer did to
     * to the coordinate system.
     *
     * @param g2    the Graphics2D object to use to render
     * @param xleft the leftmost pixel of the rectangle
     * @param ytop  the topmost pixel of the rectangle
     * @param obj   the Locatable object to draw
     */
    private void drawOccupant(Graphics2D g2, int xleft, int ytop, Object obj) {
        Rectangle cellToDraw = new Rectangle(xleft, ytop, cellSize, cellSize);

        // Only draw if the object is visible within the current clipping
        // region.
        if (cellToDraw.intersects(g2.getClip().getBounds())) {
            Graphics2D g2copy = (Graphics2D) g2.create();
            g2copy.clip(cellToDraw);
            // Get the drawing object to display this occupant.
            Display displayObj = displayMap.findDisplayFor(obj.getClass());
            displayObj.draw(obj, this, g2copy, cellToDraw);
            g2copy.dispose();
        }
    }

    /**
     * Draw the gridlines for the grid. We only draw the portion of the
     * lines that intersect the current clipping bounds.
     *
     * @param g2 the Graphics2 object to use to render
     */
    private void drawGridlines(Graphics2D g2) {
        Rectangle curClip = g2.getClip().getBounds();
        int top = getInsets().top, left = getInsets().left;

        int miny = Math.max(0, (curClip.y - top) / (cellSize + 1)) * (cellSize + 1) + top;
        int minx = Math.max(0, (curClip.x - left) / (cellSize + 1)) * (cellSize + 1) + left;
        int maxy = Math.min(numRows,
                (curClip.y + curClip.height - top + cellSize) / (cellSize + 1))
                * (cellSize + 1) + top;
        int maxx = Math.min(numCols,
                (curClip.x + curClip.width - left + cellSize) / (cellSize + 1))
                * (cellSize + 1) + left;

        g2.setColor(Color.GRAY);
        for (int y = miny; y <= maxy; y += cellSize + 1)
            for (int x = minx; x <= maxx; x += cellSize + 1) {
                Location loc = locationForPoint(
                        new Point(x + cellSize / 2, y + cellSize / 2));
                if (loc != null && !grid.isValid(loc))
                    g2.fillRect(x + 1, y + 1, cellSize, cellSize);
            }

        g2.setColor(Color.BLACK);
        for (int y = miny; y <= maxy; y += cellSize + 1)
            // draw horizontal lines
            g2.drawLine(minx, y, maxx, y);

        for (int x = minx; x <= maxx; x += cellSize + 1)
            // draw vertical lines
            g2.drawLine(x, miny, x, maxy);
    }

    /**
     * Draws the occupants of the grid.
     *
     * @param g2 the graphics context
     */
    private void drawOccupants(Graphics2D g2) {
        ArrayList<Location> occupantLocs = grid.getOccupiedLocations();
        for (int index = 0; index < occupantLocs.size(); index++) {
            Location loc = (Location) occupantLocs.get(index);

            int xleft = colToXCoord(loc.getCol());
            int ytop = rowToYCoord(loc.getRow());
            drawOccupant(g2, xleft, ytop, grid.get(loc));
        }
    }

    //XXX added by me
    public void drawBoxOnLocation(Graphics2D g2, Location loc) {
        if ("hide".equals(System.getProperty("info.gridworld.gui.selection")))
            return;
        if (loc != null) {
            Point p = pointForLocation(loc);
            g2.drawRect(p.x - cellSize / 2 - 2, p.y - cellSize / 2 - 2,
                    cellSize + 3, cellSize + 3);
        }
    }


    /**
     * Draws a square that marks the current location.
     *
     * @param g2 the graphics context
     */
    private void drawCurrentLocation(Graphics2D g2) {
        if ("hide".equals(System.getProperty("info.gridworld.gui.selection")))
            return;
        if (currentLocation != null) {
            Point p = pointForLocation(currentLocation);
            g2.drawRect(p.x - cellSize / 2 - 2, p.y - cellSize / 2 - 2,
                    cellSize + 3, cellSize + 3);
        }
    }


    /**
     * Draws a watermark that shows the version number if it is < 1.0
     *
     * @param g2 the graphics context
     */
    private void drawWatermark(Graphics2D g2) {
        if ("hide".equals(System.getProperty("info.gridworld.gui.watermark")))
            return;
        g2 = (Graphics2D) g2.create();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        Rectangle rect = getBounds();
        g2.setPaint(new Color(0xE3, 0xD3, 0xD3));
        final int WATERMARK_FONT_SIZE = 100;
        String s = resources.getString("version.id");
        if ("1.0".compareTo(s) <= 0) return;
        g2.setFont(new Font("SansSerif", Font.BOLD, WATERMARK_FONT_SIZE));
        FontRenderContext frc = g2.getFontRenderContext();
        Rectangle2D bounds = g2.getFont().getStringBounds(s, frc);
        float centerX = rect.x + rect.width / 2;
        float centerY = rect.y + rect.height / 2;
        float leftX = centerX - (float) bounds.getWidth() / 2;
        LineMetrics lm = g2.getFont().getLineMetrics(s, frc);
        float baselineY = centerY - lm.getHeight() / 2 + lm.getAscent();
        g2.drawString(s, leftX, baselineY);
    }

    /**
     * Enables/disables showing of tooltip giving information about the
     * occupant beneath the mouse.
     *
     * @param flag true/false to enable/disable tool tips
     */
    public void setToolTipsEnabled(boolean flag) {
        if ("hide".equals(System.getProperty("info.gridworld.gui.tooltips")))
            flag = false;
        if (flag)
            ToolTipManager.sharedInstance().registerComponent(this);
        else
            ToolTipManager.sharedInstance().unregisterComponent(this);
        toolTipsEnabled = flag;
    }

    /**
     * Sets the grid being displayed. Reset the cellSize to be the
     * largest that fits the entire grid in the current visible area (use
     * default if grid is too large).
     *
     * @param gr the grid to display
     */
    public void setGrid(Grid<?> gr) {
        currentLocation = new Location(0, 0);
        JViewport vp = getEnclosingViewport(); // before changing, reset
        // scroll/pan position
        if (vp != null)
            vp.setViewPosition(new Point(0, 0));

        grid = gr;
        originRow = originCol = 0;

        if (grid.getNumRows() == -1 && grid.getNumCols() == -1) {
            numRows = numCols = 2000;
            // This determines the "virtual" size of the pan world
        } else {
            numRows = grid.getNumRows();
            numCols = grid.getNumCols();
        }
        recalculateCellSize(MIN_CELL_SIZE);
    }

    // private helpers to calculate extra width/height needs for borders/insets.
    private int extraWidth() {
        return getInsets().left + getInsets().right;
    }

    private int extraHeight() {
        return getInsets().top + getInsets().left;
    }

    /**
     * Returns the desired size of the display, for use by layout manager.
     *
     * @return preferred size
     */
    public Dimension getPreferredSize() {
        return new Dimension(numCols * (cellSize + 1) + 1 + extraWidth(),
                numRows * (cellSize + 1) + 1 + extraHeight());
    }

    /**
     * Returns the minimum size of the display, for use by layout manager.
     *
     * @return minimum size
     */
    public Dimension getMinimumSize() {
        return new Dimension(numCols * (MIN_CELL_SIZE + 1) + 1 + extraWidth(),
                numRows * (MIN_CELL_SIZE + 1) + 1 + extraHeight());
    }

    /**
     * Zooms in the display by doubling the current cell size.
     */
    public void zoomIn() {
        cellSize *= 1.5;
        revalidate();
    }

    /**
     * Zooms out the display by halving the current cell size.
     */
    public void zoomOut() {
        cellSize = (int) Math.max(cellSize / 1.5, MIN_CELL_SIZE);
        revalidate();
    }

    /**
     * Pans the display back to the origin, so that 0, 0 is at the the upper
     * left of the visible viewport.
     */
    public void recenter(Location loc) {
        originRow = loc.getRow();
        originCol = loc.getCol();
        repaint();
        JViewport vp = getEnclosingViewport();
        if (vp != null) {
            if (!isPannableUnbounded()
                    || !(vp instanceof PseudoInfiniteViewport))
                vp.setViewPosition(pointForLocation(loc));
            else
                showPanTip();
        }
    }

    /**
     * Given a Point determine which grid location (if any) is under the
     * mouse. This method is used by the GUI when creating Fish by clicking on
     * cells in the display.
     *
     * @param p the Point in question (in display's coordinate system)
     * @return the Location beneath the event (which may not be a
     * valid location in the grid)
     */
    public Location locationForPoint(Point p) {
        return new Location(yCoordToRow(p.y), xCoordToCol(p.x));
    }

    public Point pointForLocation(Location loc) {
        if (null == loc) {
            return null;
        }
        return new Point(colToXCoord(loc.getCol()) + cellSize / 2,
                rowToYCoord(loc.getRow()) + cellSize / 2);
    }

    // private helpers to convert between (x,y) and (row,col)
    private int xCoordToCol(int xCoord) {
        return (xCoord - 1 - getInsets().left) / (cellSize + 1) + originCol;
    }

    private int yCoordToRow(int yCoord) {
        return (yCoord - 1 - getInsets().top) / (cellSize + 1) + originRow;
    }

    private int colToXCoord(int col) {
        return (col - originCol) * (cellSize + 1) + 1 + getInsets().left;
    }

    private int rowToYCoord(int row) {
        return (row - originRow) * (cellSize + 1) + 1 + getInsets().top;
    }

    /**
     * Given a MouseEvent, determine what text to place in the floating tool tip
     * when the the mouse hovers over this location. If the mouse is over a
     * valid grid cell. we provide some information about the cell and
     * its contents. This method is automatically called on mouse-moved events
     * since we register for tool tips.
     *
     * @param evt the MouseEvent in question
     * @return the tool tip string for this location
     */
    public String getToolTipText(MouseEvent evt) {
        Location loc = locationForPoint(evt.getPoint());
        return getToolTipText(loc);
    }

    private String getToolTipText(Location loc) {
        if (!toolTipsEnabled || loc == null || !grid.isValid(loc)) {
            return null;
        }
        Object f = grid.get(loc);
        TerrainGrid tg;
        if (grid instanceof TerrainGrid) {
            tg = (TerrainGrid) grid;
            loc = tg.getLocationArray()[loc.getRow()][loc.getCol()];
        }

        if (!(loc instanceof Terrain)) {

            if (f != null)
                return MessageFormat.format(resources
                        .getString("cell.tooltip.nonempty"), new Object[]
                        {loc, f});
            else
                return MessageFormat.format(resources
                        .getString("cell.tooltip.empty"), new Object[]
                        {loc, f});
        } else {
            Terrain tLoc = (Terrain) loc;
            String ans = tLoc.getClass().getSimpleName() + tLoc + " " + tLoc.getDefenseStarsAsString() + " defense";
            if (f != null) {
                if (f instanceof Air) {
                    ans += " (no def+ for air)";
                }
            }
            if (tLoc instanceof Property) {
                Property pLoc = (Property) tLoc;
                Player owner = (pLoc).getOwner();
                String xtraDesc = owner != null ? "Player " + owner.id : "(neutral)";
                ans += " | owned by: " + xtraDesc;
                if (pLoc.getCapTimer() != Property.FULL_CAP_TIMER) {
                    ans += " | cap timer=" + pLoc.getCapTimer();
                }
            }
            return ans;
        }
    }

    /**
     * Sets the current location.
     *
     * @param loc the new location
     */
    public void setCurrentLocation(Location loc) {
        currentLocation = loc;
    }

    /**
     * Gets the current location.
     *
     * @return the currently selected location (marked with a bold square)
     */
    public Location getCurrentLocation() {
        if (!(avw.getGrid() instanceof TerrainGrid)) {
            return currentLocation;
        }
        TerrainGrid<Actor> tg = (TerrainGrid<Actor>) avw.getGrid();
        return tg.getLocationArray()[currentLocation.getRow()][currentLocation.getCol()];

    }

    /**
     * Moves the current location by a given amount.
     *
     * @param dr the number of rows by which to move the location
     * @param dc the number of columns by which to move the location
     */
    public void moveLocation(int dr, int dc) {
        Location newLocation = new Location(currentLocation.getRow() + dr,
                currentLocation.getCol() + dc);
        if (!grid.isValid(newLocation))
            return;

        currentLocation = newLocation;

        JViewport viewPort = getEnclosingViewport();
        if (isPannableUnbounded()) {
            if (originRow > currentLocation.getRow())
                originRow = currentLocation.getRow();
            if (originCol > currentLocation.getCol())
                originCol = currentLocation.getCol();
            Dimension dim = viewPort.getSize();
            int rows = dim.height / (cellSize + 1);
            int cols = dim.width / (cellSize + 1);
            if (originRow + rows - 1 < currentLocation.getRow())
                originRow = currentLocation.getRow() - rows + 1;
            if (originCol + rows - 1 < currentLocation.getCol())
                originCol = currentLocation.getCol() - cols + 1;
        } else if (viewPort != null) {
            int dx = 0;
            int dy = 0;
            Point p = pointForLocation(currentLocation);
            Rectangle locRect = new Rectangle(p.x - cellSize / 2, p.y
                    - cellSize / 2, cellSize + 1, cellSize + 1);

            Rectangle viewRect = viewPort.getViewRect();
            if (!viewRect.contains(locRect)) {
                while (locRect.x < viewRect.x + dx)
                    dx -= cellSize + 1;
                while (locRect.y < viewRect.y + dy)
                    dy -= cellSize + 1;
                while (locRect.getMaxX() > viewRect.getMaxX() + dx)
                    dx += cellSize + 1;
                while (locRect.getMaxY() > viewRect.getMaxY() + dy)
                    dy += cellSize + 1;

                Point pt = viewPort.getViewPosition();
                pt.x += dx;
                pt.y += dy;
                viewPort.setViewPosition(pt);
            }
        }
        repaint();
        showTip(getToolTipText(currentLocation),
                pointForLocation(currentLocation));
    }

    /**
     * Show a tool tip.
     *
     * @param tipText the tool tip text
     * @param pt      the pixel position over which to show the tip
     */
    public void showTip(String tipText, Point pt) {
        if (getRootPane() == null)
            return;
        // draw in glass pane to appear on top of other components
        if (glassPane == null) {
            getRootPane().setGlassPane(glassPane = new JPanel());
            glassPane.setOpaque(false);
            glassPane.setLayout(null); // will control layout manually
            glassPane.add(tip = new JToolTip());
            tipTimer = new Timer(TIP_DELAY, new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    glassPane.setVisible(false);
                }
            });
            tipTimer.setRepeats(false);
        }
        if (tipText == null)
            return;

        // set tip text to identify current origin of pannable view
        tip.setTipText(tipText);

        // position tip to appear at upper left corner of viewport
        tip.setLocation(SwingUtilities.convertPoint(this, pt, glassPane));
        tip.setSize(tip.getPreferredSize());

        // show glass pane (it contains tip)
        glassPane.setVisible(true);
        glassPane.repaint();

        // this timer will hide the glass pane after a short delay
        tipTimer.restart();
    }

    /**
     * Calculate the cell size to use given the current viewable region and the
     * the number of rows and columns in the grid. We use the largest
     * cellSize that will fit in the viewable region, bounded to be at least the
     * parameter minSize.
     */
    private void recalculateCellSize(int minSize) {
        if (numRows == 0 || numCols == 0) {
            cellSize = 0;
        } else {
            JViewport vp = getEnclosingViewport();
            Dimension viewableSize = (vp != null) ? vp.getSize() : getSize();
            int desiredCellSize = Math.min(
                    (viewableSize.height - extraHeight()) / numRows,
                    (viewableSize.width - extraWidth()) / numCols) - 1;
            // now we want to approximate this with
            // DEFAULT_CELL_SIZE * Math.pow(2, k)
            cellSize = DEFAULT_CELL_SIZE;
            if (cellSize <= desiredCellSize)
                while (2 * cellSize <= desiredCellSize)
                    cellSize *= 2;
            else
                while (cellSize / 2 >= Math.max(desiredCellSize, MIN_CELL_SIZE))
                    cellSize /= 2;
        }
        revalidate();
    }

    // helper to get our parent viewport, if we are in one.
    private JViewport getEnclosingViewport() {
        Component parent = getParent();
        return (parent instanceof JViewport) ? (JViewport) parent : null;
    }

    // GridPanel implements the Scrollable interface to get nicer behavior in a
    // JScrollPane. The 5 methods below are the methods in that interface

    public int getScrollableUnitIncrement(Rectangle visibleRect,
                                          int orientation, int direction) {
        return cellSize + 1;
    }

    public int getScrollableBlockIncrement(Rectangle visibleRect,
                                           int orientation, int direction) {
        if (orientation == SwingConstants.VERTICAL)
            return (int) (visibleRect.height * .9);
        else
            return (int) (visibleRect.width * .9);
    }

    public boolean getScrollableTracksViewportWidth() {
        return false;
    }

    public boolean getScrollableTracksViewportHeight() {
        return false;
    }

    public Dimension getPreferredScrollableViewportSize() {
        return new Dimension(DEFAULT_CELL_COUNT * (DEFAULT_CELL_SIZE + 1) + 1 + extraWidth(),
                DEFAULT_CELL_COUNT * (DEFAULT_CELL_SIZE + 1) + 1 + extraHeight());
    }

    // GridPanel implements the PseudoInfiniteViewport.Pannable interface to
    // play nicely with the pan behavior for unbounded view.
    // The 3 methods below are the methods in that interface.

    public void panBy(int hDelta, int vDelta) {
        originCol += hDelta / (cellSize + 1);
        originRow += vDelta / (cellSize + 1);
        repaint();
    }

    public boolean isPannableUnbounded() {
        return grid != null && (grid.getNumRows() == -1 || grid.getNumCols() == -1);
    }

    /**
     * Shows a tool tip over the upper left corner of the viewport with the
     * contents of the pannable view's pannable tip text (typically a string
     * identifiying the corner point). Tip is removed after a short delay.
     */
    public void showPanTip() {
        String tipText = null;
        Point upperLeft = new Point(0, 0);
        JViewport vp = getEnclosingViewport();
        if (!isPannableUnbounded() && vp != null)
            upperLeft = vp.getViewPosition();
        Location loc = locationForPoint(upperLeft);
        if (loc != null)
            tipText = getToolTipText(loc);

        showTip(tipText, getLocation());
    }
} public class MenuMaker<T> {

    /**
     * hacky display fixer lol
     */
    public static void noBugsPls(GridPanel display, TerrainGrid gr) {
        //cheaty bugfix hopefully
        if (gr != null) {
            Location clickMeForBugFree = gr.getLocationArray()[0][0];
            display.avw.locationClicked(clickMeForBugFree);
        }
        display.avw.getWorldFrame().repaint();
    }

    /**
     * This will be the common repaint.
     *
     * @param display
     */
    public static void tryRepaint(GridPanel display) {
        //display.revalidate();
        Container c = display.getParent();
        display.revalidate();
        display.paintImmediately(display.getBounds());
        //display.avw.getWorldFrame().repaint();
        //c.repaint();
        //display.repaint();

    }

    /**
     * Constructs a menu maker for a given world.
     *
     * @param parent     the frame in which the world is displayed
     * @param resources  the resource bundle
     * @param displayMap the display map
     */
    public MenuMaker(WorldFrame<T> parent, ResourceBundle resources,
                     DisplayMap displayMap) {
        this.parent = parent;
        this.resources = resources;
        this.displayMap = displayMap;
    }

    public GridPanel display;

    public Terrain newLoc;

    /**
     * Makes a menu that displays all available actions that a Unit may perform
     * when moving to this tile
     *
     * @param occupant the unit whose available actions should be displayed
     * @param loc      the location that the Unit would move to
     * @return the menu to pop up
     */
    public JPopupMenu makeMoveMenu(T occupant, Location loc) {
        this.occupant = occupant;
        // new ver
        Unit u = (Unit) occupant;

        display.currentLocation = loc;
        currentLocation = loc;
        // TODO tidy up
        //TODO put in another method
        StringBuilder dispMessage = new StringBuilder(50);
        dispMessage.append(u.getInfo());
        if (u instanceof Carry) {
            Carry c = (Carry) u;
            dispMessage.append("\nCurrently Carrying(" + c.getUnits().size() + "/" + c.getMaxCapacity() + "): ");
            ArrayList<String> types = new ArrayList<>();
            for (Unit carried : c.getUnits()) {
                types.add(carried.getConciseInfo());
            }
            if (!types.isEmpty()) {
                dispMessage.append(types.toString());
            } else {
                dispMessage.append("(empty)");
            }
        } else {
            dispMessage.append("\n");
        }
        dispMessage
                .append(u.canMove() ? "\nClick highlighted tile to move, click unhighlighted to cancel"
                        : "\nUnit currently immobile; wait until next turn to move again.");
        if (u instanceof HiddenUnit) {
            dispMessage = new StringBuilder("Currently selected: none.\n\nUse your units to move. Click your factories to build. " +
                    //					 "DO NOT use arrow keys or Enter"+
                    "P1 money: " + Runner.players[1].getMoney() + "  P2 money: " + Runner.players[2].getMoney());
        }
        display.avw.setMessage(dispMessage.toString());
        display.repaint();
        // inefficient
        //if(u.getHealth()>0){ FIXME
        Set<Terrain> validMoveSpaces = u.getValidMoveSpaces();
        display.shouldBeHighlighted = validMoveSpaces;
        display.avw.resetClickedLocation();
        //}
        //TODO thread stuff
        //		new Thread(new Runnable() {
        //			@Override
        //			public void run() {
        newLoc = (Terrain) display.avw.getLocationWhenClicked();
        //			}
        //		}).start();
        if (!validMoveSpaces.contains(newLoc)) {
            display.avw.setMessage("Currently selected: none.\n\nUse your units to move. Click your factories to build. " +
                    //					 "DO NOT use arrow keys or Enter"+
                    "P1 money: " + Runner.players[1].getMoney() + "  P2 money: " + Runner.players[2].getMoney());
            display.shouldBeHighlighted.clear();
            return null;
        }
        System.out.println("line 133 mm" + newLoc);
        try {
            // u.move(newLoc);
            // display.shouldBeHighlighted.clear();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("line 209 Menu: currentLocation set to loc\nloc is "
                + newLoc.getClass().getName() + "at " + newLoc);
        System.out.println("line 211 Menu: old loc set to loc\nloc is "
                + currentLocation.getClass().getName() + "at "
                + currentLocation);
        JPopupMenu menu = new JPopupMenu();
        ArrayList<JMenuItem> actions = null;
        try {
            actions = getValidActions(newLoc);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        System.out.println("methods is " + actions.size() + " size.");
        for (int i = 0; i < actions.size(); i++) {
            menu.add(actions.get(i));
//			System.out.println("added method, see line 226 of MenuMaker");
        }
        return menu;
    }

    public static ImageIcon get16xIcon(URL imagePath) {
        try {
            ImageIcon pic = new ImageIcon(imagePath); // load the image to a imageIcon
            Image image = pic.getImage(); // transform it
            Image newimg = image.getScaledInstance(16, 16, java.awt.Image.SCALE_SMOOTH); // scale it the smooth way
            pic = new ImageIcon(newimg);  // transform it back
            return pic;
        } catch (NullPointerException e) {
            e.printStackTrace();
            return null;
        }
    }

    private ArrayList<JMenuItem> getValidActions(Location newLoc)
            throws NoSuchMethodException, SecurityException {
        Unit u = (Unit) occupant;
        ArrayList<JMenuItem> ans = new ArrayList<JMenuItem>();
        if (u instanceof HiddenUnit) {
            return ans;
        }
        // every unit has the wait option if not loading into carry
        Unit newLocOcc = (Unit) u.getGrid().get(newLoc);
        if (u.canMove() && (null == newLocOcc || u == newLocOcc)) {
            if (!(u == newLocOcc)) {//if moving somewhere
                JMenuItem waitOption = new JMenuItem();
                Action a = new Action() {
                    public boolean enabled = true;

                    @Override
                    public void actionPerformed(ActionEvent arg0) {
                        try {
                            u.move(((Terrain) newLoc), true);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        display.shouldBeHighlighted.clear();
                        display.repaint();

                    }

                    @Override
                    public void addPropertyChangeListener(
                            PropertyChangeListener arg0) {
                    }

                    @Override
                    public Object getValue(String arg0) {
                        return null; // idk
                    }

                    @Override
                    public boolean isEnabled() {
                        return enabled;
                    }

                    @Override
                    public void putValue(String key, Object value) {
                    }

                    @Override
                    public void removePropertyChangeListener(
                            PropertyChangeListener listener) {
                    }

                    @Override
                    public void setEnabled(boolean b) {
                        enabled = b;
                    }
                };
                a.setEnabled(true);
                waitOption.setAction(a);
                URL imagePath = this.getClass().getClassLoader().getResource("32x/wait.png");
                waitOption.setIcon(get16xIcon(imagePath));
                waitOption.setText("Wait");
                ans.add(waitOption);
            }

            // if unit is carry, can drop something off
            System.out.println("checking drop");
            ArrayList<Unit> carriedUnits = new ArrayList<>();
            Carry carryU;
            if (u instanceof Carry
                    && (!((carriedUnits = (carryU = (Carry) u).getUnits())
                    .isEmpty()))) {
                for (Unit carried : carriedUnits) {
                    ArrayList<Terrain> validLZs = new ArrayList<>();
                    for (Terrain t : ((Terrain) newLoc)
                            .getAllAdjacentTerrains()) {
                        if ((null == u.getGrid().get(t) || (u == u.getGrid().get(t)/*&&!newLoc.equals(t)*/))
                                && 999 != t.getMoveCost(carried.getMovementType())
                                && 999 != ((Terrain) newLoc).getMoveCost(carried.getMovementType())) {
                            validLZs.add(t);
                        }
                    }
                    if (!validLZs.isEmpty()) {
                        JMenuItem tmpDropOption = new JMenuItem();
                        Action tmpDropAction = new Action() {
                            public boolean enabled = true;

                            @Override
                            public void actionPerformed(ActionEvent arg0) {

                                display.shouldBeHighlighted = new HashSet<Terrain>(
                                        validLZs);
                                display.avw.setMessage("Dropping off: " + carried.getConciseInfo()
                                        + "\n\nClick where you would like to drop " + carried.getType() + " off."
                                        + "  Click an unhighlighted tile to cancel.");
                                display.avw.resetClickedLocation();
                                display.repaint();
                                display.invalidate();
                                new Thread(new Runnable() {

                                    @Override
                                    public void run() {
                                        Location LZ = display.avw
                                                .getLocationWhenClicked();
                                        if (!validLZs.contains(LZ)) {
                                            display.shouldBeHighlighted.clear();
                                            display.repaint();
                                            display.invalidate();
                                            // drop canceled
                                            return;
                                        } else {
                                            try {
                                                u.move(((Terrain) newLoc), true);
                                                display.repaint();
                                                display.invalidate();
                                            } catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                            carried.putSelfInGrid(u.getGrid(), LZ);
                                            carried.immobilize();
                                            carryU.getUnits().remove(carried);
                                            display.shouldBeHighlighted.clear();
                                            display.repaint();

                                        }
                                    }
                                }).start();

                            }

                            public void addPropertyChangeListener(
                                    PropertyChangeListener arg0) {
                            }

                            public Object getValue(String arg0) {
                                return null;
                            }

                            public boolean isEnabled() {
                                return enabled;
                            }

                            public void putValue(String key, Object value) {
                            }

                            public void removePropertyChangeListener(
                                    PropertyChangeListener listener) {
                            }

                            public void setEnabled(boolean b) {
                                enabled = b;
                            }
                        };
                        tmpDropOption.setAction(tmpDropAction);
                        tmpDropOption.setText("Drop: " + carried.getType());
                        tmpDropOption.setIcon(get16xIcon(this.getClass().getClassLoader().getResource(
                                "units/" + carried.getType() + ".png")));
                        ans.add(tmpDropOption);
                    }
                }
            }

            // units can fire on enemies if not unarmed and in range and unit can move
            System.out.println("checking weps");
            if (u.canMove() && u.getAmmo() > 0 && u.getWeapons()[0].getWeaponType() != WeaponType.NONE
                    || null != u.getWeapons()[1]) {
                Set<Unit> targetable = new HashSet<>();
                ArrayList<Location> occupied = u.getGrid().getOccupiedLocations();
                for (Location l : occupied) {
                    Unit tmp = (Unit) u.getGrid().get(l);
                    if (u.couldTarget(tmp, (Terrain) newLoc)
                            && (!u.getOwner().equals(tmp.getOwner()))) {
                        targetable.add(tmp);
                    }
                }
                if (!targetable.isEmpty()) {

                    Action fireAction = new Action() {
                        public boolean enabled = true;

                        @Override
                        public void actionPerformed(ActionEvent e) {
                            display.shouldBeHighlighted.clear();
                            for (Unit targetableUnit : targetable) {
                                Terrain targetableTerrain = (Terrain) targetableUnit.getLocation();
                                display.shouldBeHighlighted.add(targetableTerrain);
                            }
                            display.avw.setMessage("Click highlighted tile to attack.\n\nClick unhighlighted to cancel");
                            display.repaint();
                            new Thread(new Runnable() {

                                @Override
                                public void run() {
                                    Location targetLocation = display.avw.getLocationWhenClicked();
                                    noBugsPls(display, (TerrainGrid) u.getGrid());
                                    Unit targetedUnit = (Unit) u.getGrid().get(targetLocation);
                                    if (!targetable.contains(targetedUnit)) {
                                        display.shouldBeHighlighted.clear();
                                        tryRepaint(display);
                                        System.err.println("attack canceled");
                                        // attack canceled
                                        return;
                                    } else {
                                        try {
                                            u.move(MenuMaker.this.newLoc, true);
                                        } catch (Exception e1) {
                                            e1.printStackTrace();
                                        }
                                        u.fire(targetedUnit);
                                        display.paintImmediately(display.getBounds());
                                        URL fireIconLocation = this.getClass().getClassLoader().getResource(
                                                "32x/fire.png");
                                        Set<Terrain> where = new HashSet<Terrain>();
                                        where.add((Terrain) targetLocation);
                                        if (targetedUnit.getHealth() > 0 && u.getHealth() > 0 && targetedUnit.canCounter(u)) {
                                            where.add((Terrain) u.getLocation());
                                        }
                                        for (int i = 0; i < 2; i++) {
                                            display.showIconsOnSetOfLocations(new ImageIcon(fireIconLocation).getImage(), where);
                                            try {
                                                Thread.sleep(500);
                                            } catch (InterruptedException e) {
                                                e.printStackTrace();
                                            }
                                            tryRepaint(display);
                                        }
                                        noBugsPls(display, (TerrainGrid) u.getGrid());
                                        //display.avw.resetClickedLocation();
                                        //display.paintImmediately(display.getBounds());


                                    }
                                }

                            }).start();
                        }

                        @Override
                        public void addPropertyChangeListener(
                                PropertyChangeListener listener) {
                        }

                        @Override
                        public Object getValue(String key) {
                            return null;
                        }

                        @Override
                        public boolean isEnabled() {
                            return enabled;
                        }

                        @Override
                        public void putValue(String key, Object value) {
                            // TODO Auto-generated method stub

                        }

                        @Override
                        public void removePropertyChangeListener(
                                PropertyChangeListener listener) {
                            // TODO Auto-generated method stub

                        }

                        @Override
                        public void setEnabled(boolean b) {
                            enabled = b;
                        }
                    };
                    JMenuItem fireOption = new JMenuItem();
                    fireOption.setAction(fireAction);
                    fireOption.setText("Fire");
                    fireOption.setIcon(get16xIcon(this.getClass().getClassLoader().getResource(
                            "32x/fire.png")));
                    ans.add(fireOption);


                    System.out.println("added fireOption: MM " + "line "
                            + new Throwable().getStackTrace()[0].getLineNumber());
                    System.out.println("targetable = " + targetable);
                }
            }

            //check resupply
            System.out.println("checking resupply");
            if (u.canMove() && u instanceof Carry && ((Carry) u).canResupply()) {
                Terrain newTerrain = (Terrain) newLoc;
                ArrayList<Unit> adjacentAlliedUnits = new ArrayList<>();
                for (Terrain t : newTerrain.getAllAdjacentTerrains()) {
                    Unit tOcc = (Unit) u.getGrid().get(t);
                    if (tOcc != null && tOcc != u && tOcc.getOwner() == u.getOwner()) {
                        adjacentAlliedUnits.add(tOcc);
                    }
                }
                if (!adjacentAlliedUnits.isEmpty()) {
                    JMenuItem resupplyOption = new JMenuItem();
                    URL supplyPicLocation = this.getClass().getClassLoader().getResource(
                            "32x/supply.png");
                    ImageIcon supplyIcon = get16xIcon(supplyPicLocation);
                    ImageIcon bigSupplyIcon = new ImageIcon(supplyPicLocation);
                    Action resupplyAction = new Action() {
                        public boolean enabled = true;

                        @Override
                        public void actionPerformed(ActionEvent e) {
                            try {
                                u.move((Terrain) newLoc, true);
                            } catch (Exception e1) {
                                e1.printStackTrace();
                            }
                            display.shouldBeHighlighted.clear();
                            display.paintImmediately(display.getBounds());
                            ((Carry) u).resupply();
                            new Thread(new Runnable() {
                                public void run() {
                                    Set<Terrain> whereToDraw = new HashSet<>();
                                    for (Unit tOcc : adjacentAlliedUnits) {
                                        whereToDraw.add((Terrain) tOcc.getLocation());
                                    }
                                    whereToDraw.remove(u.getLocation());
                                    for (int x = 0; x < 4; x++) {
                                        display.showIconsOnSetOfLocations(bigSupplyIcon.getImage(), whereToDraw);
                                        try {
                                            Thread.sleep(250);
                                        } catch (InterruptedException e1) {
                                            e1.printStackTrace();
                                        }
                                        display.paintImmediately(display.getBounds());
                                    }
                                }
                            }).start();

                        }

                        @Override
                        public void setEnabled(boolean b) {
                            enabled = b;
                        }

                        @Override
                        public void removePropertyChangeListener(
                                PropertyChangeListener listener) {
                        }

                        @Override
                        public void putValue(String key, Object value) {
                        }

                        @Override
                        public boolean isEnabled() {
                            return enabled;
                        }

                        @Override
                        public Object getValue(String key) {
                            return null;
                        }

                        @Override
                        public void addPropertyChangeListener(
                                PropertyChangeListener listener) {
                        }
                    };
                    resupplyOption.setAction(resupplyAction);
                    resupplyOption.setText("resupply");
                    resupplyOption.setIcon(supplyIcon);
                    ans.add(resupplyOption);
                }
            }
            if (u instanceof Stealth) {
                Stealth s = (Stealth) u;
                {
                    JMenuItem hidingOption = new JMenuItem();
                    ImageIcon hideIco = get16xIcon(MenuMaker.class.getClassLoader().getResource("32x/hide.png"));
                    ImageIcon hidePic = new ImageIcon(MenuMaker.class.getClassLoader().getResource("32x/poof.png"));
                    Action hidingAction = new Action() {
                        public boolean enabled = true;

                        @Override
                        public void actionPerformed(ActionEvent arg0) {
                            try {
                                u.move((Terrain) newLoc, true);
                            } catch (Exception e1) {
                                e1.printStackTrace();
                            }
                            if (!s.isHidden()) {
                                s.hide();
                            } else {
                                s.unHide();
                            }
                            new Thread(new Runnable() {

                                @Override
                                public void run() {
                                    Set<Terrain> where = new HashSet<>();
                                    where.add((Terrain) newLoc);
                                    noBugsPls(display, (TerrainGrid) u.getGrid());
                                    for (int i = 0; i < 2; i++) {
                                        display.showIconsOnSetOfLocations(hidePic.getImage(), where);
                                        try {
                                            Thread.sleep(500);
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                        tryRepaint(display);
                                    }
                                    noBugsPls(display, (TerrainGrid) u.getGrid());

                                }
                            }).start();
                            display.repaint();
                        }

                        @Override
                        public void addPropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        @Override
                        public Object getValue(String arg0) {
                            return null;
                        }

                        @Override
                        public boolean isEnabled() {
                            return enabled;
                        }

                        @Override
                        public void putValue(String arg0, Object arg1) {
                        }

                        @Override
                        public void removePropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        @Override
                        public void setEnabled(boolean arg0) {
                            enabled = arg0;
                        }
                    };
                    hidingOption.setAction(hidingAction);
                    hidingOption.setIcon(hideIco);
                    hidingOption.setText(s.isHidden() ? "unhide" : "hide");
                    ans.add(hidingOption);
                }
            } else if (u instanceof Stealth2) {
                Stealth2 s = (Stealth2) u;
                {
                    JMenuItem hidingOption = new JMenuItem();
                    ImageIcon hideIco = get16xIcon(MenuMaker.class.getClassLoader().getResource("32x/hide.png"));
                    ImageIcon hidePic = new ImageIcon(MenuMaker.class.getClassLoader().getResource("32x/poof.png"));
                    Action hidingAction = new Action() {
                        public boolean enabled = true;

                        @Override
                        public void actionPerformed(ActionEvent arg0) {
                            try {
                                u.move((Terrain) newLoc, true);
                            } catch (Exception e1) {
                                e1.printStackTrace();
                            }
                            if (!s.isHidden()) {
                                s.hide();
                            } else {
                                s.unHide();
                            }
                            new Thread(new Runnable() {

                                @Override
                                public void run() {
                                    Set<Terrain> where = new HashSet<>();
                                    where.add((Terrain) newLoc);
                                    noBugsPls(display, (TerrainGrid) u.getGrid());
                                    for (int i = 0; i < 2; i++) {
                                        display.showIconsOnSetOfLocations(hidePic.getImage(), where);
                                        try {
                                            Thread.sleep(500);
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                        tryRepaint(display);
                                    }
                                    noBugsPls(display, (TerrainGrid) u.getGrid());
                                }
                            }).start();
                            display.repaint();
                        }

                        public void addPropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        public Object getValue(String arg0) {
                            return null;
                        }

                        public boolean isEnabled() {
                            return enabled;
                        }

                        public void putValue(String arg0, Object arg1) {
                        }

                        public void removePropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        public void setEnabled(boolean arg0) {
                            enabled = arg0;
                        }
                    };
                    hidingOption.setAction(hidingAction);
                    hidingOption.setIcon(hideIco);
                    hidingOption.setText(s.isHidden() ? "unhide" : "hide");
                    ans.add(hidingOption);
                }
            }

            // infantry can capture
            if (u instanceof Infantry) {
                if (newLoc instanceof Property
                        && ((Property) newLoc).getOwner() != u.getOwner()) {
                    JMenuItem captureOption = new JMenuItem();
                    Action captureAction = new Action() {
                        public boolean enabled = true;

                        @Override
                        public void actionPerformed(ActionEvent arg0) {
                            Infantry i = (Infantry) u;
                            try {
                                i.move(((Terrain) newLoc), true);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            i.capture();
                            URL flagIconLoc = MenuMaker.class.getClassLoader().getResource("32x/capture.png");
                            ImageIcon flagIco = new ImageIcon(flagIconLoc);
                            new Thread(new Runnable() {

                                @Override
                                public void run() {
                                    Set<Terrain> where = new HashSet<>();
                                    where.add((Terrain) newLoc);
                                    noBugsPls(display, (TerrainGrid) u.getGrid());
                                    for (int i = 0; i < 2; i++) {
                                        display.showIconsOnSetOfLocations(flagIco.getImage(), where);
                                        try {
                                            Thread.sleep(500);
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                        tryRepaint(display);
                                    }
                                    noBugsPls(display, (TerrainGrid) u.getGrid());

                                }
                            }).start();
                            display.repaint();
                        }

                        @Override
                        public void addPropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        @Override
                        public Object getValue(String arg0) {
                            return null;
                        }

                        @Override
                        public boolean isEnabled() {
                            return enabled;
                        }

                        @Override
                        public void putValue(String arg0, Object arg1) {
                        }

                        @Override
                        public void removePropertyChangeListener(PropertyChangeListener arg0) {
                        }

                        @Override
                        public void setEnabled(boolean arg0) {
                            enabled = arg0;
                        }
                    };
                    captureOption.setAction(captureAction);
                    URL capImagePath = this.getClass().getClassLoader().getResource("32x/capture.png");
                    captureOption.setIcon(get16xIcon(capImagePath));
                    captureOption.setText("Capture");
                    ans.add(captureOption);
                }
            }
        } else if (newLocOcc instanceof Carry
                && !((Carry) newLocOcc).isFull()
                && ((Carry) newLocOcc).canCarry(u)) {
            Carry c = (Carry) newLocOcc;
            JMenuItem loadOption = new JMenuItem();
            Action loadAction = new Action() {
                public boolean enabled = true;

                @Override
                public void actionPerformed(ActionEvent e) {
                    try {
                        u.move((Terrain) newLoc);
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                    if (u instanceof Stealth && ((Stealth) u).isHidden()) {
                        ((Stealth) u).unHide();
                    }
                    c.addUnit(u);
                    u.removeSelfFromGrid();
                    new Thread(new Runnable() {
                        public void run() {
                            display.shouldBeHighlighted.clear();
                            display.repaint();
                        }
                    }).start();

                }

                @Override
                public void setEnabled(boolean b) {
                    enabled = b;
                }

                @Override
                public void removePropertyChangeListener(
                        PropertyChangeListener listener) {
                }

                @Override
                public void putValue(String key, Object value) {
                }

                @Override
                public boolean isEnabled() {
                    return enabled;
                }

                @Override
                public Object getValue(String key) {
                    return null;
                }

                @Override
                public void addPropertyChangeListener(
                        PropertyChangeListener listener) {
                }
            };
            loadOption.setAction(loadAction);
            URL imagePath = this.getClass().getClassLoader().getResource("32x/load.png");
            loadOption.setIcon(get16xIcon(imagePath));
            loadOption.setText("Load");
            ans.add(loadOption);
        } else {
            //do nothing
        }

        //always a cancel option if anything else is do able
        if (!ans.isEmpty()) {
            JMenuItem cancelOption = new JMenuItem("cancel",
                    get16xIcon(this.getClass().getClassLoader().getResource("32x/cancel.png")));
            ans.add(cancelOption);
        }
        return ans;

        // return (Method[]) (ans.toArray());
    }

    // Arrays.sort(methods, new Comparator<Method>()
    // {
    // public int compare(Method m1, Method m2)
    // {
    // int d1 = depth(m1.getDeclaringClass());
    // int d2 = depth(m2.getDeclaringClass());
    // if (d1 != d2)
    // return d2 - d1;
    // int d = m1.getName().compareTo(m2.getName());
    // if (d != 0)
    // return d;
    // d1 = m1.getParameterTypes().length;
    // d2 = m2.getParameterTypes().length;
    // return d1 - d2;
    // }
    //
    // private int depth(Class cl)
    // {
    // if (cl == null)
    // return 0;
    // else
    // return 1 + depth(cl.getSuperclass());
    // }
    // });
    // return methods;
    // }

    /**
     * Makes a menu that displays all public methods of an object
     *
     * @param occupant the object whose methods should be displayed
     * @param loc      the location of the occupant
     * @return the menu to pop up
     */
    public JPopupMenu makeMethodMenu(T occupant, Location loc) {

        return makeMoveMenu(occupant, loc);
        // old code

        // this.occupant = occupant;
        // this.currentLocation = loc;
        // JPopupMenu menu = new JPopupMenu();
        // Method[] methods = getMethods();
        // Class oldDcl = null;
        // for (int i = 0; i < methods.length; i++)
        // {
        // Class dcl = methods[i].getDeclaringClass();
        // if (dcl != Object.class)
        // {
        // if (i > 0 && dcl != oldDcl)
        // menu.addSeparator();
        // menu.add(new MethodItem(methods[i]));
        // }
        // oldDcl = dcl;
        // }
        // return menu;

    }

    /**
     * Makes a menu that displays all public constructors of a collection of
     * classes.
     *
     * @param classes the classes whose constructors should be displayed
     * @param loc     the location of the occupant to be constructed
     * @return the menu to pop up
     */
    public JPopupMenu makeConstructorMenu(Collection<Class> classes,
                                          Location loc) {
        this.currentLocation = loc;
        JPopupMenu menu = new JPopupMenu();
        // ???
        // boolean first = true;

        //
        if (!(loc instanceof Factory)) {
            return menu;
        } else if (((Property) loc).getOwner() != Runner.getTurnPlayer()) {
            return menu;
        }
        try {
            Factory fac = (Factory) loc;
            for (Constructor<? extends Unit> constructor : fac
                    .getBuildableUnits()) {
                JMenuItem tmp = new JMenuItem();
                Action a = new Action() {
                    public boolean enabled = true;

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        fac.buildUnit(constructor);
                        Unit occ = (Unit) fac.getHostGrid().get(fac);
                        if (null != occ) {
                            (occ).immobilize();
                        }
                        MenuMaker.noBugsPls(display, fac.getHostGrid());
                    }

                    @Override
                    public void setEnabled(boolean b) {
                        enabled = b;
                    }

                    @Override
                    public void removePropertyChangeListener(
                            PropertyChangeListener listener) {
                    }

                    @Override
                    public void putValue(String key, Object value) {
                    }

                    @Override
                    public boolean isEnabled() {
                        return enabled;
                    }

                    @Override
                    public Object getValue(String key) {
                        return null;
                    }

                    @Override
                    public void addPropertyChangeListener(
                            PropertyChangeListener listener) {
                    }
                };
                a.setEnabled(true);
                tmp.setAction(a);
                Unit u = constructor.newInstance((Player)(null));
                int cost = u.getBuildCost();
                String spaces = (cost < 1000 ? "  " : "");
                String name = constructor.getDeclaringClass().getSimpleName();
                name = +cost + spaces + " | " + name;
                // ResourceBundle b = new ResourceBundle("")
                // TODO nicer naming, picture
                tmp.setText(name);
                URL imagePath = this.getClass().getClassLoader().getResource(
                        "units/" + constructor.getDeclaringClass().getSimpleName() + ".png");
                tmp.setIcon(get16xIcon(imagePath));
                menu.add(tmp);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return menu;

        //
        // Iterator<Class> iter = classes.iterator();
        // while (iter.hasNext())
        // {
        // if (first)
        // first = false;
        // else
        // menu.addSeparator();
        // Class cl = iter.next();
        // Constructor[] cons = (Constructor[]) cl.getConstructors();
        // for (int i = 0; i < cons.length; i++)
        // {
        // menu.add(new OccupantConstructorItem(cons[i]));
        // }
        // }

    }

    /**
     * Adds menu items that call all public constructors of a collection of
     * classes to a menu
     *
     * @param menu    the menu to which the items should be added
     * @param classes the collection of classes
     */
    public void addConstructors(JMenu menu, Collection<Class> classes) {
        boolean first = true;
        Iterator<Class> iter = classes.iterator();
        while (iter.hasNext()) {
            if (first)
                first = false;
            else
                menu.addSeparator();
            Class cl = iter.next();
            Constructor[] cons = cl.getConstructors();
            for (int i = 0; i < cons.length; i++) {
                menu.add(new GridConstructorItem(cons[i]));
            }
        }
    }

    private Method[] getMethods() {
        Class cl = occupant.getClass();
        Method[] methods = cl.getMethods();
        Arrays.sort(methods, new Comparator<Method>() {
            public int compare(Method m1, Method m2) {
                int d1 = depth(m1.getDeclaringClass());
                int d2 = depth(m2.getDeclaringClass());
                if (d1 != d2)
                    return d2 - d1;
                int d = m1.getName().compareTo(m2.getName());
                if (d != 0)
                    return d;
                d1 = m1.getParameterTypes().length;
                d2 = m2.getParameterTypes().length;
                return d1 - d2;
            }

            private int depth(Class cl) {
                if (cl == null)
                    return 0;
                else
                    return 1 + depth(cl.getSuperclass());
            }
        });
        return methods;
    }

    /**
     * A menu item that shows a method or constructor.
     */
    private class MCItem extends JMenuItem {
        public String getDisplayString(Class retType, String name,
                                       Class[] paramTypes) {
            StringBuffer b = new StringBuffer();
            b.append("<html>");
            if (retType != null)
                appendTypeName(b, retType.getName());
            b.append(" <font color='blue'>");
            appendTypeName(b, name);
            b.append("</font>( ");
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0)
                    b.append(", ");
                appendTypeName(b, paramTypes[i].getName());
            }
            b.append(" )</html>");
            return b.toString();
        }

        public void appendTypeName(StringBuffer b, String name) {
            int i = name.lastIndexOf('.');
            if (i >= 0) {
                String prefix = name.substring(0, i + 1);
                if (!prefix.equals("java.lang")) {
                    b.append("<font color='gray'>");
                    b.append(prefix);
                    b.append("</font>");
                }
                b.append(name.substring(i + 1));
            } else
                b.append(name);
        }

        public Object makeDefaultValue(Class type) {
            if (type == int.class)
                return new Integer(0);
            else if (type == boolean.class)
                return Boolean.FALSE;
            else if (type == double.class)
                return new Double(0);
            else if (type == String.class)
                return "";
            else if (type == Color.class)
                return Color.BLACK;
            else if (type == Location.class)
                return currentLocation;
            else if (Grid.class.isAssignableFrom(type))
                return currentGrid;
            else {
                try {
                    return type.newInstance();
                } catch (Exception ex) {
                    return null;
                }
            }
        }
    }

    private abstract class ConstructorItem extends MCItem {
        public ConstructorItem(Constructor c) {
            setText(getDisplayString(null, c.getDeclaringClass().getName(),
                    c.getParameterTypes()));
            this.c = c;
        }

        public Object invokeConstructor() {
            Class[] types = c.getParameterTypes();
            Object[] values = new Object[types.length];

            for (int i = 0; i < types.length; i++) {
                values[i] = makeDefaultValue(types[i]);
            }

            if (types.length > 0) {
                PropertySheet sheet = new PropertySheet(types, values);
                JOptionPane.showMessageDialog(this, sheet,
                        resources.getString("dialog.method.params"),
                        JOptionPane.QUESTION_MESSAGE);
                values = sheet.getValues();
            }

            try {
                return c.newInstance(values);
            } catch (InvocationTargetException ex) {
                parent.new GUIExceptionHandler().handle(ex.getCause());
                return null;
            } catch (Exception ex) {
                parent.new GUIExceptionHandler().handle(ex);
                return null;
            }
        }

        private Constructor c;
    }

    private class OccupantConstructorItem extends ConstructorItem implements
            ActionListener {
        public OccupantConstructorItem(Constructor c) {
            super(c);
            addActionListener(this);
            setIcon(displayMap.getIcon(c.getDeclaringClass(), 16, 16));
        }

        @SuppressWarnings("unchecked")
        public void actionPerformed(ActionEvent event) {
            T result = (T) invokeConstructor();
            parent.getWorld().add(currentLocation, result);
            parent.repaint();
        }
    }

    private class GridConstructorItem extends ConstructorItem implements
            ActionListener {
        public GridConstructorItem(Constructor c) {
            super(c);
            addActionListener(this);
            setIcon(displayMap.getIcon(c.getDeclaringClass(), 16, 16));
        }

        @SuppressWarnings("unchecked")
        public void actionPerformed(ActionEvent event) {
            Grid<T> newGrid = (Grid<T>) invokeConstructor();
            parent.setGrid(newGrid);
        }
    }

    private class MethodItem extends MCItem implements ActionListener {
        public MethodItem(Method m) {
            setText(getDisplayString(m.getReturnType(), m.getName(),
                    m.getParameterTypes()));
            this.m = m;
            addActionListener(this);
            setIcon(displayMap.getIcon(m.getDeclaringClass(), 16, 16));
        }

        public void actionPerformed(ActionEvent event) {
            Class[] types = m.getParameterTypes();
            Object[] values = new Object[types.length];

            for (int i = 0; i < types.length; i++) {
                values[i] = makeDefaultValue(types[i]);
            }

            if (types.length > 0) {
                PropertySheet sheet = new PropertySheet(types, values);
                JOptionPane.showMessageDialog(this, sheet,
                        resources.getString("dialog.method.params"),
                        JOptionPane.QUESTION_MESSAGE);
                values = sheet.getValues();
            }

            try {
                Object result = m.invoke(occupant, values);
                parent.repaint();
                if (m.getReturnType() != void.class) {
                    String resultString = result.toString();
                    Object resultObject;
                    final int MAX_LENGTH = 50;
                    final int MAX_HEIGHT = 10;
                    if (resultString.length() < MAX_LENGTH)
                        resultObject = resultString;
                    else {
                        int rows = Math.min(MAX_HEIGHT,
                                1 + resultString.length() / MAX_LENGTH);
                        JTextArea pane = new JTextArea(rows, MAX_LENGTH);
                        pane.setText(resultString);
                        pane.setLineWrap(true);
                        resultObject = new JScrollPane(pane);
                    }
                    JOptionPane.showMessageDialog(parent, resultObject,
                            resources.getString("dialog.method.return"),
                            JOptionPane.INFORMATION_MESSAGE);
                }
            } catch (InvocationTargetException ex) {
                parent.new GUIExceptionHandler().handle(ex.getCause());
            } catch (Exception ex) {
                parent.new GUIExceptionHandler().handle(ex);
            }
        }

        private Method m;
    }

    private T occupant;
    private Grid currentGrid;
    private Location currentLocation;
    private WorldFrame<T> parent;
    private DisplayMap displayMap;
    private ResourceBundle resources;
} public class Player {

    public static int numberOfPlayers;
    public final CO CO;
    public final int id;
    private ArrayList<Unit> unitsControlled;
    private int money;
    private ArrayList<Property> propertiesOwned;
    private int commTowers;
    private Color teamColor;

    /**
     * Constructs a Player
     *
     * @param startingMoney starting amount of money
     * @param teamColor     team color to be assigned to this Player
     */
    public Player(CO commandingOfficer, int startingMoney, Color teamColor) {
        //if error when setting starting funds, set to 0
        if (setMoney(startingMoney) == -1) {
            setMoney(0);
        } else {
            setMoney(startingMoney);
        }
        this.id = ++Player.numberOfPlayers;
        this.CO = commandingOfficer;
        this.teamColor = teamColor;
        this.unitsControlled = new ArrayList<>();
        this.propertiesOwned = new ArrayList<>();
    }

    /**
     * @return the player's team color
     */
    public Color getTeamColor() {
        return this.teamColor;
    }

    /**
     * @return the amount of money this player has
     */
    public int getMoney() {
        return this.money;
    }

    /**
     * @param money a positive integer to set money to
     * @return the amount of money that this player has after execution
     * or  -1 if the amount was invalid
     */
    public int setMoney(int money) {
        if (money >= 0) {
            this.money = money;
            return this.money;
        } else {
            return -1;
        }
    }

    /**
     * @return a list of all of the units that this player controls
     */
    public ArrayList<Unit> getUnitsControlled() {
        return unitsControlled;
    }

    /**
     * @return the number of Units that this player controls
     */
    public int getNumUnitControlled() {
        return getUnitsControlled().size();
    }

    /**
     * @return a list of all of the Properties that this player owns
     */
    public ArrayList<Property> getPropertiesOwned() {
        return this.propertiesOwned;
    }

    /**
     * @return the number of Properties that this player controls
     */
    public int getNumPropertiesOwned() {
        return getPropertiesOwned().size();
    }

    public int getCommTowers() {
        return commTowers;
    }

    public void setCommTowers(int commTowers) {
        this.commTowers = commTowers;
    }

    public boolean hasHQ() {
        for (Property prop : getPropertiesOwned()) {
            if (prop instanceof HQ) {
                return true;
            }
        }
        return false;
    }
} public class TerrainGrid<E> extends AbstractGrid<E> {
    public AVWorld hostWorld;
    private Object[][] occupantArray; // the array storing the grid elements
    private Location[][] locationArray; // array containing map

    public Location[][] getLocationArray() {
        return locationArray;
    }

    public ArrayList<Location> getAllLocations() {
        ArrayList<Location> ans = new ArrayList<>();
        for (Location[] l0 : locationArray) {
            ans.addAll(new ArrayList<>(Arrays.asList(l0)));
        }
        return ans;
    }

    /**
     * Constructs an empty bounded grid with the given dimensions.
     * (Precondition: <code>rows > 0</code> and <code>cols > 0</code>.)
     *
     * @param rows number of rows in TerrainGrid
     * @param cols number of columns in TerrainGrid
     */
    public TerrainGrid(int rows, int cols) {
        if (rows <= 0)
            throw new IllegalArgumentException("rows <= 0");
        if (cols <= 0)
            throw new IllegalArgumentException("cols <= 0");
        occupantArray = new Object[rows][cols];
        locationArray = new Location[rows][cols];
    }

    public int getNumRows() {
        return occupantArray.length;
    }

    public int getNumCols() {
        // Note: according to the constructor precondition, numRows() > 0, so
        // theGrid[0] is non-null.
        return occupantArray[0].length;
    }

    public boolean isValid(Location loc) {
        if (null == loc) {
            return false;
        }
        return 0 <= loc.getRow() && loc.getRow() < getNumRows()
                && 0 <= loc.getCol() && loc.getCol() < getNumCols();
    }

    public ArrayList<Location> getOccupiedLocations() {
        ArrayList<Location> theLocations = new ArrayList<Location>();

        // Look at all grid locations.
        for (int r = 0; r < getNumRows(); r++) {
            for (int c = 0; c < getNumCols(); c++) {
                // If there's an object at this location, put it in the array.
                Location loc = locationArray[r][c];
                //FIXME something weird
                if (null != loc) {
                    if (get(loc) != null) {
                        theLocations.add(loc);
                    }
                } else {
                    System.out.println("grid[" + r + "][" + c + "] is null, moving on");
                }
            }
        }

        return theLocations;
    }

    public E get(Location loc) {
        //TODO
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid");
        return (E) occupantArray[loc.getRow()][loc.getCol()]; // unavoidable warning
    }

    public E put(Location loc, E obj) {
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid, cannot put in grid");
        if (obj == null)
            throw new NullPointerException("obj == null, cannot put in grid");

        // Add the object to the grid.
        E oldOccupant = get(loc);
        occupantArray[loc.getRow()][loc.getCol()] = obj;
        //System.out.println("success putting in unit. See line 106 of TerrainGrid");
        return oldOccupant;
    }

    public E remove(Location loc) {
        if (!isValid(loc))
            throw new IllegalArgumentException("Location " + loc
                    + " is not valid");

        // Remove the object from the grid.
        E r = get(loc);
        occupantArray[loc.getRow()][loc.getCol()] = null;
        return r;
    }
} public enum COFlag {
    COST,
    ATTACK,
    DEFENSE,
    COUNTER,
    MOVE,
    DAILY_COST,
    STEALTH_COST,
    LUCK,
    CAPTURE
} public abstract class Terrain extends Location {

    protected TerrainGrid hostGrid;

    /**
     * Constructs a Terrain with given row and column coordinates.
     *
     * @param r the row
     * @param c the column
     */
    public Terrain(int r, int c, TerrainGrid<Actor> hostGrid) {
        super(r, c);
        this.hostGrid = hostGrid;
    }

    /**
     * @return the defense points of this Terrain
     * non-air units occupying the terrain gain a 10% defense bonus per defense point
     */
    public abstract int getDefense();

    /**
     * @return the cost of mobility points that a unit of movementType would take
     * to traverse this terrain
     */
    public double getMoveCost(MoveType movementType) {
        switch (movementType) {
            case AIR:
                return getMoveCostAir();
            case SEA:
                return getMoveCostBoat();
            case FOOT:
                return getMoveCostFoot();
            case TIRES:
                return getMoveCostTires();
            case TREADS:
                return getMoveCostTreads();
            case LANDER:
                return getMoveCostLander();
            default:
                throw new IllegalArgumentException();
        }
    }

    /**
     * @return the cost of mobility points that a unit of movementType.FOOT would take
     * to traverse this terrain -- override to set
     */
    protected abstract double getMoveCostFoot();

    /**
     * @return the cost of mobility points that a unit of movementType.TIRES would take
     * to traverse this terrain -- override to set
     */
    protected abstract double getMoveCostTires();

    /**
     * @return the cost of mobility points that a unit of movementType.TREADS would take
     * to traverse this terrain -- override to set
     */
    protected abstract double getMoveCostTreads();

    /**
     * @return the cost of mobility points that a unit of movementType.AIR would take
     * to traverse this terrain -- override to set
     */
    protected double getMoveCostAir() {
        return 1;
    }

    /**
     * @return the cost of mobility points that a unit of movementType.SEA would take
     * to traverse this terrain -- override to set
     */
    protected double getMoveCostBoat() {
        return 999;
    }

    /**
     * @return the cost of mobility points that a unit of movementType.LANDER would take
     * to traverse this terrain -- override to set
     */
    protected double getMoveCostLander() {
        return 999;
    }

    /**
     * @return Manhattan distance to other terrain t
     */
    public int getDistanceTo(Terrain t) {
        return Math.abs(this.getRow() - t.getRow()) + Math.abs(this.getCol() - t.getCol());
    }

    @Override
    public Location getAdjacentLocation(int direction) {
        int adjustedDirection = (direction + HALF_RIGHT / 2) % FULL_CIRCLE;
        if (adjustedDirection < 0)
            adjustedDirection += FULL_CIRCLE;

        adjustedDirection = (adjustedDirection / HALF_RIGHT) * HALF_RIGHT;
        int dc = 0;
        int dr = 0;
        if (adjustedDirection == EAST)
            dc = 1;
        else if (adjustedDirection == SOUTHEAST) {
            dc = 1;
            dr = 1;
        } else if (adjustedDirection == SOUTH)
            dr = 1;
        else if (adjustedDirection == SOUTHWEST) {
            dc = -1;
            dr = 1;
        } else if (adjustedDirection == WEST)
            dc = -1;
        else if (adjustedDirection == NORTHWEST) {
            dc = -1;
            dr = -1;
        } else if (adjustedDirection == NORTH)
            dr = -1;
        else if (adjustedDirection == NORTHEAST) {
            dc = 1;
            dr = -1;
        }
        try {
            return hostGrid.getLocationArray()[getRow() + dr][getCol() + dc];
        } catch (ArrayIndexOutOfBoundsException e) {
            return null;
        }
    }

    /**
     * @return list of Manhattan adjacent Terrains
     */
    public ArrayList<Terrain> getAllAdjacentTerrains() {
        ArrayList<Terrain> adjacent = new ArrayList<Terrain>();
        //45 deg for all, 90 for Manhattan
        for (int dir = 0; dir < 360; dir += 90) {
            Terrain t = (Terrain) getAdjacentLocation(dir);
            if (t != null) {
                adjacent.add(t);
            }
        }
        return adjacent;
    }

    public String getUIName() {
        switch (this.getClass().getSimpleName()) {
            case "Property":
                return "City";
            default:
                return this.getClass().getSimpleName();
        }
    }

    public String getDefenseStarsAsString() {
        Object a;
        if ((a = hostGrid.get(this)) != null && (a instanceof Air)) {
            return "☆☆☆☆☆";
        }
        int def = getDefense();
        return "★★★★★".substring(0, def) + "☆☆☆☆☆".substring(def, 5);
    }

    public TerrainGrid getHostGrid() {
        return hostGrid;
    }
} public abstract class Air extends Unit {

    /**
     * calls super(Player) from child classes
     * don't invoke this
     *
     * @param owner owner of unit
     */
    public Air(Player owner) {
        super(owner);
    }

    @Override
    public void outOfFuel() {
        //TODO crash animation
        this.selfDestruct(true);
    }

    @Override
    public MoveType getMovementType() {
        return MoveType.AIR;
    }

} public abstract class Sea extends Unit {

    /**
     * calls super(Player) from child classes
     * don't invoke this
     *
     * @param owner owner of unit
     */
    public Sea(Player owner) {
        super(owner);
    }

    @Override
    public MoveType getMovementType() {
        return MoveType.SEA;
    }

    @Override
    public void outOfFuel() {
        //TODO sinking animation
        this.selfDestruct();
    }


} public abstract class Unit extends Actor {
    public boolean hasMoved;

    //constructor
    /**
     * Not a lazy boolean flag, as ability to move involves a myriad of
     * factors including  {@link fuel}, participation in
     * combat, transport, and other special circumstances like {@link CO} powers and electronic
     * warfare (EW)
     */
    protected boolean canMove;
    //ownership
    private Player owner;
    //combat defense
    private Suit suit;
    private int health = 100;
    private Weapon[] weapons = new Weapon[2];
    private int ammo;
    private double fuel;
    private int dailyCost;
    private double mobility;
    private double maxMobility;

    /**
     * Constructs a Unit owned by owner
     * also loads the values for daily fuel cost and max mobility
     */
    public Unit(Player owner) {
        if (null != owner) {
            setOwner(owner);
            this.setDailyCost(loadDailyCost());
            this.setMaxMobility(loadMaxMobility());
            //this.canMove = true;
            this.setFuel(99);
            this.resetMovement();
        } else {
            //FOR FACTORY MENU, FIXME in Factory class NOT VERY EFFICIENT
            //don't worry, it will get deleted soon
        }
    }

    /**
     * @deprecated debug purposes only
     */
    public Unit() {
    }

    private static Queue<Terrain> genPath(Terrain t, Map<Terrain, Terrain> parentMap) throws IllegalArgumentException {
        if (!parentMap.containsKey(t)) {
            throw new IllegalArgumentException();
        }
        Queue<Terrain> ans = new LinkedList<>();
        Terrain parent = parentMap.get(t);
        if (parent == null) {
            //we started here, no need to move
            return ans;
        } else {
            ans = genPath(parent, parentMap);
            ans.add(t);
            return ans;
        }
    }

    /**
     * @deprecated never call this
     */
    @Override
    public void act() {
    }

    public Player getOwner() {
        return this.owner;
    }

    public void setOwner(Player newOwner) {
        if (null != newOwner) {
            this.owner = newOwner;
            this.owner.getUnitsControlled().add(this);
            this.setColor(owner.getTeamColor());
        } else {
            throw new IllegalArgumentException("owner is null");
        }
    }

    //money
    public abstract int getBuildCost();

    public Suit getSuit() {
        return suit;
    }

    public void setSuit(Suit s) {
        this.suit = s;
    }

    /**
     * @return percent of damage taken generally as decimal
     */
    public abstract double getBaseArmorResistance();

    public int getHealth() {
        return health;
    }

    /**
     * only use this for true dmg aka ICBM or internally
     *
     * @param hp to set to
     */
    public void setHealth(int hp) {
        health = hp;
        if (health <= 0) {
            this.selfDestruct();
        }
    }

    public UnitType getUnitType() {
        ResourceBundle b = ResourceBundle.getBundle("unit_to_unit_type");
        return UnitType.valueOf(b.getString(getType()));
    }

    public String getType() {
        return this.getClass().getSimpleName();
    }

    //TODO override this to set resistances
    public double resist(double damage, String source) {
        return damage;
    }

    //taking damage and dying
    public void takeDamage(double damage, String source) {
        if (suit != null) {
            damage = suit.resist(damage, source);
            damage = this.resist(damage, source);
        }
        this.health -= Math.round(damage);
        if (this.health <= 0) {
            this.health = 0;
            this.selfDestruct();
        }
    }

    protected void selfDestruct(boolean... showNoFuelIcon) {
        //TODO animation

        URL fireIconLocation = this.getClass().getClassLoader().getResource("32x/fire.png");
        URL noFuelIconLocation = this.getClass().getClassLoader().getResource("32x/noFuel.png");
        Set<Terrain> where = new HashSet<Terrain>();
        where.add((Terrain) getLocation());
        try {
            TerrainGrid tg = (TerrainGrid) getGrid();
            GridPanel display = tg.hostWorld.getWorldFrame().control.display;
            AVWorld avw = tg.hostWorld;
            final Unit _this = this;
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        if (showNoFuelIcon.length != 0 && showNoFuelIcon[0]) {
                            for (int x = 0; x < 3; x++) {
                                display.showIconsOnSetOfLocations(new ImageIcon(noFuelIconLocation).getImage(), where);
                                Thread.sleep(250);
                            }
                        }
                        display.repaint();
                        for (int x = 0; x < 3; x++) {
                            display.showIconsOnSetOfLocations(new ImageIcon(fireIconLocation).getImage(), where);
                            Thread.sleep(250);

                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        _this.removeSelfFromGrid();
                        _this.getOwner().getUnitsControlled().remove(_this);
                        display.repaint();
                    }

                }

            }).start();

        } catch (ClassCastException cce) {
            System.err.println("?? Host grid is not TerrainGrid. wat. PROBABLY FATAL ERROR Moving on...");
            this.removeSelfFromGrid();
            this.getOwner().getUnitsControlled().remove(this);
            return;
        }
    }

    //combat offense
    protected void setWeapon(int index, String weaponType) {
        this.weapons[index] = new Weapon(weaponType, this);
        if (index == 0) {
            this.setAmmo(weapons[0].getMaxAmmo());
        }
    }

    //movement

    public Weapon[] getWeapons() {
        return weapons;
    }

    public int getAmmo() {
        return ammo;
    }

    public void setAmmo(int x) {
        if (x >= 0) {
            ammo = x;
        } else {
            throw new IllegalArgumentException("bad attempt to set ammo to negative");
        }
    }

    /**
     * @return whether or not this unit can attack Unit u
     * Gets overridden for ranged units, indirect fire, etc
     */
    public boolean canTarget(Unit u) {
        if ((WeaponType.NONE.equals(getWeapons()[0].getWeaponType()) && null == getWeapons()[1]) || (getAmmo() == 0)) {
            return false;
        }
        return couldTarget(u, (Terrain) getLocation());
    }

    /**
     * @return whether or not this unit can attack Unit toCheck from Terrain hypothetical
     * Gets overridden for ranged units, indirect fire, etc
     */
    public boolean couldTarget(Unit toCheck, Terrain hypothetical) {
        if (null == toCheck || toCheck instanceof HiddenUnit) {
            return false; //can't target nothing or hidden things
        }
        return hypothetical.getDistanceTo((Terrain) toCheck.getLocation()) == 1;
    }

    /**
     * @return whether or not this unit can counterattack Unit u
     * Gets overridden for ranged units, indirect fire, etc
     */
    public boolean canCounter(Unit u) {
        return canTarget(u);
    }

    /**
     * * This Unit attacks the targeted Unit. If the target is still alive and
     * can counter this unit, as determined by canCounter(Unit), it counterattacks afterwards
     * Precondition: ammo > 0 or has secondary weapon, target is in range
     * as determined by canTarget(Unit)
     */
    public void fire(Unit target) {
        immobilize();
        //fire first
        //stronger weapon
        double wep1, wep2, damage;
        wep1 = this.weapons[0].damageCalc(target);
        if (null != this.weapons[1]) {
            wep2 = this.weapons[1].damageCalc(target);
        } else {
            wep2 = 0;
        }
        boolean usedPrimary = (wep1 >= wep2);
        if (usedPrimary) {
            this.setAmmo(getAmmo() - 1);
            damage = wep1;
        } else {
            damage = wep2;
        }
        //luck
        double luck = (double) Weapon.luck();
        luck = this.owner.CO.passive(luck, COFlag.LUCK, getUnitType());
        damage += luck;
        String source;
        if (usedPrimary) {
            source = this.weapons[0].getWeaponType();
        } else {
            source = this.weapons[1].getWeaponType();
        }
        damage = this.owner.CO.passive(damage, COFlag.ATTACK, this.getUnitType());
        target.takeDamage(damage, source);
        //attacking uncloaks
        if (this instanceof Stealth) {
            Stealth me = (Stealth) (this);
            if (me.isHidden()) {
                me.unHide();
            }
        } else if (this instanceof Stealth2) {
            Stealth2 me = (Stealth2) (this);
            if (me.isHidden()) {
                me.unHide();
            }
        }
        //counter attack if target is alive and direct fire
        if (target.getHealth() > 0 && target.canCounter(this)) {
            wep1 = target.weapons[0].damageCalc(this);
            if (null != target.weapons[1]) {
                wep2 = target.weapons[1].damageCalc(this);
            } else {
                wep2 = 0;
            }
            usedPrimary = wep1 >= wep2;
            if (usedPrimary) {
                target.ammo--;
                damage = wep1;
            } else {
                damage = wep2;
            }
            //luck
            luck = Math.random() * 10 + 0.1;
            //CO power affects luck
            luck = target.owner.CO.passive(luck, COFlag.LUCK, target.getUnitType());
            damage += luck;
            if (usedPrimary) {
                source = target.weapons[0].getWeaponType();
            } else {
                source = target.weapons[1].getWeaponType();
            }
            damage = target.owner.CO.passive(damage, COFlag.COUNTER, this.getUnitType());
            this.takeDamage(damage, source);
        }
    }

    public void deductDailyCost() {
        this.setFuel(getFuel() - getDailyCost());
    }

    /**
     * As you may have noticed, this has void return type.
     * This method is usually invoked upon fuel reaching or dropping below 0,
     * by the <i>{@link setFuel()}</i> method
     * This is public because it can also be invoked by other special
     * circumstances like {@link CO} powers and electronic warfare (EW)
     */
    public abstract void outOfFuel();

    public void setMobility(double x) {
        if (x >= 0) {
            this.mobility = x;
        } else {
            throw new IllegalArgumentException("bad attempt to set negative mobility");
        }
    }

    /**
     * @return Current {@link mobility} of the {@link Unit}
     */
    public double getCurrentMobility() {
        return this.mobility;
    }

    /**
     * @return Maximum {@link mobility} of the {@link Unit}
     * {@link mobility} is set to this value at the start of each turn
     * by <i>{@link resetMovement()}</i>  or by special circumstances like {@link CO} powers
     */
    public double getMaxMobility() {
        return this.maxMobility;
    }

    /**
     * sets the maximum mobility of the unit to maxMobility
     * should invoked from the constructor
     * Precondition: maxMobility is a positive integer
     * Postcondition: this.maxMobility is set to maxMobility
     */
    private void setMaxMobility(double maxMobility) {
        if (maxMobility >= 0) {
            this.maxMobility = maxMobility;
        } else {
            throw new IllegalArgumentException("bad attempt to max mobility (0 or negative)");
        }
    }

    /**
     * loads the maximum mobility for this unit from a resourcebundle
     * for use in constructor for setMaxMobility()
     *
     * @return maximum mobility value for the unit
     */
    private double loadMaxMobility() {
        ResourceBundle b = ResourceBundle.getBundle("unit_move");
        return (double) ((int) (Double.parseDouble(b.getString(getType())) + 0.5));
    }

    /**
     * loads the daily fuel cost for this unit from a resourcebundle
     * for use in constructor for setDailyCost()
     *
     * @return maximum mobility value for the unit
     */
    private int loadDailyCost() {
        if (this instanceof HiddenUnit) {
            HiddenUnit me = (HiddenUnit) this;
            Unit cont = (me).getContainedUnit();
            return (cont.loadDailyCost());
        }
        ResourceBundle b = ResourceBundle.getBundle("unit_daily_fuel");
        try {
            double ans = Double.parseDouble(b.getString(getType()));
            return (int) (this.owner.CO.passive(ans, COFlag.DAILY_COST, getUnitType()));
        } catch (NumberFormatException e) {
	    System.err.println(Arrays.toString(e.getStackTrace()));
	    System.err.println("Corrupt File?");
            System.err.println("Method: Unit.loadDailyCost()");
            throw e;
        }
    }

    /**
     * @return the daily fuel cost of the unit
     * override this method to "set" a different daily fuel cost of the unit
     * should be 0 for land units, different for air and sea units
     */
    public int getDailyCost() {
        return dailyCost;
    }

    /**
     * sets the daily fuel cost of the unit to cost
     * should invoked from the constructor
     * Precondition: fuelCost is a positive integer
     * Postcondition: this.dailyCost is set to fuelCost
     */
    private void setDailyCost(int x) {
        this.dailyCost = x;
    }

    /**
     * @return The {@link MoveType} of this {@link Unit}
     */
    public abstract MoveType getMovementType();

    /**
     * @return Amount of fuel remaining. Fuel is required to move,
     * and is consumed when moving. {@link fuel} is truncated to integer at the start of each turn by
     * the resetMovement() method.
     */
    public double getFuel() {
        return this.fuel;
    }

    /**
     * @return Amount of fuel remaining. Mobility is required to move, and is consumed when moving.
     * maxMobility fuel is converted into mobility at the start of each turn by resetMovement().
     * fuel is truncated to integer at the start of each turn by the resetMovement() method.
     */
    public void setFuel(double d) {
        this.fuel = d;
        if (fuel < 0) {
            outOfFuel();
        }
    }

    /**
     * @return Whether or not this unit can still move. A {@link Unit} can move
     * until they runs out of {@link mobility}, participates in combat,
     * or is dropped off from a carrying unit. A {@link Unit} currently carried cannot move,
     * but will move with the carrying unit.
     */
    public boolean canMove() {
        if (this.getCurrentMobility() == 0) {
            immobilize();
            return false;
        }
        return this.canMove;
    }

    /**
     * sets canMove to false, changes colors
     *
     * @return whether or not the unit was already UNABLE to move
     */
    public boolean immobilize() {
        if (!canMove) {
            return true;
        } else {
            canMove = false;
            if (getColor() != null) {
                setColor(getColor().darker());
            }
            return false;
        }
    }

    /**
     * Resets movement-related variables for new day or {@link CO} power, namely:
     * <p>{@link canMove}<p>{@link fuel}
     * Postcondition: The daily fuel cost of the unit is deducted from fuel.
     * up to maxMobility is deducted from fuel and the same amount is
     * added to mobility.
     */
    public void resetMovement() {
        //this.hasMoved = false;
        if (!canMove && getColor() != null) {
            setColor(getColor().brighter());
        }
        this.canMove = true;
        this.fuel = (int) (getFuel());
        if (null != getLocation()) {//If not carried
            this.setFuel(this.getFuel() - (this.maxMobility - mobility));
            this.deductDailyCost();
        }
        if (getFuel() > 0) {
            this.mobility = this.maxMobility;
        }
    }

    /**
     * Precondition: the unit has enough mobility to make it to the Terrain toMoveto
     * Postcondition: the unit moves to the Terrain toMoveTo, traversing each terrain in a good path
     *
     * @throws Exception
     */
    public void move(Terrain toMoveTo, boolean... realMove) throws Exception {
        Terrain t = (Terrain) getLocation();
        boolean moveSuccess = realMove.length == 0 ?
                move(findPathTo(toMoveTo)) : move(findPathTo(toMoveTo), realMove[0]);
        if (moveSuccess) {
            immobilize();
            t = (Terrain) getLocation();
            for (Terrain checkingForHidden : t.getAllAdjacentTerrains()) {
                Actor occ = getGrid().get(checkingForHidden);
                if (occ instanceof HiddenUnit && ((HiddenUnit) occ).getContainedUnit().getOwner() != this.getOwner()) {
                    HiddenUnit hu = (HiddenUnit) occ;
                    Unit aha = hu.unBox();
                    aha.immobilize();
                    if (aha instanceof Stealth) {
                        ((Stealth) aha).unHide();
                    } else if (aha instanceof Stealth2) {
                        ((Stealth2) aha).unHide();
                    }
                }
            }
        } else {
            System.out.println("move failed, see line 441 of Unit");
        }
    }

    /**
     * uses DFS
     *
     * @return a set of Terrains that the unit can move to, given constraints of the surrounding
     * terrain, units, and current mobility level
     */
    public Set<Terrain> getValidMoveSpaces() {
        Set<Terrain> ans = new HashSet<>();
        ans.add((Terrain) this.getLocation());
        if (!this.canMove()) {
            return ans;
        }
        Stack<Terrain> toCheck = new Stack<>();
        toCheck.push((Terrain) this.getLocation());
        Map<Terrain, Double> distances = new HashMap<>();
        distances.put((Terrain) this.getLocation(), 0.0);
        while (0 != toCheck.size()) {
            double distTo;
            Terrain current = toCheck.pop();
            ArrayList<Terrain> adjacent = current.getAllAdjacentTerrains();
            for (Terrain t : adjacent) {
                if ((distTo = distances.get(current) + t.getMoveCost(this.getMovementType())) <= mobility) {
                    if (null == getGrid().get(t)) {
                        if (!distances.containsKey(t) || distTo < distances.get(t)) {
                            distances.put(t, distTo);
                            toCheck.push(t);
                        }
                        ans.add(t);
                    } else {
                        Unit u = (Unit) getGrid().get(t);
                        if (u.getOwner() == this.getOwner()) {
                            if (distances.containsKey(t)) {
                                if (distTo < distances.get(t)) {
                                    distances.put(t, distTo);
                                    toCheck.push(t);
                                }
                            } else {
                                distances.put(t, distTo);
                                toCheck.push(t);
                            }
                            if (u instanceof Carry) {
                                Carry c = (Carry) u;
                                if (c.canCarry(this)) {
                                    ans.add(t);
                                }
                            }
                        }
                    }
                }
            }
        }
        return ans;

    }

    //	@SuppressWarnings({ "unchecked", "rawtypes" })
//	private Queue<Terrain> findpathTo_2(Terrain target) throws IllegalArgumentException{
//		if(null==target){
//			throw new IllegalArgumentException("target is null");
//		}
//		Map<Terrain, Integer> shortDistances = new HashMap<>();
//		Map<Terrain, Terrain> previousLink = new HashMap<>();
//		Terrain current = (Terrain) getLocation();
//		Comparator<Terrain> comp = new Comparator<Terrain>() {
//			/**
//			 * Approximates which has shorter path to get to
//			 */
//			@Override
//			public int compare(Terrain arg0, Terrain arg1) {
//				int x;
//				//heuristic will never overestimate total cost
//				Integer c0 = ((-1==(x = calculatePathCost(arg0))?Integer.MAX_VALUE:x))+arg0.getDistanceTo(target);
//				Integer c1 = ((-1==(x = calculatePathCost(arg1))?Integer.MAX_VALUE:x))+arg1.getDistanceTo(target);
//				return c0.compareTo(c1);
//			}
//			private int calculatePathCost(Terrain t){
//				if(!previousLink.containsKey(t)){
//					return -1;
//				}
//				Terrain parent = previousLink.get(t);
//				if(null==parent){
//					return 0;
//				}else{
//					return (int) (calculatePathCost(previousLink.get(parent))+parent.getMoveCost(getMovementType()));
//				}
//			}
//			
//		};
//		PriorityQueue<Terrain> toCheck = new PriorityQueue<>(comp);
//		//add all tiles in range
//		toCheck.addAll(getValidMoveSpaces());
//		
//		
//		
//		
//	}

    private int totalCost(Queue<Terrain> path) {
        if (null == path || path.isEmpty()) {
            return 0;
        } else {
            int ans = 0;
            for (Terrain t : path) {
                ans += t.getMoveCost(getMovementType());
            }
            return ans;
        }
    }

    private int totalCost(Terrain t, Map<Terrain, Terrain> previousLink) {
        if (!previousLink.containsKey(t)) {
            throw new IllegalStateException("terrain not mapped to parent — how did we get here?");
        }
        Terrain parent = previousLink.get(t);
        if (null == parent) {
//			return (int) t.getMoveCost(getMovementType());
            //we started here, 0
            return 0;
        } else {
            return (int) (t.getMoveCost(getMovementType()) + totalCost(parent, previousLink));
        }
    }

    /**
     * Precondition: the unit has enough mobility to reach the Terrain that the path is being found to.
     * The returned path will be optimal, uses A*
     *
     * @return a queue of Terrains that could be traversed to reach the destination
     * @throws Exception
     */
    private Queue<Terrain> findPathTo(Terrain target) throws Exception {
        Queue<Terrain> ans = new LinkedList<>();

        if (null == target) {
            return new LinkedList<>();
        }

        if (target == getLocation()) {
            //you are already here
            return ans;
        }
        Map<Terrain, Terrain> previousLink = new HashMap<>();
        previousLink.put((Terrain) getLocation(), null);
        @SuppressWarnings({"unchecked", "rawtypes"})
        Comparator<Terrain> comp = new Comparator() {
            @Override
            /**
             * Approximates which path is better
             */
            public int compare(Object o1, Object o2) {
                Terrain t1 = (Terrain) o1;
                Terrain t2 = (Terrain) o2;
//				Integer c1 = totalCost(savedPaths.get(t1))+t1.getDistanceTo(target);
//				Integer c2 = totalCost(savedPaths.get(t2))+t2.getDistanceTo(target);
                Integer c1 = totalCost(t1, previousLink) + t1.getDistanceTo(target);
                Integer c2 = totalCost(t2, previousLink) + t2.getDistanceTo(target);
                return c1.compareTo(c2);
            }

        };
        PriorityQueue<Terrain> toCheck = new PriorityQueue<>(comp);
        toCheck.add((Terrain) getLocation());
        while (!toCheck.isEmpty()) {
            Terrain current = toCheck.poll();
            for (Terrain t : current.getAllAdjacentTerrains()) {
                if (999 != t.getMoveCost(getMovementType()) &&
                        (null == getGrid().get(t) || ((Unit) (getGrid().get(t))).getOwner() == this.getOwner())) {

                    boolean updated = false;
                    if (!previousLink.containsKey(t)) {
                        previousLink.put(t, current);
                        updated = true;
                    }

                    if (t == target) {
                        return genPath(t, previousLink);
                    }

                    if (t.getMoveCost(getMovementType()) + totalCost(current, previousLink) < totalCost(t, previousLink)) {
                        previousLink.put(t, current);
                        updated = true;
                    }
                    if (totalCost(t, previousLink) <= getCurrentMobility() && updated) {
                        if (!toCheck.contains(t)) {
                            toCheck.add(t);
                        }

                    }
                }
            }
        }
        //if you got here, then there is no path
        //See Precondition: there is a path, checked by getValidMoveSpaces
        throw new Exception("no path, precondition not met");

    }

    /**
     * Should only be invoked by {@code move(Queue<Terrain> path)} method
     * Postcondition: this.getLocation returns t
     */
    private void traverse(Grid<Actor> gr, Terrain t) {
        double moveCost = t.getMoveCost(this.getMovementType());
        setMobility(getCurrentMobility() - moveCost);
    }

    /**
     * Moves the {@link Unit} along a path of {@link Terrain}s.
     *
     * @param path A {@link Queue} of adjacent {@link Terrain}s
     * @return Whether or not {@link Unit} was successful in moving all the way along the path.
     * For now, it should always be successful because fog of war/land mines etc are not implemented yet
     */
    public boolean move(Queue<Terrain> path, boolean... realMove) {
        if (0 == path.size()) {
            //already done
            return true;
        }
        Terrain t = path.poll();
        Grid<Actor> gr = this.getGrid();
        //reached end of path, success
        if (t == null) {
            return true;
        }
        //invalid Terrain, fail -- should never happen
        if (!(gr.isValid(t))) {
            System.err.println("invalid terrain, not sure what happened here");
            return false;
        }
        //not enough mobility -- should never happen
        if (t.getMoveCost((this.getMovementType())) > this.getCurrentMobility()) {
            System.err.println("not enough mobi -- check BFS algorithm???");
            return false;
        }
        //Terrain is occupied by something other than allied Unit, fail
        try {
            Unit u = (Unit) (gr.get(t));
            if (null != u && u.getOwner() != this.getOwner()) {
                System.err.println("Terrain is occupied by something other than allied Unit, fail");
                return false;
            }
        } catch (ClassCastException actorIsNotAUnit) {
            System.err.println(Arrays.toString(actorIsNotAUnit.getStackTrace()));
            System.err.println("why do you have non-units in the grid");
            return false;
        }
        //move on
        traverse(getGrid(), t);
        if (path.peek() == null && realMove.length != 0) {
            teleport(t);
        }
        return realMove.length == 0 ? move(path) : move(path, true);
    }

    private void teleport(Terrain t) {
        Grid gr = getGrid();
        this.removeSelfFromGrid();
        this.putSelfInGrid(gr, t);
    }


    /**
     * Precondition: Carry c canCarry(this) is true
     *
     * @param c the Carry that this Unit will be loaded into
     * @throws Exception if unit cannot be carried
     */
    public void load(Carry c) throws Exception {
        if (c.canCarry(this)) {
            c.addUnit(this);
        } else {
            throw new Exception("cannot be carried, precondition not met");
        }
        immobilize();
    }

    public String getWeaponInfo() {
        if (this instanceof HiddenUnit) {
            return null;
        }
        if (WeaponType.NONE.equals(getWeapons()[0].getWeaponType())) {
            if (getWeapons()[1] == null) {
                return "Unarmed";
            } else {
                return "∞ ammo: " + getWeapons()[1].getWeaponType();
            }
        } else {
            return getAmmo() + " ammo: " + getWeapons()[0].getWeaponType() +
                    ((null != getWeapons()[1]) ? ", ∞ ammo: " + getWeapons()[1].getWeaponType() : "");
        }
    }

    public String getInfo() {
        return getType() + ((null != getLocation() && getLocation() instanceof Terrain) ? " at " + ((Terrain) getLocation()).getUIName() + getLocation() : "")
                + " [" + getHealth() + " HP, " + ((int) getFuel()) + " fuel, " + getWeaponInfo() + " ]" +
                (((this instanceof Stealth && ((Stealth) this).isHidden()) || (this instanceof Stealth2 && ((Stealth2) this).isHidden())) ?
                        "[hidden]" : "");
    }

    public String getConciseInfo() {
        return getType() + " [" + getHealth() + " HP]";
    }

    /**
     * @return whether or not this unit is a jet aircraft
     */
    public boolean isJet() {
        return (this instanceof Fighter || this instanceof Bomber || this instanceof StealthBomber ||
                this instanceof AdvFighter || this instanceof JSF || this instanceof CAS || this instanceof DropShip);
    }
} public class Infantry extends Unit {

    /**
     * Constructs an Infantry owned by owner
     * also sets the correct weapons
     */
    public Infantry(Player p) {
        super(p);
        setWeapon(0, WeaponType.NONE); //no primary -- secondary has infinite ammo
        setWeapon(1, WeaponType.RIFLE);
    }

    public Infantry(Player p, Suit suit) {
        this(p);
        this.setSuit(suit);
    }

    @Override
    public int getBuildCost() {
        return 100;
    }

    @Override
    public double getBaseArmorResistance() {
        //0% resistance
        return 1;
    }

    @Override
    public void outOfFuel() {
        //do nothing
    }

    @Override
    public MoveType getMovementType() {
        return MoveType.FOOT;
    }

    /**
     * Precondition: Infantry is on an enemy
     * owned or neutral Property
     * Ticks the cap timer of the Property with tickCapTimer()
     */
    public void capture() {
        System.out.println("capturing city");
        Terrain t = (Terrain) this.getLocation();
        if (t instanceof Property) {
            Property p = (Property) t;
            System.out.println("capTimer = " + p.getCapTimer());
            p.tickCapTimer(this);
        } else {
            System.err.println("?? not on property, loc = " + getLocation().getClass().getSimpleName() + " " + getLocation());
        }
        this.immobilize();
    }

    @Override
    public boolean couldTarget(Unit toCheck, Terrain hypothetical) { //cannot target jet fighters, boats
        return super.couldTarget(toCheck, hypothetical) && !toCheck.isJet() && !MoveType.SEA.equals(toCheck.getMovementType());
    }

}
import cbskarmory.units.Air;
import cbskarmory.units.Sea;
import cbskarmory.units.Unit;
import cbskarmory.units.land.Infantry;
import info.gridworld.actor.Actor;
import info.gridworld.gui.GridPanel;
import info.gridworld.gui.MenuMaker;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import cbskarmory.Player;
import cbskarmory.TerrainGrid;
import cbskarmory.PassiveFlag.COFlag;
import cbskarmory.terrain.Terrain;
/*
 * Copyright(c) 2017 CBSkarmory (https://github.com/CBSkarmory)
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * This code 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.
 *
 * @author CBSkarmory
 */

package cbskarmory.terrain.properties;





/**
 * Property represents all capturable Terrains Instantiated, it is a plain city.
 * Provides 30% defense boost to occupying land units. Units of MoveType FOOT,
 * TIRES, TREADS costs 1 mobility to traverse. SEA costs 999.
 */
public class Property extends Terrain {

    // cap timer
    public static final int FULL_CAP_TIMER = 200;
    private int capTimer;
    // capture mechanics
    private Player owner;

    /**
     * Constructs a Property with given row and column coordinates.
     *
     * @param r     the row
     * @param c     the column
     * @param owner owner of property, null if neutral
     */
    public Property(int r, int c, TerrainGrid<Actor> hostGrid, Player owner) {
        super(r, c, hostGrid);
        this.setOwner(owner);
        this.capTimer = 200;
    }

    /**
     * @return the current capture timer
     */
    public int getCapTimer() {
        return this.capTimer;
    }

    /**
     * resets the capture timer to the FULL_CAP_TIMER
     */
    public void resetCapTimer() {
        this.capTimer = Property.FULL_CAP_TIMER;
    }

    /**
     * @return the current owner of the property If the property is neutral, it
     * returns null.
     */
    public Player getOwner() {
        return this.owner;
    }

    /**
     * Sets the owner of the property. Should only be invoked internally by
     * tickCapTimer() Precondition: Player p is not null
     */
    public void setOwner(Player p) {
        this.owner = p;
        if (null != owner) {
            p.getPropertiesOwned().add(this);
        }
    }

    /**
     * Decrements the capture timer by the health of the occupying infantry unit
     * If capTimer falls below or equal to 0, the property is captured.
     */
    public void tickCapTimer(Unit u) {
        // currently, only infantry can cap. CO powers will probably allow other
        // units to cap later.
        if (!(u instanceof Infantry)) {