/** * Copyright (C) 2012 The FreeCol-Android Team * * This file is part of FreeCol-Android. * * FreeCol-Android 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, either version 2 of the License, or * (at your option) any later version. * * FreeCol 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. * * You should have received a copy of the GNU General Public License * along with FreeCol-Android. If not, see <http://www.gnu.org/licenses/>. */ package org.freecolandroid.ui.colony; import static org.freecolandroid.Constants.LOG_TAG; import java.util.List; import java.util.Locale; import net.sf.freecol.client.ClientOptions; import net.sf.freecol.client.FreeColClient; import net.sf.freecol.client.gui.ImageLibrary; import net.sf.freecol.client.gui.i18n.Messages; import net.sf.freecol.common.model.AbstractGoods; import net.sf.freecol.common.model.Colony; import net.sf.freecol.common.model.ColonyTile; import net.sf.freecol.common.model.GoodsType; import net.sf.freecol.common.model.Map.Direction; import net.sf.freecol.common.model.Player; import net.sf.freecol.common.model.Player.NoClaimReason; import net.sf.freecol.common.model.StringTemplate; import net.sf.freecol.common.model.Tile; import net.sf.freecol.common.model.TileType; import net.sf.freecol.common.model.Unit; import net.sf.freecol.common.model.UnitLocation.NoAddReason; import net.sf.freecol.common.model.WorkLocation; import org.freecolandroid.debug.FCLog; import org.freecolandroid.repackaged.java.awt.Color; import org.freecolandroid.repackaged.java.awt.Graphics2D; import android.annotation.SuppressLint; import android.content.ClipData; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.DragEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; import android.view.View; import android.widget.ImageView; public class ColonyMapCanvas extends SurfaceView implements Callback { private class PaintThread extends Thread { private boolean mRunning; @SuppressLint("WrongCall") @Override public void run() { Log.d(LOG_TAG, "ColonyMapCanvas.PaintThread.run() - start"); System.out.println(); Canvas canvas = null; while (mRunning) { try { canvas = mHolder.lockCanvas(); onDraw(canvas); } catch (Exception e) { Log.w(LOG_TAG, "Exception while drawing", e); } finally { if (canvas != null) { mHolder.unlockCanvasAndPost(canvas); } } } Log.d(LOG_TAG, "ColonyMapCanvas.PaintThread.run() - stop"); } public void setRunning(boolean running) { mRunning = running; } } private final Tile[][] mTiles = new Tile[3][3]; private final PaintThread mPaintThread = new PaintThread(); private final SurfaceHolder mHolder; private Colony mColony; private FreeColClient mClient; private Graphics2D mGraphics; private Paint mPaint; private OnColonyUpdatedListener mListener; private ImageView mDragShadowView; public ColonyMapCanvas(Context context) { super(context); mHolder = getHolder(); mHolder.addCallback(this); } public ColonyMapCanvas(Context context, AttributeSet attrs) { super(context, attrs); mHolder = getHolder(); mHolder.addCallback(this); } public void init(FreeColClient client, Colony colony, ImageView dragShadowView) { mPaint = new Paint(); mPaint.setColor(android.graphics.Color.RED); mPaint.setStyle(Style.FILL); mPaint.setStrokeWidth(5); mDragShadowView = dragShadowView; mClient = client; mColony = colony; mGraphics = new Graphics2D(); Tile tile = colony.getTile(); mTiles[0][0] = tile.getNeighbourOrNull(Direction.N); mTiles[0][1] = tile.getNeighbourOrNull(Direction.NE); mTiles[0][2] = tile.getNeighbourOrNull(Direction.E); mTiles[1][0] = tile.getNeighbourOrNull(Direction.NW); mTiles[1][1] = tile; mTiles[1][2] = tile.getNeighbourOrNull(Direction.SE); mTiles[2][0] = tile.getNeighbourOrNull(Direction.W); mTiles[2][1] = tile.getNeighbourOrNull(Direction.SW); mTiles[2][2] = tile.getNeighbourOrNull(Direction.S); } @Override protected void onDraw(Canvas canvas) { if (mColony != null && canvas != null) { mGraphics.setCanvas(canvas); mGraphics.setColor(Color.black); mGraphics.fillRect(0, 0, getWidth(), getHeight()); TileType tileType = mColony.getTile().getType(); ImageLibrary library = mClient.getGUI().getImageLibrary(); int tileWidth = library.getTerrainImageWidth(tileType) / 2; int tileHeight = library.getTerrainImageHeight(tileType) / 2; // Draw terrain and improvements (fields, roads) for (int x = 0; x < 3; x++) { for (int y = 0; y < 3; y++) { if (mTiles[x][y] != null) { int xx = ((2 - x) + y) * tileWidth; int yy = (x + y) * tileHeight; canvas.save(); canvas.translate(xx, yy); mClient.getGUI().getColonyTileGUI() .displayColonyTile(mGraphics, mTiles[x][y], mColony); canvas.restore(); } } } // Draw production and working units for (int x = 0; x < 3; x++) { for (int y = 0; y < 3; y++) { if (mTiles[x][y] != null) { int xx = ((2 - x) + y) * tileWidth; int yy = (x + y) * tileHeight; ColonyTile tile = mColony.getColonyTile(mTiles[x][y]); List<Unit> workUnits = tile.getUnitList(); if (workUnits != null && !workUnits.isEmpty()) { canvas.save(); Unit unit = workUnits.get(0); Bitmap unitIcon = library.getUnitImageIcon(unit).getImage().getBitmap(); canvas.translate(xx + tileWidth - unitIcon.getWidth() / 2, yy); canvas.drawBitmap(unitIcon, 0, 0, mPaint); canvas.restore(); } canvas.save(); canvas.translate(xx, yy); // Central tile containing the colony drawProduction(tile.getProduction(), canvas, tileWidth * 2, tileHeight * 2); canvas.restore(); } } } } } private void drawProduction(List<AbstractGoods> production, Canvas canvas, int tileWidth, int tileHeight) { int verticalStops = production.size() + 1; float gapHeigh = tileHeight / (float) verticalStops; for (AbstractGoods goods : production) { // Move one step down canvas.translate(0, gapHeigh); int horizontalStops = goods.getAmount() + 1; float gapWidth = tileWidth / (float) horizontalStops; Bitmap goodsImage = mClient.getGUI().getImageLibrary().getGoodsImage(goods.getType()) .getBitmap(); canvas.save(); for (int i = 0; i < goods.getAmount(); i++) { // Move one step right canvas.translate(gapWidth, 0); // Adjust for bitmap width & height canvas.save(); canvas.translate(-goodsImage.getWidth() / 2.0f, -goodsImage.getHeight() / 2.0f); canvas.drawBitmap(goodsImage, 0, 0, mPaint); canvas.restore(); } canvas.restore(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { mPaintThread.setRunning(true); mPaintThread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { boolean retry = true; mPaintThread.setRunning(false); while (retry) { try { mPaintThread.join(); retry = false; } catch (InterruptedException e) { } } } @Override public boolean onDragEvent(DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: DragHolder holder = (DragHolder) event.getLocalState(); return holder.unit != null; case DragEvent.ACTION_DROP: DragHolder dragHolder = (DragHolder) event.getLocalState(); Unit unit = dragHolder.unit; handleUnitDrop(unit, (WorkLocation) dragHolder.origin, (int) event.getX(), (int) event.getY()); return true; default: return true; } } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { // Check if we are tapping a Tile and if that tile contains an Unit ColonyTile workTile = getColonyTileAt((int) event.getX(), (int) event.getY()); if (workTile != null) { List<Unit> units = workTile.getUnitList(); if (units != null && !units.isEmpty()) { Unit unit = units.get(0); mDragShadowView.setImageBitmap(mClient.getGUI().getImageLibrary() .getUnitImageIcon(unit).getImage().getBitmap()); DragHolder dragHolder = new DragHolder(unit, workTile); startDrag(ClipData.newPlainText("Drag", "Drag"), new View.DragShadowBuilder( mDragShadowView), dragHolder, 0); } } } return true; } private void handleUnitDrop(Unit unit, WorkLocation origin, int x, int y) { FCLog.log("Unit dropped at x=" + x + ", y=" + y); // Find the tile that the unit was dropped on ColonyTile workTile = getColonyTileAt(x, y); if (workTile != null && workTile != origin) { boolean canWork = tryWork(workTile, unit); if (canWork) { mListener.onUnitLocationUpdated(unit, workTile); } } } private boolean tileContains(int px, int py, int tileWidth, int tileHeight) { int dx = Math.abs(tileWidth / 2 - px); int dy = Math.abs(tileHeight / 2 - py); return (dx + tileWidth * dy / tileHeight) <= tileWidth / 2; } private ColonyTile getColonyTileAt(int x, int y) { Rect tileRect = new Rect(); TileType tileType = mColony.getTile().getType(); ImageLibrary library = mClient.getGUI().getImageLibrary(); int tileWidth = library.getTerrainImageWidth(tileType) / 2; int tileHeight = library.getTerrainImageHeight(tileType) / 2; // Draw terrain and improvements (fields, roads) for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (mTiles[i][j] != null) { int xx = ((2 - i) + j) * tileWidth; int yy = (i + j) * tileHeight; tileRect.set(xx, yy, xx + 2 * tileWidth, yy + 2 * tileHeight); if (tileRect.contains(x, y)) { if (tileContains(x - tileRect.left, y - tileRect.top, 2 * tileWidth, 2 * tileHeight)) { return mColony.getColonyTile(mTiles[i][j]); } } } } } return null; } /** * Try to work this tile with a specified unit. * * @param unit * The <code>Unit</code> to work the tile. * @return True if the unit succeeds. */ private boolean tryWork(ColonyTile colonyTile, Unit unit) { Tile tile = colonyTile.getWorkTile(); Player player = unit.getOwner(); if (tile.getOwningSettlement() != mColony) { // Need to acquire the tile before working it. NoClaimReason claim = player.canClaimForSettlementReason(tile); switch (claim) { case NONE: case NATIVES: if (mClient.getInGameController().claimLand(tile, mColony, 0) && tile.getOwningSettlement() == mColony) { FCLog.log("Colony " + mColony.getName() + " claims tile " + tile.toString() + " with unit " + unit.getId()); } else { FCLog.log("Colony " + mColony.getName() + " did not claim " + tile.toString() + " with unit " + unit.getId()); return false; } break; default: // Otherwise, can not use land mClient.getGUI().errorMessage( "noClaimReason." + claim.toString().toLowerCase(Locale.US)); return false; } // Check reason again, claim should be satisfied. if (tile.getOwningSettlement() != mColony) { throw new IllegalStateException("Claim failed"); } } // Claim sorted, but complain about other failure. NoAddReason reason = colonyTile.getNoAddReason(unit); if (reason != NoAddReason.NONE) { mClient.getGUI() .errorMessage("noAddReason." + reason.toString().toLowerCase(Locale.US)); return false; } // Choose the work to be done. // FTM, do not change the work type unless explicitly // told to as this destroys experience (TODO: allow // multiple experience accumulation?). GoodsType workType = unit.getWorkType(); if (workType == null) { // Try to use expertise, then tile-specific workType = unit.getType().getExpertProduction(); if (workType == null) { workType = colonyTile.getWorkType(unit); } } // Set the unit to work. Note this might upgrade the // unit, and possibly even change its work type as the // server has the right to maintain consistency. mClient.getInGameController().work(unit, colonyTile); // Now recheck, and see if we want to change to the // expected work type. if (workType != null && workType != unit.getWorkType()) { mClient.getInGameController().changeWorkType(unit, workType); } if (mClient.getClientOptions().getBoolean(ClientOptions.SHOW_NOT_BEST_TILE)) { ColonyTile best = mColony.getVacantColonyTileFor(unit, false, workType); if (best != null && colonyTile != best && (colonyTile.getProductionOf(unit, workType) < best.getProductionOf(unit, workType))) { StringTemplate template = StringTemplate.template("colonyPanel.notBestTile") .addStringTemplate("%unit%", Messages.getLabel(unit)) .add("%goods%", workType.getNameKey()) .addStringTemplate("%tile%", best.getLabel()); mClient.getGUI().showInformationMessage(template); } } return true; } public void setOnUnitLocationUpdatedListener(OnColonyUpdatedListener listener) { mListener = listener; } }