summaryrefslogtreecommitdiffstats
path: root/core/src/ch/asynk/rustanddust
diff options
context:
space:
mode:
authorJérémy Zurcher <jeremy@asynk.ch>2015-07-19 13:20:33 +0200
committerJérémy Zurcher <jeremy@asynk.ch>2015-07-19 13:20:33 +0200
commitde0463bcf0f76ef8b07f2719679c9e0d72745c5d (patch)
tree9a33df947ceeea16a3e20b400585b1d3c304e77e /core/src/ch/asynk/rustanddust
parente66f9f2a61d3dab4545e996046486de0d44e2901 (diff)
downloadRustAndDust-de0463bcf0f76ef8b07f2719679c9e0d72745c5d.zip
RustAndDust-de0463bcf0f76ef8b07f2719679c9e0d72745c5d.tar.gz
welcome RustAndDust
Diffstat (limited to 'core/src/ch/asynk/rustanddust')
-rw-r--r--core/src/ch/asynk/rustanddust/RustAndDust.java222
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Attack.java28
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Board.java554
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Faction.java6
-rw-r--r--core/src/ch/asynk/rustanddust/engine/HeadedPawn.java88
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Meteorology.java24
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Move.java158
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Objective.java49
-rw-r--r--core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java78
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Order.java16
-rw-r--r--core/src/ch/asynk/rustanddust/engine/OrderList.java64
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Orientation.java134
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Path.java62
-rw-r--r--core/src/ch/asynk/rustanddust/engine/PathBuilder.java266
-rw-r--r--core/src/ch/asynk/rustanddust/engine/PathIterator.java73
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Pawn.java365
-rw-r--r--core/src/ch/asynk/rustanddust/engine/SearchBoard.java545
-rw-r--r--core/src/ch/asynk/rustanddust/engine/SelectedTile.java80
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Tile.java175
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/Animation.java8
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java10
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java16
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java99
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java76
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java62
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java141
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java87
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java222
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java126
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java98
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java67
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java83
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java76
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java38
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java196
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java48
-rw-r--r--core/src/ch/asynk/rustanddust/game/Army.java30
-rw-r--r--core/src/ch/asynk/rustanddust/game/Battle.java40
-rw-r--r--core/src/ch/asynk/rustanddust/game/Command.java225
-rw-r--r--core/src/ch/asynk/rustanddust/game/Config.java76
-rw-r--r--core/src/ch/asynk/rustanddust/game/Ctrl.java336
-rw-r--r--core/src/ch/asynk/rustanddust/game/Engagement.java114
-rw-r--r--core/src/ch/asynk/rustanddust/game/Hex.java138
-rw-r--r--core/src/ch/asynk/rustanddust/game/HexSet.java29
-rw-r--r--core/src/ch/asynk/rustanddust/game/Hud.java307
-rw-r--r--core/src/ch/asynk/rustanddust/game/Map.java659
-rw-r--r--core/src/ch/asynk/rustanddust/game/Player.java213
-rw-r--r--core/src/ch/asynk/rustanddust/game/State.java35
-rw-r--r--core/src/ch/asynk/rustanddust/game/Unit.java361
-rw-r--r--core/src/ch/asynk/rustanddust/game/UnitList.java20
-rw-r--r--core/src/ch/asynk/rustanddust/game/Zone.java14
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/BattleCommon.java151
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/BattleCounterAttack.java150
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/BattleFrontalAssault.java124
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/BattleHeadToHead.java118
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/BattleLastStand.java136
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/BattleNightAction.java153
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/BattleStabToTheFlank.java143
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/BattleTest.java128
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/Factory.java192
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/MapA.java77
-rw-r--r--core/src/ch/asynk/rustanddust/game/battles/MapB.java75
-rw-r--r--core/src/ch/asynk/rustanddust/game/hud/ActionButtons.java185
-rw-r--r--core/src/ch/asynk/rustanddust/game/hud/EngagementPanel.java255
-rw-r--r--core/src/ch/asynk/rustanddust/game/hud/PlayerInfo.java202
-rw-r--r--core/src/ch/asynk/rustanddust/game/hud/StatisticsPanel.java120
-rw-r--r--core/src/ch/asynk/rustanddust/game/hud/UnitDock.java226
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateAnimation.java37
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateBreak.java90
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateCommon.java68
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateDeployment.java138
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateEngage.java105
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateMove.java193
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StatePromote.java42
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateReinforcement.java87
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateRotate.java111
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateSelect.java132
-rw-r--r--core/src/ch/asynk/rustanddust/game/states/StateWithdraw.java71
-rw-r--r--core/src/ch/asynk/rustanddust/loading/LoadingBar.java32
-rw-r--r--core/src/ch/asynk/rustanddust/menu/MainMenu.java67
-rw-r--r--core/src/ch/asynk/rustanddust/menu/OptionsMenu.java257
-rw-r--r--core/src/ch/asynk/rustanddust/menu/ScenariosMenu.java141
-rw-r--r--core/src/ch/asynk/rustanddust/menu/TutorialsMenu.java94
-rw-r--r--core/src/ch/asynk/rustanddust/screens/GameCamera.java212
-rw-r--r--core/src/ch/asynk/rustanddust/screens/GameScreen.java221
-rw-r--r--core/src/ch/asynk/rustanddust/screens/MenuCamera.java112
-rw-r--r--core/src/ch/asynk/rustanddust/screens/MenuScreen.java258
-rw-r--r--core/src/ch/asynk/rustanddust/ui/Bg.java28
-rw-r--r--core/src/ch/asynk/rustanddust/ui/Label.java72
-rw-r--r--core/src/ch/asynk/rustanddust/ui/LabelImage.java72
-rw-r--r--core/src/ch/asynk/rustanddust/ui/LabelStack.java72
-rw-r--r--core/src/ch/asynk/rustanddust/ui/Menu.java93
-rw-r--r--core/src/ch/asynk/rustanddust/ui/Msg.java79
-rw-r--r--core/src/ch/asynk/rustanddust/ui/OkCancel.java115
-rw-r--r--core/src/ch/asynk/rustanddust/ui/Patch.java28
-rw-r--r--core/src/ch/asynk/rustanddust/ui/Position.java239
-rw-r--r--core/src/ch/asynk/rustanddust/ui/Widget.java93
97 files changed, 12831 insertions, 0 deletions
diff --git a/core/src/ch/asynk/rustanddust/RustAndDust.java b/core/src/ch/asynk/rustanddust/RustAndDust.java
new file mode 100644
index 0000000..52732a3
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/RustAndDust.java
@@ -0,0 +1,222 @@
+package ch.asynk.rustanddust;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.Game;
+import com.badlogic.gdx.assets.AssetManager;
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.rustanddust.screens.MenuScreen;
+import ch.asynk.rustanddust.screens.GameScreen;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Config;
+import ch.asynk.rustanddust.game.battles.Factory;
+
+public class RustAndDust extends Game
+{
+ public AssetManager manager;
+ public Factory factory;
+ public Ctrl ctrl;
+ public Config config;
+ public int hudCorrection;
+
+ public TextureAtlas uiAtlas;
+ public TextureAtlas menuAtlas;
+ public BitmapFont fontB;
+ public BitmapFont fontW;
+
+ public enum State
+ {
+ MENU,
+ GAME,
+ NONE
+ }
+ private State state;
+
+ public static void debug(String msg)
+ {
+ debug("", msg);
+ }
+
+ public static void debug(String dom, String msg)
+ {
+ Gdx.app.debug(dom, msg);
+ }
+
+ @Override
+ public void create ()
+ {
+ Gdx.app.setLogLevel(Gdx.app.LOG_DEBUG);
+ this.hudCorrection = ((int) (125 * Gdx.graphics.getDensity()) - 75);
+ debug("RustAndDust", "create() [" + Gdx.graphics.getWidth() + ";" + Gdx.graphics.getHeight() + "] " + Gdx.graphics.getDensity() + " -> " + hudCorrection);
+
+ manager = new AssetManager();
+ factory = new Factory(this);
+ config = new Config();
+
+ state = State.NONE;
+ loadUiAssets();
+ switchToMenu();
+ }
+
+ public void switchToMenu()
+ {
+ if (state == State.GAME) {
+ unloadGameAssets();
+ factory.dispose();
+ ctrl.dispose();
+ getScreen().dispose();
+ }
+ loadMenuAssets();
+ state = State.MENU;
+ setScreen(new MenuScreen(this));
+ }
+
+ public void switchToGame()
+ {
+ unloadMenuAssets();
+ getScreen().dispose();
+ factory.assetsLoaded();
+ state = State.GAME;
+ setScreen(new GameScreen(this));
+ }
+
+ public void loadGameAssets()
+ {
+ if (config.battle.getMapType() == Factory.MapType.MAP_A)
+ manager.load("data/map_a.png", Texture.class);
+ if (config.battle.getMapType() == Factory.MapType.MAP_B)
+ manager.load("data/map_b.png", Texture.class);
+ int i = config.graphics.i;
+ manager.load(String.format("data/units%d.atlas",i), TextureAtlas.class);
+ manager.load(String.format("data/unit-overlays%d.atlas", i), TextureAtlas.class);
+ manager.load("data/hex.png", Texture.class);
+ manager.load("data/hud.atlas", TextureAtlas.class);
+ manager.load("data/hex-overlays.atlas", TextureAtlas.class);
+ manager.load("data/dice.png", Texture.class);
+ manager.load("data/infantry_fire.png", Texture.class);
+ manager.load("data/tank_fire.png", Texture.class);
+ manager.load("data/explosions.png", Texture.class);
+ manager.load("sounds/dice.mp3", Sound.class);
+ manager.load("sounds/tank_move.mp3", Sound.class);
+ manager.load("sounds/infantry_move.mp3", Sound.class);
+ manager.load("sounds/infantry_fire.mp3", Sound.class);
+ manager.load("sounds/tank_fire.mp3", Sound.class);
+ manager.load("sounds/tank_fire_short.mp3", Sound.class);
+ manager.load("sounds/explosion.mp3", Sound.class);
+ manager.load("sounds/explosion_short.mp3", Sound.class);
+ manager.load("sounds/promote_us.mp3", Sound.class);
+ manager.load("sounds/promote_ge.mp3", Sound.class);
+ debug("RustAndDust", " assets loaded : " + (Gdx.app.getJavaHeap()/1024.0f) + "KB");
+ }
+
+ private void unloadGameAssets()
+ {
+ if (config.battle.getMapType() == Factory.MapType.MAP_A)
+ manager.unload("data/map_a.png");
+ if (config.battle.getMapType() == Factory.MapType.MAP_B)
+ manager.unload("data/map_b.png");
+ int i = config.graphics.i;
+ manager.unload(String.format("data/units%d.atlas",i));
+ manager.unload(String.format("data/unit-overlays%d.atlas", i));
+ manager.unload("data/hex.png");
+ manager.unload("data/hud.atlas");
+ manager.unload("data/hex-overlays.atlas");
+ manager.unload("data/dice.png");
+ manager.unload("data/infantry_fire.png");
+ manager.unload("data/tank_fire.png");
+ manager.unload("data/explosions.png");
+ manager.unload("sounds/dice.mp3");
+ manager.unload("sounds/tank_move.mp3");
+ manager.unload("sounds/infantry_move.mp3");
+ manager.unload("sounds/infantry_fire.mp3");
+ manager.unload("sounds/tank_fire.mp3");
+ manager.unload("sounds/tank_fire_short.mp3");
+ manager.unload("sounds/explosion.mp3");
+ manager.unload("sounds/explosion_short.mp3");
+ manager.unload("sounds/promote_us.mp3");
+ manager.unload("sounds/promote_ge.mp3");
+ debug("RustAndDust", " assets unloaded : " + (Gdx.app.getJavaHeap()/1024.0f) + "KB");
+ }
+
+ private void loadUiAssets()
+ {
+ manager.load("data/ui.atlas", TextureAtlas.class);
+ manager.finishLoading();
+ uiAtlas = manager.get("data/ui.atlas", TextureAtlas.class);
+ fontB = new BitmapFont(Gdx.files.internal("skin/veteran.fnt"), uiAtlas.findRegion("veteran-black"));
+ fontW = new BitmapFont(Gdx.files.internal("skin/veteran.fnt"), uiAtlas.findRegion("veteran-white"));
+ }
+
+ private void unloadUiAssets()
+ {
+ fontB.dispose();
+ fontW.dispose();
+ manager.unload("data/ui.atlas");
+ }
+
+ private void loadMenuAssets()
+ {
+ manager.load("data/map_a.png", Texture.class);
+ manager.load("data/menu.atlas", TextureAtlas.class);
+ manager.finishLoading();
+ menuAtlas = manager.get("data/menu.atlas", TextureAtlas.class);
+ }
+
+ private void unloadMenuAssets()
+ {
+ manager.unload("data/map_a.png");
+ manager.unload("data/menu.atlas");
+ }
+
+ // @Override
+ // public void render ()
+ // {
+ // Gdx.gl.glClearColor(0, 0, 0, 1);
+ // Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
+ // super.render();
+ // }
+
+ // @Override
+ // public void resize(int width, int height)
+ // {
+ // debug("RustAndDust", "resize(" + width + ", " + height + ")");
+ // super.resize(width, height);
+ // }
+
+ @Override
+ public void dispose()
+ {
+ debug("RustAndDust", "dispose()");
+ debug("RustAndDust", "diagnostics:\n" + manager.getDiagnostics() );
+ getScreen().dispose();
+ unloadUiAssets();
+ switch(state) {
+ case MENU:
+ unloadMenuAssets();
+ break;
+ case GAME:
+ unloadGameAssets();
+ factory.dispose();
+ ctrl.dispose();
+ break;
+ }
+ debug("RustAndDust", "diagnostics:\n" + manager.getDiagnostics() );
+ manager.clear();
+ manager.dispose();
+ }
+
+ // @Override
+ // public void pause()
+ // {
+ // debug("RustAndDust", "pause()");
+ // }
+
+ // @Override
+ // public void resume()
+ // {
+ // debug("RustAndDust", "resume()");
+ // }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Attack.java b/core/src/ch/asynk/rustanddust/engine/Attack.java
new file mode 100644
index 0000000..e64399a
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Attack.java
@@ -0,0 +1,28 @@
+package ch.asynk.rustanddust.engine;
+
+public class Attack
+{
+ public Pawn attacker;
+ public Pawn target;
+ public int distance;
+ public boolean isClear;
+ public boolean isFlank;
+
+ public Attack(Pawn attacker)
+ {
+ this.attacker = attacker;
+ }
+
+ public String toString()
+ {
+ return String.format("attack : %s -> %s dist:%d clear:%b flank:%b", attacker, target, distance, isClear, isFlank);
+ }
+
+ public void reset()
+ {
+ target = null;
+ distance = 0;;
+ isClear = false;
+ isFlank = false;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Board.java b/core/src/ch/asynk/rustanddust/engine/Board.java
new file mode 100644
index 0000000..e4aba6d
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Board.java
@@ -0,0 +1,554 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.Iterator;
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+
+import com.badlogic.gdx.Gdx;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import com.badlogic.gdx.math.Matrix4;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+import ch.asynk.rustanddust.engine.gfx.animations.AnimationSequence;
+import ch.asynk.rustanddust.engine.gfx.animations.RunnableAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.MoveToAnimation.MoveToAnimationCb;
+
+public abstract class Board implements Disposable, Animation
+{
+ private int cols;
+ private int rows;
+ private final Tile neighbours[] = new Tile[6];
+
+ public interface TileBuilder
+ {
+ public Tile getNewTile(float x, float y, int col, int row, boolean offmap);
+ }
+
+ public static class Config
+ {
+ public int cols;
+ public int rows;
+ public int x0; // bottom left x offset
+ public int y0; // bottom left y offset
+ public int w; // hex width
+ public int dw; // half hex : w/2
+ public int s; // hex side
+ public float dh; // hex top : s/2
+ public float h; // square height : s + dh
+ public float slope; // north-west side slope : (dh / (float) dw)
+ }
+
+ private Config cfg;
+ private Tile[] tiles;
+ private SearchBoard searchBoard;
+ private Sprite board;
+ private Orientation sides[];
+
+ private boolean transform;
+ private Matrix4 prevTransform;
+ private Matrix4 nextTransform;
+
+ private int tileCount = 0;
+ private int pawnCount = 0;
+ private int animationCount = 0;
+ private final ArrayList<Animation> animations = new ArrayList<Animation>(2);
+ private final ArrayList<Animation> nextAnimations = new ArrayList<Animation>(2);
+ private final LinkedHashSet<Tile> tilesToDraw = new LinkedHashSet<Tile>();
+
+ protected SelectedTile selectedTile;
+
+ protected Board(int cols, int rows)
+ {
+ // add a frame of OFFMAP Tiles
+ this.cols = (cols + 2);
+ this.rows = (rows + 2);
+ searchBoard = new SearchBoard(this, cols, rows);
+ initSides();
+ }
+
+ public Board(TileBuilder tileBuilder, Config cfg, Texture boardTexture, SelectedTile selectedTile)
+ {
+ board = new Sprite(boardTexture);
+ this.cfg = cfg;
+ // add a frame of OFFMAP Tiles
+ this.cols = (cfg.cols + 2);
+ this.rows = (cfg.rows + 2);
+ this.tiles = new Tile[this.cols * this.rows];
+ searchBoard = new SearchBoard(this, cfg.cols, cfg.rows);
+
+ int idx = 0;
+ boolean evenRow = false;
+ float y = cfg.y0 - cfg.dh + cfg.s - cfg.h;
+ for (int i = -1; i < (cfg.rows + 1); i++) {
+ float x = cfg.x0 + cfg.dw - cfg.w;
+ if (!evenRow) x += cfg.dw;
+ for ( int j = -1; j < (cfg.cols + 1); j ++) {
+ boolean offmap = ((j < 0) || (i < 0) || (j >= cfg.cols) || (i >= cfg.rows));
+ this.tiles[idx] = tileBuilder.getNewTile(x, y, (j + ((i + 1) / 2)), i, offmap);
+ idx += 1;
+ x += cfg.w;
+ }
+ y += cfg.h;
+ evenRow = !evenRow;
+ }
+
+ initSides();
+
+ this.selectedTile = selectedTile;
+ }
+
+ private void initSides()
+ {
+ this.sides = new Orientation[6];
+ sides[0] = Orientation.NORTH;
+ sides[1] = Orientation.NORTH_EAST;
+ sides[2] = Orientation.SOUTH_EAST;
+ sides[3] = Orientation.SOUTH;
+ sides[4] = Orientation.SOUTH_WEST;
+ sides[5] = Orientation.NORTH_WEST;
+ }
+
+ @Override
+ public void dispose()
+ {
+ for (int i = 0; i < (this.cols * this.rows); i++)
+ tiles[i].dispose();
+ tilesToDraw.clear();
+ for (int i = 0, n = nextAnimations.size(); i < n; i++)
+ nextAnimations.get(i).dispose();
+ animations.clear();
+ for (int i = 0, n = animations.size(); i < n; i++)
+ animations.get(i).dispose();
+ animations.clear();
+ if (selectedTile != null)
+ selectedTile.dispose();
+ Move.clearPool();
+ Path.clearPool();
+ }
+
+ public float getWidth()
+ {
+ return board.getWidth();
+ }
+
+ public float getHeight()
+ {
+ return board.getHeight();
+ }
+
+ public void setPosition(float x, float y)
+ {
+ board.setPosition(x, y);
+ if ((x != 0.0f) || (y != 0.0f)) {
+ transform = true;
+ prevTransform = new Matrix4();
+ nextTransform = new Matrix4();
+ nextTransform.translate(x, y, 0);
+ } else
+ transform = false;
+ }
+
+ public Orientation getSide(int i)
+ {
+ return sides[i];
+ }
+
+ protected int getTileOffset(int col, int row)
+ {
+ col = (col + 1 - ((row + 1) / 2));
+ row = (row + 1);
+ if ((col < 0) || (row < 0) || (row >= this.rows) || (col >= this.cols))
+ return -1;
+
+ return (col + (row * this.cols));
+ }
+
+ protected Tile getTile(int col, int row)
+ {
+ int offset = getTileOffset(col, row);
+ if (offset < 0)
+ return null;
+ return tiles[offset];
+ }
+
+ public void setAdjacentTiles(Tile tile, Tile tiles[])
+ {
+ tiles[0] = getAdjTileAt(tile, sides[0].opposite());
+ tiles[1] = getAdjTileAt(tile, sides[1].opposite());
+ tiles[2] = getAdjTileAt(tile, sides[2].opposite());
+ tiles[3] = getAdjTileAt(tile, sides[3].opposite());
+ tiles[4] = getAdjTileAt(tile, sides[4].opposite());
+ tiles[5] = getAdjTileAt(tile, sides[5].opposite());
+ }
+
+ public Tile getAdjTileAt(Tile tile, Orientation o)
+ {
+ Tile t = null;
+ switch(o) {
+ case NORTH:
+ t = getTile((tile.col + 1), tile.row);
+ break;
+ case NORTH_EAST:
+ t = getTile(tile.col, (tile.row - 1));
+ break;
+ case SOUTH_EAST:
+ t = getTile((tile.col - 1), (tile.row - 1));
+ break;
+ case SOUTH:
+ t = getTile((tile.col - 1), tile.row);
+ break;
+ case SOUTH_WEST:
+ t = getTile(tile.col, (tile.row + 1));
+ break;
+ case NORTH_WEST:
+ t = getTile((tile.col + 1), (tile.row + 1));
+ break;
+ }
+ return t;
+ }
+
+ protected abstract void animationsOver();
+
+ protected void addAnimation(Animation a)
+ {
+ nextAnimations.add(a);
+ }
+
+ public int animationCount()
+ {
+ return animations.size();
+ }
+
+ private void stats()
+ {
+ boolean print = false;
+
+ if (tileCount != tilesToDraw.size()) {
+ tileCount = tilesToDraw.size();
+ print = true;
+ }
+
+ if (animationCount != animations.size()) {
+ animationCount = animations.size();
+ print = true;
+ }
+
+ if (print)
+ Gdx.app.debug("Board", " tiles:" + tileCount + " pawns:" + pawnCount + " animations:" + animationCount);
+ }
+
+ public boolean animate(float delta)
+ {
+ boolean over = (animations.size() > 0);
+ Iterator<Animation> iter = animations.iterator();
+ while (iter.hasNext()) {
+ Animation a = iter.next();
+ if (a.animate(delta))
+ iter.remove();
+ }
+ if (over && (animations.size() == 0))
+ animationsOver();
+
+ for (int i = 0, n = nextAnimations.size(); i < n; i++)
+ animations.add(nextAnimations.get(i));
+ nextAnimations.clear();
+
+ selectedTile.animate(delta);
+
+ return true;
+ }
+
+ public void draw(Batch batch)
+ {
+ board.draw(batch);
+
+ if (transform) {
+ prevTransform.set(batch.getTransformMatrix());
+ batch.setTransformMatrix(nextTransform);
+ }
+
+ Iterator<Tile> tileIter = tilesToDraw.iterator();
+ while (tileIter.hasNext())
+ tileIter.next().draw(batch);
+
+ Iterator<Animation> animationIter = animations.iterator();
+ while (animationIter.hasNext())
+ animationIter.next().draw(batch);
+
+ selectedTile.draw(batch);
+
+ if (transform)
+ batch.setTransformMatrix(prevTransform);
+ }
+
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ stats();
+ if (transform) {
+ prevTransform.set(debugShapes.getTransformMatrix());
+ debugShapes.setTransformMatrix(nextTransform);
+ }
+
+ Iterator<Tile> iter = tilesToDraw.iterator();
+ while (iter.hasNext())
+ iter.next().drawDebug(debugShapes);
+
+ Iterator<Animation> animationIter = animations.iterator();
+ while (animationIter.hasNext())
+ animationIter.next().drawDebug(debugShapes);
+
+ if (transform)
+ debugShapes.setTransformMatrix(prevTransform);
+ }
+
+ protected int collectPossibleMoves(Pawn pawn, Collection<Tile> moves)
+ {
+ return searchBoard.possibleMovesFrom(pawn, moves);
+ }
+
+ protected int collectPossibleTargets(Pawn pawn, Collection<Pawn> targets)
+ {
+ return searchBoard.possibleTargetsFrom(pawn, targets);
+ }
+
+ protected int collectPossibleTargets(Pawn pawn, Collection<Pawn> units, Collection<Pawn> targets)
+ {
+ targets.clear();
+ for (Pawn target : units) {
+ if (pawn.canEngage(target) && searchBoard.canAttack(pawn, target, true))
+ targets.add(target);
+ }
+
+ return targets.size();
+ }
+
+ protected int collectMoveAssists(Pawn pawn, Collection<Pawn> assists)
+ {
+ assists.clear();
+ setAdjacentTiles(pawn.getTile(), neighbours);
+ for (int i = 0; i < 6; i++) {
+ Tile tile = neighbours[i];
+ if (tile != null) {
+ Iterator<Pawn> pawns = tile.iterator();
+ while(pawns.hasNext()) {
+ Pawn p = pawns.next();
+ if (!pawn.isEnemy(p) && p.canMove())
+ assists.add(p);
+ }
+ }
+ }
+ return assists.size();
+ }
+
+ protected int collectAttackAssists(Pawn pawn, Pawn target, Collection<Pawn> units, Collection<Pawn> assists)
+ {
+ assists.clear();
+ for (Pawn p : units) {
+ if ((p != pawn) && p.canEngage(target) && searchBoard.canAttack(p, target, !p.canAssistEngagementWithoutLos()))
+ assists.add(p);
+ }
+
+ return assists.size();
+ }
+
+ public Orientation findBestEntry(Pawn pawn, Tile to, int allowedMoves)
+ {
+ Orientation entry = Orientation.KEEP;
+ int cost = Integer.MAX_VALUE;
+ boolean road = false;
+
+ setAdjacentTiles(to, neighbours);
+ for (int i = 0; i < 6; i++) {
+ Tile t = neighbours[i];
+ if (t.isOffMap()) {
+ Orientation o = Orientation.fromAdj(t.col, t.row, to.col, to.row);
+ if (o.isInSides(allowedMoves)) {
+ o = o.opposite();
+ boolean r = to.road(o);
+ int c = to.costFrom(pawn, o);
+ if ((c < cost) || (r && (c == cost))) {
+ entry = o;
+ cost = c;
+ road = r;
+ }
+ }
+ }
+ }
+
+ return entry.opposite();
+ }
+
+ public void enableOverlayOn(Tile tile, int i, boolean enable)
+ {
+ if(tile.enableOverlay(i, enable))
+ tilesToDraw.add(tile);
+ else
+ tilesToDraw.remove(tile);
+ }
+
+ public void enableOverlayOn(Tile tile, int i, Orientation o, boolean enable)
+ {
+ if(tile.enableOverlay(i, enable, o.r()))
+ tilesToDraw.add(tile);
+ else
+ tilesToDraw.remove(tile);
+ }
+
+ private int pushPawnOnto(Pawn pawn, Tile tile)
+ {
+ if (!tile.isOffMap())
+ tilesToDraw.add(tile);
+ return tile.push(pawn);
+ }
+
+ public int removePawn(Pawn pawn)
+ {
+ Tile tile = pawn.getTile();
+ if (tile == null)
+ return 0;
+ int n = tile.remove(pawn);
+ if (!tile.mustBeDrawn())
+ tilesToDraw.remove(tile);
+ return n;
+ }
+
+ public Pawn setPawnOnto(Pawn pawn, Move move)
+ {
+ pawn.move(move);
+ return setPawnOnto(pawn, move.to, move.orientation);
+ }
+
+ public Pawn setPawnOnto(Pawn pawn, Tile tile, Orientation o)
+ {
+ pawn.setOnTile(tile, o.r());
+ pushPawnOnto(pawn, tile);
+ return pawn;
+ }
+
+ private RunnableAnimation getSetPawnOntoAnimation(final Pawn pawn)
+ {
+ return RunnableAnimation.get(pawn, new Runnable() {
+ @Override
+ public void run() {
+ Tile to = pawn.move.to;
+ if (!to.isOffMap())
+ setPawnOnto(pawn, to, pawn.move.orientation);
+ }
+ });
+ }
+
+ protected void movePawn(final Pawn pawn, Move move, MoveToAnimationCb cb)
+ {
+ pawn.move(move);
+ removePawn(pawn);
+
+ AnimationSequence seq = pawn.getMoveAnimation(move.iterator(), (move.steps() + 1), cb);
+ seq.addAnimation(getSetPawnOntoAnimation(pawn));
+ addAnimation(seq);
+ }
+
+ protected void enterPawn(final Pawn pawn, Move move)
+ {
+ pawn.move(move);
+ setPawnOnto(pawn, move.to, move.orientation);
+ }
+
+ protected void revertLastPawnMove(final Pawn pawn)
+ {
+ removePawn(pawn);
+
+ addAnimation(RunnableAnimation.get(pawn, new Runnable() {
+ @Override
+ public void run() {
+ pushPawnOnto(pawn, pawn.getTile());
+ }
+ }));
+
+ pawn.revertLastMove();
+ }
+
+ public void attack(final Pawn pawn, final Pawn target, boolean clearVisibility)
+ {
+ if (!pawn.canEngage(target) || !searchBoard.canAttack(pawn, target, clearVisibility))
+ throw new RuntimeException(String.format("%s cannot attack %s", pawn, target));
+ }
+
+ public Tile getTileAt(float mx, float my)
+ {
+ // compute row
+ float y = (my - cfg.y0);
+ int row = (int) (y / cfg.h);
+ boolean oddRow = ((row % 2) == 1);
+ if (y < 0.f) {
+ row = -1;
+ oddRow = true;
+ }
+
+ // compute col
+ float x = (mx - cfg.x0);
+ if (oddRow) x -= cfg.dw;
+ int col = (int) (x / cfg.w);
+ if (x < 0.f)
+ col = -1;
+
+ int colOffset = ((row + 1) / 2);
+
+ // check upper boundaries
+ float dy = (y - (row * cfg.h));
+ if (dy > cfg.s) {
+ dy -= cfg.s;
+ float dx = (x - (col * cfg.w));
+ col += colOffset;
+ if (dx < cfg.dw) {
+ if ((dx * cfg.slope) < dy) {
+ // upper left corner
+ row += 1;
+ colOffset = ((row +1) / 2);
+ }
+ } else {
+ if (((cfg.w - dx) * cfg.slope) < dy) {
+ // upper right corner
+ row += 1;
+ col += 1;
+ colOffset = ((row +1) / 2);
+ }
+ }
+ } else
+ col += colOffset;
+
+ return getTile(col, row);
+ }
+
+ public int distance(Tile from, Tile to)
+ {
+ return distance(from.col, from.row, to.col, to.row);
+ }
+
+ public int distance(int col0, int row0, int col1, int row1)
+ {
+ int dx = Math.abs(col1 - col0);
+ int dy = Math.abs(row1 - row0);
+ int dz = Math.abs((col0 - row0) - (col1 - row1));
+
+ if (dx > dy) {
+ if (dx > dz)
+ return dx;
+ else
+ return dz;
+ } else {
+ if (dy > dz)
+ return dy;
+ else
+ return dz;
+ }
+ }
+}
+
diff --git a/core/src/ch/asynk/rustanddust/engine/Faction.java b/core/src/ch/asynk/rustanddust/engine/Faction.java
new file mode 100644
index 0000000..e108fc4
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Faction.java
@@ -0,0 +1,6 @@
+package ch.asynk.rustanddust.engine;
+
+public interface Faction
+{
+ public boolean isEnemy(Faction other);
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/HeadedPawn.java b/core/src/ch/asynk/rustanddust/engine/HeadedPawn.java
new file mode 100644
index 0000000..67e1d44
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/HeadedPawn.java
@@ -0,0 +1,88 @@
+package ch.asynk.rustanddust.engine;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import com.badlogic.gdx.math.Vector3;
+
+public abstract class HeadedPawn extends Pawn
+{
+ private Sprite head;
+ protected Orientation orientation;
+
+ public HeadedPawn(Faction faction, String pawn, String head, TextureAtlas pawns, TextureAtlas overlays)
+ {
+ super(faction, pawn, pawns, overlays);
+ this.head = new Sprite(pawns.findRegion(head));
+ this.orientation = Orientation.KEEP;
+ this.descr += " " + orientation;
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ }
+
+ @Override
+ public void setAlpha(float alpha)
+ {
+ super.setAlpha(alpha);
+ head.setAlpha(alpha);
+ }
+
+ @Override
+ public float getRotation()
+ {
+ return orientation.r();
+ }
+
+ @Override
+ public Orientation getOrientation()
+ {
+ return orientation;
+ }
+
+ @Override
+ public void setPosition(float x, float y)
+ {
+ super.setPosition(x, y);
+ float cx = x + (getWidth() / 2f);
+ float cy = y + (getHeight() / 2f);
+ head.setPosition((cx - (head.getWidth() / 2f)), (cy - (head.getHeight() / 2f)));
+ }
+
+ @Override
+ public void setRotation(float z)
+ {
+ getPosition().z = z;
+ head.setRotation(z);
+ this.orientation = Orientation.fromRotation(z);
+ }
+
+ @Override
+ public void setPosition(float x, float y, float z)
+ {
+ setPosition(x, y);
+ setRotation(z);
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ head.draw(batch);
+ super.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ float w = head.getWidth();
+ float h = head.getHeight();
+ debugShapes.rect(head.getX(), head.getY(), (w / 2f), (h / 2f), w, h, head.getScaleX(), head.getScaleY(), head.getRotation());
+ super.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Meteorology.java b/core/src/ch/asynk/rustanddust/engine/Meteorology.java
new file mode 100644
index 0000000..9addf63
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Meteorology.java
@@ -0,0 +1,24 @@
+package ch.asynk.rustanddust.engine;
+
+public class Meteorology
+{
+ public enum Day { DAY, NIGHT };
+ public enum Season { SUMMER, SPRING, WINTER, FALL };
+ public enum Weather { CLEAR, RAIN, SNOW, WIND };
+
+ public Day day;
+ public Season season;
+ public Weather weather;
+
+ public Meteorology()
+ {
+ clear();
+ }
+
+ public void clear()
+ {
+ day = Day.DAY;
+ season = Season.SUMMER;
+ weather = Weather.CLEAR;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Move.java b/core/src/ch/asynk/rustanddust/engine/Move.java
new file mode 100644
index 0000000..077823f
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Move.java
@@ -0,0 +1,158 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.Iterator;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.math.Vector3;
+
+public class Move extends Path implements Iterable<Vector3>
+{
+ public enum MoveType
+ {
+ REGULAR,
+ SET,
+ ENTER,
+ EXIT;
+ }
+
+ private static final Pool<Move> movePool = new Pool<Move>()
+ {
+ @Override
+ protected Move newObject() {
+ return new Move();
+ }
+ };
+
+ public static Move get(Pawn pawn, Tile from, Tile to, Orientation orientation, Path path)
+ {
+ Move m = movePool.obtain();
+ m.pawn = pawn;
+ m.from = from;
+ m.to = to;
+ m.orientation = orientation;
+ if (path != null) {
+ m.init(path.tiles.size());
+ m.cost = path.cost;
+ m.roadMarch = path.roadMarch;
+ for (Tile tile : path.tiles)
+ m.tiles.add(tile);
+ } else {
+ m.init(0);
+ }
+
+ return m;
+ }
+
+ public static void clearPool()
+ {
+ movePool.clear();
+ }
+
+ public static Move getEnter(Pawn pawn, Tile to, Orientation orientation)
+ {
+ Move m = get(pawn, null, to, orientation, null);
+ m.type = MoveType.ENTER;
+ m.cost = to.costFrom(pawn, orientation);
+ return m;
+ }
+
+ public static Move getSet(Pawn pawn, Tile to, Orientation orientation)
+ {
+ Move m = get(pawn, null, to, orientation, null);
+ m.type = MoveType.SET;
+ m.cost = 0;
+ return m;
+ }
+
+ public Pawn pawn;
+ public Tile from;
+ public Tile to;
+ public Orientation orientation;
+ public MoveType type;
+
+ public Move()
+ {
+ super();
+ this.pawn = null;
+ this.from = null;
+ this.to = null;
+ this.orientation = Orientation.KEEP;
+ this.type = MoveType.REGULAR;
+ }
+
+ @Override
+ public void reset()
+ {
+ pawn = null;
+ from = null;
+ to = null;
+ orientation = Orientation.KEEP;
+ type = MoveType.REGULAR;
+ super.reset();
+ }
+
+ @Override
+ public void dispose()
+ {
+ tiles.clear();
+ movePool.free(this);
+ }
+
+ public boolean isSet()
+ {
+ return (type == MoveType.SET);
+ }
+
+ public boolean isEnter()
+ {
+ return (type == MoveType.ENTER);
+ }
+
+ public boolean isRegular()
+ {
+ return (type == MoveType.REGULAR);
+ }
+
+ public boolean isFinal()
+ {
+ return (type != MoveType.ENTER);
+ }
+
+ public int steps()
+ {
+ int steps = 0;
+
+ Tile tile = from;
+ Orientation o = pawn.getOrientation();
+ for (Tile next : tiles) {
+ Orientation nextO = Orientation.fromMove(tile.col, tile.row, next.col, next.row);
+ if (nextO != o) {
+ steps += 2;
+ o = nextO;
+ } else
+ steps += 1;
+ tile = next;
+ }
+ if (orientation != Orientation.fromMove(tile.col, tile.row, to.col, to.row))
+ steps += 2;
+ else
+ steps +=1;
+
+ return steps;
+ }
+
+ @Override
+ public String toString()
+ {
+ if (from == null)
+ return String.format("%s %s c:%d", to.toShort(), orientation, cost);
+ else
+ return String.format("%s->%s %s c:%d", from.toShort(), to.toShort(), orientation, cost);
+ }
+
+ @Override
+ public Iterator<Vector3> iterator()
+ {
+ return new PathIterator(pawn, from, to, orientation, tiles);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Objective.java b/core/src/ch/asynk/rustanddust/engine/Objective.java
new file mode 100644
index 0000000..de1c7d3
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Objective.java
@@ -0,0 +1,49 @@
+package ch.asynk.rustanddust.engine;
+
+public class Objective
+{
+ protected Faction curFaction;
+ protected Faction prevFaction;
+ private boolean persistent;
+
+ public Objective(Faction faction, boolean persistent)
+ {
+ this.curFaction = faction;
+ this.prevFaction = faction;
+ this.persistent = persistent;
+ }
+
+ public boolean is(Faction faction)
+ {
+ return (curFaction == faction);
+ }
+
+ public Faction faction()
+ {
+ return curFaction;
+ }
+
+ public boolean set(Faction faction)
+ {
+ if (faction == curFaction)
+ return false;
+
+ prevFaction = curFaction;
+ curFaction = faction;
+ return true;
+ }
+
+ public boolean unset()
+ {
+ if (persistent)
+ return false;
+ revert();
+ return true;
+ }
+
+ public Faction revert()
+ {
+ curFaction = prevFaction;
+ return curFaction;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java b/core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java
new file mode 100644
index 0000000..5618a9d
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java
@@ -0,0 +1,78 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.Collection;
+import java.util.HashMap;
+
+public class ObjectiveSet extends HashMap<Tile, Objective>
+{
+ public interface ObjectiveCb
+ {
+ public void showObjective(Tile tile, Faction faction);
+ }
+
+ private final Board board;
+ private final HashMap<Objective, Tile> modified;
+
+ public ObjectiveSet(Board board, int n)
+ {
+ super(n);
+ this.board = board;
+ this.modified = new HashMap<Objective, Tile>(10);
+ }
+
+ public void add(Tile tile, Faction faction, boolean persistent)
+ {
+ put(tile, new Objective(faction, persistent));
+ }
+
+ public int count(Faction faction)
+ {
+ int n = 0;
+ for (Objective objective : values()) {
+ if (objective.is(faction))
+ n += 1;
+ }
+ return n;
+ }
+
+ public Faction claim(Tile tile, Faction faction)
+ {
+ Objective objective = get(tile);
+ if (objective == null)
+ return null;
+
+ if (objective.set(faction))
+ modified.put(objective, tile);
+ return objective.faction();
+ }
+
+ public Faction unclaim(Tile tile)
+ {
+ Objective objective = get(tile);
+ if (objective == null)
+ return null;
+
+ if (objective.unset())
+ modified.remove(objective);
+ return objective.faction();
+ }
+
+ public void forget()
+ {
+ modified.clear();
+ }
+
+ public int modifiedCount()
+ {
+ return modified.size();
+ }
+
+ public void revert(ObjectiveCb cb)
+ {
+ for (Objective objective : modified.keySet()) {
+ objective.revert();
+ cb.showObjective(modified.get(objective), objective.faction());
+ }
+ modified.clear();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Order.java b/core/src/ch/asynk/rustanddust/engine/Order.java
new file mode 100644
index 0000000..960b126
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Order.java
@@ -0,0 +1,16 @@
+package ch.asynk.rustanddust.engine;
+
+import java.lang.Comparable;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.utils.Json;
+
+public abstract class Order implements Disposable, Pool.Poolable, Json.Serializable, Comparable<Pawn>
+{
+ public interface OrderType
+ {
+ }
+
+ public abstract boolean isA(OrderType type);
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/OrderList.java b/core/src/ch/asynk/rustanddust/engine/OrderList.java
new file mode 100644
index 0000000..4488cfe
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/OrderList.java
@@ -0,0 +1,64 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.LinkedList;
+import java.util.Iterator;
+
+import com.badlogic.gdx.utils.Json;
+import com.badlogic.gdx.utils.JsonValue;
+import com.badlogic.gdx.utils.JsonWriter.OutputType;
+
+public class OrderList extends LinkedList<Order> implements Json.Serializable
+{
+ public void dispose(Pawn pawn)
+ {
+ Iterator<Order> it = iterator();
+ while(it.hasNext()) {
+ Order order = it.next();
+ if (order.compareTo(pawn) == 0) {
+ it.remove();
+ order.dispose();
+ }
+ }
+ }
+
+ public void dispose(Pawn pawn, Order.OrderType type)
+ {
+ Iterator<Order> it = iterator();
+ while(it.hasNext()) {
+ Order order = it.next();
+ if ((order.compareTo(pawn) == 0) && (order.isA(type))) {
+ it.remove();
+ order.dispose();
+ }
+ }
+ }
+
+ public void dispose()
+ {
+ for (Order o : this)
+ o.dispose();
+ clear();
+ }
+
+ public String toJson()
+ {
+ Json json = new Json();
+ json.setOutputType(OutputType.json);
+ return json.toJson(this);
+ }
+
+ @Override
+ public void write(Json json)
+ {
+ json.writeArrayStart("commands");
+ for (Order o : this)
+ json.writeValue(o);
+ json.writeArrayEnd();
+ }
+
+ @Override
+ public void read(Json json, JsonValue jsonMap)
+ {
+ // TODO read(Json json, JsonValue jsonMap)
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Orientation.java b/core/src/ch/asynk/rustanddust/engine/Orientation.java
new file mode 100644
index 0000000..2c3ef58
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Orientation.java
@@ -0,0 +1,134 @@
+package ch.asynk.rustanddust.engine;
+
+public enum Orientation
+{
+ ALL(0, 63),
+ KEEP(0, 0),
+ NORTH(270, 1),
+ NORTH_EAST(210, 2),
+ SOUTH_EAST(150, 4),
+ SOUTH(90, 8),
+ SOUTH_WEST (30, 16),
+ NORTH_WEST(330, 32);
+
+ public static int offset = 0;
+ public static float delta = 5f;
+ private final int r;
+ public final int s;
+
+ Orientation(int r, int s) { this.r = r; this.s = s; }
+
+ public float r() { return offset + r; }
+
+ public boolean isInSides(int sides)
+ {
+ return ((sides & s) == s);
+ }
+
+ public Orientation left()
+ {
+ if (this == NORTH) return NORTH_WEST;
+ else return fromSide(s >> 1);
+ }
+
+ public Orientation right()
+ {
+ if (this == NORTH_WEST) return NORTH;
+ else return fromSide(s << 1);
+ }
+
+ public Orientation opposite()
+ {
+ return left().left().left();
+ }
+
+ public int allBut()
+ {
+ return ALL.s & (s ^ 0xFFFF);
+ }
+
+ public int getFrontSides()
+ {
+ return s | left().s | right().s;
+ }
+
+ public int getBackSides()
+ {
+ return opposite().getFrontSides();
+ }
+
+ public static Orientation fromSide(int s)
+ {
+ if (s == NORTH.s) return NORTH;
+ else if (s == NORTH_EAST.s) return NORTH_EAST;
+ else if (s == SOUTH_EAST.s) return SOUTH_EAST;
+ else if (s == SOUTH.s) return SOUTH;
+ else if (s == SOUTH_WEST.s) return SOUTH_WEST;
+ else if (s == NORTH_WEST.s) return NORTH_WEST;
+ else return KEEP;
+ }
+
+ public static Orientation fromRotation(float r)
+ {
+ if (r < 0) r += 360f;
+ if ((r > (NORTH.r - 5f)) && (r < (NORTH.r + 5f))) return NORTH;
+ else if ((r > (NORTH_EAST.r - delta)) && (r < (NORTH_EAST.r + delta))) return NORTH_EAST;
+ else if ((r > (SOUTH_EAST.r - delta)) && (r < (SOUTH_EAST.r + delta))) return SOUTH_EAST;
+ else if ((r > (SOUTH.r - delta)) && (r < (SOUTH.r + delta))) return SOUTH;
+ else if ((r > (SOUTH_WEST.r - delta)) && (r < (SOUTH_WEST.r + delta))) return SOUTH_WEST;
+ else if ((r > (NORTH_WEST.r - delta)) && (r < (NORTH_WEST.r + delta))) return NORTH_WEST;
+ else return KEEP;
+ }
+
+ public static Orientation fromMove(int col0, int row0, int col1, int row1)
+ {
+ int dx = col1 - col0;
+ int dy = row1 - row0;
+
+ if (dy == 0) {
+ if (dx == 0) return KEEP;
+ if (dx > 0) return NORTH;
+ return SOUTH;
+ }
+ if (dy > 0) {
+ if (dx > 0) return NORTH_WEST;
+ return SOUTH_WEST;
+ } else {
+ if (dx < 0) return SOUTH_EAST;
+ return NORTH_EAST;
+ }
+ }
+
+ public static Orientation fromAdj(Tile from, Tile to)
+ {
+ return fromAdj(from.col, from.row, to.col, to.row);
+ }
+
+ public static Orientation fromAdj(int col0, int row0, int col1, int row1)
+ {
+ Orientation o = KEEP;
+
+ if (row1 == row0) {
+ if (col1 == (col0 - 1)) {
+ o = SOUTH;
+ } else if (col1 == (col0 + 1)) {
+ o = NORTH;
+ }
+ } else if (row1 == (row0 - 1)) {
+ if (col1 == (col0 - 1)) {
+ o = SOUTH_EAST;
+ } else if (col1 == col0) {
+ o = NORTH_EAST;
+ }
+
+ } else if (row1 == (row0 + 1)) {
+ if (col1 == col0) {
+ o = SOUTH_WEST;
+ } else if (col1 == (col0 + 1)) {
+ o = NORTH_WEST;
+ }
+ }
+
+ return o;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Path.java b/core/src/ch/asynk/rustanddust/engine/Path.java
new file mode 100644
index 0000000..2ddf3e5
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Path.java
@@ -0,0 +1,62 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.ArrayList;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.utils.Disposable;
+
+public class Path implements Disposable, Pool.Poolable
+{
+ private static final Pool<Path> pathPool = new Pool<Path>() {
+ @Override
+ protected Path newObject() {
+ return new Path();
+ }
+ };
+
+ public static Path get(int size)
+ {
+ Path p = pathPool.obtain();
+ p.init(size);
+ return p;
+ }
+
+ public static void clearPool()
+ {
+ pathPool.clear();
+ }
+
+ public int cost;
+ public boolean roadMarch;
+ public ArrayList<Tile> tiles;
+
+ public Path()
+ {
+ this.cost = -1;
+ this.roadMarch = true;
+ this.tiles = null;
+ }
+
+ protected void init(int size)
+ {
+ if (tiles == null)
+ tiles = new ArrayList<Tile>(size);
+ else
+ tiles. ensureCapacity(size);
+ }
+
+ @Override
+ public void reset()
+ {
+ cost = -1;
+ roadMarch = true;
+ tiles.clear();
+ }
+
+ @Override
+ public void dispose()
+ {
+ tiles.clear();
+ pathPool.free(this);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/PathBuilder.java b/core/src/ch/asynk/rustanddust/engine/PathBuilder.java
new file mode 100644
index 0000000..3692a91
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/PathBuilder.java
@@ -0,0 +1,266 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.math.Vector3;
+
+public class PathBuilder implements Disposable
+{
+ private final Board board;
+
+ public Pawn pawn;
+ public Tile from;
+ public Tile to;
+ public int distance;
+ public Orientation orientation;
+ private List<Tile> stack;
+ private List<Tile> ctrlTiles;
+ private List<Path> paths;
+ private List<Path> filteredPaths;
+ private HashSet<Tile> tiles;
+
+ public PathBuilder(Board board, int tSize, int stSize, int ftSize, int vectSize)
+ {
+ this.board = board;
+ this.tiles = new LinkedHashSet<Tile>(tSize);
+ this.stack = new ArrayList<Tile>(stSize);
+ this.ctrlTiles = new ArrayList<Tile>(ftSize);
+ this.paths = new LinkedList<Path>();
+ this.filteredPaths = new LinkedList<Path>();
+ this.to = null;
+ this.pawn = null;
+ this.orientation = Orientation.KEEP;
+ }
+
+ public void init(Pawn pawn, Tile from)
+ {
+ this.pawn = pawn;
+ this.from = from;
+ }
+
+ public void init(Pawn pawn)
+ {
+ init(pawn, pawn.getTile());
+ }
+
+ public void initRotation(Pawn pawn, Orientation o)
+ {
+ init(pawn, pawn.getTile());
+ build(pawn.getTile());
+ orientation = o;
+ }
+
+ public boolean isSet()
+ {
+ return (to != null);
+ }
+
+ @Override
+ public void dispose()
+ {
+ clear();
+ }
+
+ public void clear()
+ {
+ this.to = null;
+ this.distance = -1;
+ this.orientation = Orientation.KEEP;
+ for (Path path : this.paths) path.dispose();
+ this.tiles.clear();
+ this.stack.clear();
+ this.ctrlTiles.clear();
+ this.paths.clear();
+ this.filteredPaths.clear();
+ }
+
+ public int size()
+ {
+ if (ctrlTiles.size() == 0)
+ return paths.size();
+ return filteredPaths.size();
+ }
+
+ public boolean contains(Tile tile)
+ {
+ return tiles.contains(tile);
+ }
+
+ public void enable(int i, boolean enable)
+ {
+ for (Tile tile : tiles)
+ board.enableOverlayOn(tile, i, enable);
+ }
+
+ public int build(Tile to)
+ {
+ clear();
+ this.to = to;
+ // from and to are not part of the path
+ this.distance = board.distance(from, to);
+ if (distance < 2) {
+ Orientation o = Orientation.fromMove(to.col, to.row, from.col, from.row);
+ Path path = Path.get(0);
+ path.roadMarch = to.road(o);
+ path.cost = to.costFrom(pawn, o);
+ paths.add(path);
+ } else {
+ findAllPaths(from, pawn.getMovementPoints(), true);
+ }
+
+ // printToErr("paths", paths);
+ stack.clear();
+ return paths.size();
+ }
+
+ private void findAllPaths(Tile from, int mvtLeft, boolean roadMarch)
+ {
+ Tile moves[] = new Tile[6];
+ board.setAdjacentTiles(from, moves);
+
+ for(int i = 0; i < 6; i++) {
+ Tile next = moves[i];
+ if ((next == null) || next.isOffMap()) continue;
+
+ Orientation o = board.getSide(i);
+ int m = (mvtLeft - next.costFrom(pawn, o));
+ boolean r = roadMarch & next.road(o);
+
+ int l = (m + (r ? pawn.getRoadMarchBonus() : 0));
+
+ if ((board.distance(next, to) <= l)) {
+ if (next == to) {
+ Path path = Path.get(stack.size() + 1);
+ for (Tile t: stack) {
+ path.tiles.add(t);
+ tiles.add(t);
+ }
+ path.roadMarch = r;
+ path.cost = (pawn.getMovementPoints() - m);
+ paths.add(path);
+ } else {
+ stack.add(next);
+ findAllPaths(next, m, r);
+ stack.remove(stack.size() - 1);
+ }
+ }
+ }
+ }
+
+ public int toggleCtrlTile(Tile tile)
+ {
+ if (ctrlTiles.contains(tile))
+ ctrlTiles.remove(tile);
+ else
+ ctrlTiles.add(tile);
+ return filterPaths();
+ }
+
+ private int filterPaths()
+ {
+ int s = ctrlTiles.size();
+
+ tiles.clear();
+ filteredPaths.clear();
+ for (Path path : paths) {
+ int ok = 0;
+ for (Tile filter : ctrlTiles) {
+ if (path.tiles.contains(filter))
+ ok += 1;
+ }
+ if (ok == s) {
+ if (path.tiles.size() == (s + 0)) { // from and to are not part of the path
+ filteredPaths.clear();
+ filteredPaths.add(path);
+ tiles.clear();
+ for (Tile tile : path.tiles) tiles.add(tile);
+ break;
+ } else {
+ filteredPaths.add(path);
+ for (Tile tile : path.tiles) tiles.add(tile);
+ }
+ }
+ }
+
+ // printToErr("filteredPaths", filteredPaths);
+ return filteredPaths.size();
+ }
+
+ public int pathCost(int i)
+ {
+ return paths.get(i).cost;
+ }
+
+ public Move getMove()
+ {
+ if (size() != 1) {
+ System.err.println("ask for only move but they are many");
+ return null;
+ }
+
+ return Move.get(pawn, from, to, orientation, getPath(0));
+ }
+
+ public Move getExitMove()
+ {
+ Move move = getMove();
+ move.type = Move.MoveType.EXIT;
+ return move;
+ }
+
+ public boolean canExit(Orientation o)
+ {
+ List<Path> ps;
+ if (ctrlTiles.size() == 0)
+ ps = paths;
+ else
+ ps = filteredPaths;
+
+ int mvt = pawn.getMovementPoints();
+ int rBonus = pawn.getRoadMarchBonus();
+ boolean road = to.road(o);
+ int cost = to.exitCost();
+
+ for (Path p : ps) {
+ int c = (p.cost + cost);
+ if ((c <= mvt) || (p.roadMarch && road && (c <= (mvt + rBonus))))
+ return true;
+ }
+ return false;
+ }
+
+ public Path getPath(int i)
+ {
+ if (ctrlTiles.size() == 0)
+ return paths.get(i);
+ return filteredPaths.get(i);
+ }
+
+ public void setExit(Orientation o)
+ {
+ orientation = o;
+ Path path = getPath(0);
+ if (from != to) {
+ path.cost += 1;
+ path.tiles.add(to);
+ }
+ to = board.getAdjTileAt(to, o);
+ }
+
+ private void printToErr(String what, List<Path> paths)
+ {
+ System.err.println(what + pawn + " ("+paths.size()+") " + from + " -> " + to);
+ for (Path path : paths) {
+ System.err.println(String.format(" - path (l:%d c:%d r:%b)", path.tiles.size(), path.cost, path.roadMarch));
+ for(Tile tile : path.tiles)
+ System.err.println(" " + tile.toString());
+ }
+ System.err.println();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/PathIterator.java b/core/src/ch/asynk/rustanddust/engine/PathIterator.java
new file mode 100644
index 0000000..53f2f82
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/PathIterator.java
@@ -0,0 +1,73 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.List;
+import java.util.Iterator;
+
+import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.math.Vector3;
+
+public class PathIterator implements Iterator<Vector3>
+{
+ private Pawn pawn;
+ private Tile to;
+ private Orientation o;
+ private Orientation orientation;
+ private Tile tile;
+ private Vector2 pos = new Vector2();
+ private Vector3 v = new Vector3();
+ private int i;
+ private List<Tile> path;
+
+ public PathIterator(Pawn pawn, Tile from, Tile to, Orientation orientation, List<Tile> path)
+ {
+ this.pawn = pawn;
+ this.to = to;
+ this.tile = from;
+ this.orientation = orientation;
+ this.path = path;
+ this.o = pawn.getOrientation();
+ this.v.set(pawn.getPosition().x, pawn.getPosition().y, o.r());
+ this.i = 0;
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ if ((tile == to) && (o == orientation))
+ return false;
+ return true;
+ }
+
+ @Override
+ public Vector3 next()
+ {
+ if (tile == to) {
+ v.z = orientation.r();
+ o = orientation;
+ return v;
+ }
+ Tile nextTile;
+ if (i < path.size())
+ nextTile = path.get(i);
+ else
+ nextTile = to;
+ Orientation nextO = Orientation.fromMove(tile.col, tile.row, nextTile.col, nextTile.row);
+ if (nextO != o) {
+ v.z = nextO.r();
+ o = nextO;
+ return v;
+ }
+ pawn.getPosAt(nextTile, pos);
+ v.x = pos.x;
+ v.y = pos.y;
+ tile = nextTile;
+ i += 1;
+ return v;
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Pawn.java b/core/src/ch/asynk/rustanddust/engine/Pawn.java
new file mode 100644
index 0000000..f95347f
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Pawn.java
@@ -0,0 +1,365 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.Iterator;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.math.Vector3;
+
+import ch.asynk.rustanddust.engine.gfx.Moveable;
+import ch.asynk.rustanddust.engine.gfx.StackedImages;
+import ch.asynk.rustanddust.engine.gfx.animations.MoveToAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.RunnableAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.AnimationSequence;
+
+public abstract class Pawn implements Moveable, Disposable
+{
+ public interface PawnType
+ {
+ }
+
+ public interface PawnId
+ {
+ }
+
+ private static final float MOVE_TIME = 0.4f;
+
+ private Vector3 position;
+ private Vector3 prevPosition;
+ private Tile tile;
+ private Tile prevTile;
+ protected Faction faction;
+ protected String descr;
+ private Sprite sprite;
+ private StackedImages overlays;
+ protected Attack attack;
+ protected Move move;
+
+ public abstract int getMovementPoints();
+ public abstract int getRoadMarchBonus();
+ public abstract int getAngleOfAttack();
+ public abstract int getFlankSides();
+ public abstract int getEngagementRangeFrom(Tile tile);
+ public abstract int getDefense(Tile tile);
+
+ public abstract boolean isUnit();
+ public abstract boolean isA(PawnId id);
+ public abstract boolean isA(PawnType type);
+ public abstract boolean isHq();
+ public abstract boolean isHqOf(Pawn other);
+ public abstract boolean isHardTarget();
+
+ public abstract boolean canMove();
+ public abstract boolean canRotate();
+ public abstract boolean canEngage();
+ public abstract boolean canEngage(Pawn other);
+ public abstract boolean canAssistEngagementWithoutLos();
+
+ public abstract void move();
+ public abstract void engage();
+
+ public abstract void revertLastMove();
+
+ protected Pawn()
+ {
+ this.tile = null;
+ this.prevTile = null;
+ this.position = new Vector3(0f, 0f, 0f);
+ this.prevPosition = new Vector3(0f, 0f, 0f);
+ this.attack = new Attack(this);
+ }
+
+ public Pawn(Faction faction, String name, TextureAtlas pawns, TextureAtlas overlays)
+ {
+ this();
+ this.faction = faction;
+ this.descr = descr;
+ this.sprite = new Sprite(pawns.findRegion(name));
+ this.overlays = new StackedImages(overlays);
+ }
+
+ @Override
+ public String toString()
+ {
+ return descr;
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ @Override
+ public Faction getFaction()
+ {
+ return faction;
+ }
+
+ public void reset()
+ {
+ move = null;
+ attack.reset();
+ }
+
+ public void move(Move move)
+ {
+ switch(move.type)
+ {
+ case REGULAR:
+ if ((this.move != null) && (!this.move.isEnter()))
+ throw new RuntimeException("try to override an existing move instance");
+ break;
+ case ENTER:
+ if (this.move != null)
+ throw new RuntimeException("try to override an existing move instance");
+ break;
+ case SET:
+ break;
+ default:
+ throw new RuntimeException("unsupported MoveType");
+ }
+
+ this.move = move;
+ move();
+ }
+
+ public void setAttack(Pawn target, int distance)
+ {
+ attack.reset();
+ attack.target = target;
+ attack.distance = distance;
+ }
+
+ public boolean justEntered()
+ {
+ return ((move != null) && move.isEnter());
+ }
+
+ public boolean is(Faction faction)
+ {
+ return (this.faction == faction);
+ }
+
+ public boolean isEnemy(Faction other)
+ {
+ return faction.isEnemy(other);
+ }
+
+ public boolean isEnemy(Pawn other)
+ {
+ return faction.isEnemy(other.faction);
+ }
+
+ public boolean isFlankAttack()
+ {
+ return (attack.isClear && attack.isFlank);
+ }
+
+ public int attackDistance()
+ {
+ return attack.distance;
+ }
+
+ public Tile getTile()
+ {
+ return tile;
+ }
+
+ public Tile getPreviousTile()
+ {
+ return prevTile;
+ }
+
+ public Vector3 getPosition()
+ {
+ return position;
+ }
+
+ public Vector3 getPreviousPosition()
+ {
+ return prevPosition;
+ }
+
+ private void revertPosition()
+ {
+ this.tile = this.prevTile;
+ this.prevTile = null;
+ position.set(prevPosition);
+ prevPosition.set(0f, 0f, 0f);
+ setPosition(position.x, position.y, position.z);
+ }
+
+ public float getCenterX()
+ {
+ return (getX() + (getWidth() / 2f));
+ }
+
+ public float getCenterY()
+ {
+ return (getY() + (getHeight() / 2f));
+ }
+
+ public Vector2 getPosAt(Tile tile, Vector2 pos)
+ {
+ float x = (tile.getX() - (getWidth() / 2f));
+ float y = (tile.getY() - (getHeight() / 2f));
+ if (pos == null)
+ return new Vector2(x, y);
+ else
+ pos.set(x, y);
+ return pos;
+ }
+
+ public void setOnTile(Tile tile, float z)
+ {
+ this.prevTile = this.tile;
+ this.tile = tile;
+ float x = (tile.getX() - (getWidth() / 2f));
+ float y = (tile.getY() - (getHeight() / 2f));
+ setPosition(x, y, z);
+ }
+
+ @Override
+ public void setAlpha(float alpha)
+ {
+ sprite.setAlpha(alpha);
+ overlays.setAlpha(alpha);
+ }
+
+ @Override
+ public float getX()
+ {
+ return sprite.getX();
+ }
+
+ @Override
+ public float getY()
+ {
+ return sprite.getY();
+ }
+
+ @Override
+ public float getWidth()
+ {
+ return sprite.getWidth();
+ }
+
+ @Override
+ public float getHeight()
+ {
+ return sprite.getHeight();
+ }
+
+ @Override
+ public float getRotation()
+ {
+ return sprite.getRotation();
+ }
+
+ public Orientation getOrientation()
+ {
+ return Orientation.fromRotation(getRotation());
+ }
+
+ public void translate(float dx, float dy)
+ {
+ setPosition((getX() + dx), (getY() + dy));
+ }
+
+ public void centerOn(float x, float y)
+ {
+ setPosition((x - (getWidth() / 2f)), (y - (getHeight() / 2f)));
+ }
+
+ @Override
+ public void setPosition(float x, float y)
+ {
+ position.set(x, y, 0f);
+ sprite.setPosition(x, y);
+ float cx = x + (getWidth() / 2f);
+ float cy = y + (getHeight() / 2f);
+ overlays.centerOn(cx, cy);
+ }
+
+ public void setRotation(float z)
+ {
+ position.z = z;
+ sprite.setRotation(z);
+ overlays.setRotation(z);
+ }
+
+ @Override
+ public void setPosition(float x, float y, float z)
+ {
+ setPosition(x, y);
+ setRotation(z);
+ }
+
+ public boolean hasOverlayEnabled()
+ {
+ return overlays.isEnabled();
+ }
+
+ public boolean enableOverlay(int i, boolean enable)
+ {
+ overlays.enable(i, enable);
+ if (enable) return true;
+ return hasOverlayEnabled();
+ }
+
+ public AnimationSequence getRotateAnimation(float z, int size)
+ {
+ prevPosition.set(position);
+ AnimationSequence seq = AnimationSequence.get(1 + size);
+ seq.addAnimation(MoveToAnimation.get(this, position.x, position.y, z, MOVE_TIME));
+
+ return seq;
+ }
+
+ public AnimationSequence getMoveAnimation(Iterator<Vector3> vectors, int size, MoveToAnimation.MoveToAnimationCb cb)
+ {
+ prevPosition.set(position);
+ AnimationSequence seq = AnimationSequence.get(size);
+ while (vectors.hasNext())
+ seq.addAnimation(MoveToAnimation.get(this, vectors.next(), MOVE_TIME, cb));
+
+ return seq;
+ }
+
+ public AnimationSequence getRevertLastMoveAnimation(int size)
+ {
+ AnimationSequence seq = AnimationSequence.get(2 + size);
+ seq.addAnimation(MoveToAnimation.get(this, prevPosition, MOVE_TIME));
+ seq.addAnimation(RunnableAnimation.get(this, new Runnable() {
+ @Override
+ public void run() {
+ revertPosition();
+ }
+ }));
+
+ return seq;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ sprite.draw(batch);
+ overlays.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ float w = sprite.getWidth();
+ float h = sprite.getHeight();
+ debugShapes.rect(sprite.getX(), sprite.getY(), (w / 2f), (h / 2f), w, h, sprite.getScaleX(), sprite.getScaleY(), sprite.getRotation());
+ overlays.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/SearchBoard.java b/core/src/ch/asynk/rustanddust/engine/SearchBoard.java
new file mode 100644
index 0000000..1d8ed88
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/SearchBoard.java
@@ -0,0 +1,545 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.Collection;
+
+public class SearchBoard
+{
+ public class Node
+ {
+ public int col;
+ public int row;
+ public int search;
+ public int remaining;
+ public Node parent;
+ public boolean roadMarch;
+
+ public Node(int col, int row)
+ {
+ this.col = col;
+ this.row = row;
+ }
+
+ @Override
+ public String toString()
+ {
+ return col + ";" + row;
+ }
+ }
+
+ private int cols;
+ private int rows;
+ private Board board;
+ private int searchCount;
+ private Node nodes[];
+
+ private ArrayDeque<Node> stack;
+ private LinkedList<Node> queue;
+ private ArrayDeque<Node> roadMarch;
+ private List<Node> los;
+
+ public SearchBoard(Board board, int cols, int rows)
+ {
+ this.cols = cols;
+ this.rows = rows;
+ this.board = board;
+ this.searchCount = 0;
+
+ this.nodes = new Node[cols * rows];
+ for (int j = 0; j < rows; j++) {
+ int dx = ((j + 1) / 2);
+ for (int i = 0; i < cols; i++)
+ nodes[i + (j * cols)] = new Node((i + dx), j);
+ }
+
+ this.queue = new LinkedList<Node>();
+ this.stack = new ArrayDeque<Node>(20);
+ this.roadMarch = new ArrayDeque<Node>(5);
+ this.los = new ArrayList<Node>(10);
+ }
+
+ private boolean inMap(int col, int row)
+ {
+ if ((row < 0) || (row >= rows))
+ return false;
+
+ int colOffset = ((row + 1) / 2);
+ if ((col < colOffset) || ((col - colOffset) >= cols))
+ return false;
+
+ return true;
+ }
+
+ private Tile getTile(Node node)
+ {
+ return board.getTile(node.col, node.row);
+ }
+
+ private Node getNode(Tile tile)
+ {
+ return getNode(tile.col, tile.row);
+ }
+
+ protected Node getNode(int col, int row)
+ {
+ int colOffset = ((row + 1) / 2);
+ if ((col < colOffset) || (row < 0) || (row >= rows) || ((col - colOffset) >= cols))
+ return null;
+
+ return nodes[((col - colOffset)) + (row * cols)];
+ }
+
+ public int distance(Node from, Node to)
+ {
+ return board.distance(from.col, from.row, to.col, to.row);
+ }
+
+ public void adjacentMoves(Node src, Node a[])
+ {
+ // move to enter dst by sides[i]
+ a[0] = getNode((src.col - 1), src.row);
+ a[1] = getNode(src.col, (src.row + 1));
+ a[2] = getNode((src.col + 1), (src.row + 1));
+ a[3] = getNode((src.col + 1), src.row);
+ a[4] = getNode(src.col, (src.row - 1));
+ a[5] = getNode((src.col - 1), (src.row - 1));
+ }
+
+ public int possibleMovesFrom(Pawn pawn, Collection<Tile> moves)
+ {
+ moves.clear();
+ searchCount += 1;
+
+ Node adjacents[] = new Node[6];
+
+ Node from = getNode(pawn.getTile());
+ from.parent = null;
+ from.search = searchCount;
+ from.remaining = pawn.getMovementPoints();
+ from.roadMarch = true;
+
+ if (from.remaining <= 0)
+ return moves.size();
+
+ int roadMarchBonus = pawn.getRoadMarchBonus();
+ boolean first = true;
+
+ stack.push(from);
+
+ while (stack.size() != 0) {
+ Node src = stack.pop();
+
+ if (src.remaining < 0)
+ continue;
+ if (src.remaining == 0) {
+ if (src.roadMarch)
+ roadMarch.push(src);
+ continue;
+ }
+
+ adjacentMoves(src, adjacents);
+
+ for(int i = 0; i < 6; i++) {
+ Node dst = adjacents[i];
+ if (dst != null) {
+
+ Tile t = getTile(dst);
+ int cost = t.costFrom(pawn, board.getSide(i));
+ boolean mayMoveOne = first && t.atLeastOneMove(pawn);
+ int r = src.remaining - cost;
+ boolean roadMarch = (src.roadMarch && t.road(board.getSide(i)));
+
+ if (dst.search == searchCount) {
+ if ((r >= 0) && ((r > dst.remaining) || (roadMarch && ((r + roadMarchBonus) >= dst.remaining)))) {
+ dst.remaining = r;
+ dst.parent = src;
+ dst.roadMarch = roadMarch;
+ stack.push(dst);
+ moves.add(getTile(dst));
+ }
+ } else {
+ dst.search = searchCount;
+ if ((r >= 0) || mayMoveOne) {
+ dst.parent = src;
+ dst.remaining = r;
+ dst.roadMarch = roadMarch;
+ stack.push(dst);
+ moves.add(getTile(dst));
+ } else {
+ dst.parent = null;
+ dst.remaining = -1;
+ }
+ }
+ }
+ }
+ first = false;
+ }
+
+ for (Node n : roadMarch) n.remaining = roadMarchBonus;
+ while(roadMarch.size() != 0) {
+ Node src = roadMarch.pop();
+
+ adjacentMoves(src, adjacents);
+
+ for(int i = 0; i < 6; i++) {
+ Node dst = adjacents[i];
+ if (dst != null) {
+
+ Tile t = getTile(dst);
+ if (!t.road(board.getSide(i)))
+ continue;
+ int cost = t.costFrom(pawn, board.getSide(i));
+ int r = src.remaining - cost;
+
+ if (dst.search == searchCount) {
+ if ((r >= 0) && (r > dst.remaining)) {
+ dst.remaining = r;
+ dst.parent = src;
+ dst.roadMarch = true;
+ roadMarch.push(dst);
+ moves.add(getTile(dst));
+ }
+ } else {
+ dst.search = searchCount;
+ if (r >= 0) {
+ dst.parent = src;
+ dst.remaining = r;
+ dst.roadMarch = true;
+ roadMarch.push(dst);
+ moves.add(getTile(dst));
+ } else {
+ dst.parent = null;
+ dst.remaining = -1;
+ }
+ }
+ }
+ }
+ }
+
+ return moves.size();
+ }
+
+ private void adjacentTargets(Node src, int angle, Node a[])
+ {
+ // move in allowed directions
+ if (Orientation.NORTH.isInSides(angle))
+ a[0] = getNode((src.col + 1), src.row);
+ else
+ a[0] = null;
+
+ if (Orientation.NORTH_EAST.isInSides(angle))
+ a[1] = getNode(src.col, (src.row - 1));
+ else
+ a[1] = null;
+
+ if (Orientation.SOUTH_EAST.isInSides(angle))
+ a[2] = getNode((src.col - 1), (src.row - 1));
+ else
+ a[2] = null;
+
+ if (Orientation.SOUTH.isInSides(angle))
+ a[3] = getNode((src.col - 1), src.row);
+ else
+ a[3] = null;
+
+ if (Orientation.SOUTH_WEST.isInSides(angle))
+ a[4] = getNode(src.col, (src.row + 1));
+ else
+ a[4] = null;
+
+ if (Orientation.NORTH_WEST.isInSides(angle))
+ a[5] = getNode((src.col + 1), (src.row + 1));
+ else
+ a[5] = null;
+ }
+
+ public int possibleTargetsFrom(Pawn pawn, Collection<Pawn> targets)
+ {
+ targets.clear();
+ searchCount += 1;
+
+ Node adjacents[] = new Node[6];
+
+ int range = pawn.getEngagementRangeFrom(pawn.getTile());
+ int angle = pawn.getAngleOfAttack();
+ int extendedAngle = pawn.getOrientation().opposite().allBut();
+
+ Node from = getNode(pawn.getTile());
+ from.search = searchCount;
+ from.remaining = range;
+
+ if (range <= 0)
+ return targets.size();
+
+ queue.add(from);
+
+ boolean first = true;
+ while (queue.size() != 0) {
+ Node src = queue.remove();
+
+ if (src.remaining <= 0)
+ continue;
+
+ if (!first && (((range - src.remaining) % 2) == 0))
+ adjacentTargets(src, extendedAngle, adjacents);
+ else
+ adjacentTargets(src, angle, adjacents);
+
+ first = false;
+ int rangeLeft = src.remaining - 1;
+
+ for(int i = 0; i < 6; i++) {
+ Node dst = adjacents[i];
+ if (dst != null) {
+ if (dst.search == searchCount) {
+ if ((rangeLeft > dst.remaining))
+ dst.remaining = rangeLeft;
+ } else {
+ dst.search = searchCount;
+ dst.remaining = rangeLeft;
+ queue.add(dst);
+ Tile t = getTile(dst);
+ if (hasClearLineOfSight(from, dst, angle)) {
+ Iterator<Pawn> it = t.iterator();
+ while (it.hasNext()) {
+ Pawn target = it.next();
+ if (pawn.canEngage(target))
+ targets.add(target);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return targets.size();
+ }
+
+ public boolean canAttack(Pawn pawn, Pawn target, boolean clearVisibility)
+ {
+ Node from = getNode(pawn.getTile());
+ Node to = getNode(target.getTile());
+
+ pawn.setAttack(target, distance(from, to));
+
+ if (pawn.attack.distance > pawn.getEngagementRangeFrom(pawn.getTile()))
+ return false;
+
+ List<Node> los = lineOfSight(from.col, from.row, to.col, to.row, clearVisibility);
+ Node last = los.get(los.size() -1);
+ if (last != to)
+ return false;
+
+ if (!validatePathAngle(pawn.getAngleOfAttack(), los))
+ return false;
+
+ pawn.attack.isClear = isClearAttack(getTile(from), los);
+ pawn.attack.isFlank = isFlankAttack(target.getFlankSides(), los);
+
+ return true;
+ }
+
+ private boolean hasClearLineOfSight(Node from, Node to, int angleOfAttack)
+ {
+ List<Node> los = lineOfSight(from.col, from.row, to.col, to.row, true);
+ Node last = los.get(los.size() -1);
+ if ((last.col != to.col) || (last.row != to.row))
+ return false;
+ return validatePathAngle(angleOfAttack, los);
+ }
+
+ private boolean isFlankAttack(int angle, List<Node> los)
+ {
+ Node from = los.get(los.size() - 2);
+ Node to = los.get(los.size() - 1);
+ Orientation o = Orientation.fromMove(to.col, to.row, from.col, from.row);
+ return o.isInSides(angle);
+ }
+
+ private boolean isClearAttack(Tile from, List<Node> los)
+ {
+ int n = los.size() - 1;
+ for (int i = 1; i < n; i++) {
+ if (getTile(los.get(i)).blockLineOfSightFrom(from))
+ return false;
+ }
+ return true;
+ }
+
+ private boolean validatePathAngle(int angle, List<Node> los)
+ {
+ int forth = 0;
+ Node prev = null;
+ for (Node next : los) {
+ if (prev != null) {
+ Orientation o = Orientation.fromMove(prev.col, prev.row, next.col, next.row);
+ if (!o.isInSides(angle)) {
+ forth -= 1;
+ if (forth < 0)
+ return false;
+ }
+ forth += 1;
+ }
+ prev = next;
+ }
+
+ return true;
+ }
+
+ public List<Node> lineOfSight(int x0, int y0, int x1, int y1, boolean clearVisibility)
+ {
+ los.clear();
+ Tile from = board.getTile(x0, y0);
+
+ // orthogonal axis
+ int ox0 = x0 - ((y0 +1) / 2);
+ int ox1 = x1 - ((y1 +1) / 2);
+
+ int dy = y1 - y0;
+ int dx = ox1 - ox0;
+
+ int xs = 1;
+ int ys = 1;
+ if (dx < 0) xs = -1;
+ if (dy < 0) ys = -1;
+ boolean sig = !(((dx < 0) && (dy >= 0)) || ((dx >= 0) && (dy < 0)));
+
+ dy = Math.abs(dy);
+ dx = Math.abs(2 * dx);
+ if ((dy % 2) == 1) {
+ if ((y0 % 2) == 0) dx += xs;
+ else {
+ dx -= xs;
+ Math.abs(dx);
+ }
+ }
+
+ if (dx == 0)
+ return verticalLineOfSight(x0, y0, x1, y1, clearVisibility);
+ if (dx == (3 * dy))
+ return diagonalLineOfSight(x0, y0, x1, y1, clearVisibility);
+
+ int dx3 = 3 * dx;
+ int dy3 = 3 * dy;
+
+ int x = x0;
+ int y = y0;
+ int e = -2 * dx;
+
+ boolean flat = (dx > (3 * dy));
+ boolean diag = (dx == (3 * dy));
+
+ los.add(getNode(x, y));
+ while((x != x1) || (y != y1)) {
+ if (e > 0) {
+ e -= (dy3 + dx3);
+ y += ys;
+ if (!sig)
+ x -= xs;
+ } else {
+ e += dy3;
+ if ((e > -dx) || (!flat && (e == -dx))) {
+ e -= dx3;
+ y += ys;
+ if (sig)
+ x += xs;
+ } else if ((e < -dx3) || (diag && (e == -dx3))) {
+ e += dx3;
+ y -= ys;
+ if (!sig)
+ x += xs;
+ } else {
+ e += dy3;
+ x += xs;
+ }
+ }
+ los.add(getNode(x, y));
+ if(clearVisibility && board.getTile(x, y).blockLineOfSightFrom(from)) return los;
+ }
+
+ return los;
+ }
+
+ private List<Node> verticalLineOfSight(int x0, int y0, int x1, int y1, boolean clearVisibility)
+ {
+ Tile from = board.getTile(x0, y0);
+
+ int d = ( (y1 > y0) ? 1 : -1);
+ int x = x0;
+ int y = y0;
+
+ Tile t = null;
+ los.add(getNode(x, y));
+ while((x != x1) || (y != y1)) {
+ boolean ok = false;
+
+ y += d;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ if (!clearVisibility || !t.blockLineOfSightFrom(from))
+ ok = true;
+
+ x += d;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ if (!clearVisibility || !t.blockLineOfSightFrom(from))
+ ok = true;
+
+ if (!ok)
+ return los;
+
+ y += d;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ }
+
+ return los;
+ }
+
+ private List<Node> diagonalLineOfSight(int x0, int y0, int x1, int y1, boolean clearVisibility)
+ {
+ Tile from = board.getTile(x0, y0);
+
+ int dy = ( (y1 > y0) ? 1 : -1);
+ int dx = ( (x1 > x0) ? 1 : -1);
+ boolean sig = !(((dx < 0) && (dy >= 0)) || ((dx >= 0) && (dy < 0)));
+
+ int x = x0;
+ int y = y0;
+
+ Tile t = null;
+ los.add(getNode(x, y));
+ while((x != x1) || (y != y1)) {
+ boolean ok = false;
+
+ x += dx;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ if (!clearVisibility || !t.blockLineOfSightFrom(from))
+ ok = true;
+
+ y += dy;
+ if (!sig)
+ x -= dx;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ if (!clearVisibility || !t.blockLineOfSightFrom(from))
+ ok = true;
+
+ if (!ok)
+ return los;
+
+ x += dx;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ }
+
+ return los;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/SelectedTile.java b/core/src/ch/asynk/rustanddust/engine/SelectedTile.java
new file mode 100644
index 0000000..0e1d8ac
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/SelectedTile.java
@@ -0,0 +1,80 @@
+package ch.asynk.rustanddust.engine;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+import ch.asynk.rustanddust.engine.gfx.animations.Sprites;
+
+public class SelectedTile implements Disposable, Drawable, Animation
+{
+ private Sprites sprites;
+ public Tile tile;
+ public boolean visible;
+ public float x;
+ public float y;
+ private float elapsed;
+ private int frame;
+ private float[] seq;
+
+ public SelectedTile(Texture texture, float[] seq)
+ {
+ this.sprites = new Sprites(texture, seq.length, 1);
+ this.visible = false;
+ this.tile = null;
+ this.elapsed = 0f;
+ this.seq = seq;
+ }
+
+ public void hide()
+ {
+ tile = null;
+ visible = false;
+ }
+
+ public void set(Tile tile)
+ {
+ this.visible = true;
+ this.tile = tile;
+ this.frame = 0;
+ this.elapsed = 0f;
+ this.x = (tile.getX() - (sprites.width / 2f));
+ this.y = (tile.getY() - (sprites.height / 2f));
+ }
+
+ public void dispose()
+ {
+ sprites.dispose();
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if (visible) {
+ elapsed += delta;
+ if (elapsed > seq[frame]) {
+ frame = ((frame + 1) % sprites.frames.length);
+ elapsed = 0f;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (visible)
+ batch.draw(sprites.frames[frame], x, y);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ if (visible)
+ debugShapes.rect(x, y, sprites.width, sprites.height);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Tile.java b/core/src/ch/asynk/rustanddust/engine/Tile.java
new file mode 100644
index 0000000..f44e763
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Tile.java
@@ -0,0 +1,175 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayDeque;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.math.Vector2;
+
+import ch.asynk.rustanddust.engine.Board;
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.StackedImages;
+
+public abstract class Tile implements Drawable, Disposable, Iterable<Pawn>
+{
+ public interface TileTerrain
+ {
+ }
+
+ protected int col;
+ protected int row;
+ protected float x;
+ protected float y;
+ private StackedImages overlays;
+ protected ArrayDeque<Pawn> stack;
+
+ public abstract int defense();
+ public abstract int exitCost();
+ public abstract int costFrom(Pawn pawn, Orientation side);
+
+ public abstract boolean isOffMap();
+ public abstract boolean isA(TileTerrain terrain);
+ public abstract boolean road(Orientation side);
+ public abstract boolean atLeastOneMove(Pawn pawn);
+ public abstract boolean blockLineOfSightFrom(Tile tile);
+
+ protected Tile(int col, int row)
+ {
+ this.col = col;
+ this.row = row;
+ }
+
+ public Tile(float x, float y, int col, int row, TextureAtlas atlas)
+ {
+ this.stack = new ArrayDeque<Pawn>();
+ this.x = x;
+ this.y = y;
+ this.col = col;
+ this.row = row;
+ this.overlays = new StackedImages(atlas);
+ this.overlays.centerOn(x, y);
+ }
+
+ public float getX() { return x; }
+ public float getY() { return y; }
+ public int getCol() { return col; }
+ public int getRow() { return row; }
+
+ @Override
+ public String toString()
+ {
+ return String.format("(%d;%d) %s", col, row, (isOffMap() ? "x" : ""));
+ }
+
+ public String toShort()
+ {
+ return String.format("(%d;%d)", col, row);
+ }
+
+ @Override
+ public void dispose()
+ {
+ stack.clear();
+ overlays.dispose();
+ }
+
+ public boolean isEmpty()
+ {
+ return stack.isEmpty();
+ }
+
+ public Iterator<Pawn> iterator()
+ {
+ return stack.iterator();
+ }
+
+ public int push(Pawn pawn)
+ {
+ if (!stack.contains(pawn))
+ stack.push(pawn);
+ return stack.size();
+ }
+
+ public int remove(Pawn pawn)
+ {
+ stack.remove(pawn);
+ return stack.size();
+ }
+
+ private Pawn getTopPawn()
+ {
+ if (isEmpty()) return null;
+ return stack.getFirst();
+ }
+
+ public boolean hasUnits()
+ {
+ if (isEmpty()) return false;
+ Iterator<Pawn> itr = iterator();
+ while(itr.hasNext()) {
+ if (itr.next().isUnit())
+ return true;
+ }
+ return false;
+ }
+
+ public boolean mustBeDrawn()
+ {
+ if (!isEmpty()) return true;
+ return hasOverlayEnabled();
+ }
+
+ public boolean disableOverlays()
+ {
+ overlays.disableAll();
+ return !isEmpty();
+ }
+
+ public boolean hasOverlayEnabled()
+ {
+ return overlays.isEnabled();
+ }
+
+ public boolean isOverlayEnabled(int i)
+ {
+ return overlays.isEnabled(i);
+ }
+
+ public boolean enableOverlay(int i, boolean enable)
+ {
+ overlays.enable(i, enable);
+ if (enable) return true;
+ return mustBeDrawn();
+ }
+
+ public boolean enableOverlay(int i, boolean enable, float r)
+ {
+ overlays.enable(i, enable);
+ overlays.rotate(i, r);
+ if (enable) return true;
+ return mustBeDrawn();
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ overlays.draw(batch);
+ Pawn pawn = getTopPawn();
+ if (pawn != null)
+ pawn.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ overlays.drawDebug(debugShapes);
+ Pawn pawn = getTopPawn();
+ if (pawn != null)
+ pawn.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/Animation.java b/core/src/ch/asynk/rustanddust/engine/gfx/Animation.java
new file mode 100644
index 0000000..eb973de
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/Animation.java
@@ -0,0 +1,8 @@
+package ch.asynk.rustanddust.engine.gfx;
+
+import com.badlogic.gdx.utils.Disposable;
+
+public interface Animation extends Disposable, Drawable
+{
+ public boolean animate(float delta);
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java b/core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java
new file mode 100644
index 0000000..d405faa
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java
@@ -0,0 +1,10 @@
+package ch.asynk.rustanddust.engine.gfx;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public interface Drawable
+{
+ public void draw(Batch batch);
+ public void drawDebug(ShapeRenderer debugShapes);
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java b/core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java
new file mode 100644
index 0000000..e8790ab
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java
@@ -0,0 +1,16 @@
+package ch.asynk.rustanddust.engine.gfx;
+
+import ch.asynk.rustanddust.engine.Faction;
+
+public interface Moveable extends Drawable
+{
+ public void setAlpha(float alpha);
+ public float getX();
+ public float getY();
+ public float getWidth();
+ public float getHeight();
+ public float getRotation();
+ public void setPosition(float x, float y);
+ public void setPosition(float x, float y, float r);
+ public Faction getFaction();
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java b/core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java
new file mode 100644
index 0000000..6d4fd1f
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java
@@ -0,0 +1,99 @@
+package ch.asynk.rustanddust.engine.gfx;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.utils.Array;
+
+public class StackedImages implements Drawable, Disposable
+{
+ private boolean enabled[];
+ private Array<Sprite> sprites;
+
+ public StackedImages(TextureAtlas atlas)
+ {
+ this.sprites = atlas.createSprites();
+ this.enabled = new boolean[sprites.size];
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ public void disableAll()
+ {
+ for (int i = 0; i < sprites.size; i++)
+ enabled[i] = false;
+ }
+
+ public void enable(int i, boolean enable)
+ {
+ enabled[i] = enable;
+ }
+
+ public boolean isEnabled(int i)
+ {
+ return enabled[i];
+ }
+
+ public boolean isEnabled()
+ {
+ for (int i = 0; i < sprites.size; i++)
+ if (enabled[i]) return true;
+ return false;
+ }
+
+ public void setAlpha(float alpha)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++)
+ sprites.get(i).setAlpha(alpha);
+ }
+
+ public void rotate(int i, float r)
+ {
+ sprites.get(i).setRotation(r);
+ }
+
+ public void setRotation(float r)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++)
+ sprites.get(i).setRotation(r);
+ }
+
+ public void translate(float dx, float dy)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++)
+ sprites.get(i).translate(dx, dy);
+ }
+
+ public void centerOn(float cx, float cy)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++) {
+ float x = (cx - (sprites.get(i).getWidth() / 2f));
+ float y = (cy - (sprites.get(i).getHeight() / 2f));
+ sprites.get(i).setPosition(x, y);
+ }
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++) {
+ if (enabled[i])
+ sprites.get(i).draw(batch);
+ }
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ Sprite sprite = sprites.get(0);
+ float w = sprite.getWidth();
+ float h = sprite.getHeight();
+ shapes.rect(sprite.getX(), sprite.getY(), (w / 2f), (h / 2f), w, h, sprite.getScaleX(), sprite.getScaleY(), sprite.getRotation());
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java
new file mode 100644
index 0000000..fdd1e80
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java
@@ -0,0 +1,76 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.ArrayList;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class AnimationSequence implements Animation, Pool.Poolable
+{
+ private ArrayList<Animation> animations;
+
+ private static final Pool<AnimationSequence> animationSequencePool = new Pool<AnimationSequence>() {
+ @Override
+ protected AnimationSequence newObject() {
+ return new AnimationSequence();
+ }
+ };
+
+ public static AnimationSequence get(int capacity)
+ {
+ AnimationSequence seq = animationSequencePool.obtain();
+ if (seq.animations == null)
+ seq.animations = new ArrayList<Animation>(capacity);
+ else
+ seq.animations.ensureCapacity(capacity);
+
+ return seq;
+ }
+
+ @Override
+ public void reset()
+ {
+ for (int i = 0, n = animations.size(); i < n; i++)
+ animations.get(i).dispose();
+ animations.clear();
+ }
+
+ @Override
+ public void dispose()
+ {
+ animationSequencePool.free(this);
+ }
+
+ public void addAnimation(Animation animation)
+ {
+ animations.add(animation);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if(animations.isEmpty()) return true;
+
+ Animation animation = animations.get(0);
+ if (animation.animate(delta)) {
+ animations.remove(0);
+ }
+
+ return (animations.isEmpty());
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ animations.get(0).draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ animations.get(0).drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java
new file mode 100644
index 0000000..d1fc1bb
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java
@@ -0,0 +1,62 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Moveable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class DestroyAnimation implements Disposable, Animation
+{
+ private static final float DELAY = 1.5f;
+ private static final float DURATION = 1.5f;
+
+ private Moveable moveable;
+ private float x;
+ private float y;
+ private int alphaP;
+ private float elapsed;
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ public void set(float duration, Moveable moveable)
+ {
+ this.moveable = moveable;
+ this.alphaP = 0;
+ this.elapsed = 0f;
+ this.x = (moveable.getX() + (moveable.getWidth() / 2f));
+ this.y = (moveable.getY() + (moveable.getHeight() / 2f));
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+ if (elapsed < DELAY)
+ return false;
+
+ int a = (int) (((elapsed - DELAY) / DURATION) * 10);
+ if (a != alphaP) {
+ alphaP = a;
+ moveable.setAlpha(1f - (alphaP / 10f));
+ }
+
+ return (elapsed >= (DELAY + DURATION));
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ moveable.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ moveable.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java
new file mode 100644
index 0000000..1a0a3bb
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java
@@ -0,0 +1,141 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class DiceAnimation implements Animation, Drawable
+{
+ private static final float DURATION = 0.7f;
+ private static final float DURATION_SCATTERING = 0.5f;
+ private static final int DICE_DIMENSION = 24;
+
+ private static Random random = new Random();
+ private static Sprites dice;
+ private static Sound sound;
+ private static double sndId;
+ private static float volume;
+ private static int[][] rolls = new int[][]{
+ { 25, 40, 55, 70, 85, 100, 115, 99, 83, 67, 51, 36, 37, 52, 67, 66, 65, 64 },
+ { 58, 74, 59, 60, 45, 62, 78, 94, 109, 108, 123, 106, 89, 71, 70, 69, 68 },
+ { 106, 121, 120, 103, 86, 70, 54, 37, 20, 19, 18, 34, 50, 51, 52, 69, 86, 103, 119, 128 },
+ { 95, 79, 93, 92, 91, 90, 104, 103, 102, 85, 84, 67, 66, 65, 49, 32, 16, 0 },
+ { 22, 39, 56, 73, 90, 107, 124, 128, 113, 98, 83, 68, 53, 38, 23, 0, 25, 42, 59, 76 },
+ { 79, 78, 61, 76, 91, 106, 121, 120, 119, 102, 101, 84, 68, 52, 37, 38, 39, 40, 41, 58, 75, 74, 73, 72 },
+ };
+
+ private float x;
+ private float y;
+ private int frame;
+ private int[] roll;
+ private float elapsed;
+ private float duration;
+ // public boolean stop;
+
+ public static void init(Texture texture, int cols, int rows, Sound s)
+ {
+ dice = new Sprites(texture, cols, rows);
+ sound = s;
+ sndId = -1;
+ }
+
+ public static void initSound(float v)
+ {
+ sndId = -1;
+ volume = v;
+ }
+
+ public static void free()
+ {
+ sound.dispose();
+ dice.dispose();
+ }
+
+ public void translate(float dx, float dy)
+ {
+ x += dx;
+ y += dy;
+ }
+
+ public float getX()
+ {
+ return x;
+ }
+
+ public float getY()
+ {
+ return y;
+ }
+
+ public int getWidth()
+ {
+ return DICE_DIMENSION;
+ }
+
+ public int getHeight()
+ {
+ return DICE_DIMENSION;
+ }
+
+ public void setPosition(float x, float y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+
+ public void set(int result)
+ {
+ this.frame = 0;
+ this.elapsed = 0f;
+ this.roll = rolls[result - 1];
+ this.duration = DURATION + (DURATION_SCATTERING * random.nextFloat());
+ // this.stop = false;
+ }
+
+ public boolean isDone()
+ {
+ return (elapsed >= duration);
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ // if (stop)
+ // return true;
+ elapsed += delta;
+ if (elapsed < duration) {
+ int idx = (int) (roll.length * elapsed / duration);
+ if (idx >= roll.length)
+ idx = (roll.length -1);
+ frame = roll[idx];
+ }
+ if (sndId == -1)
+ sndId = sound.play(volume);
+
+ return false;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ batch.draw(dice.frames[frame], x, y, DICE_DIMENSION, DICE_DIMENSION);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ debugShapes.rect(x, y, dice.frames[frame].getRegionWidth(), dice.frames[frame].getRegionHeight());
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java
new file mode 100644
index 0000000..5835525
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java
@@ -0,0 +1,87 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.Texture;
+
+public class FireAnimation
+{
+ public static Random random = new Random();
+
+ public static Sprites infantryFire;
+ public static Sprites tankFire;
+ public static Sprites explosion;
+
+ public static Sound infantryFireSnd;
+ public static Sound tankFireSnd;
+ public static Sound tankFireSndLong;
+ public static Sound explosionSnd;
+ public static Sound explosionSndLong;
+
+ public static double infantryFireSndLongId;
+ public static double tankFireSndLongId;
+ public static double explosionSndLongId;
+
+ public static void init(
+ Texture infantryFireT, int iCols, int iRows,
+ Texture tankFireT, int sCols, int sRows,
+ Texture explosionT, int eCols, int eRows,
+ Sound infantryFireS,
+ Sound tankFireS,
+ Sound tankFireLongS,
+ Sound explosionS,
+ Sound explosionLongS)
+ {
+ infantryFire = new Sprites(infantryFireT, iCols, iRows);
+ tankFire = new Sprites(tankFireT, sCols, sRows);
+ explosion = new Sprites(explosionT, eCols, eRows);
+ infantryFireSnd = infantryFireS;
+ tankFireSnd = tankFireS;
+ tankFireSndLong = tankFireLongS;
+ explosionSnd = explosionS;
+ explosionSndLong = explosionLongS;
+
+ reset();
+ }
+
+ public static void reset()
+ {
+ infantryFireSndLongId = -1;
+ tankFireSndLongId = -1;
+ explosionSndLongId = -1;
+ }
+
+ public static void free()
+ {
+ tankFire.dispose();
+ explosion.dispose();
+
+ tankFireSnd.dispose();
+ tankFireSndLong.dispose();
+ explosionSnd.dispose();
+ explosionSndLong.dispose();
+ }
+
+ public static void infantryFireSndPlay(float volume)
+ {
+ if (infantryFireSndLongId == -1)
+ infantryFireSndLongId = infantryFireSnd.play(volume);
+ }
+
+ public static void tankFireSndPlay(float volume)
+ {
+ if (tankFireSndLongId == -1)
+ tankFireSndLongId = tankFireSndLong.play(volume);
+ else
+ tankFireSnd.play(volume);
+ }
+
+ public static void explosionSndPlay(float volume)
+ {
+ if (explosionSndLongId == -1)
+ explosionSndLongId = explosionSndLong.play(volume);
+ else
+ explosionSnd.play(volume);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java
new file mode 100644
index 0000000..233305a
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java
@@ -0,0 +1,222 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.utils.Pool;
+
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class InfantryFireAnimation implements Disposable, Animation, Pool.Poolable
+{
+ class Shot
+ {
+ public TextureRegion fireRegion;
+ public float fire_a;
+ public float fire_x;
+ public float fire_y;
+ public float fire_w;
+ public float fire_dx;
+ public float fire_dy;
+ public float fire_dw;
+
+ public boolean fired;
+ public boolean hit;
+ public boolean completed;
+
+ public float fire_time;
+ public float hit_time;
+ public float end_time;
+
+ public int hit_frame;
+
+ public Shot(TextureRegion region)
+ {
+ this.fireRegion = region;
+ }
+
+ public void set(float delay, float x0, float y0, float x1, float y1, float w, float a)
+ {
+ float dx = (x1 - x0);
+ float dy = (y1 - y0);
+
+ // timing
+ float fire_duration = ((FireAnimation.random.nextFloat() * TIME_SCATTERING) + (w / SHOT_SPEED));
+ float hit_duration = (FireAnimation.infantryFire.rows * HIT_FRAME_DURATION);
+
+ this.fired = false;
+ this.fire_time = delay;
+ this.hit_time = (this.fire_time + fire_duration);
+ this.end_time = (this.hit_time + hit_duration);
+
+ // fire vars
+ this.fire_a = a;
+ this.fire_x = x0;
+ this.fire_y = y0;
+ this.fire_w = 0;
+ this.fire_dx = (dx / fire_duration);
+ this.fire_dy = (dy / fire_duration);
+ this.fire_dw = (w / fire_duration);
+ this.hit_frame = 0;
+ }
+
+ public boolean animate(float delta)
+ {
+ if (!fired && (elapsed < fire_time))
+ return false;
+
+ if (!fired) {
+ fired = true;
+ FireAnimation.infantryFireSndPlay(volume);
+ }
+
+ if (!hit && (elapsed < hit_time)) {
+ fire_w += (fire_dw * delta);
+ fire_x += (fire_dx * delta);
+ fire_y += (fire_dy * delta);
+ fireRegion.setRegionWidth((int) fire_w);
+ return false;
+ }
+
+ if (!hit)
+ hit = true;
+
+ if (elapsed < end_time) {
+ int frame = (int) ((elapsed - hit_time) / HIT_FRAME_DURATION);
+ if (frame != hit_frame) {
+ hit_frame = frame;
+ fireRegion.setRegion(FireAnimation.infantryFire.frames[hit_frame]);
+ fireRegion.setRegionWidth((int) fire_w);
+ }
+ return false;
+ }
+
+ completed = true;
+ return true;
+ }
+
+ public void draw(Batch batch)
+ {
+ if (fired && !completed)
+ batch.draw(fireRegion, fire_x, fire_y, 0, 0, fireRegion.getRegionWidth(), fireRegion.getRegionHeight(), 1f, 1f, fire_a);
+ }
+ }
+
+ private static final int SHOT_COUNT = 19;
+ private static final float SHOT_DELAY = (1.6f / SHOT_COUNT);
+ private static final float SHOT_SCATTERING = 40f;
+ private static final float TIME_SCATTERING = 0.6f;
+ private static final float START_DELAY = 0.8f;
+ private static final float SHOT_SPEED = 1000f;
+ private static final float HIT_FRAME_DURATION = 0.05f;
+
+ private Shot[] shots;
+
+ private float elapsed;
+
+ private float volume;
+
+ private static final Pool<InfantryFireAnimation> fireAnimationPool = new Pool<InfantryFireAnimation>() {
+ @Override
+ protected InfantryFireAnimation newObject() {
+ return new InfantryFireAnimation();
+ }
+ };
+
+ public static InfantryFireAnimation get(float volume, float x0, float y0, float x1, float y1, float halfWidth)
+ {
+ InfantryFireAnimation a = fireAnimationPool.obtain();
+ a.set(volume, x0, y0, x1, y1, halfWidth);
+ return a;
+ }
+
+ public InfantryFireAnimation()
+ {
+ this.shots = new Shot[SHOT_COUNT];
+ for (int i = 0; i < shots.length; i++)
+ shots[i] = new Shot(new TextureRegion(FireAnimation.infantryFire.frames[0]));
+ }
+
+ private void set(float volume, float x0, float y0, float x1, float y1, float halfWidth)
+ {
+ this.volume = volume;
+ this.elapsed = 0f;
+
+ float delay = START_DELAY + (FireAnimation.random.nextFloat() * TIME_SCATTERING);
+
+ y0 -= (FireAnimation.infantryFire.height / 2.0f);
+ double r = Math.atan2((y0 - y1), (x0 - x1));
+ x0 -= ((float) (Math.cos(r) * halfWidth));
+ y0 -= ((float) (Math.sin(r) * halfWidth));
+
+ float dx = (x1 - x0);
+ float dy = (y1 - y0);
+ float w = (float) Math.sqrt((dx * dx) + (dy * dy));
+ double dr = (Math.atan2(halfWidth, w) / 2f);
+
+ double a = (r + (dr / 2f));
+ double da = (dr / (float) SHOT_COUNT);
+
+ for (Shot shot : shots) {
+ float x = (float) (x0 - (Math.cos(a) * w));
+ float y = (float) (y0 - (Math.sin(a) * w));
+
+ shot.set(delay, x0, y0, x, y, w, (float) Math.toDegrees(a));
+
+ delay += SHOT_DELAY;
+ a -= 2 * (da * FireAnimation.random.nextFloat());
+ }
+ }
+
+ @Override
+ public void reset()
+ {
+ }
+
+ @Override
+ public void dispose()
+ {
+ fireAnimationPool.free(this);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+
+ boolean completed = true;
+ for (Shot shot : shots)
+ completed &= shot.animate(delta);
+
+ return completed;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ for (Shot shot : shots)
+ shot.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ // debugShapes.end();
+ // debugShapes.begin(ShapeRenderer.ShapeType.Line);
+ // debugShapes.identity();
+ // debugShapes.translate(fire_x, fire_y, 0);
+ // debugShapes.rotate(0, 0, 1, fire_a);
+ // debugShapes.translate(-fire_x, -fire_y, 0);
+ // debugShapes.rect(fire_x, fire_y, fire_w, FireAnimation.infantryFire.height);
+ // debugShapes.end();
+ // debugShapes.begin(ShapeRenderer.ShapeType.Line);
+ // debugShapes.identity();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java
new file mode 100644
index 0000000..f6380bc
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java
@@ -0,0 +1,126 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Moveable;
+
+public class MoveToAnimation extends TimedAnimation
+{
+ public interface MoveToAnimationCb {
+ void moveToAnimationLeave(Moveable moveable, float x, float y, float r);
+ void moveToAnimationEnter(Moveable moveable, float x, float y, float r);
+ void moveToAnimationDone(Moveable moveable, float x, float y, float r);
+ }
+
+ private Moveable moveable;
+ private float fromX;
+ private float fromY;
+ private float fromR;
+ private float toX;
+ private float toY;
+ private float toR;
+ private float rDelta;
+ private boolean notified;
+ private MoveToAnimationCb cb;
+
+ private static final Pool<MoveToAnimation> moveToAnimationPool = new Pool<MoveToAnimation>() {
+ @Override
+ protected MoveToAnimation newObject() {
+ return new MoveToAnimation();
+ }
+ };
+
+ public static MoveToAnimation get(Moveable moveable, Vector3 v, float duration)
+ {
+ return get(moveable, v.x, v.y, v.z, duration);
+ }
+
+ public static MoveToAnimation get(Moveable moveable, Vector3 v, float duration, MoveToAnimationCb cb)
+ {
+ return get(moveable, v.x, v.y, v.z, duration, cb);
+ }
+
+ public static MoveToAnimation get(Moveable moveable, float x, float y, float r, float duration)
+ {
+ return get(moveable, x, y, r, duration, null);
+ }
+
+ public static MoveToAnimation get(Moveable moveable, float x, float y, float r, float duration, MoveToAnimationCb cb)
+ {
+ MoveToAnimation a = moveToAnimationPool.obtain();
+
+ a.moveable = moveable;
+ a.toX = x;
+ a.toY = y;
+ a.toR = r;
+ a.duration = duration;
+ a.cb = cb;
+ a.rDelta = 0;
+ a.notified = false;
+
+ return a;
+ }
+
+ @Override
+ public void dispose()
+ {
+ moveToAnimationPool.free(this);
+ }
+
+ @Override
+ protected void begin()
+ {
+ fromX = moveable.getX();
+ fromY = moveable.getY();
+ fromR = moveable.getRotation();
+ notified = ((fromX == toX) && (fromY == toY));
+
+ if (Math.abs(toR - fromR) <= 180.f)
+ rDelta = (toR - fromR);
+ else {
+ if (toR > fromR)
+ rDelta = (toR - 360 - fromR);
+ else
+ rDelta = (toR + 360 - fromR);
+ }
+ }
+
+ @Override
+ protected void end()
+ {
+ if (cb != null)
+ cb.moveToAnimationDone(moveable, (toX + (moveable.getWidth() / 2)), (toY + (moveable.getHeight() / 2)), toR);
+ dispose();
+ }
+
+ @Override
+ protected void update(float percent)
+ {
+ if ((cb != null) && !notified && (percent >= 0.5)) {
+ float dw = (moveable.getWidth() / 2);
+ float dh = (moveable.getHeight() / 2);
+ cb.moveToAnimationLeave(moveable, (fromX + dw), (fromY + dh), fromR);
+ cb.moveToAnimationEnter(moveable, (toX + dw), (toY + dh), toR);
+ notified = true;
+ }
+ if (percent == 1f)
+ moveable.setPosition(toX, toY, (int) toR);
+ else
+ moveable.setPosition(fromX + ((toX - fromX) * percent), fromY + ((toY - fromY) * percent), (fromR + (rDelta * percent)));
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ moveable.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ moveable.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java
new file mode 100644
index 0000000..24eac18
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java
@@ -0,0 +1,98 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.lang.Math;
+
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class PromoteAnimation implements Animation, Drawable
+{
+ private static PromoteAnimation instance = new PromoteAnimation();
+
+ private static final float DURATION = 0.3f;
+ private static final float MAX_SCALE = 2f;
+
+ private static Sound usSound;
+ private static Sound geSound;
+ private static Sound snd;
+ private static TextureRegion region;
+
+ private float x0;
+ private float y0;
+ private float x;
+ private float y;
+ private float scale;
+ private float step;
+ private float volume;
+ private float elapsed;
+
+ public static void init(TextureAtlas atlas, Sound usSnd, Sound geSnd)
+ {
+ region = atlas.findRegion("stars");
+ usSound = usSnd;
+ geSound = geSnd;
+ }
+
+ public static void free()
+ {
+ }
+
+ protected void PromoteAnimation()
+ {
+ }
+
+ public static PromoteAnimation get(boolean us, float x, float y, float v)
+ {
+ x = (x - (region.getRegionWidth() / 2.0f));
+ y = (y - (region.getRegionHeight() / 2.0f));
+
+ instance.volume = v;
+ instance.x0 = x;
+ instance.y0 = y;
+ instance.scale = 0f;
+ instance.elapsed = 0f;
+ snd = (us ? usSound : geSound);
+
+ return instance;
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+ if (elapsed >= DURATION) {
+ snd.play(volume);
+ return true;
+ }
+
+ float s = MAX_SCALE * (float) Math.sin(Math.PI / DURATION * elapsed);
+ scale = 1f + s;
+ x = x0 - ((region.getRegionWidth() * scale) / 4f);
+ y = y0 - ((region.getRegionHeight() * scale) / 4f);
+
+ return false;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ batch.draw(region, x, y, 0, 0, region.getRegionWidth(), region.getRegionHeight(), scale, scale, 0f);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ debugShapes.rect(x, y, region.getRegionWidth(), region.getRegionHeight());
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java
new file mode 100644
index 0000000..231f859
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java
@@ -0,0 +1,67 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Moveable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class RunnableAnimation implements Animation, Pool.Poolable
+{
+ private Runnable runnable;
+ private Moveable moveable;
+ private boolean ran;
+
+ private static final Pool<RunnableAnimation> runnableAnimationPool = new Pool<RunnableAnimation>() {
+ @Override
+ protected RunnableAnimation newObject() {
+ return new RunnableAnimation();
+ }
+ };
+
+ public static RunnableAnimation get(Moveable moveable, Runnable runnable)
+ {
+ RunnableAnimation a = runnableAnimationPool.obtain();
+ a.runnable = runnable;
+ a.moveable = moveable;
+ return a;
+ }
+
+ @Override
+ public void reset()
+ {
+ ran = false;
+ }
+
+ @Override
+ public void dispose()
+ {
+ runnableAnimationPool.free(this);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if (ran) return true;
+
+ runnable.run();
+ runnable = null;
+ ran = true;
+ dispose();
+
+ return true;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ moveable.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ moveable.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java
new file mode 100644
index 0000000..7e83f38
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java
@@ -0,0 +1,83 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public class SoundAnimation extends TimedAnimation
+{
+ public enum Action
+ {
+ FADE_IN,
+ FADE_OUT
+ };
+
+ private Sound sound;
+ private long soundId;
+ private Action action;
+ private float volume;
+
+ private static final Pool<SoundAnimation> soundAnimationPool = new Pool<SoundAnimation>() {
+ @Override
+ protected SoundAnimation newObject() {
+ return new SoundAnimation();
+ }
+ };
+
+ public static SoundAnimation get(Action action, Sound sound, long soundId, float volume, float duration)
+ {
+ SoundAnimation a = soundAnimationPool.obtain();
+
+ a.action = action;
+ a.sound = sound;
+ a.soundId = soundId;
+ a.volume = volume;
+ a.duration = duration;
+
+ return a;
+ }
+
+ @Override
+ public void dispose()
+ {
+ soundAnimationPool.free(this);
+ }
+
+ @Override
+ protected void begin()
+ {
+ }
+
+ @Override
+ protected void end()
+ {
+ dispose();
+ }
+
+ @Override
+ protected void update(float percent)
+ {
+ float v;
+ switch(action) {
+ case FADE_IN:
+ v = ( volume * percent);
+ sound.setVolume(soundId, v);
+ break;
+ case FADE_OUT:
+ v = (volume - ( volume * percent));
+ sound.setVolume(soundId, v);
+ break;
+ }
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java
new file mode 100644
index 0000000..4d10210
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java
@@ -0,0 +1,76 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class SpriteAnimation implements Disposable, Animation
+{
+ private static Random random = new Random();
+ private Sprites sprites;
+ private float duration;
+ private float frameDuration;
+ private float elapsed;
+ private float x0;
+ private float y0;
+ private float x1;
+ private float y1;
+ private int randFreq;
+
+ public SpriteAnimation(Texture texture, int cols, int rows, int randFreq)
+ {
+ this.sprites = new Sprites(texture, cols, rows);
+ this.randFreq = randFreq;
+ }
+
+ @Override
+ public void dispose()
+ {
+ sprites.dispose();
+ }
+
+ public void init(float duration, float x, float y)
+ {
+ this.duration = duration;
+ this.frameDuration = (duration / (float) sprites.frames.length);
+ this.x0 = x - (sprites.width / 2f);
+ this.y0 = y - (sprites.height / 2f);
+ this.elapsed = 0f;
+ randPos();
+ }
+
+ private void randPos()
+ {
+ this.x1 = this.x0 + (random.nextInt(sprites.width) - (sprites.width / 2));
+ this.y1 = this.y0 + (random.nextInt(sprites.height) - (sprites.height / 2));
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+ return (elapsed >= duration);
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ int n = (((int)(elapsed / frameDuration)) % sprites.frames.length);
+ if ((n > 0) && (n % randFreq) == 0)
+ randPos();
+ batch.draw(sprites.frames[n], x1, y1);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java
new file mode 100644
index 0000000..2d2a0c1
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java
@@ -0,0 +1,38 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+
+public class Sprites implements Disposable
+{
+ public Texture texture;
+ public TextureRegion[] frames;
+ public final int width;
+ public final int height;
+ public final int cols;
+ public final int rows;
+
+ public Sprites(Texture texture, int cols, int rows)
+ {
+ this.cols = cols;
+ this.rows = rows;
+ this.width = (texture.getWidth() / cols);
+ this.height = (texture.getHeight() / rows);
+ this.texture = texture;
+ TextureRegion[][] tmp = TextureRegion.split(texture, width, height);
+ frames = new TextureRegion[cols * rows];
+ int idx = 0;
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ frames[idx++] = tmp[i][j];
+ }
+ }
+ }
+
+ @Override
+ public void dispose()
+ {
+ texture.dispose();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java
new file mode 100644
index 0000000..82a87fd
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java
@@ -0,0 +1,196 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.utils.Pool;
+
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class TankFireAnimation implements Disposable, Animation, Pool.Poolable
+{
+ private static final float SHOT_SCATTERING = 60f;
+ private static final float TIME_SCATTERING = 0.6f;
+ private static final float START_DELAY = 0.8f;
+ private static final float SHOT_SPEED = 900f;
+ private static final float EXPLOSION_FRAME_DURATION = 0.07f;
+
+ private TextureRegion fireRegion;
+ private float fire_a;
+ private float fire_x;
+ private float fire_y;
+ private float fire_w;
+ private float fire_dx;
+ private float fire_dy;
+ private float fire_dw;
+
+ private float smoke_df;
+ private int smoke_frame;
+
+ private float explosion_x;
+ private float explosion_y;
+ private float explosion_df;
+ private int explosion_frame;
+
+ private boolean fired;
+ private boolean hit;
+ private float elapsed;
+ private float fire_time;
+ private float hit_time;
+ private float end_time;
+
+ private float volume;
+
+ private static final Pool<TankFireAnimation> fireAnimationPool = new Pool<TankFireAnimation>() {
+ @Override
+ protected TankFireAnimation newObject() {
+ return new TankFireAnimation();
+ }
+ };
+
+ public static TankFireAnimation get(float volume, float x0, float y0, float x1, float y1, float halfWidth)
+ {
+ TankFireAnimation a = fireAnimationPool.obtain();
+ a.set(volume, x0, y0, x1, y1, halfWidth);
+ return a;
+ }
+
+ public TankFireAnimation()
+ {
+ this.fireRegion = new TextureRegion(FireAnimation.tankFire.frames[0]);
+ }
+
+ private void set(float volume, float x0, float y0, float x1, float y1, float halfWidth)
+ {
+ this.fired = false;
+ this.hit = false;
+ this.volume = volume;
+
+ // fire geometry
+ y0 -= (FireAnimation.tankFire.height / 2.0f);
+ x1 += ((SHOT_SCATTERING * FireAnimation.random.nextFloat()) - (SHOT_SCATTERING / 2f));
+ y1 += ((SHOT_SCATTERING * FireAnimation.random.nextFloat()) - (SHOT_SCATTERING / 2f));
+
+ double r = Math.atan2((y0 - y1), (x0 - x1));
+ float xadj = (float) (Math.cos(r) * halfWidth);
+ float yadj = (float) (Math.sin(r) * halfWidth);
+ x0 -= xadj;
+ y0 -= yadj;
+
+ float a = (float) Math.toDegrees(r);
+ float dx = (x1 - x0);
+ float dy = (y1 - y0);
+ float w = (float) Math.sqrt((dx * dx) + (dy * dy));
+
+ // timing
+ float delay = START_DELAY + (FireAnimation.random.nextFloat() * TIME_SCATTERING);
+ float fire_duration = ((FireAnimation.random.nextFloat() * TIME_SCATTERING) + (w / SHOT_SPEED));
+ float explosion_duration = (FireAnimation.explosion.cols * EXPLOSION_FRAME_DURATION);
+
+ this.elapsed = 0f;
+ this.fire_time = delay;
+ this.hit_time = (fire_time + fire_duration);
+ this.end_time = (hit_time + explosion_duration);
+
+ // fire vars
+ this.fire_a = a;
+ this.fire_x = x0;
+ this.fire_y = y0;
+ this.fire_w = 0;
+ this.fire_dx = (dx / fire_duration);
+ this.fire_dy = (dy / fire_duration);
+ this.fire_dw = (w / fire_duration);
+
+ // smoke var
+ this.smoke_df = (FireAnimation.tankFire.rows / explosion_duration);
+ this.smoke_frame = 0;
+
+ // explosion vars
+ this.explosion_x = (x1 - (FireAnimation.explosion.width / 2.0f));
+ this.explosion_y = (y1 - (FireAnimation.explosion.height / 2.0f));
+ this.explosion_df = (FireAnimation.explosion.cols / explosion_duration);
+ this.explosion_frame = (FireAnimation.random.nextInt(FireAnimation.explosion.rows) * FireAnimation.explosion.cols);
+ }
+
+ @Override
+ public void reset()
+ {
+ }
+
+ @Override
+ public void dispose()
+ {
+ fireAnimationPool.free(this);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+
+ if (!fired && (elapsed < fire_time))
+ return false;
+
+ if (!fired) {
+ fired = true;
+ FireAnimation.tankFireSndPlay(volume);
+ }
+
+ if (!hit && (elapsed < hit_time)) {
+ fire_w += (fire_dw * delta);
+ fire_x += (fire_dx * delta);
+ fire_y += (fire_dy * delta);
+ fireRegion.setRegionWidth((int) fire_w);
+ return false;
+ }
+
+ if (!hit) {
+ hit = true;
+ FireAnimation.explosionSndPlay(volume);
+ }
+
+ if (elapsed < end_time) {
+ int frame = (int) ((elapsed - hit_time) * smoke_df);
+ if (frame != smoke_frame) {
+ smoke_frame = frame;
+ fireRegion.setRegion(FireAnimation.tankFire.frames[smoke_frame]);
+ fireRegion.setRegionWidth((int) fire_w);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (fired)
+ batch.draw(fireRegion, fire_x, fire_y, 0, 0, fireRegion.getRegionWidth(), fireRegion.getRegionHeight(), 1f, 1f, fire_a);
+
+ if (hit) {
+ int frame = (explosion_frame + (int) ((elapsed - hit_time) * explosion_df));
+ batch.draw(FireAnimation.explosion.frames[frame], explosion_x, explosion_y);
+ }
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ debugShapes.end();
+ debugShapes.begin(ShapeRenderer.ShapeType.Line);
+ debugShapes.identity();
+ debugShapes.translate(fire_x, fire_y, 0);
+ debugShapes.rotate(0, 0, 1, fire_a);
+ debugShapes.translate(-fire_x, -fire_y, 0);
+ debugShapes.rect(fire_x, fire_y, fire_w, FireAnimation.tankFire.height);
+ debugShapes.end();
+ debugShapes.begin(ShapeRenderer.ShapeType.Line);
+ debugShapes.identity();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java
new file mode 100644
index 0000000..0c0d14d
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java
@@ -0,0 +1,48 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Pool;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public abstract class TimedAnimation implements Animation, Pool.Poolable
+{
+ private float time;
+ private boolean began;
+ private boolean completed;
+ protected float duration;
+
+ abstract protected void begin();
+ abstract protected void end();
+ abstract protected void update(float percent);
+
+ @Override
+ public void reset()
+ {
+ time = 0f;
+ began = false;
+ completed = false;
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if (completed) return true;
+
+ if (!began) {
+ begin();
+ began = true;
+ }
+
+ time += delta;
+ completed = (time >= duration);
+
+ if (!completed) {
+ update(time / duration);
+ return false;
+ }
+
+ update(1);
+ end();
+ return true;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Army.java b/core/src/ch/asynk/rustanddust/game/Army.java
new file mode 100644
index 0000000..21cad9f
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Army.java
@@ -0,0 +1,30 @@
+package ch.asynk.rustanddust.game;
+
+import ch.asynk.rustanddust.engine.Faction;
+
+public enum Army implements Faction
+{
+ NONE("None"),
+ GE("German"),
+ US("US"),
+ USSR("Soviet"),
+ EN("English");
+
+ private String s;
+
+ Army(String s) {
+ this.s = s;
+ }
+
+ @Override
+ public String toString()
+ {
+ return s;
+ }
+
+ @Override
+ public boolean isEnemy(Faction other)
+ {
+ return (this != other);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Battle.java b/core/src/ch/asynk/rustanddust/game/Battle.java
new file mode 100644
index 0000000..242e147
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Battle.java
@@ -0,0 +1,40 @@
+package ch.asynk.rustanddust.game;
+
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.battles.Factory.MapType;
+import ch.asynk.rustanddust.ui.Position;
+
+public interface Battle
+{
+ public void init();
+
+ public String getName();
+
+ public String getDescription();
+
+ public Player getPlayer();
+
+ public Player opponent(Player player);
+
+ public MapType getMapType();
+
+ public Map getMap();
+
+ public Player checkVictory(Ctrl ctrl);
+
+ public boolean getReinforcement(Ctrl ctrl, Map map);
+
+ public Zone getEntryZone(Unit unit);
+
+ public Zone getExitZone(Unit unit);
+
+ public Position getHudPosition(Player player);
+
+ public State.StateType getState(Player player);
+
+ public boolean deploymentDone(Player player);
+
+ public void setup(Ctrl ctrl, Map map);
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Command.java b/core/src/ch/asynk/rustanddust/game/Command.java
new file mode 100644
index 0000000..40d467a
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Command.java
@@ -0,0 +1,225 @@
+package ch.asynk.rustanddust.game;
+
+import java.util.List;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.utils.Json;
+import com.badlogic.gdx.utils.JsonValue;
+
+import ch.asynk.rustanddust.engine.Order;
+import ch.asynk.rustanddust.engine.Move;
+import ch.asynk.rustanddust.engine.Pawn;
+import ch.asynk.rustanddust.engine.Tile;
+
+public class Command extends Order
+{
+ public enum CommandType implements Order.OrderType
+ {
+ NONE,
+ MOVE,
+ ENGAGE,
+ PROMOTE,
+ END_OF_TURN;
+ }
+
+ private static final Pool<Command> commandPool = new Pool<Command>()
+ {
+ @Override
+ protected Command newObject() {
+ return new Command();
+ }
+ };
+
+ public static void clearPool()
+ {
+ commandPool.clear();
+ }
+
+ public static Command get(Player player)
+ {
+ Command c = commandPool.obtain();
+ c.player = player;
+ c.ap = player.getAp();
+ c.turn = player.getCurrentTurn();
+ return c;
+ }
+
+ public CommandType type;
+ public Player player;
+ public int ap;
+ public int turn;
+ public Unit unit;
+ public Unit.UnitId unitId;
+ public Unit.UnitType unitType;
+ public Tile unitTile;
+ public Move move;
+ public Engagement engagement;
+
+ private Command()
+ {
+ reset();
+ }
+
+ @Override
+ public void dispose()
+ {
+ commandPool.free(this);
+ }
+
+ @Override
+ public void reset()
+ {
+ this.type = CommandType.NONE;
+ this.player = null;
+ this.unit = null;
+ if (this.move != null) {
+ this.move.dispose();
+ this.move = null;
+ }
+ if (this.engagement != null) {
+ this.engagement.dispose();
+ this.engagement = null;
+ }
+ }
+
+ @Override
+ public int compareTo(Pawn pawn)
+ {
+ if (pawn == unit)
+ return 0;
+ return 1;
+ }
+
+ @Override
+ public boolean isA(OrderType type)
+ {
+ return (type == this.type);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s : %s", type, unit.id);
+ }
+
+ public void setMove(Unit unit, Move move)
+ {
+ this.type = CommandType.MOVE;
+ this.move = move;
+ setUnit(unit);
+ }
+
+ public void setPromote(Unit unit)
+ {
+ this.type = CommandType.PROMOTE;
+ setUnit(unit);
+ }
+
+ public void setEngage(Unit unit, Unit target)
+ {
+ this.type = CommandType.ENGAGE;
+ this.engagement = Engagement.get(unit, target);
+ setUnit(unit);
+ }
+
+ private void setUnit(Unit unit)
+ {
+ this.unit = unit;
+ this.unitId = unit.id;
+ this.unitType = unit.type;
+ this.unitTile = unit.getTile();
+ }
+
+ @Override
+ public void write(Json json)
+ {
+ json.writeValue("type", type);
+ json.writeObjectStart("player");
+ json.writeValue("army", player.getName());
+ json.writeValue("turn", turn);
+ json.writeValue("aps", ap);
+ json.writeObjectEnd();
+ json.writeObjectStart("unit");
+ json.writeValue("id", unitId);
+ json.writeValue("type", unitType);
+ json.writeValue("ace", unit.ace);
+ writeTile(json, "tile", unitTile);
+ json.writeObjectEnd();
+ if (move != null) writeMove(json, "move", move);
+ if (engagement != null) writeEngagement(json, "engagement", engagement);
+ }
+
+ private void writeMove(Json json, String key, Move m)
+ {
+ json.writeObjectStart(key);
+ json.writeValue("type", move.type);
+ writeTile(json, "from", move.from);
+ writeTile(json, "to", move.to);
+ json.writeValue("orientation", move.orientation.r());
+ writeTiles(json, "path", move.tiles);
+ json.writeObjectEnd();
+ }
+
+ private void writeEngagement(Json json, String key, Engagement e)
+ {
+ json.writeObjectStart(key);
+ writeUnit(json, "attacker", e.attacker);
+ writeUnit(json, "defender", e.defender);
+ writeUnits(json, "assists", e.assists);
+ json.writeObjectStart("dice");
+ json.writeValue("d1", e.d1);
+ json.writeValue("d2", e.d2);
+ json.writeValue("d3", e.d3);
+ json.writeValue("d4", e.d4);
+ json.writeObjectEnd();
+ json.writeObjectStart("results");
+ json.writeValue("success", e.success);
+ json.writeValue("attackSum", e.attackSum);
+ json.writeValue("defenseSum", e.defenseSum);
+ json.writeObjectEnd();
+ json.writeObjectEnd();
+ }
+
+ private void writeUnit(Json json, String key, Unit u)
+ {
+ if (key != null) json.writeObjectStart(key);
+ else json.writeObjectStart();
+ json.writeValue("id", u.id);
+ json.writeValue("ace", u.ace);
+ json.writeValue("army", u.getArmy());
+ writeTile(json, "tile", u.getTile());
+ json.writeObjectEnd();
+ }
+
+ private void writeUnits(Json json, String key, List<Unit> units)
+ {
+ json.writeArrayStart(key);
+ for (Unit u : units)
+ writeUnit(json, null, u);
+ json.writeArrayEnd();
+ }
+
+ private void writeTile(Json json, String key, Tile t)
+ {
+ if (t == null) return;
+ if (key != null) json.writeObjectStart(key);
+ else json.writeObjectStart();
+ json.writeValue("col", t.getCol());
+ json.writeValue("row", t.getRow());
+ json.writeObjectEnd();
+ }
+
+ private void writeTiles(Json json, String key, List<Tile> tiles)
+ {
+ json.writeArrayStart(key);
+ for (Tile t : tiles)
+ writeTile(json, null, t);
+ json.writeArrayEnd();
+ }
+
+ @Override
+ public void read(Json json, JsonValue jsonMap)
+ {
+ // FIXME Command.read(Json, JsonValue);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Config.java b/core/src/ch/asynk/rustanddust/game/Config.java
new file mode 100644
index 0000000..2c27c35
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Config.java
@@ -0,0 +1,76 @@
+package ch.asynk.rustanddust.game;
+
+public class Config
+{
+ public enum Graphics {
+ MINE("mine", 0),
+ ORIGINAL("original", 1);
+ public String s;
+ public int i;
+ Graphics(String s, int i)
+ {
+ this.s = s;
+ this.i = i;
+ }
+ public Graphics next()
+ {
+ if (this == ORIGINAL)
+ return MINE;
+ return ORIGINAL;
+ }
+ };
+
+ public enum GameMode
+ {
+ SOLO("Solo", 0),
+ PVE("Player vs AI", 1),
+ PVP("Player vs Player", 2);
+ public String s;
+ public int i;
+ GameMode(String s, int i)
+ {
+ this.s = s;
+ this.i = i;
+ }
+ public GameMode next()
+ {
+ if (this == SOLO)
+ return PVE;
+ if(this == PVE)
+ return PVP;
+ return SOLO;
+ }
+ };
+
+ public GameMode gameMode;
+ public boolean showMoves;
+ public boolean showTargets;
+ public boolean showMoveAssists;
+ public boolean canCancel;
+ public boolean mustValidate;
+ public boolean showEnemyPossibilities;
+ public boolean debug;
+ public Battle battle;
+ public float fxVolume;
+ public Graphics graphics;
+
+ public Config()
+ {
+ this.gameMode = GameMode.SOLO;
+ this.debug = false;
+ this.showMoves = true;
+ this.showTargets = true;
+ this.showMoveAssists = true;
+ this.canCancel = false;
+ this.mustValidate = false;
+ this.showEnemyPossibilities = false;
+ this.graphics = Graphics.MINE;
+ this.battle = null;
+ this.fxVolume = 1f;
+ }
+
+ public boolean gameModeImplemented()
+ {
+ return (gameMode == GameMode.SOLO);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Ctrl.java b/core/src/ch/asynk/rustanddust/game/Ctrl.java
new file mode 100644
index 0000000..3b93ad8
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Ctrl.java
@@ -0,0 +1,336 @@
+package ch.asynk.rustanddust.game;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.math.Vector3;
+
+import ch.asynk.rustanddust.RustAndDust;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.game.State.StateType;
+import ch.asynk.rustanddust.game.states.StateSelect;
+import ch.asynk.rustanddust.game.states.StateMove;
+import ch.asynk.rustanddust.game.states.StateRotate;
+import ch.asynk.rustanddust.game.states.StatePromote;
+import ch.asynk.rustanddust.game.states.StateEngage;
+import ch.asynk.rustanddust.game.states.StateBreak;
+import ch.asynk.rustanddust.game.states.StateAnimation;
+import ch.asynk.rustanddust.game.states.StateReinforcement;
+import ch.asynk.rustanddust.game.states.StateDeployment;
+import ch.asynk.rustanddust.game.states.StateWithdraw;
+
+public class Ctrl implements Disposable
+{
+ private final RustAndDust game;
+ public final Battle battle;
+
+ public Map map;
+ public Hud hud;
+ public Config cfg;
+ public Player player;
+ public Player opponent;
+ public boolean blockMap;
+ public boolean blockHud;
+
+ public Vector3 mapTouch = new Vector3();
+ public Vector3 hudTouch = new Vector3();
+
+ private State selectState;
+ private State pathState;
+ private State rotateState;
+ private State promoteState;
+ private State engageState;
+ private State breakState;
+ private State animationState;
+ private State reinforcementState;
+ private State deploymentState;
+ private State withdrawState;
+
+ private int animationCount = 0;
+
+ private State state;
+ private StateType stateType;
+ private StateType stateAfterAnimation;
+
+ public Ctrl(final RustAndDust game, final Battle battle)
+ {
+ this.game = game;
+ this.battle = battle;
+ this.cfg = game.config;
+ game.ctrl = this;
+
+ battle.init();
+
+ this.map = battle.getMap();
+ battle.setup(this, map);
+ this.map.init();
+ this.player = battle.getPlayer();
+ this.opponent = battle.opponent(player);
+
+ this.selectState = new StateSelect(this, map);
+ this.pathState = new StateMove();
+ this.rotateState = new StateRotate();
+ this.promoteState = new StatePromote();
+ this.engageState = new StateEngage();
+ this.breakState = new StateBreak();
+ this.animationState = new StateAnimation();
+ this.reinforcementState = new StateReinforcement();
+ this.deploymentState = new StateDeployment();
+ this.withdrawState = new StateWithdraw();
+
+ this.state = selectState;
+ this.stateType = StateType.DONE;
+
+ this.hud = new Hud(this, game);
+ this.blockMap = false;
+ this.blockHud = false;
+
+ hud.notify(battle.toString(), 2, Position.MIDDLE_CENTER, false);
+ startPlayerTurn();
+ }
+
+ @Override
+ public void dispose()
+ {
+ hud.dispose();
+ map.dispose();
+ }
+
+ public Player getPlayer(Army army)
+ {
+ return (player.is(army) ? player : opponent);
+ }
+
+ public boolean isInAction()
+ {
+ return (state != selectState);
+ }
+
+ public void animationsOver()
+ {
+ if (hud.dialogActive())
+ return;
+ if (stateType == StateType.ANIMATION)
+ leaveAnimationState();
+ }
+
+ private void leaveAnimationState()
+ {
+
+ StateType tmp = stateAfterAnimation;
+ stateAfterAnimation = StateType.DONE;
+ setState(tmp);
+ }
+
+ private void startPlayerTurn()
+ {
+ player.turnStart();
+ // hud.notify(player.getName() + "'s turn", 2, Position.MIDDLE_CENTER, true);
+ if (battle.getReinforcement(this, map))
+ hud.notify("You have reinforcement", 2, Position.MIDDLE_CENTER, true);
+ hud.update();
+ setState(battle.getState(player));
+ }
+
+ private void endPlayerTurn()
+ {
+ player.turnEnd();
+ Player winner = battle.checkVictory(this);
+ if (winner != null)
+ hud.victory(winner, ((winner == player) ? opponent : player));
+ }
+
+ private StateType actionAborted()
+ {
+ hud.notify("Action canceled");
+ StateType nextState = this.state.abort();
+
+ if (nextState == StateType.ABORT)
+ nextState = battle.getState(player);
+
+ return nextState;
+ }
+
+ private void turnDone()
+ {
+ map.turnDone();
+ endPlayerTurn();
+ player = battle.getPlayer();
+ opponent = battle.opponent(player);
+ startPlayerTurn();
+ }
+
+ private StateType actionDone()
+ {
+ StateType nextState = this.state.execute();
+
+ if (nextState == StateType.DONE) {
+ map.actionDone();
+ if (map.activatedUnits.size() > 0) {
+ RustAndDust.debug("Ctrl", "burn down 1AP");
+ hud.notify("1 Action Point burnt", 0.6f, Position.BOTTOM_CENTER, false);
+ player.burnDownOneAp();
+ hud.update();
+ }
+ if (player.apExhausted())
+ hud.notifyNoMoreAP();
+ }
+
+ if (nextState == StateType.DONE)
+ nextState = battle.getState(player);
+
+ return nextState;
+ }
+
+ private StateType deploymentDone()
+ {
+ map.actionDone();
+ return this.state.execute();
+ }
+
+ public void setState(StateType nextState)
+ {
+ if (nextState == StateType.ABORT)
+ nextState = actionAborted();
+ else if (nextState == StateType.DONE) {
+ if (stateType == StateType.DEPLOYMENT)
+ nextState = deploymentDone();
+ else
+ nextState = actionDone();
+ }
+
+ if (stateType == StateType.ANIMATION) {
+ this.blockMap = hud.dialogActive();
+ }
+ hud.playerInfo.blockEndOfTurn(nextState != StateType.SELECT);
+
+ this.state.leave(nextState);
+
+ RustAndDust.debug("Ctrl", String.format(" %s -> %s : %s", stateType, nextState, player));
+
+ switch(nextState) {
+ case SELECT:
+ this.state = selectState;
+ break;
+ case MOVE:
+ this.state = pathState;
+ break;
+ case ROTATE:
+ this.state = rotateState;
+ break;
+ case PROMOTE:
+ this.state = promoteState;
+ break;
+ case ENGAGE:
+ this.state = engageState;
+ break;
+ case BREAK:
+ this.state = breakState;
+ break;
+ case WITHDRAW:
+ this.state = withdrawState;
+ break;
+ case ANIMATION:
+ this.blockMap = true;
+ this.state = animationState;
+ break;
+ case REINFORCEMENT:
+ this.state = reinforcementState;
+ break;
+ case DEPLOYMENT:
+ this.state = deploymentState;
+ break;
+ default:
+ break;
+ }
+
+ StateType tmp = stateType;
+ stateType = nextState;
+
+ this.state.enter(tmp);
+
+ }
+
+ public void touchDown()
+ {
+ if (!blockHud && hud.touchDown(hudTouch.x, hudTouch.y))
+ return;
+
+ if (!blockMap && state.downInMap(mapTouch.x, mapTouch.y))
+ state.touchDown();
+ }
+
+ public void touchUp()
+ {
+ if (!blockHud && hud.touchUp(hudTouch.x, hudTouch.y))
+ return;
+
+ if (!blockMap && state.upInMap(mapTouch.x, mapTouch.y))
+ state.touchUp();
+ }
+
+ public void stateTouchUp()
+ {
+ state.downInMap(-1, -1);
+ state.upInMap(-1, -1);
+ state.touchUp();
+ }
+
+ public boolean isInAnimation()
+ {
+ return (this.stateType == StateType.ANIMATION);
+ }
+
+ public void setAfterAnimationState(StateType after)
+ {
+ stateAfterAnimation = after;
+ }
+
+ public boolean checkDeploymentDone()
+ {
+ boolean done = battle.deploymentDone(player);
+ if (done)
+ hud.askEndDeployment();
+ return done;
+ }
+
+ public void reinforcementHit()
+ {
+ if (this.stateType == StateType.SELECT)
+ setState(StateType.REINFORCEMENT);
+ else if (this.stateType == StateType.REINFORCEMENT)
+ setState(StateType.SELECT);
+ }
+
+ // Hud callbacks
+ public void engagementPanelClosed()
+ {
+ if (animationCount == 0)
+ leaveAnimationState();
+ }
+
+ public void endDeployment()
+ {
+ setState(StateType.DONE);
+ turnDone();
+ }
+
+ public void endGame()
+ {
+ game.switchToMenu();
+ }
+
+ public void endPlayerTurn(boolean abort)
+ {
+ if (abort)
+ state.abort();
+ turnDone();
+ }
+
+ public void exitBoard(boolean doit)
+ {
+ if (doit)
+ setState(StateType.DONE);
+ else
+ setState(StateType.ABORT);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Engagement.java b/core/src/ch/asynk/rustanddust/game/Engagement.java
new file mode 100644
index 0000000..b7630ed
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Engagement.java
@@ -0,0 +1,114 @@
+package ch.asynk.rustanddust.game;
+
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Random;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.utils.Disposable;
+
+public class Engagement implements Disposable, Pool.Poolable
+{
+ private static Random rand = new Random();
+
+ private static final Pool<Engagement> engagementPool = new Pool<Engagement>() {
+ @Override
+ protected Engagement newObject() {
+ return new Engagement();
+ }
+ };
+
+ public static Engagement get(Unit attacker, Unit defender)
+ {
+ Engagement e = engagementPool.obtain();
+ e.attacker = attacker;
+ e.defender = defender;
+ e.diceRoll();
+
+ return e;
+ }
+
+ public static void clearPool()
+ {
+ engagementPool.clear();
+ }
+
+ public Unit attacker;
+ public Unit defender;
+ public List<Unit> assists;
+ public boolean success;
+ public int d1;
+ public int d2;
+ public int d3;
+ public int d4;
+ public int unitCount;
+ public int flankBonus;
+ public int unitDefense;
+ public int terrainDefense;
+ public int weatherDefense;
+ public int attackSum;
+ public int defenseSum;
+
+ public Engagement()
+ {
+ assists = new LinkedList<Unit>();
+ reset();
+ }
+
+ @Override
+ public void reset()
+ {
+ attacker = null;
+ defender = null;
+ assists.clear();
+ }
+
+ @Override
+ public void dispose()
+ {
+ assists.clear();
+ engagementPool.free(this);
+ }
+
+ public void addAssist(Unit unit)
+ {
+ assists.add(unit);
+ }
+
+ private void diceRoll()
+ {
+ d1 = rand.nextInt(6) + 1;
+ d2 = rand.nextInt(6) + 1;
+ d3 = rand.nextInt(6) + 1;
+ d4 = rand.nextInt(6) + 1;
+ }
+
+ public void set(int cnt, int flk, int def, int tdf, int wdf)
+ {
+ this.unitCount = cnt;
+ this.flankBonus = flk;
+ this.unitDefense = def;
+ this.terrainDefense = tdf;
+ this.weatherDefense = wdf;
+ if (d3 == 0)
+ this.attackSum = (d1 + d2 + unitCount + flankBonus);
+ else
+ this.attackSum = (d3 + d4 + unitCount + flankBonus);
+ this.defenseSum = (unitDefense + terrainDefense + weatherDefense);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ int a, b;
+ if (d3 == 0) {
+ a = d1;
+ b = d2;
+ } else {
+ a = d3;
+ b = d4;
+ }
+ return String.format("Engagement : (%d + %d + %d + %d) vs (%d + %d + %d) -> %b", a, b, unitCount, flankBonus, unitDefense, terrainDefense, weatherDefense, success);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Hex.java b/core/src/ch/asynk/rustanddust/game/Hex.java
new file mode 100644
index 0000000..b805146
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Hex.java
@@ -0,0 +1,138 @@
+package ch.asynk.rustanddust.game;
+
+import java.util.List;
+import java.util.Iterator;
+
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.rustanddust.engine.Board;
+import ch.asynk.rustanddust.engine.Pawn;
+import ch.asynk.rustanddust.engine.Tile;
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.game.Unit.UnitType;
+
+public class Hex extends Tile
+{
+ public enum Terrain implements TileTerrain
+ {
+ OFFMAP,
+ BLOCKED,
+ CLEAR,
+ HILLS,
+ WOODS,
+ TOWN
+ }
+
+ public static final int FOG = 0;
+ public static final int SELECT = 1;
+ public static final int AREA = 2;
+ public static final int MOVE = 3;
+ public static final int DIRECTIONS = 4;
+ public static final int ORIENTATION = 5;
+ public static final int OBJECTIVE = 6;
+ public static final int OBJECTIVE_HOLD = 7;
+ public static final int OBJECTIVE_GE = 8;
+ public static final int OBJECTIVE_US = 9;
+ public static final int EXIT = 10;
+
+ public Terrain terrain;
+ public int roads;
+
+ public String toString()
+ {
+ return String.format("(%d;%d) [%f;%f] t:%s r:%d", col, row, x, y, terrain, roads);
+ }
+
+ public String toShort()
+ {
+ return String.format("(%d;%d)", col, row);
+ }
+
+ public Hex(float x, float y, int col, int row, TextureAtlas atlas)
+ {
+ super(x, y, col, row, atlas);
+ this.terrain = Terrain.CLEAR;
+ this.roads = 0;
+ }
+
+ public Unit getUnit()
+ {
+ return (Unit) stack.peekFirst();
+ }
+
+ @Override
+ public boolean isA(TileTerrain terrain)
+ {
+ return (this.terrain == terrain);
+ }
+
+ @Override
+ public boolean isOffMap()
+ {
+ return isA(Terrain.OFFMAP);
+ }
+
+ @Override
+ public boolean blockLineOfSightFrom(Tile tile)
+ {
+ if (isA(Terrain.CLEAR) && !hasUnits())
+ return false;
+
+ if (tile.isA(Terrain.HILLS) && isA(Terrain.CLEAR))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public boolean atLeastOneMove(Pawn pawn)
+ {
+ if (hasUnits() || isA(Terrain.BLOCKED) || isA(Terrain.OFFMAP))
+ return false;
+ return true;
+ }
+
+ @Override
+ public boolean road(Orientation side)
+ {
+ return (side.s == (roads & side.s));
+ }
+
+ @Override
+ public int exitCost()
+ {
+ return 1;
+ }
+
+ @Override
+ public int costFrom(Pawn pawn, Orientation side)
+ {
+ if (side == Orientation.KEEP) return 0;
+ if (hasUnits()) return (Integer.MAX_VALUE / 2);
+ if (road(side)) return 1;
+
+ int c = 0;
+ switch(terrain) {
+ case CLEAR:
+ case HILLS:
+ c = 1;
+ break;
+ case WOODS:
+ case TOWN:
+ c = 2;
+ break;
+ case OFFMAP:
+ case BLOCKED:
+ c = (Integer.MAX_VALUE / 2);
+ break;
+ }
+
+ return c;
+ }
+
+ @Override
+ public int defense()
+ {
+ return (isA(Terrain.TOWN) ? 1 : 0);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/HexSet.java b/core/src/ch/asynk/rustanddust/game/HexSet.java
new file mode 100644
index 0000000..4382fdb
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/HexSet.java
@@ -0,0 +1,29 @@
+package ch.asynk.rustanddust.game;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+import ch.asynk.rustanddust.engine.Tile;
+
+public class HexSet extends LinkedHashSet<Hex>
+{
+ private final Map map;
+
+ public HexSet(Map map, int n)
+ {
+ super(n);
+ this.map = map;
+ }
+
+ public void enable(int i, boolean enable)
+ {
+ for (Hex hex : this)
+ map.enableOverlayOn(hex, i, enable);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Collection<Tile> asTiles()
+ {
+ return (Collection) this;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Hud.java b/core/src/ch/asynk/rustanddust/game/Hud.java
new file mode 100644
index 0000000..8f9343c
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Hud.java
@@ -0,0 +1,307 @@
+package ch.asynk.rustanddust.game;
+
+import java.util.LinkedList;
+
+import com.badlogic.gdx.Gdx;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+import ch.asynk.rustanddust.game.State.StateType;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.ui.Msg;
+import ch.asynk.rustanddust.ui.OkCancel;
+import ch.asynk.rustanddust.ui.Widget;
+import ch.asynk.rustanddust.game.hud.PlayerInfo;
+import ch.asynk.rustanddust.game.hud.ActionButtons;
+import ch.asynk.rustanddust.game.hud.StatisticsPanel;
+import ch.asynk.rustanddust.game.hud.EngagementPanel;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public class Hud implements Disposable, Animation
+{
+ public static final float OFFSET = 10f;
+ public static final float NOTIFY_DURATION = 2f;
+
+ private final RustAndDust game;
+ private final Ctrl ctrl;
+
+ private Object hit;
+
+ public PlayerInfo playerInfo;
+ public ActionButtons actionButtons;
+
+ private Msg msg;
+ private StatisticsPanel stats;
+ private EngagementPanel engagement;
+ private OkCancel okCancel;
+ private LinkedList<Widget> dialogs = new LinkedList<Widget>();
+
+ public enum OkCancelAction
+ {
+ EXIT_BOARD,
+ ABORT_TURN,
+ END_TURN,
+ END_DEPLOYMENT,
+ }
+ private OkCancelAction okCancelAction;
+
+ public Hud(final Ctrl ctrl, final RustAndDust game)
+ {
+ this.game = game;
+ this.ctrl = ctrl;
+
+ TextureAtlas hudAtlas = game.factory.hudAtlas;
+ playerInfo = new PlayerInfo(ctrl, game.fontW, game.uiAtlas, hudAtlas);
+ actionButtons = new ActionButtons(ctrl, game.uiAtlas, hudAtlas);
+ actionButtons.hide();
+ msg = new Msg(game.fontB, game.uiAtlas);
+ okCancel = new OkCancel(game.fontB, game.uiAtlas);
+ stats = new StatisticsPanel(game.fontB, game.uiAtlas);
+ engagement = new EngagementPanel(game.fontB, game.uiAtlas, hudAtlas);
+ }
+
+ @Override
+ public void dispose()
+ {
+ playerInfo.dispose();
+ actionButtons.dispose();
+ msg.dispose();
+ okCancel.dispose();
+ engagement.dispose();
+ stats.dispose();
+ }
+
+ public void resize(int left, int bottom, int width, int height)
+ {
+ Position.update(left, bottom, width, height);
+ playerInfo.updatePosition();
+ actionButtons.updatePosition();
+ msg.updatePosition();
+ stats.updatePosition();
+ engagement.updatePosition();
+ okCancel.updatePosition();
+ }
+
+ public void update()
+ {
+ Position position = ctrl.battle.getHudPosition(ctrl.player);
+ playerInfo.update(ctrl.player, position);
+ actionButtons.update(position.horizontalMirror());
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ msg.animate(delta);
+ playerInfo.animate(delta);
+ engagement.animate(delta);
+ return false;
+ }
+
+ public void draw(Batch batch, boolean debug)
+ {
+ draw(batch);
+ if (debug)
+ game.fontB.draw(batch, String.format("FPS: %d", Gdx.graphics.getFramesPerSecond()), 80, 25);
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ playerInfo.draw(batch);
+ actionButtons.draw(batch);
+ msg.draw(batch);
+ okCancel.draw(batch);
+ engagement.draw(batch);
+ stats.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ playerInfo.drawDebug(debugShapes);
+ actionButtons.drawDebug(debugShapes);
+ msg.drawDebug(debugShapes);
+ okCancel.drawDebug(debugShapes);
+ engagement.drawDebug(debugShapes);
+ stats.drawDebug(debugShapes);
+ }
+
+ public void pushNotify(String s)
+ {
+ notify(s, NOTIFY_DURATION, Position.TOP_CENTER, true);
+ }
+
+ public void notify(String s)
+ {
+ notify(s, NOTIFY_DURATION, Position.TOP_CENTER, false);
+ }
+
+ public void notify(String s, float duration, Position position, boolean push)
+ {
+ if (push) msg.pushWrite(s, duration, position);
+ else msg.write(s, duration, position);
+ }
+
+ public boolean touchDown(float x, float y)
+ {
+ hit = null;
+
+ if (dialogs.size() > 0) {
+ Widget dialog = dialogs.getFirst();
+ if (dialog.hit(x, y)) {
+ hit = dialog;
+ return true;
+ }
+ return false;
+ }
+
+ if (ctrl.isInAnimation())
+ return false;
+
+ if (hit == null) {
+ if (actionButtons.touchDown(x, y))
+ hit = actionButtons;
+ else if (playerInfo.touchDown(x, y))
+ hit = playerInfo;
+ }
+
+ return (hit != null);
+ }
+
+ public boolean touchUp(float x, float y)
+ {
+ if (hit == null)
+ return false;
+
+ if (dialogs.size() > 0) {
+ Widget dialog = dialogs.getFirst();
+ if (hit == dialog) {
+ if (dialog.hit(x, y))
+ closeDialog();
+ hit = null;
+ }
+ } else {
+ if (hit == actionButtons) {
+ actionButtons.touchUp(x, y);
+ }
+ else if (hit == playerInfo) {
+ playerInfo.touchUp(x, y);
+ }
+
+ hit = null;
+ }
+
+ return true;
+ }
+
+ private void closeDialog()
+ {
+ Widget dialog = dialogs.removeFirst();
+ dialog.visible = false;
+
+ if (dialog == okCancel)
+ closeOkCancel();
+ else if (dialog == stats)
+ ctrl.endGame();
+ else if (dialog == engagement)
+ ctrl.engagementPanelClosed();
+
+ if (dialogs.size() > 0)
+ dialogs.getFirst().visible = true;
+ else
+ ctrl.blockMap = false;
+ }
+
+ private void closeOkCancel()
+ {
+ boolean ok = okCancel.ok;
+
+ switch(okCancelAction) {
+ case EXIT_BOARD:
+ ctrl.exitBoard(ok);
+ break;
+ case END_TURN:
+ if (ok)
+ ctrl.endPlayerTurn(false);
+ break;
+ case ABORT_TURN:
+ if (ok)
+ ctrl.endPlayerTurn(true);
+ break;
+ case END_DEPLOYMENT:
+ if (ok)
+ ctrl.endDeployment();
+ break;
+ }
+ }
+
+ public boolean dialogActive()
+ {
+ return (dialogs.size() > 0);
+ }
+
+ private void pushDialog(Widget dialog)
+ {
+ ctrl.blockMap = true;
+ if (dialogs.size() != 0)
+ dialog.visible = false;
+ dialogs.addLast(dialog);
+ }
+
+ public void notifyDeploymentDone()
+ {
+ this.okCancelAction = OkCancelAction.END_TURN;
+ okCancel.show("Deployment Phase completed.");
+ okCancel.noCancel();
+ pushDialog(okCancel);
+ }
+
+ public void notifyNoMoreAP()
+ {
+ this.okCancelAction = OkCancelAction.END_TURN;
+ okCancel.show("No more Action Point left.");
+ okCancel.noCancel();
+ pushDialog(okCancel);
+ }
+
+ public void askExitBoard()
+ {
+ this.okCancelAction = OkCancelAction.EXIT_BOARD;
+ okCancel.show("Do you want this unit to escape the battle field ?");
+ pushDialog(okCancel);
+ }
+
+ public void askEndOfTurn()
+ {
+ this.okCancelAction = OkCancelAction.ABORT_TURN;
+ okCancel.show("You still have Action Points left.\nEnd your Turn anyway ?");
+ pushDialog(okCancel);
+ }
+
+ public void askEndDeployment()
+ {
+ this.okCancelAction = OkCancelAction.END_DEPLOYMENT;
+ okCancel.show("Deployment unit count reached.\nEnd Deployment phase ?");
+ pushDialog(okCancel);
+ }
+
+ public void engagementSummary(Engagement e, float volume)
+ {
+ engagement.show(e, Position.BOTTOM_CENTER, volume);
+ pushDialog(engagement);
+ }
+
+ public void victory(Player winner, Player loser)
+ {
+ stats.show(winner, loser, Position.MIDDLE_CENTER);
+ pushDialog(stats);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Map.java b/core/src/ch/asynk/rustanddust/game/Map.java
new file mode 100644
index 0000000..62f5723
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Map.java
@@ -0,0 +1,659 @@
+package ch.asynk.rustanddust.game;
+
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.assets.AssetManager;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.RustAndDust;
+import ch.asynk.rustanddust.engine.Pawn;
+import ch.asynk.rustanddust.engine.Board;
+import ch.asynk.rustanddust.engine.Tile;
+import ch.asynk.rustanddust.engine.Faction;
+import ch.asynk.rustanddust.engine.Move;
+import ch.asynk.rustanddust.engine.SelectedTile;
+import ch.asynk.rustanddust.engine.ObjectiveSet;
+import ch.asynk.rustanddust.engine.OrderList;
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.engine.Meteorology;
+import ch.asynk.rustanddust.engine.PathBuilder;
+import ch.asynk.rustanddust.engine.gfx.Moveable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+import ch.asynk.rustanddust.engine.gfx.animations.AnimationSequence;
+import ch.asynk.rustanddust.engine.gfx.animations.DiceAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.FireAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.TankFireAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.InfantryFireAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.PromoteAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.DestroyAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.SoundAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.RunnableAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.MoveToAnimation.MoveToAnimationCb;
+
+import ch.asynk.rustanddust.ui.Position;
+
+public abstract class Map extends Board implements MoveToAnimationCb, ObjectiveSet.ObjectiveCb
+{
+ private final Ctrl ctrl;
+
+ public final HexSet possibleMoves;
+ public final PathBuilder pathBuilder;
+
+ public final UnitList moveableUnits;
+ public final UnitList possibleTargets;
+ public final UnitList engagementAssists;
+ public final UnitList activatedUnits;
+ public final UnitList breakUnits;
+ public final ObjectiveSet objectives;
+
+ public final Meteorology meteorology;
+
+ private final DestroyAnimation destroy;
+ private final Sound tankMoveSound;
+ private final Sound infantryMoveSound;
+ private Sound sound;
+ private long soundId = -1;
+
+ private OrderList commands;
+
+ protected abstract void setup();
+
+ public Map(final RustAndDust game, Board.Config cfg, String textureName)
+ {
+ super(game.factory, cfg, game.manager.get(textureName, Texture.class),
+ new SelectedTile(game.manager.get("data/hex.png", Texture.class), new float[] {.2f, .1f, .1f, .1f, .2f, .1f} ));
+ this.ctrl = game.ctrl;
+ this.destroy = new DestroyAnimation();
+ this.tankMoveSound = game.manager.get("sounds/tank_move.mp3", Sound.class);
+ this.infantryMoveSound = game.manager.get("sounds/infantry_move.mp3", Sound.class);
+ DiceAnimation.init(game.manager.get("data/dice.png", Texture.class), 16, 9, game.manager.get("sounds/dice.mp3", Sound.class));
+ PromoteAnimation.init(game.manager.get("data/hud.atlas", TextureAtlas.class),
+ game.manager.get("sounds/promote_us.mp3", Sound.class),
+ game.manager.get("sounds/promote_ge.mp3", Sound.class));
+ FireAnimation.init(
+ game.manager.get("data/infantry_fire.png", Texture.class), 1, 8,
+ game.manager.get("data/tank_fire.png", Texture.class), 1, 8,
+ game.manager.get("data/explosions.png", Texture.class), 16, 8,
+ game.manager.get("sounds/infantry_fire.mp3", Sound.class),
+ game.manager.get("sounds/tank_fire.mp3", Sound.class),
+ game.manager.get("sounds/tank_fire_short.mp3", Sound.class),
+ game.manager.get("sounds/explosion.mp3", Sound.class),
+ game.manager.get("sounds/explosion_short.mp3", Sound.class)
+ );
+
+ setup();
+
+ possibleMoves = new HexSet(this, 40);
+ pathBuilder = new PathBuilder(this, 10, 20, 5, 10);
+ moveableUnits = new UnitList(6);
+
+ possibleTargets = new UnitList(10);
+ engagementAssists = new UnitList(6);
+ activatedUnits = new UnitList(7);
+ breakUnits = new UnitList(4);
+
+ objectives = new ObjectiveSet(this, 4);
+
+ meteorology = new Meteorology();
+ commands = new OrderList();
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ clearAll();
+ destroy.dispose();
+ pathBuilder.dispose();
+ DiceAnimation.free();
+ PromoteAnimation.free();
+ FireAnimation.free();
+ commands.dispose();
+ Command.clearPool();
+ Engagement.clearPool();
+ }
+
+ public void clearAll()
+ {
+ possibleMoves.clear();
+ possibleTargets.clear();
+ pathBuilder.clear();
+ moveableUnits.clear();
+ engagementAssists.clear();
+ activatedUnits.clear();
+ breakUnits.clear();
+ }
+
+ public Hex getHexAt(float x, float y)
+ {
+ return (Hex) getTileAt(x, y);
+ }
+
+ public Hex getHex(int col, int row)
+ {
+ return (Hex) getTile(col, row);
+ }
+
+ public void addObjective(int col, int row, Army army)
+ {
+ addObjective(col, row, army, true);
+ }
+
+ public void addHoldObjective(int col, int row, Army army)
+ {
+ addObjective(col, row, army, false);
+ }
+
+ private void addObjective(int col, int row, Army army, boolean persistent)
+ {
+ Hex hex = getHex(col, row);
+ objectives.add(hex, army, persistent);
+ showObjective(hex, army, !persistent);
+ }
+
+ private void claim(Hex hex, Army army)
+ {
+ showObjective(hex, objectives.claim(hex, army));
+ }
+
+ private void unclaim(Hex hex)
+ {
+ showObjective(hex, objectives.unclaim(hex));
+ }
+
+ public int collectPossibleMoves(Unit unit)
+ {
+ if (!unit.canMove()) {
+ possibleMoves.clear();
+ return 0;
+ }
+ return collectPossibleMoves(unit, possibleMoves.asTiles());
+ }
+
+ public int togglePathBuilderHex(Hex hex)
+ {
+ return pathBuilder.toggleCtrlTile(hex);
+ }
+
+ public int collectPossibleTargets(Unit unit, UnitList foes)
+ {
+ if (!unit.canEngage()) {
+ possibleTargets.clear();
+ return 0;
+ }
+ // return collectPossibleTargets(unit, possibleTargets);
+ return collectPossibleTargets(unit, foes.asPawns(), possibleTargets.asPawns());
+ }
+
+ public int collectMoveableUnits(Unit unit)
+ {
+ if (unit.canHQMove()) {
+ collectMoveAssists(unit, moveableUnits.asPawns());
+ } else {
+ moveableUnits.clear();
+ }
+ if (unit.canMove())
+ moveableUnits.add(unit);
+ return moveableUnits.size();
+ }
+
+ public int collectAttackAssists(Unit unit, Unit target, UnitList units)
+ {
+ int s = collectAttackAssists(unit, target, units.asPawns(), engagementAssists.asPawns());
+ activatedUnits.add(unit);
+ return s;
+ }
+
+ public boolean toggleAttackAssist(Unit unit)
+ {
+ if (activatedUnits.contains(unit)) {
+ activatedUnits.remove(unit);
+ unit.hideAttack();
+ unit.showAttackAssist();
+ return false;
+ } else {
+ activatedUnits.add(unit);
+ unit.showAttack();
+ unit.hideAttackAssist();
+ return true;
+ }
+ }
+
+ public void collectAndShowMovesAndAssits(Unit unit)
+ {
+ hidePossibleMoves();
+ hideMoveableUnits();
+ collectPossibleMoves(unit);
+ collectMoveableUnits(unit);
+ showPossibleMoves();
+ showMoveableUnits();
+ activatedUnits.clear();
+ }
+
+ // -> implement MoveToAnimationCb
+
+ @Override
+ public void moveToAnimationEnter(Moveable moveable, float x, float y, float r)
+ {
+ claim(getHexAt(x, y), (Army) moveable.getFaction());
+ }
+
+ @Override
+ public void moveToAnimationLeave(Moveable moveable, float x, float y, float r)
+ {
+ unclaim(getHexAt(x, y));
+ }
+
+ @Override
+ public void moveToAnimationDone(Moveable moveable, float x, float y, float r)
+ {
+ }
+
+ // <- implement MoveToAnimationCb
+
+ private int process(Unit unit, Move move)
+ {
+ RustAndDust.debug(" Move", String.format("%s %s", move.type, move.toString()));
+
+ int r = 1;
+
+ switch(move.type) {
+ case REGULAR:
+ initMove(unit);
+ movePawn(unit, move, this);
+ r = moveableUnits.size();
+ break;
+ case EXIT:
+ initMove(unit);
+ movePawn(unit, move, this);
+ ctrl.player.unitWithdraw(unit);
+ r = moveableUnits.size();
+ break;
+ case SET:
+ setPawnOnto(unit, move);
+ ctrl.player.unitEntry(unit);
+ claim((Hex) move.to, unit.getArmy());
+ break;
+ case ENTER:
+ enterPawn(unit, move);
+ ctrl.player.unitEntry(unit);
+ claim((Hex) move.to, unit.getArmy());
+ break;
+ default:
+ System.err.println(String.format("process wrong Move type %s", move.type));
+ r = -1;
+ break;
+ }
+
+ return r;
+ }
+
+ private int promoteUnit(final Unit unit, final Player player)
+ {
+ activatedUnits.add(unit);
+
+ Hex hex = unit.getHex();
+ AnimationSequence seq = AnimationSequence.get(2);
+ seq.addAnimation(PromoteAnimation.get((unit.getArmy() == Army.US), hex.getX(), hex.getY(), ctrl.cfg.fxVolume));
+ seq.addAnimation ( RunnableAnimation.get(unit, new Runnable() {
+ @Override
+ public void run() {
+ player.promote(unit);
+ }
+ }));
+ addAnimation(seq);
+ return 1;
+ }
+
+ private int process(Command cmd)
+ {
+ RustAndDust.debug("Command", cmd.toString());
+
+ int r = 1;
+
+ switch(cmd.type) {
+ case MOVE:
+ r = process(cmd.unit, cmd.move);
+ break;
+ case PROMOTE:
+ r = promoteUnit(cmd.unit, cmd.player);
+ break;
+ case ENGAGE:
+ resolveEngagement(cmd.engagement);
+ r = doEngagement(cmd.engagement);
+ break;
+ default:
+ System.err.println(String.format("process wrong Command type %s", cmd.type));
+ r = -1;
+ break;
+ }
+
+ if (r != -1)
+ commands.add(cmd);
+
+ return r;
+ }
+
+ // Ctrl Methods
+
+ public void init()
+ {
+ actionDone();
+ }
+
+ public void turnDone()
+ {
+ RustAndDust.debug("TurnDone", String.format(" Processed Commands : %d", commands.size()));
+
+ if (objectives.modifiedCount() > 0)
+ throw new RuntimeException("objectives not cleared");
+
+ // FIXME do something with these Commands
+ commands.dispose();
+ }
+
+ public void actionDone()
+ {
+ objectives.forget();
+ }
+
+ // STATES ENTRY ->
+
+ public void showOnBoard(final Unit unit, Hex to, Orientation o)
+ {
+ setPawnOnto(unit, to, o);
+ }
+
+ public boolean setOnBoard(final Unit unit, Hex to, Orientation entry)
+ {
+ commands.dispose(unit);
+ return (process(getMoveCommand(unit, Move.getSet(unit, to, entry))) == 1);
+ }
+
+ public boolean enterBoard(final Unit unit, Hex to, int allowedMoves)
+ {
+ Orientation entry = findBestEntry(unit, to, allowedMoves);
+ if (entry == Orientation.KEEP)
+ return false;
+
+ return (process(getMoveCommand(unit, Move.getEnter(unit, to, entry))) == 1);
+ }
+
+ public int exitBoard(final Unit unit)
+ {
+ return process(getMoveCommand(unit, pathBuilder.getExitMove()));
+ }
+
+ public int moveUnit(final Unit unit)
+ {
+ return process(getMoveCommand(unit, pathBuilder.getMove()));
+ }
+
+ public void revertMoves()
+ {
+ for (Unit unit: activatedUnits) {
+ RustAndDust.debug(" revertMove() " + unit);
+ revertLastPawnMove(unit);
+ commands.dispose(unit, Command.CommandType.MOVE);
+ }
+ activatedUnits.clear();
+ objectives.revert(this);
+ }
+
+ public void revertEnter(final Unit unit)
+ {
+ RustAndDust.debug(" revertEnter() "+ unit);
+ removePawn(unit);
+ objectives.revert(this);
+ ctrl.player.revertUnitEntry(unit);
+ commands.dispose(unit);
+ unit.reset();
+ }
+
+ public boolean engageUnit(final Unit unit, final Unit target)
+ {
+ attack(unit, target, true);
+
+ Command cmd = Command.get(ctrl.player);
+ cmd.setEngage(unit, target);
+ return (process(cmd) == 1);
+ }
+
+ public void promoteUnit(final Unit unit)
+ {
+ Command cmd = Command.get(ctrl.player);
+ cmd.setPromote(unit);
+ process(cmd);
+ }
+
+ // STATES ENTRY <-
+
+ private Command getMoveCommand(Unit unit, Move move)
+ {
+ Command cmd = Command.get(ctrl.player);
+ cmd.setMove(unit, move);
+ return cmd;
+ }
+
+ private void initMove(Unit unit)
+ {
+ moveableUnits.remove(unit);
+ activatedUnits.add(unit);
+ playMoveSound(unit);
+ }
+
+ private void playMoveSound(Unit unit)
+ {
+ if (unit.isA(Unit.UnitType.INFANTRY))
+ sound = infantryMoveSound;
+ else
+ sound = tankMoveSound;
+ soundId = sound.play(ctrl.cfg.fxVolume);
+ }
+
+ @Override
+ protected void animationsOver()
+ {
+ if (soundId >= 0) {
+ addAnimation( SoundAnimation.get(SoundAnimation.Action.FADE_OUT, sound, soundId, ctrl.cfg.fxVolume, 0.5f));
+ soundId = -1;
+ return;
+ }
+ ctrl.animationsOver();
+ }
+
+ private void addEngagementAnimation(Unit target)
+ {
+ FireAnimation.reset();
+ Hex to = target.getHex();
+ for (Unit u : activatedUnits) {
+ Hex from = u.getHex();
+ float halfWidth = (u.getWidth() / 2f);
+ if (u.isA(Unit.UnitType.INFANTRY))
+ addAnimation(InfantryFireAnimation.get(ctrl.cfg.fxVolume, from.getX(), from.getY(), to.getX(), to.getY(), halfWidth));
+ else
+ addAnimation(TankFireAnimation.get(ctrl.cfg.fxVolume, from.getX(), from.getY(), to.getX(), to.getY(), halfWidth));
+ }
+ }
+
+ private void resolveEngagement(Engagement e)
+ {
+ int dice = e.d1 + e.d2;
+
+ int distance = 0;
+ boolean mayReroll = false;
+ boolean night = (meteorology.day == Meteorology.Day.NIGHT);
+ boolean flankAttack = false;
+ boolean terrainBonus = true;
+
+ for (Unit unit : activatedUnits) {
+ if (unit != e.attacker)
+ e.addAssist(unit);
+ if (unit.isAce())
+ mayReroll = true;
+ if (unit.isFlankAttack())
+ flankAttack = true;
+ if (unit.isA(Unit.UnitType.INFANTRY))
+ terrainBonus = false;
+ if (night) {
+ if (distance < unit.attackDistance())
+ distance = unit.attackDistance();
+ }
+ }
+
+ int cnt = activatedUnits.size();
+ int def = e.defender.getDefense(e.attacker.getTile());
+ int flk = (flankAttack ? Unit.FLANK_ATTACK_BONUS : 0);
+ int tdf = (terrainBonus ? e.defender.getTile().defense() : 0);
+ int wdf = 0;
+ if (night) {
+ if (distance > 3)
+ wdf = 3;
+ else if (distance > 2)
+ wdf = 2;
+ else if (distance > 1)
+ wdf = 1;
+ }
+ int s1 = (dice + cnt + flk);
+ int s2 = (def + tdf + wdf);
+
+ boolean success = false;
+ if (dice == 2) {
+ success = false;
+ } else if (dice == 12) {
+ success = true;
+ } else {
+ success = (s1 >= s2);
+ }
+ if (!success && mayReroll) {
+ dice = e.d3 + e.d4;
+ s1 = (dice + cnt + flk);
+ if (dice == 2) {
+ success = false;
+ } else if (dice == 12) {
+ success = true;
+ } else {
+ success = (s1 >= s2);
+ }
+ } else {
+ e.d3 = 0;
+ e.d4 = 0;
+ }
+
+ e.set(cnt, flk, def, tdf, wdf);
+ e.success = success;
+ }
+
+ private int doEngagement(Engagement e)
+ {
+ breakUnits.clear();
+ activatedUnits.clear();
+
+ activatedUnits.add(e.attacker);
+ for (Unit u : e.assists)
+ activatedUnits.add(u);
+
+ for (Unit u : activatedUnits) {
+ u.engage();
+ if (u.isA(Unit.UnitType.INFANTRY))
+ breakUnits.add(u);
+ }
+
+ if (e.success) {
+ unclaim(e.defender.getHex());
+ removePawn(e.defender);
+ destroy.set(2f, e.defender);
+ addAnimation(destroy);
+ }
+
+ if ((activatedUnits.size() == 1) && e.attacker.isA(Unit.UnitType.AT_GUN) && e.defender.isHardTarget())
+ activatedUnits.clear();
+
+ ctrl.hud.engagementSummary(e, ctrl.cfg.fxVolume);
+ addEngagementAnimation(e.defender);
+
+ return (e.success ? 1 : 0);
+ }
+
+ // SHOW / HIDE
+
+ public void togglePathOverlay(Hex hex)
+ {
+ boolean enable= !hex.isOverlayEnabled(Hex.MOVE);
+ enableOverlayOn(hex, Hex.MOVE, enable);
+ }
+
+ private void showUnitsOverlay(UnitList units, int overlay, boolean on)
+ {
+ for (Unit unit : units)
+ unit.enableOverlay(overlay, on);
+ }
+
+ public void showMoveableUnits() { showUnitsOverlay(moveableUnits, Unit.MOVE, true); }
+ public void hideMoveableUnits() { showUnitsOverlay(moveableUnits, Unit.MOVE, false); }
+ public void showPossibleTargets() { showUnitsOverlay(possibleTargets, Unit.TARGET, true); }
+ public void hidePossibleTargets() { showUnitsOverlay(possibleTargets, Unit.TARGET, false); }
+ public void showAttackAssists() { showUnitsOverlay(engagementAssists, Unit.MAY_FIRE, true); }
+ public void hideAttackAssists() { showUnitsOverlay(engagementAssists, Unit.FIRE, false);
+ showUnitsOverlay(engagementAssists, Unit.MAY_FIRE, false); }
+ public void showBreakUnits() { showUnitsOverlay(breakUnits, Unit.MOVE, true); }
+ public void hideBreakUnits() { showUnitsOverlay(breakUnits, Unit.MOVE, false); }
+
+ public void showPossibleMoves() { possibleMoves.enable(Hex.AREA, true); }
+ public void hidePossibleMoves() { possibleMoves.enable(Hex.AREA, false); }
+ public void showPathBuilder() { pathBuilder.enable(Hex.AREA, true); }
+ public void hidePathBuilder() { pathBuilder.enable(Hex.AREA, false); }
+ public void showPath(Hex dst) { pathBuilder.enable(Hex.MOVE, true); showMove(dst); }
+ public void hidePath(Hex dst) { pathBuilder.enable(Hex.MOVE, false); hideMove(dst); }
+
+ public void selectHex(Hex hex) { selectedTile.set(hex); }
+ public void unselectHex(Hex hex) { selectedTile.hide(); }
+ public void showMove(Hex hex) { enableOverlayOn(hex, Hex.MOVE, true); }
+ public void hideMove(Hex hex) { enableOverlayOn(hex, Hex.MOVE, false); }
+ public void showDirections(Hex hex) { enableOverlayOn(hex, Hex.DIRECTIONS, true); }
+ public void hideDirections(Hex hex) { enableOverlayOn(hex, Hex.DIRECTIONS, false); }
+ public void showOrientation(Hex hex, Orientation o) { enableOverlayOn(hex, Hex.ORIENTATION, o, true); }
+ public void hideOrientation(Hex hex) { enableOverlayOn(hex, Hex.ORIENTATION, false); }
+ public void showExit(Hex hex) { enableOverlayOn(hex, Hex.EXIT, true); }
+ public void hideExit(Hex hex) { enableOverlayOn(hex, Hex.EXIT, false); }
+
+ public void showObjective(Hex hex, Army army, boolean hold)
+ {
+ if (hold)
+ enableOverlayOn(hex, Hex.OBJECTIVE_HOLD, true);
+ else
+ enableOverlayOn(hex, Hex.OBJECTIVE, true);
+ }
+
+
+ // -> implement ObjectiveSet.ObjectiveCb
+
+ public void showObjective(Tile tile, Faction faction)
+ {
+ showObjective((Hex) tile, (Army) faction);
+ }
+
+ // <- implement MoveToAnimationCb
+
+ public void showObjective(Hex hex, Army army)
+ {
+ if (army == null)
+ army = Army.NONE;
+ switch(army) {
+ case GE:
+ enableOverlayOn(hex, Hex.OBJECTIVE_GE, true);
+ enableOverlayOn(hex, Hex.OBJECTIVE_US, false);
+ break;
+ case US:
+ enableOverlayOn(hex, Hex.OBJECTIVE_GE, false);
+ enableOverlayOn(hex, Hex.OBJECTIVE_US, true);
+ break;
+ case NONE:
+ default:
+ enableOverlayOn(hex, Hex.OBJECTIVE_GE, false);
+ enableOverlayOn(hex, Hex.OBJECTIVE_US, false);
+ break;
+ }
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Player.java b/core/src/ch/asynk/rustanddust/game/Player.java
new file mode 100644
index 0000000..e368101
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Player.java
@@ -0,0 +1,213 @@
+package ch.asynk.rustanddust.game;
+
+import java.util.Random;
+import java.util.List;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public class Player
+{
+ private static final float MOVE_TIME = 0.4f;
+
+ private static Random rand = new Random();
+
+ private int turn;
+ private int apSpent;
+ private int actionPoints;
+ private boolean deploymentDone;
+
+ public Army army;
+ public UnitList units;
+ public UnitList casualties;
+ public UnitList reinforcement;
+ public UnitList withdrawed;
+
+ public int actionCount;
+ public int lostEngagementCount;
+ public int wonEngagementCount;
+
+ public Player(final RustAndDust game, Army army, int n)
+ {
+ this.army = army;
+ this.units = new UnitList(n);
+ this.casualties = new UnitList(n);
+ this.reinforcement = new UnitList(n);
+ this.withdrawed = new UnitList(n);
+ this.turn = 0;
+ this.apSpent = 0;
+ this.actionPoints = 0;
+ this.deploymentDone = false;
+ this.actionCount = 0;
+ this.lostEngagementCount = 0;
+ this.wonEngagementCount = 0;
+ }
+
+ public String getName()
+ {
+ return army.toString();
+ }
+
+ public String toString()
+ {
+ return String.format("%s Turn:%d AP:%d units:%d casualties:%d", army, turn, actionPoints, units.size(), casualties.size());
+ }
+
+ public String getStats()
+ {
+ return String.format("%s\n%4d\n%4d\n%4d\n%4d\n%4d\n%4d", getName(), actionCount, unitsLeft(), withdrawed(), casualties(), wonEngagementCount, lostEngagementCount);
+ }
+
+ public boolean is(Army army)
+ {
+ return (this.army == army);
+ }
+
+ public boolean isEnemy(Unit unit)
+ {
+ return unit.isEnemy(army);
+ }
+
+ public boolean isEnemy(Army other)
+ {
+ return army.isEnemy(other);
+ }
+
+ public int unitsLeft()
+ {
+ return (units.size() + reinforcement.size());
+ }
+
+ public int reinforcement()
+ {
+ return reinforcement.size();
+ }
+
+ public int casualties()
+ {
+ return casualties.size();
+ }
+
+ public int withdrawed()
+ {
+ return withdrawed.size();
+ }
+
+ public void addUnit(Unit unit)
+ {
+ units.add(unit);
+ }
+
+ public void addReinforcement(Unit unit)
+ {
+ reinforcement.add(unit);
+ }
+
+ public void unitEntry(Unit unit)
+ {
+ reinforcement.remove(unit);
+ units.add(unit);
+ }
+
+ public void revertUnitEntry(Unit unit)
+ {
+ units.remove(unit);
+ reinforcement.add(unit);
+ }
+
+ public void casualty(Unit unit)
+ {
+ units.remove(unit);
+ casualties.add(unit);
+ }
+
+ public void unitWithdraw(Unit unit)
+ {
+ units.remove(unit);
+ withdrawed.add(unit);
+ }
+
+ public int getAp()
+ {
+ return ((apSpent < actionPoints) ? (apSpent + 1) : apSpent);
+ }
+
+ public int getTurnDone()
+ {
+ return turn;
+ }
+
+ public int getCurrentTurn()
+ {
+ return (turn + 1);
+ }
+
+ public boolean apExhausted()
+ {
+ return (apSpent == actionPoints);
+ }
+
+ public boolean isDeploymentDone()
+ {
+ return (deploymentDone || (reinforcement.size() == 0));
+ }
+
+ public void burnDownOneAp()
+ {
+ apSpent += 1;
+ actionCount += 1;
+ if (apSpent > actionPoints) RustAndDust.debug("ERROR: spent too much AP, please report");
+ }
+
+ public void turnEnd()
+ {
+ if (deploymentDone)
+ turn += 1;
+ else
+ deploymentDone = (reinforcement.size() == 0);
+ for (Unit unit : units)
+ unit.reset();
+ }
+
+ public void turnStart()
+ {
+ if (isDeploymentDone())
+ computeActionPoints();
+ }
+
+ public int d6()
+ {
+ return rand.nextInt(6) + 1;
+ }
+
+ private void computeActionPoints()
+ {
+ this.actionPoints = 2;
+ if (d6() > 2) {
+ this.actionPoints += 1;
+ if (d6() > 3)
+ this.actionPoints += 1;
+ }
+ apSpent = 0;
+ }
+
+ public boolean canPromote(Unit unit)
+ {
+ if (unit.isHq()) return false;
+ for (Unit p: casualties)
+ if (p.isHqOf(unit)) return true;
+ return false;
+ }
+
+ public boolean promote(Unit unit)
+ {
+ for (Unit p: casualties) {
+ if (p.isHqOf(unit)) {
+ unit.promote();
+ p.degrade();
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/State.java b/core/src/ch/asynk/rustanddust/game/State.java
new file mode 100644
index 0000000..db0d6af
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/State.java
@@ -0,0 +1,35 @@
+package ch.asynk.rustanddust.game;
+
+public interface State
+{
+ enum StateType {
+ SELECT,
+ MOVE,
+ ROTATE,
+ ENGAGE,
+ BREAK,
+ PROMOTE,
+ ANIMATION,
+ REINFORCEMENT,
+ DEPLOYMENT,
+ WITHDRAW,
+ ABORT,
+ DONE
+ };
+
+ public void enter(StateType prevState);
+
+ public void leave(StateType nextState);
+
+ public StateType abort();
+
+ public StateType execute();
+
+ public void touchDown();
+
+ public void touchUp();
+
+ public boolean downInMap(float x, float y);
+
+ public boolean upInMap(float x, float y);
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Unit.java b/core/src/ch/asynk/rustanddust/game/Unit.java
new file mode 100644
index 0000000..472bd20
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Unit.java
@@ -0,0 +1,361 @@
+package ch.asynk.rustanddust.game;
+
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.rustanddust.engine.Pawn;
+import ch.asynk.rustanddust.engine.Tile;
+import ch.asynk.rustanddust.engine.HeadedPawn;
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.game.Hex.Terrain;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public class Unit extends HeadedPawn
+{
+ public static final int MOVE = 0;
+ public static final int TARGET = 1;
+ public static final int FIRE = 2;
+ public static final int MAY_FIRE = 3;
+ public static final int ACE = 4;
+ public static final int HQ = 5;
+ public static final int HAS_FIRED = 6;
+ public static final int HAS_MOVED = 7;
+
+ public static final int FLANK_ATTACK_BONUS = 1;
+
+ public enum UnitType implements Pawn.PawnType
+ {
+ HARD_TARGET,
+ HARD_TARGET_HQ,
+ INFANTRY,
+ AT_GUN,
+ ARTILLERY
+ }
+
+ public enum UnitId implements Pawn.PawnId
+ {
+ GE_AT_GUN("German Anti-Tank Gun"),
+ GE_INFANTRY("German Infantry"),
+ GE_KINGTIGER("German King Tiger"),
+ GE_PANZER_IV("German Panzer IV"),
+ GE_PANZER_IV_HQ("German Panzer IV HQ"),
+ GE_TIGER("German Tiger"),
+ GE_WESPE("German Wespe"),
+
+ US_AT_GUN("US Anti-Tank Gun"),
+ US_INFANTRY("US Infantry"),
+ US_PERSHING("US Pershing"),
+ US_PERSHING_HQ("US Pershing HQ"),
+ US_PRIEST("US Priest"),
+ US_SHERMAN("US Sherman"),
+ US_SHERMAN_HQ("US Sherman HQ"),
+ US_WOLVERINE("US Wolverine");
+
+ private String s;
+ UnitId(String s) { this.s = s; }
+ public String toString() { return s; }
+ }
+
+ public int rng;
+ public int def;
+ public int cdef;
+ public int mp;
+ public int mpLeft;
+ public UnitType type;
+ public UnitId id;
+ public boolean ace;
+ private boolean hasMoved;
+ private boolean hasFired;
+
+ protected Unit(Army army, String pawn, String head, TextureAtlas pawns, TextureAtlas overlays)
+ {
+ super(army, pawn, head, pawns, overlays);
+ ace = false;
+
+ }
+
+ private void commonSetup()
+ {
+ mpLeft = mp;
+ enableOverlay(HQ, isHq());
+ this.hasMoved = false;
+ this.hasFired = false;
+ updateDescr();
+ }
+
+ private void updateDescr()
+ {
+ if (cdef == -1)
+ this.descr = id.toString() + (ace ? " Ace " : "") + " (" + rng + "-" + def + "-" + mp + ")";
+ else
+ this.descr = id.toString() + (ace ? " Ace " : "") + " (" + rng + "-" + def + "/" + cdef + "-" + mp + ")";
+ }
+
+ // hard tager
+ public Unit(Army army, UnitId id, UnitType type, int range, int defense, int movementPoints, String unit, String head, TextureAtlas pawns, TextureAtlas overlays)
+ {
+ this(army, unit, head, pawns, overlays);
+ this.rng = range;
+ this.def = defense;
+ this.cdef = -1;
+ this.mp = movementPoints;
+ this.id = id;
+ this.type = type;
+ commonSetup();
+ }
+
+ // soft tager
+ public Unit(Army army, UnitId id, UnitType type, int range, int defense, int concealedDefense, int movementPoints, String unit, String head, TextureAtlas pawns, TextureAtlas overlays)
+ {
+ this(army, unit, head, pawns, overlays);
+ this.rng = range;
+ this.def = defense;
+ this.cdef = concealedDefense;
+ this.mp = movementPoints;
+ this.id = id;
+ this.type = type;
+ commonSetup();
+ }
+
+ public Army getArmy()
+ {
+ return (Army) getFaction();
+ }
+
+ public Hex getHex()
+ {
+ return (Hex) getTile();
+ }
+
+ public boolean isAce()
+ {
+ return ace;
+ }
+
+ public void setAce(boolean ace)
+ {
+ this.ace = ace;
+ updateDescr();
+ enableOverlay(ACE, ace);
+ }
+
+ @Override
+ public int getMovementPoints()
+ {
+ return mpLeft;
+ }
+
+ @Override
+ public int getRoadMarchBonus()
+ {
+ return 1;
+ }
+
+ @Override
+ public int getEngagementRangeFrom(Tile tile)
+ {
+ if (!isA(UnitType.INFANTRY) && tile.isA(Terrain.HILLS))
+ return rng + 1;
+ return rng;
+ }
+
+ @Override
+ public int getAngleOfAttack()
+ {
+ return orientation.getFrontSides();
+ }
+
+ @Override
+ public int getFlankSides()
+ {
+ return orientation.getBackSides();
+ }
+
+ @Override
+ public int getDefense(Tile tile)
+ {
+ if (!isHardTarget() && (tile.isA(Terrain.HILLS) || tile.isA(Terrain.WOODS) || tile.isA(Terrain.TOWN)))
+ return cdef;
+
+ return def;
+ }
+
+ @Override
+ public boolean isUnit()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isA(PawnId i)
+ {
+ return (id == i);
+ }
+
+ @Override
+ public boolean isA(PawnType t)
+ {
+ return (type == t);
+ }
+
+ @Override
+ public boolean isHq()
+ {
+ return isA(UnitType.HARD_TARGET_HQ);
+ }
+
+ @Override
+ public boolean isHqOf(Pawn other)
+ {
+ if (isA(UnitId.GE_PANZER_IV_HQ) && other.isA(UnitId.GE_PANZER_IV)) return true;
+ if (isA(UnitId.US_PERSHING_HQ) && other.isA(UnitId.US_PERSHING)) return true;
+ if (isA(UnitId.US_SHERMAN_HQ) && other.isA(UnitId.US_SHERMAN)) return true;
+ return false;
+ }
+
+ public void promote()
+ {
+ if (isA(UnitId.GE_PANZER_IV))
+ id = UnitId.GE_PANZER_IV_HQ;
+ else if (isA(UnitId.US_PERSHING))
+ id = UnitId.US_PERSHING_HQ;
+ else if (isA(UnitId.US_SHERMAN))
+ id = UnitId.US_SHERMAN_HQ;
+ else
+ return;
+
+ type = UnitType.HARD_TARGET_HQ;
+ enableOverlay(HQ, true);
+ updateDescr();
+ }
+
+ public void degrade()
+ {
+ if (isA(UnitId.GE_PANZER_IV_HQ))
+ id = UnitId.GE_PANZER_IV;
+ else if (isA(UnitId.US_PERSHING_HQ))
+ id = UnitId.US_PERSHING;
+ else if (isA(UnitId.US_SHERMAN_HQ))
+ id = UnitId.US_SHERMAN;
+ else
+ return;
+
+ type = UnitType.HARD_TARGET;
+ enableOverlay(HQ, false);
+ updateDescr();
+ }
+
+ @Override
+ public boolean isHardTarget()
+ {
+ return (isA(UnitType.HARD_TARGET) || isA(UnitType.HARD_TARGET_HQ) || isA(UnitType.ARTILLERY));
+ }
+
+ @Override
+ public boolean canRotate()
+ {
+ return canMove();
+ }
+
+ @Override
+ public boolean canMove()
+ {
+ if (isHardTarget()) return !hasMoved;
+ return (!hasMoved && !hasFired);
+ }
+
+ @Override
+ public boolean canEngage()
+ {
+ if (isHardTarget()) return !hasFired;
+ return (!hasMoved && !hasFired);
+ }
+
+ @Override
+ public boolean canAssistEngagementWithoutLos()
+ {
+ return isA(UnitType.ARTILLERY);
+ }
+
+ @Override
+ public boolean canEngage(Pawn other)
+ {
+ return (isEnemy(other) && canEngage());
+ }
+
+ public boolean canHQMove()
+ {
+ return (isHq() && ((move == null) || (!move.isEnter())));
+ }
+
+ public void setMoved()
+ {
+ hasMoved = true;
+ updateOverlays();
+ }
+
+ @Override
+ public void move()
+ {
+ int cost = move.cost;
+
+ if (move.roadMarch && (cost > mpLeft))
+ cost -= getRoadMarchBonus();
+
+ if (cost > mpLeft)
+ RustAndDust.debug("ERROR: Movement point exceeded: " + cost + "/" + mpLeft + " please report");
+
+ if (move.isFinal())
+ setMoved();
+
+ mpLeft -= cost;
+ }
+
+ @Override
+ public void engage()
+ {
+ hasFired = true;
+ updateOverlays();
+ }
+
+ @Override
+ public void reset()
+ {
+ super.reset();
+ mpLeft = mp;
+ hasFired = false;
+ hasMoved = false;
+ hideHasMoved();
+ hideHasFired();
+ }
+
+ @Override
+ public void revertLastMove()
+ {
+ hasMoved = false;
+ mpLeft = mp;
+ updateOverlays();
+ move = null;
+ }
+
+ private void updateOverlays()
+ {
+ enableOverlay(HAS_MOVED, !canMove());
+ enableOverlay(HAS_FIRED, !canEngage());
+ }
+
+ // SHOW / HIDE
+ public void showMoveable() { enableOverlay(MOVE, true); }
+ public void hideMoveable() { enableOverlay(MOVE, false); }
+ public void showTarget() { enableOverlay(TARGET, true); }
+ public void hideTarget() { enableOverlay(TARGET, false); }
+ public void showAttack() { enableOverlay(FIRE, true); }
+ public void hideAttack() { enableOverlay(FIRE, false); }
+ public void showAttackAssist() { enableOverlay(MAY_FIRE, true); }
+ public void hideAttackAssist() { enableOverlay(MAY_FIRE, false); }
+ public void showHasMoved() { enableOverlay(HAS_MOVED, true); }
+ public void hideHasMoved() { enableOverlay(HAS_MOVED, false); }
+ public void showHasFired() { enableOverlay(HAS_FIRED, true); }
+ public void hideHasFired() { enableOverlay(HAS_FIRED, false); }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/UnitList.java b/core/src/ch/asynk/rustanddust/game/UnitList.java
new file mode 100644
index 0000000..9637036
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/UnitList.java
@@ -0,0 +1,20 @@
+package ch.asynk.rustanddust.game;
+
+import java.util.Collection;
+import java.util.ArrayList;
+
+import ch.asynk.rustanddust.engine.Pawn;
+
+public class UnitList extends ArrayList<Unit>
+{
+ public UnitList(int n)
+ {
+ super(n);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Collection<Pawn> asPawns()
+ {
+ return (Collection) this;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/Zone.java b/core/src/ch/asynk/rustanddust/game/Zone.java
new file mode 100644
index 0000000..ff15299
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/Zone.java
@@ -0,0 +1,14 @@
+package ch.asynk.rustanddust.game;
+
+import ch.asynk.rustanddust.engine.Orientation;
+
+public class Zone extends HexSet
+{
+ public int allowedMoves;
+ public Orientation orientation;
+
+ public Zone(Map map, int n)
+ {
+ super(map, n);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleCommon.java b/core/src/ch/asynk/rustanddust/game/battles/BattleCommon.java
new file mode 100644
index 0000000..731c616
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/BattleCommon.java
@@ -0,0 +1,151 @@
+package ch.asynk.rustanddust.game.battles;
+
+import java.util.Random;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Battle;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.HexSet;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Unit.UnitId;
+import ch.asynk.rustanddust.game.State.StateType;
+
+public abstract class BattleCommon implements Battle
+{
+ protected final static Random random = new Random();
+
+ protected Factory.MapType mapType;
+ protected String name;
+ protected String description;
+ protected Factory factory;
+ protected Player usPlayer;
+ protected Player gePlayer;
+ protected ArrayList<Zone> entryZone = new ArrayList<Zone>();
+ protected ArrayList<Zone> exitZone = new ArrayList<Zone>();
+ protected HashMap<Unit, Zone> unitEntry = new HashMap<Unit, Zone>();
+ protected HashMap<Unit, Zone> unitExit = new HashMap<Unit, Zone>();
+
+ public BattleCommon(Factory factory)
+ {
+ this.factory = factory;
+ }
+
+ @Override
+ public void init()
+ {
+ this.usPlayer = factory.getPlayer(Army.US);
+ this.gePlayer = factory.getPlayer(Army.GE);
+ }
+
+ @Override
+ public String toString()
+ {
+ return getName();
+ }
+
+ @Override
+ public String getName()
+ {
+ return name;
+ }
+
+ @Override
+ public String getDescription()
+ {
+ return description;
+ }
+
+ @Override
+ public Factory.MapType getMapType()
+ {
+ return mapType;
+ }
+
+ @Override
+ public Map getMap()
+ {
+ return factory.getMap(mapType);
+ }
+
+ @Override
+ public Player opponent(Player player)
+ {
+ if (player == usPlayer)
+ return gePlayer;
+ return usPlayer;
+ }
+
+ @Override
+ public boolean deploymentDone(Player player)
+ {
+ return player.isDeploymentDone();
+ }
+
+ @Override
+ public StateType getState(Player player)
+ {
+ if (!player.isDeploymentDone())
+ return StateType.DEPLOYMENT;
+ return StateType.SELECT;
+ }
+
+ @Override
+ public boolean getReinforcement(Ctrl ctrl, Map map)
+ {
+ return false;
+ }
+
+ @Override
+ public Zone getEntryZone(Unit unit)
+ {
+ return unitEntry.get(unit);
+ }
+
+ @Override
+ public Zone getExitZone(Unit unit)
+ {
+ return unitExit.get(unit);
+ }
+
+ public void addEntryZone(Zone entry)
+ {
+ entryZone.add(entry);
+ }
+
+ public void addExitZone(Zone exit)
+ {
+ exitZone.add(exit);
+ exit.enable(Hex.EXIT, true);
+ }
+
+ public void addReinforcement(Player player, Zone entryZone, UnitId unitId)
+ {
+ addReinforcement(player, entryZone, unitId, false);
+ }
+
+ public void addReinforcement(Player player, Zone entryZone, Zone exitZone, UnitId unitId)
+ {
+ addReinforcement(player, entryZone, exitZone, unitId, false);
+ }
+
+ public void addReinforcement(Player player, Zone entryZone, UnitId unitId, boolean ace)
+ {
+ addReinforcement(player, entryZone, null, unitId, ace);
+ }
+
+ public void addReinforcement(Player player, Zone entryZone, Zone exitZone, UnitId unitId, boolean ace)
+ {
+ Unit unit = factory.getUnit(unitId);
+ unit.setAce(ace);
+ player.addReinforcement(unit);
+ unitEntry.put(unit, entryZone);
+ if (exitZone != null)
+ unitExit.put(unit, exitZone);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleCounterAttack.java b/core/src/ch/asynk/rustanddust/game/battles/BattleCounterAttack.java
new file mode 100644
index 0000000..cea1d13
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/BattleCounterAttack.java
@@ -0,0 +1,150 @@
+package ch.asynk.rustanddust.game.battles;
+
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Unit.UnitId;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.engine.Orientation;
+
+public class BattleCounterAttack extends BattleCommon
+{
+ public BattleCounterAttack(Factory factory)
+ {
+ super(factory);
+ name = "Counterattack";
+ mapType = Factory.MapType.MAP_B;
+ }
+
+ @Override
+ public Position getHudPosition(Player player)
+ {
+ return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT);
+ }
+
+ @Override
+ public Player getPlayer()
+ {
+ if (!gePlayer.isDeploymentDone())
+ return gePlayer;
+ if (!usPlayer.isDeploymentDone())
+ return usPlayer;
+ if (gePlayer.getTurnDone() == usPlayer.getTurnDone())
+ return gePlayer;
+ return usPlayer;
+ }
+
+ public Player checkVictory(Ctrl ctrl)
+ {
+ if (ctrl.opponent.unitsLeft() == 0)
+ return ctrl.player;
+
+ if (gePlayer.withdrawed() >= 3)
+ return gePlayer;
+
+ if ((ctrl.player.getTurnDone() < 9) || (ctrl.opponent.getTurnDone() < 9))
+ return null;
+
+ return usPlayer;
+ }
+
+ @Override
+ public boolean getReinforcement(Ctrl ctrl, Map map)
+ {
+ if (ctrl.player.is(Army.GE))
+ return false;
+ if (ctrl.player.getCurrentTurn() != 5)
+ return false;
+
+ // hex row 1
+ Zone usEntry = new Zone(map, 9);
+ usEntry.allowedMoves = (Orientation.SOUTH.s | Orientation.SOUTH_EAST.s | Orientation.SOUTH_WEST.s);
+ usEntry.add(map.getHex(9, 0));
+ usEntry.add(map.getHex(9, 1));
+ usEntry.add(map.getHex(10, 2));
+ usEntry.add(map.getHex(10, 3));
+ usEntry.add(map.getHex(11, 4));
+ usEntry.add(map.getHex(11, 5));
+ usEntry.add(map.getHex(12, 6));
+ usEntry.add(map.getHex(12, 7));
+ usEntry.add(map.getHex(13, 8));
+ addEntryZone(usEntry);
+
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_PRIEST);
+
+ return true;
+ }
+
+ @Override
+ public void setup(Ctrl ctrl, Map map)
+ {
+ // hex row 1
+ Zone geExit = new Zone(map, 9);
+ geExit.orientation = Orientation.NORTH;
+ geExit.add(map.getHex(9, 0));
+ geExit.add(map.getHex(9, 1));
+ geExit.add(map.getHex(10, 2));
+ geExit.add(map.getHex(10, 3));
+ geExit.add(map.getHex(11, 4));
+ geExit.add(map.getHex(11, 5));
+ geExit.add(map.getHex(12, 6));
+ geExit.add(map.getHex(12, 7));
+ geExit.add(map.getHex(13, 8));
+ addExitZone(geExit);
+
+ // hex rows 8-9
+ Zone geEntry = new Zone(map, 18);
+ geEntry.orientation = Orientation.NORTH;
+ for (int i = 0; i < 2; i++) {
+ geEntry.add(map.getHex((1 + i), 0));
+ geEntry.add(map.getHex((1 + i), 1));
+ geEntry.add(map.getHex((2 + i), 2));
+ geEntry.add(map.getHex((2 + i), 3));
+ geEntry.add(map.getHex((3 + i), 4));
+ geEntry.add(map.getHex((3 + i), 5));
+ geEntry.add(map.getHex((4 + i), 6));
+ geEntry.add(map.getHex((4 + i), 7));
+ geEntry.add(map.getHex((5 + i), 8));
+ }
+ addEntryZone(geEntry);
+
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_TIGER);
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_TIGER);
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_WESPE);
+
+ // hex rows 1-4
+ Zone usEntry = new Zone(map, 36);
+ usEntry.orientation = Orientation.SOUTH;
+ for (int i = 0; i < 4; i++) {
+ usEntry.add(map.getHex((6 + i), 0));
+ usEntry.add(map.getHex((6 + i), 1));
+ usEntry.add(map.getHex((7 + i), 2));
+ usEntry.add(map.getHex((7 + i), 3));
+ usEntry.add(map.getHex((8 + i), 4));
+ usEntry.add(map.getHex((8 + i), 5));
+ usEntry.add(map.getHex((9 + i), 6));
+ usEntry.add(map.getHex((9 + i), 7));
+ usEntry.add(map.getHex((10 + i), 8));
+ }
+ addEntryZone(usEntry);
+
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE);
+ addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleFrontalAssault.java b/core/src/ch/asynk/rustanddust/game/battles/BattleFrontalAssault.java
new file mode 100644
index 0000000..372e045
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/BattleFrontalAssault.java
@@ -0,0 +1,124 @@
+package ch.asynk.rustanddust.game.battles;
+
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.HexSet;
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Unit.UnitId;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.engine.Orientation;
+
+public class BattleFrontalAssault extends BattleCommon
+{
+ public BattleFrontalAssault(Factory factory)
+ {
+ super(factory);
+ name = "Frontal Assault";
+ mapType = Factory.MapType.MAP_A;
+ }
+
+
+ @Override
+ public Player getPlayer()
+ {
+ if (!gePlayer.isDeploymentDone()) {
+ int n = gePlayer.reinforcement();
+ if (n > 4)
+ return gePlayer;
+ else {
+ if (usPlayer.isDeploymentDone())
+ return gePlayer;
+ else
+ return usPlayer;
+ }
+ }
+ if (gePlayer.getTurnDone() == usPlayer.getTurnDone())
+ return usPlayer;
+ return gePlayer;
+ }
+
+ @Override
+ public Position getHudPosition(Player player)
+ {
+ return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT);
+ }
+
+ @Override
+ public boolean deploymentDone(Player player)
+ {
+ if (player.isDeploymentDone())
+ return true;
+ return ((player.is(Army.GE) && (gePlayer.reinforcement.size() == 4)));
+ }
+
+ @Override
+ public Player checkVictory(Ctrl ctrl)
+ {
+ if (ctrl.opponent.unitsLeft() == 0)
+ return ctrl.player;
+
+ if ((ctrl.player.getTurnDone() < 10) || (ctrl.opponent.getTurnDone() < 10))
+ return null;
+
+ if (ctrl.map.objectives.count(Army.US) >= 2)
+ return usPlayer;
+ else
+ return gePlayer;
+ }
+
+ @Override
+ public void setup(Ctrl ctrl, Map map)
+ {
+ // G9, E6, H4
+ map.addObjective(2, 2, Army.NONE);
+ map.addObjective(6, 4, Army.NONE);
+ map.addObjective(6, 1, Army.NONE);
+
+ // hex rows E-H
+ Zone geEntry = new Zone(map, 38);
+ geEntry.orientation = Orientation.NORTH_WEST;
+ for (int i = 2; i < 12; i++)
+ geEntry.add(map.getHex(i, 4));
+ for (int i = 2; i < 11; i++)
+ geEntry.add(map.getHex(i, 3));
+ for (int i = 1; i < 11; i++)
+ geEntry.add(map.getHex(i, 2));
+ for (int i = 1; i < 10; i++)
+ geEntry.add(map.getHex(i, 1));
+ addEntryZone(geEntry);
+
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+
+ // hex rows A-B
+ Zone usEntry = new Zone(map, 19);
+ usEntry.orientation = Orientation.SOUTH_EAST;
+ for (int i = 4; i < 14; i++)
+ usEntry.add(map.getHex(i, 8));
+ for (int i = 4; i < 13; i++)
+ usEntry.add(map.getHex(i, 7));
+ addEntryZone(usEntry);
+
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE);
+ addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_PRIEST);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleHeadToHead.java b/core/src/ch/asynk/rustanddust/game/battles/BattleHeadToHead.java
new file mode 100644
index 0000000..0d88846
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/BattleHeadToHead.java
@@ -0,0 +1,118 @@
+package ch.asynk.rustanddust.game.battles;
+
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.HexSet;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Unit.UnitId;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.engine.Orientation;
+
+public class BattleHeadToHead extends BattleCommon
+{
+ private Army firstArmy;
+
+ public BattleHeadToHead(Factory factory)
+ {
+ super(factory);
+ name = "Head To Head";
+ firstArmy = ((random.nextInt(2) == 0) ? Army.US : Army.GE);
+ mapType = Factory.MapType.MAP_A;
+ }
+
+ @Override
+ public Player getPlayer()
+ {
+ if (gePlayer.getTurnDone() == usPlayer.getTurnDone())
+ return ((firstArmy == Army.US) ? usPlayer : gePlayer);
+ else
+ return ((firstArmy == Army.US) ? gePlayer : usPlayer);
+ }
+
+ @Override
+ public Position getHudPosition(Player player)
+ {
+ return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT);
+ }
+
+ @Override
+ public Player checkVictory(Ctrl ctrl)
+ {
+ if (ctrl.opponent.unitsLeft() == 0)
+ return ctrl.player;
+
+ if ((ctrl.player.getTurnDone() < 10) || (ctrl.opponent.getTurnDone() < 10))
+ return null;
+
+ if (ctrl.map.objectives.count(Army.US) >= 2)
+ return usPlayer;
+ if (ctrl.map.objectives.count(Army.GE) >= 2)
+ return gePlayer;
+
+ return null;
+ }
+
+ @Override
+ public void setup(Ctrl ctrl, Map map)
+ {
+ // end deployment
+ usPlayer.turnEnd();
+ gePlayer.turnEnd();
+
+ // B6, E6, H4
+ map.addObjective(7, 7, Army.NONE);
+ map.addObjective(6, 4, Army.NONE);
+ map.addObjective(6, 1, Army.NONE);
+
+ // southern hex row
+ Zone geEntry = new Zone(map, 9);
+ geEntry.allowedMoves = (Orientation.NORTH.s | Orientation.NORTH_EAST.s | Orientation.NORTH_WEST.s);
+ geEntry.add(map.getHex(0, 0));
+ geEntry.add(map.getHex(1, 1));
+ geEntry.add(map.getHex(1, 2));
+ geEntry.add(map.getHex(2, 3));
+ geEntry.add(map.getHex(2, 4));
+ geEntry.add(map.getHex(3, 5));
+ geEntry.add(map.getHex(3, 6));
+ geEntry.add(map.getHex(4, 7));
+ geEntry.add(map.getHex(4, 8));
+ addEntryZone(geEntry);
+
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+
+ // northern hex row
+ Zone usEntry = new Zone(map, 9);
+ usEntry.allowedMoves = (Orientation.SOUTH.s | Orientation.SOUTH_EAST.s | Orientation.SOUTH_WEST.s);
+ usEntry.add(map.getHex(9, 0));
+ usEntry.add(map.getHex(9, 1));
+ usEntry.add(map.getHex(10, 2));
+ usEntry.add(map.getHex(10, 3));
+ usEntry.add(map.getHex(11, 4));
+ usEntry.add(map.getHex(11, 5));
+ usEntry.add(map.getHex(12, 6));
+ usEntry.add(map.getHex(12, 7));
+ usEntry.add(map.getHex(13, 8));
+ addEntryZone(usEntry);
+
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE);
+ addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_PRIEST);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleLastStand.java b/core/src/ch/asynk/rustanddust/game/battles/BattleLastStand.java
new file mode 100644
index 0000000..f898ce9
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/BattleLastStand.java
@@ -0,0 +1,136 @@
+package ch.asynk.rustanddust.game.battles;
+
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.HexSet;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Unit.UnitId;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.engine.Orientation;
+
+public class BattleLastStand extends BattleCommon
+{
+ public BattleLastStand(Factory factory)
+ {
+ super(factory);
+ name = "Last Stand";
+ mapType = Factory.MapType.MAP_B;
+ }
+
+ @Override
+ public Position getHudPosition(Player player)
+ {
+ return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT);
+ }
+
+ @Override
+ public Player getPlayer()
+ {
+ if (!gePlayer.isDeploymentDone())
+ return gePlayer;
+ if (!usPlayer.isDeploymentDone())
+ return usPlayer;
+ if (gePlayer.getTurnDone() == usPlayer.getTurnDone())
+ return usPlayer;
+ return gePlayer;
+ }
+
+ public Player checkVictory(Ctrl ctrl)
+ {
+ if (ctrl.opponent.unitsLeft() == 0)
+ return ctrl.player;
+
+ if ((ctrl.player.getTurnDone() < 8) || (ctrl.opponent.getTurnDone() < 8))
+ return null;
+
+ int gePoints = usPlayer.casualties();
+ int usPoints = gePlayer.casualties();
+ usPoints += ctrl.map.objectives.count(Army.US);
+ for (Unit unit : gePlayer.casualties) {
+ if (unit.isAce())
+ usPoints += 1;
+ }
+
+ if (usPoints > gePoints)
+ return usPlayer;
+ else
+ return gePlayer;
+ }
+
+ @Override
+ public void setup(Ctrl ctrl, Map map)
+ {
+ // A7, E6, F6, G10
+ map.addObjective(7, 8, Army.NONE);
+ map.addObjective(6, 4, Army.NONE);
+ map.addObjective(5, 3, Army.NONE);
+ map.addObjective(1, 2, Army.NONE);
+
+ // 1 hex of E7
+ Zone geEntry = new Zone(map, 7);
+ geEntry.orientation = Orientation.NORTH;
+ geEntry.add(map.getHex(5, 5));
+ geEntry.add(map.getHex(4, 4));
+ geEntry.add(map.getHex(4, 3));
+ geEntry.add(map.getHex(5, 3));
+ geEntry.add(map.getHex(6, 4));
+ geEntry.add(map.getHex(6, 5));
+ geEntry.add(map.getHex(5, 4));
+ addEntryZone(geEntry);
+
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER, true);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER);
+
+ // hex rows 7-10
+ geEntry = new Zone(map, 32);
+ geEntry.orientation = Orientation.NORTH;
+ for (int i = 0; i < 4; i++) {
+ geEntry.add(map.getHex(i, 0));
+ geEntry.add(map.getHex((i + 1), 2));
+ geEntry.add(map.getHex((i + 2), 4));
+ geEntry.add(map.getHex((i + 3), 6));
+ geEntry.add(map.getHex((i + 4), 8));
+ }
+ for (int i = 0; i < 3; i++) {
+ geEntry.add(map.getHex((i + 1), 1));
+ geEntry.add(map.getHex((i + 2), 3));
+ geEntry.add(map.getHex((i + 3), 5));
+ geEntry.add(map.getHex((i + 4), 7));
+ }
+ addEntryZone(geEntry);
+
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_WESPE);
+
+ // hex rows hex row 1 + E2 + C2
+ Zone usEntry = new Zone(map, 11);
+ usEntry.orientation = Orientation.SOUTH;
+ usEntry.add(map.getHex(9, 0));
+ usEntry.add(map.getHex(9, 1));
+ usEntry.add(map.getHex(10, 2));
+ usEntry.add(map.getHex(10, 3));
+ usEntry.add(map.getHex(11, 4));
+ usEntry.add(map.getHex(11, 5));
+ usEntry.add(map.getHex(12, 6));
+ usEntry.add(map.getHex(12, 7));
+ usEntry.add(map.getHex(13, 8));
+ usEntry.add(map.getHex(10, 4));
+ usEntry.add(map.getHex(11, 6));
+ addEntryZone(usEntry);
+
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE);
+ addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleNightAction.java b/core/src/ch/asynk/rustanddust/game/battles/BattleNightAction.java
new file mode 100644
index 0000000..f9817db
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/BattleNightAction.java
@@ -0,0 +1,153 @@
+package ch.asynk.rustanddust.game.battles;
+
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.HexSet;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Unit.UnitId;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.engine.Meteorology;
+
+public class BattleNightAction extends BattleCommon
+{
+ public BattleNightAction(Factory factory)
+ {
+ super(factory);
+ name = "Night Action";
+ mapType = Factory.MapType.MAP_B;
+ }
+
+ @Override
+ public Position getHudPosition(Player player)
+ {
+ return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT);
+ }
+
+ @Override
+ public Player getPlayer()
+ {
+ if (!gePlayer.isDeploymentDone() || gePlayer.getCurrentTurn() == 1)
+ return gePlayer;
+ if (gePlayer.getTurnDone() > usPlayer.getTurnDone())
+ return usPlayer;
+ return gePlayer;
+ }
+
+ private boolean isClear(Map map, int col, int row)
+ {
+ Hex hex = map.getHex(col, row);
+ Unit unit = hex.getUnit();
+ if ((unit != null) && unit.is(Army.GE)) {
+ map.selectHex(hex);
+ return false;
+ }
+ map.showMove(hex);
+ return true;
+ }
+
+ public Player checkVictory(Ctrl ctrl)
+ {
+ if (ctrl.opponent.unitsLeft() == 0)
+ return ctrl.player;
+
+ if ((ctrl.player.getTurnDone() < 9) || (ctrl.opponent.getTurnDone() < 9))
+ return null;
+
+ Map map = ctrl.map;
+ boolean clear = true;
+ clear &= isClear(map, 4, 8);
+ clear &= isClear(map, 5, 8);
+ clear &= isClear(map, 6, 8);
+ clear &= isClear(map, 7, 8);
+ clear &= isClear(map, 8, 8);
+ clear &= isClear(map, 8, 7);
+ clear &= isClear(map, 8, 6);
+ boolean upLeft = clear;
+ clear = true;
+ clear &= isClear(map, 8, 6);
+ clear &= isClear(map, 9, 6);
+ clear &= isClear(map, 10, 6);
+ clear &= isClear(map, 11, 6);
+ clear &= isClear(map, 12, 6);
+ boolean upRight = clear;
+ clear = true;
+ clear &= isClear(map, 1, 2);
+ clear &= isClear(map, 2, 3);
+ clear &= isClear(map, 3, 3);
+ clear &= isClear(map, 4, 3);
+ clear &= isClear(map, 5, 3);
+ clear &= isClear(map, 6, 4);
+ clear &= isClear(map, 7, 4);
+ clear &= isClear(map, 8, 4);
+ boolean bottomLeft = clear;
+ clear &= isClear(map, 8, 4);
+ clear &= isClear(map, 9, 4);
+ clear &= isClear(map, 10, 4);
+ clear &= isClear(map, 11, 4);
+ clear = true;
+ boolean bottomRight = clear;
+ // clear &= isClear(map, 8, 6);
+ // clear &= isClear(map, 8, 5);
+ // clear &= isClear(map, 8, 4);
+ // clear = true;
+ // boolean link = clear;
+
+ if ((!upLeft || !upRight) && (!bottomLeft || !bottomRight))
+ return gePlayer;
+ return usPlayer;
+ }
+
+ @Override
+ public void setup(Ctrl ctrl, Map map)
+ {
+ map.meteorology.day = Meteorology.Day.NIGHT;
+
+ // hex row I
+ Zone geEntry = new Zone(map, 10);
+ geEntry.orientation = Orientation.NORTH_EAST;
+ for (int i = 0; i < 10; i++)
+ geEntry.add(map.getHex(i, 0));
+ addEntryZone(geEntry);
+
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_INFANTRY);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_INFANTRY);
+
+ // hex rows A-B
+ Zone usEntry = new Zone(map, 19);
+ usEntry.orientation = Orientation.SOUTH;
+ for (int i = 0; i < 10; i++) {
+ usEntry.add(map.getHex((4 + i), 8));
+ usEntry.add(map.getHex((3 + i), 6));
+ usEntry.add(map.getHex((2 + i), 4));
+ usEntry.add(map.getHex((1 + i), 2));
+ }
+ for (int i = 0; i < 9; i++) {
+ usEntry.add(map.getHex((4 + i), 7));
+ usEntry.add(map.getHex((3 + i), 5));
+ usEntry.add(map.getHex((2 + i), 3));
+ }
+ addEntryZone(usEntry);
+
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE);
+ addReinforcement(usPlayer, usEntry, UnitId.US_AT_GUN);
+ addReinforcement(usPlayer, usEntry, UnitId.US_INFANTRY);
+ addReinforcement(usPlayer, usEntry, UnitId.US_INFANTRY);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleStabToTheFlank.java b/core/src/ch/asynk/rustanddust/game/battles/BattleStabToTheFlank.java
new file mode 100644
index 0000000..c45bac4
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/BattleStabToTheFlank.java
@@ -0,0 +1,143 @@
+package ch.asynk.rustanddust.game.battles;
+
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.HexSet;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Unit.UnitId;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.engine.Orientation;
+
+public class BattleStabToTheFlank extends BattleCommon
+{
+ public BattleStabToTheFlank(Factory factory)
+ {
+ super(factory);
+ name = "Stab To The Flank";
+ mapType = Factory.MapType.MAP_B;
+ }
+
+ @Override
+ public Position getHudPosition(Player player)
+ {
+ return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT);
+ }
+
+ @Override
+ public Player getPlayer()
+ {
+ if (!usPlayer.isDeploymentDone() || usPlayer.getCurrentTurn() == 1)
+ return usPlayer;
+ if (usPlayer.getTurnDone() > gePlayer.getTurnDone())
+ return gePlayer;
+ return usPlayer;
+ }
+
+ public Player checkVictory(Ctrl ctrl)
+ {
+ if (ctrl.opponent.unitsLeft() == 0)
+ return ctrl.player;
+
+ if ((ctrl.player.getTurnDone() < 9) || (ctrl.opponent.getTurnDone() < 9))
+ return null;
+
+ int gePoints = usPlayer.casualties();
+ int usPoints = gePlayer.casualties();
+ usPoints += ctrl.map.objectives.count(Army.US);
+
+ int withdrawed = usPlayer.withdrawed();
+ if (withdrawed == 0)
+ gePoints += 1;
+ else
+ usPoints += withdrawed;
+
+ if (usPoints > gePoints)
+ return usPlayer;
+ else
+ return gePlayer;
+ }
+
+ @Override
+ public boolean getReinforcement(Ctrl ctrl, Map map)
+ {
+ if (ctrl.player.is(Army.US))
+ return false;
+ if (ctrl.player.getCurrentTurn() != 3)
+ return false;
+
+ // hex rows I
+ Zone geEntry = new Zone(map, 9);
+ geEntry.allowedMoves = (Orientation.SOUTH_WEST.s | Orientation.NORTH_WEST.s);
+ for (int i = 0; i < 10; i++)
+ geEntry.add(map.getHex(i, 0));
+ addEntryZone(geEntry);
+
+ addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER, true);
+
+ return true;
+ }
+
+ @Override
+ public void setup(Ctrl ctrl, Map map)
+ {
+ // F6, E6
+ map.addHoldObjective(5, 3, Army.NONE);
+ map.addObjective(6, 4, Army.NONE);
+
+ // hex rows D-I
+ Zone geEntry = new Zone(map, 57);
+ geEntry.orientation = Orientation.NORTH;
+ for (int i = 3; i < 12; i++)
+ geEntry.add(map.getHex(i, 5));
+ for (int i = 2; i < 12; i++)
+ geEntry.add(map.getHex(i, 4));
+ for (int i = 2; i < 11; i++)
+ geEntry.add(map.getHex(i, 3));
+ for (int i = 1; i < 11; i++)
+ geEntry.add(map.getHex(i, 2));
+ for (int i = 1; i < 10; i++)
+ geEntry.add(map.getHex(i, 1));
+ for (int i = 0; i < 10; i++)
+ geEntry.add(map.getHex(i, 0));
+
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_AT_GUN);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_INFANTRY);
+
+ // hex row I
+ Zone usExit = new Zone(map, 10);
+ usExit.orientation = Orientation.NORTH_EAST;
+ for (int i = 0; i < 10; i++)
+ geEntry.add(map.getHex(i, 0));
+ addExitZone(usExit);
+
+ // hex rows A-B
+ Zone usEntry = new Zone(map, 19);
+ usEntry.orientation = Orientation.SOUTH_EAST;
+ for (int i = 4; i < 13; i++) {
+ usEntry.add(map.getHex(i, 8));
+ usEntry.add(map.getHex(i, 7));
+ }
+ usEntry.add(map.getHex(13, 8));
+ addEntryZone(usEntry);
+
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN_HQ);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_INFANTRY);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_INFANTRY);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_PRIEST);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleTest.java b/core/src/ch/asynk/rustanddust/game/battles/BattleTest.java
new file mode 100644
index 0000000..a237bca
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/BattleTest.java
@@ -0,0 +1,128 @@
+package ch.asynk.rustanddust.game.battles;
+
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.HexSet;
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Unit.UnitId;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.engine.Orientation;
+
+public class BattleTest extends BattleCommon
+{
+ private Zone usExit;
+
+ public BattleTest(Factory factory)
+ {
+ super(factory);
+ name = "*** Test ***";
+ mapType = Factory.MapType.MAP_B;
+ }
+
+ @Override
+ public Player getPlayer()
+ {
+ if (!gePlayer.isDeploymentDone())
+ return gePlayer;
+
+ if (gePlayer.getTurnDone() == usPlayer.getTurnDone())
+ return usPlayer;
+ return gePlayer;
+ }
+
+ @Override
+ public Position getHudPosition(Player player)
+ {
+ return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT);
+ }
+
+ @Override
+ public Player checkVictory(Ctrl ctrl)
+ {
+ if (usPlayer.getTurnDone() > 2)
+ return usPlayer;
+ return null;
+ }
+
+ @Override
+ public boolean getReinforcement(Ctrl ctrl, Map map)
+ {
+ if (ctrl.player.is(Army.GE))
+ return false;
+ if (ctrl.player.getCurrentTurn() != 2)
+ return false;
+
+ Zone usEntry = new Zone(map, 1);
+ usEntry.allowedMoves = (Orientation.SOUTH.s | Orientation.SOUTH_EAST.s | Orientation.SOUTH_WEST.s);
+ usEntry.add(map.getHex(12, 6));
+ addEntryZone(usEntry);
+ addReinforcement(usPlayer, usEntry, usExit, UnitId.US_WOLVERINE);
+
+ return true;
+ }
+
+ private Unit setUnit(Map map, Player player, UnitId unitId, int col, int row, Orientation orientation, Zone exitZone)
+ {
+ return setUnit(map, player, unitId, col, row, orientation, false, exitZone);
+ }
+
+ private Unit setUnit(Map map, Player player, UnitId unitId, int col, int row, Orientation orientation, boolean ace, Zone exitZone)
+ {
+ Unit u = factory.getUnit(unitId);
+ u.setAce(ace);
+ if (exitZone != null)
+ unitExit.put(u, exitZone);
+ map.setOnBoard(u, map.getHex(col, row), orientation);
+ return u;
+ }
+
+ @Override
+ public void setup(Ctrl ctrl, Map map)
+ {
+ map.addObjective(6, 4, Army.NONE);
+ map.addHoldObjective(5, 3, Army.NONE);
+ map.addObjective(3, 4, Army.NONE);
+ map.addHoldObjective(3, 3, Army.NONE);
+
+ ctrl.player = gePlayer;
+ setUnit(map, gePlayer, UnitId.GE_WESPE, 5, 8, Orientation.NORTH, null);
+ setUnit(map, gePlayer, UnitId.GE_TIGER, 6, 4, Orientation.NORTH, null);
+ setUnit(map, gePlayer, UnitId.GE_PANZER_IV, 4, 5, Orientation.NORTH_WEST, null);
+ setUnit(map, gePlayer, UnitId.GE_INFANTRY, 1, 2, Orientation.NORTH_WEST, null);
+ setUnit(map, gePlayer, UnitId.GE_KINGTIGER, 1, 1, Orientation.NORTH_WEST, null);
+ Zone geEntry = new Zone(map, 6);
+ geEntry.orientation = Orientation.NORTH;
+ geEntry.add(map.getHex(1, 2));
+ geEntry.add(map.getHex(1, 1));
+ geEntry.add(map.getHex(3, 3));
+ geEntry.add(map.getHex(3, 4));
+ geEntry.add(map.getHex(4, 0));
+ geEntry.add(map.getHex(5, 0));
+ addEntryZone(geEntry);
+ addReinforcement(gePlayer, geEntry, UnitId.GE_AT_GUN);
+
+ usExit = new Zone(map, 9);
+ usExit.orientation = Orientation.NORTH;
+ usExit.add(map.getHex(11, 4));
+ usExit.add(map.getHex(11, 5));
+ usExit.add(map.getHex(12, 6));
+ addExitZone(usExit);
+
+ ctrl.player = usPlayer;
+ usPlayer.casualty(factory.getUnit(UnitId.US_SHERMAN_HQ));
+ setUnit(map, usPlayer, UnitId.US_PRIEST, 10, 8, Orientation.SOUTH_EAST, usExit);
+ setUnit(map, usPlayer, UnitId.US_SHERMAN, 7, 3, Orientation.SOUTH, true, usExit);
+ setUnit(map, usPlayer, UnitId.US_SHERMAN_HQ, 8, 4, Orientation.SOUTH, usExit);
+ setUnit(map, usPlayer, UnitId.US_WOLVERINE, 9, 7, Orientation.SOUTH_EAST, usExit);
+ setUnit(map, usPlayer, UnitId.US_PERSHING, 6, 6, Orientation.NORTH_EAST, usExit);
+ setUnit(map, usPlayer, UnitId.US_INFANTRY, 5, 3, Orientation.NORTH_WEST, usExit);
+ setUnit(map, usPlayer, UnitId.US_AT_GUN, 10, 3, Orientation.SOUTH, usExit);
+ usPlayer.turnEnd();
+ map.init();
+ map.turnDone();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/Factory.java b/core/src/ch/asynk/rustanddust/game/battles/Factory.java
new file mode 100644
index 0000000..4390663
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/Factory.java
@@ -0,0 +1,192 @@
+package ch.asynk.rustanddust.game.battles;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.rustanddust.RustAndDust;
+import ch.asynk.rustanddust.engine.Board;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Unit.UnitId;
+import ch.asynk.rustanddust.game.Unit.UnitType;
+import ch.asynk.rustanddust.game.Battle;
+import ch.asynk.rustanddust.game.battles.BattleHeadToHead;
+import ch.asynk.rustanddust.game.battles.BattleFrontalAssault;
+import ch.asynk.rustanddust.game.battles.BattleLastStand;
+import ch.asynk.rustanddust.game.battles.BattleCounterAttack;
+import ch.asynk.rustanddust.game.battles.BattleStabToTheFlank;
+import ch.asynk.rustanddust.game.battles.BattleNightAction;
+import ch.asynk.rustanddust.game.battles.BattleTest;
+
+public class Factory implements Board.TileBuilder, Disposable
+{
+ public enum MapType
+ {
+ MAP_A,
+ MAP_B
+ }
+
+ public enum Scenarios
+ {
+ FAKE
+ }
+
+ public boolean assetsLoaded;
+ public TextureAtlas hudAtlas;
+ public TextureAtlas pawnsAtlas;
+ public TextureAtlas pawnOverlaysAtlas;
+ public TextureAtlas tileOverlaysAtlas;
+ public Battle battles[];
+ private final RustAndDust game;
+
+ public Factory(final RustAndDust game)
+ {
+ this.game = game;
+ this.assetsLoaded = false;
+ battles = new Battle[] {
+ new BattleHeadToHead(this),
+ new BattleFrontalAssault(this),
+ new BattleLastStand(this),
+ new BattleCounterAttack(this),
+ new BattleStabToTheFlank(this),
+ new BattleNightAction(this),
+ new BattleTest(this),
+ };
+ }
+
+ public void assetsLoaded()
+ {
+ if (assetsLoaded) return;
+ int i = game.config.graphics.i;
+ this.hudAtlas = game.manager.get("data/hud.atlas", TextureAtlas.class);
+ this.tileOverlaysAtlas = game.manager.get("data/hex-overlays.atlas", TextureAtlas.class);
+ this.pawnsAtlas = game.manager.get(String.format("data/units%d.atlas", i), TextureAtlas.class);
+ this.pawnOverlaysAtlas = game.manager.get(String.format("data/unit-overlays%d.atlas", i), TextureAtlas.class);
+ this.assetsLoaded = true;
+ }
+
+ @Override
+ public void dispose()
+ {
+ if (!assetsLoaded) return;
+ hudAtlas.dispose();
+ pawnsAtlas.dispose();
+ pawnOverlaysAtlas.dispose();
+ tileOverlaysAtlas.dispose();
+ this.assetsLoaded = false;
+ }
+
+ private Board.Config config()
+ {
+ Board.Config cfg = new Board.Config();
+ cfg.cols = 10;
+ cfg.rows = 9;
+ cfg.x0 = 86;
+ cfg.y0 = 182;
+ cfg.w = 189;
+ cfg.dw = 94;
+ cfg.s = 110;
+ cfg.dh = 53.6f;
+ cfg.h = cfg.s + cfg.dh;
+ cfg.slope = (cfg.dh / (float) cfg.dw);
+
+ return cfg;
+ }
+
+ public Map getMap(MapType t)
+ {
+ Board.Config cfg = config();
+
+ Map m = null;
+ switch(t) {
+ case MAP_A:
+ m = new MapA(game, config(), "data/map_a.png");
+ break;
+ case MAP_B:
+ m = new MapB(game, config(), "data/map_b.png");
+ break;
+ }
+
+ return m;
+ }
+
+ public Player getPlayer(Army army)
+ {
+ if (army == Army.US)
+ return new Player(game, Army.US, 10);
+ else
+ return new Player(game, Army.GE, 10);
+ }
+
+ public Unit getUnit(UnitId id)
+ {
+ Unit u = null;
+ UnitType ut = UnitType.HARD_TARGET;
+ UnitType utHq = UnitType.HARD_TARGET_HQ;
+ switch(id) {
+ case GE_AT_GUN:
+ ut = UnitType.AT_GUN;
+ u = new Unit(Army.GE, id, ut, 3, 8, 9, 1, "ge-at-gun", "ge-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case GE_INFANTRY:
+ ut = UnitType.INFANTRY;
+ u = new Unit(Army.GE, id, ut, 1, 7, 10, 1, "ge-infantry", "ge-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case GE_KINGTIGER:
+ u = new Unit(Army.GE, id, ut, 3, 12, 1, "ge-kingtiger", "ge-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case GE_PANZER_IV:
+ u = new Unit(Army.GE, id, ut, 2, 9, 2, "ge-panzer-iv", "ge-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case GE_PANZER_IV_HQ:
+ u = new Unit(Army.GE, id, utHq, 2, 9, 2, "ge-panzer-iv", "ge-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case GE_TIGER:
+ u = new Unit(Army.GE, id, ut, 3, 11, 1, "ge-tiger", "ge-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case GE_WESPE:
+ ut = UnitType.ARTILLERY;
+ u = new Unit(Army.GE, id, ut, 5, 8, 1, "ge-wespe", "ge-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case US_AT_GUN:
+ ut = UnitType.AT_GUN;
+ u = new Unit(Army.US, id, ut, 1, 7, 10, 1, "us-at-gun", "us-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case US_INFANTRY:
+ ut = UnitType.INFANTRY;
+ u = new Unit(Army.US, id, ut, 1, 7, 10, 1, "us-infantry", "us-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case US_PERSHING:
+ u = new Unit(Army.US, id, ut, 3, 10, 2, "us-pershing", "us-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case US_PERSHING_HQ:
+ u = new Unit(Army.US, id, utHq, 3, 10, 2, "us-pershing", "us-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case US_PRIEST:
+ ut = UnitType.ARTILLERY;
+ u = new Unit(Army.US, id, ut, 5, 8, 1, "us-priest", "us-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case US_SHERMAN:
+ u = new Unit(Army.US, id, ut, 2, 9, 2, "us-sherman", "us-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case US_SHERMAN_HQ:
+ u = new Unit(Army.US, id, utHq, 2, 9, 2, "us-sherman", "us-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ case US_WOLVERINE:
+ u = new Unit(Army.US, id, ut, 3, 8, 3, "us-wolverine", "us-head", pawnsAtlas, pawnOverlaysAtlas);
+ break;
+ }
+
+ return u;
+ }
+
+ public Hex getNewTile(float x, float y, int col, int row, boolean offmap)
+ {
+ Hex hex = new Hex(x, y, col, row, tileOverlaysAtlas);
+ if (offmap) hex.terrain = Hex.Terrain.OFFMAP;
+ return hex;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/MapA.java b/core/src/ch/asynk/rustanddust/game/battles/MapA.java
new file mode 100644
index 0000000..491b370
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/MapA.java
@@ -0,0 +1,77 @@
+package ch.asynk.rustanddust.game.battles;
+
+import ch.asynk.rustanddust.RustAndDust;
+import ch.asynk.rustanddust.engine.Board;
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+
+public class MapA extends Map
+{
+ public MapA(final RustAndDust game, Board.Config cfg, String textureName)
+ {
+ super(game, cfg, textureName);
+ }
+
+ @Override
+ protected void setup()
+ {
+ getHex(5, 1).terrain = Hex.Terrain.HILLS;
+ getHex(7, 3).terrain = Hex.Terrain.HILLS;
+ getHex(7, 8).terrain = Hex.Terrain.HILLS;
+ getHex(8, 8).terrain = Hex.Terrain.HILLS;
+
+ getHex(5, 0).terrain = Hex.Terrain.WOODS;
+ getHex(6, 0).terrain = Hex.Terrain.WOODS;
+ getHex(3, 3).terrain = Hex.Terrain.WOODS;
+ getHex(4, 3).terrain = Hex.Terrain.WOODS;
+ getHex(10, 7).terrain = Hex.Terrain.WOODS;
+ getHex(11, 7).terrain = Hex.Terrain.WOODS;
+ getHex(11, 8).terrain = Hex.Terrain.WOODS;
+
+ getHex(6, 1).terrain = Hex.Terrain.TOWN;
+ getHex(2, 2).terrain = Hex.Terrain.TOWN;
+ getHex(6, 4).terrain = Hex.Terrain.TOWN;
+ getHex(10, 5).terrain = Hex.Terrain.TOWN;
+ getHex(7, 7).terrain = Hex.Terrain.TOWN;
+ getHex(4, 6).terrain = Hex.Terrain.TOWN;
+
+ getHex(10, 1).terrain = Hex.Terrain.OFFMAP;
+ getHex(11, 3).terrain = Hex.Terrain.OFFMAP;
+ getHex(12, 5).terrain = Hex.Terrain.OFFMAP;
+ getHex(13, 7).terrain = Hex.Terrain.OFFMAP;
+
+ int N = Orientation.NORTH.s;
+ int S = Orientation.SOUTH.s;
+ int NE = Orientation.NORTH_EAST.s;
+ int NW = Orientation.NORTH_WEST.s;
+ int SE = Orientation.SOUTH_EAST.s;
+ int SW = Orientation.SOUTH_WEST.s;
+
+ getHex(6, 1).roads = (NW | SW);
+ for (int i = 1; i < 11; i++) {
+ if (i == 6)
+ getHex(i, 2).roads = (NE | S | SW);
+ else if (i == 7)
+ getHex(i, 2).roads = (N | SE);
+ else
+ getHex(i, 2).roads = (N | S);
+ }
+ getHex(6, 3).roads = (NE | SW);
+ getHex(6, 4).roads = (N | NE | SW);
+ getHex(7, 4).roads = (N | S);
+ getHex(8, 4).roads = (NW | S);
+ getHex(6, 5).roads = (NE | SW);
+ getHex(8, 5).roads = (N | SW);
+ getHex(9, 5).roads = (N | S | NE);
+ getHex(10, 5).roads = (N | S);
+ getHex(11, 5).roads = (N | S);
+ getHex(3, 6).roads = (N | S);
+ getHex(4, 6).roads = (N | S);
+ getHex(5, 6).roads = (N | S);
+ getHex(6, 6).roads = (NE | NW | S);
+ getHex(8, 6).roads = (NE | SW);
+ getHex(7, 7).roads = (N | SE);
+ getHex(8, 7).roads = (NE | S);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/battles/MapB.java b/core/src/ch/asynk/rustanddust/game/battles/MapB.java
new file mode 100644
index 0000000..8636481
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/battles/MapB.java
@@ -0,0 +1,75 @@
+package ch.asynk.rustanddust.game.battles;
+
+import ch.asynk.rustanddust.RustAndDust;
+import ch.asynk.rustanddust.engine.Board;
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+
+public class MapB extends Map
+{
+ public MapB(final RustAndDust game, Board.Config cfg, String textureName)
+ {
+ super(game, cfg, textureName);
+ }
+
+ @Override
+ protected void setup()
+ {
+ getHex(4, 0).terrain = Hex.Terrain.HILLS;
+ getHex(5, 0).terrain = Hex.Terrain.HILLS;
+ getHex(1, 1).terrain = Hex.Terrain.HILLS;
+ getHex(9, 7).terrain = Hex.Terrain.HILLS;
+ getHex(10, 7).terrain = Hex.Terrain.HILLS;
+
+ getHex(3, 0).terrain = Hex.Terrain.WOODS;
+ getHex(6, 0).terrain = Hex.Terrain.WOODS;
+ getHex(8, 1).terrain = Hex.Terrain.WOODS;
+ getHex(9, 2).terrain = Hex.Terrain.WOODS;
+ getHex(4, 5).terrain = Hex.Terrain.WOODS;
+ getHex(5, 6).terrain = Hex.Terrain.WOODS;
+ getHex(6, 6).terrain = Hex.Terrain.WOODS;
+ getHex(11, 8).terrain = Hex.Terrain.WOODS;
+
+ getHex(1, 2).terrain = Hex.Terrain.TOWN;
+ getHex(5, 3).terrain = Hex.Terrain.TOWN;
+ getHex(6, 4).terrain = Hex.Terrain.TOWN;
+ getHex(7, 8).terrain = Hex.Terrain.TOWN;
+
+ getHex(10, 1).terrain = Hex.Terrain.OFFMAP;
+ getHex(11, 3).terrain = Hex.Terrain.OFFMAP;
+ getHex(12, 5).terrain = Hex.Terrain.OFFMAP;
+ getHex(13, 7).terrain = Hex.Terrain.OFFMAP;
+
+ int N = Orientation.NORTH.s;
+ int S = Orientation.SOUTH.s;
+ int NE = Orientation.NORTH_EAST.s;
+ int NW = Orientation.NORTH_WEST.s;
+ int SE = Orientation.SOUTH_EAST.s;
+ int SW = Orientation.SOUTH_WEST.s;
+
+ getHex(1, 2).roads = (S | NW);
+ getHex(2, 3).roads = (SE | N);
+ getHex(3, 3).roads = (S | N);
+ getHex(4, 3).roads = (S | N);
+ getHex(5, 3).roads = (S | NW);
+ getHex(6, 4).roads = (SE | N);
+ getHex(7, 4).roads = (S | N);
+ getHex(8, 4).roads = (S | SW | N);
+ getHex(9, 4).roads = (S | N);
+ getHex(10, 4).roads = (S | N);
+ getHex(11, 4).roads = (S | N);
+ getHex(4, 8).roads = (S | N);
+ getHex(5, 8).roads = (S | N);
+ getHex(6, 8).roads = (S | N);
+ getHex(7, 8).roads = (S | N);
+ getHex(8, 8).roads = (S | NE);
+ getHex(8, 7).roads = (SW | NE);
+ getHex(8, 6).roads = (SW | NE | N);
+ getHex(8, 5).roads = (SW | NE);
+ getHex(9, 6).roads = (S | N);
+ getHex(10, 6).roads = (S | N);
+ getHex(11, 6).roads = (S | N);
+ getHex(12, 6).roads = (S | N);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/hud/ActionButtons.java b/core/src/ch/asynk/rustanddust/game/hud/ActionButtons.java
new file mode 100644
index 0000000..323767f
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/hud/ActionButtons.java
@@ -0,0 +1,185 @@
+package ch.asynk.rustanddust.game.hud;
+
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.State.StateType;
+import ch.asynk.rustanddust.ui.Widget;
+import ch.asynk.rustanddust.ui.Bg;
+import ch.asynk.rustanddust.ui.Position;
+
+public class ActionButtons extends Widget
+{
+ public static int PADDING = 5;
+
+ private final Ctrl ctrl;
+
+ public enum Buttons {
+ NONE(-1, 0),
+ PROMOTE(0, 1),
+ DONE(1, 2),
+ ABORT(2, 4),
+ LAST(3, 0);
+
+ Buttons(int i, int b)
+ {
+ this.i = i;
+ this.b = b;
+ }
+
+ public int i;
+ public int b;
+ }
+
+ private Sprite bg;
+ private int idx;
+ private Bg buttons [];
+ private StateType states [];
+
+ public ActionButtons(Ctrl ctrl, TextureAtlas uiAtlas, TextureAtlas hudAtlas)
+ {
+ this.bg = new Sprite(uiAtlas.findRegion("disabled"));
+ this.ctrl = ctrl;
+ this.visible = false;
+ this.position = Position.BOTTOM_RIGHT;
+ this.idx = Buttons.NONE.i;
+
+
+ this.buttons = new Bg[Buttons.LAST.i];
+ this.buttons[Buttons.DONE.i] = new Bg(uiAtlas.findRegion("ok"));
+ this.buttons[Buttons.ABORT.i] = new Bg(uiAtlas.findRegion("cancel"));
+ this.buttons[Buttons.PROMOTE.i] = new Bg(hudAtlas.findRegion("promote"));
+
+ this.states = new StateType[Buttons.LAST.i];
+ this.states[Buttons.DONE.i] = StateType.DONE;
+ this.states[Buttons.ABORT.i] = StateType.ABORT;
+ this.states[Buttons.PROMOTE.i] = StateType.PROMOTE;
+ }
+
+ @Override
+ public void dispose()
+ {
+ for (int i = 0; i < Buttons.LAST.i; i++)
+ buttons[i].dispose();
+ }
+
+ public void update(Position position)
+ {
+ setPosition(position);
+ updatePosition();
+ }
+
+ public void updatePosition()
+ {
+ if (!visible) return;
+ float dx = (position.getX(rect.width) - rect.x);
+ float dy = (position.getY(rect.height) - rect.y);
+ translate(dx, dy);
+ for (int i = 0; i < Buttons.LAST.i; i++)
+ buttons[i].translate(dx, dy);
+ }
+
+ public void hide()
+ {
+ for (int i = 0; i < Buttons.LAST.i; i++)
+ buttons[i].visible = false;
+ this.visible = false;
+ }
+
+ private float setButton(Bg btn, float x, float y)
+ {
+ btn.visible = true;
+ btn.setPosition(x, y);
+ return (y + btn.getHeight() + PADDING);
+ }
+
+ public void show(int bits)
+ {
+ int b = bits;
+ int count = 0;
+ while (b > 0) {
+ if ((b & 0x01) == 1)
+ count += 1;
+ b /= 2;
+ }
+
+ if (count == 0) {
+ this.visible = false;
+ return;
+ }
+
+ rect.width = (buttons[0].getWidth() + (2 * PADDING));
+ rect.height = ((buttons[0].getHeight() * count) + ((count + 1) * PADDING));
+ rect.x = position.getX(rect.width);
+ rect.y = position.getY(rect.height);
+
+ float x = (rect.x + PADDING);
+ float y = (rect.y + PADDING);
+
+ b = 1;
+ for (int i = 0; i < Buttons.LAST.i; i++) {
+ if ((bits & b) == b)
+ y = setButton(buttons[i], x, y);
+ else
+ buttons[i].visible = false;
+ b *= 2;
+ }
+
+ this.visible = true;
+ }
+
+ public boolean touchDown(float x, float y)
+ {
+ idx = Buttons.NONE.i;
+
+ if (!super.hit(x,y))
+ return false;
+
+ for (int i = 0; i < Buttons.LAST.i; i++) {
+ if (buttons[i].hit(x, y)) {
+ idx = i;
+ break;
+ }
+ }
+
+ return (idx != Buttons.NONE.i);
+ }
+
+ public boolean touchUp(float x, float y)
+ {
+ if (idx == Buttons.NONE.i)
+ return false;
+
+ boolean ret = false;
+
+ if (super.hit(x,y) && buttons[idx].hit(x, y)) {
+ ctrl.setState(states[idx]);
+ ret = true;
+ }
+
+ idx = Buttons.NONE.i;
+
+ return ret;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ batch.draw(bg, rect.x, rect.y, rect.width, rect.height);
+ for (int i = 0; i < Buttons.LAST.i; i++)
+ buttons[i].draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ if (!visible) return;
+ super.drawDebug(shapes);
+ for (int i = 0; i < Buttons.LAST.i; i++)
+ buttons[i].drawDebug(shapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/hud/EngagementPanel.java b/core/src/ch/asynk/rustanddust/game/hud/EngagementPanel.java
new file mode 100644
index 0000000..790c8b5
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/hud/EngagementPanel.java
@@ -0,0 +1,255 @@
+package ch.asynk.rustanddust.game.hud;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.game.Engagement;
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+import ch.asynk.rustanddust.engine.gfx.animations.DiceAnimation;
+import ch.asynk.rustanddust.ui.Bg;
+import ch.asynk.rustanddust.ui.Label;
+import ch.asynk.rustanddust.ui.Patch;
+import ch.asynk.rustanddust.ui.Position;
+
+public class EngagementPanel extends Patch implements Animation
+{
+ private enum State { ROLL1, MOVE, ROLL2, RESULT };
+
+ public static int FLAG_HEIGHT = 24;
+ public static int OK_OFFSET = 10;
+ public static int PADDING = 20;
+ public static int VSPACING = 10;
+ public static int HSPACING = 5;
+ public static float MOVE_STEP = 2f;
+
+ private State state;
+ private boolean reroll;
+ private float rerollY;
+ private Sprite usFlag;
+ private Sprite geFlag;
+ private Sprite winner;
+ private Sprite attackImg;
+ private Sprite defenseImg;
+ private Label attack;
+ private Label defense;
+ private Label attackR;
+ private Label defenseR;
+ private Bg okBtn;
+ private DiceAnimation d1Animation;
+ private DiceAnimation d2Animation;
+ private DiceAnimation d3Animation;
+ private DiceAnimation d4Animation;
+
+ public EngagementPanel(BitmapFont font, TextureAtlas uiAtlas, TextureAtlas hudAtlas)
+ {
+ super(uiAtlas.createPatch("typewriter"));
+ usFlag = new Sprite(hudAtlas.findRegion("us-flag"));
+ geFlag = new Sprite(hudAtlas.findRegion("ge-flag"));
+ attackImg = new Sprite(hudAtlas.findRegion("attack"));
+ defenseImg = new Sprite(hudAtlas.findRegion("defense"));
+ this.attack = new Label(font);
+ this.defense = new Label(font);
+ this.attackR = new Label(font);
+ this.defenseR = new Label(font);
+ this.okBtn = new Bg(uiAtlas.findRegion("ok"));
+ this.visible = false;
+ this.d1Animation = new DiceAnimation();
+ this.d2Animation = new DiceAnimation();
+ this.d3Animation = new DiceAnimation();
+ this.d4Animation = new DiceAnimation();
+ }
+
+ public void updatePosition()
+ {
+ if (!visible) return;
+ float dx = (position.getX(rect.width) - rect.x);
+ float dy = (position.getY(rect.height) - rect.y);
+ translate(dx, dy);
+ winner.translate(dx, dy);
+ attackImg.translate(dx, dy);
+ defenseImg.translate(dx, dy);
+ attack.translate(dx, dy);
+ defense.translate(dx, dy);
+ attackR.translate(dx, dy);
+ defenseR.translate(dx, dy);
+ okBtn.translate(dx, dy);
+ d1Animation.translate(dx, dy);
+ d2Animation.translate(dx, dy);
+ d3Animation.translate(dx, dy);
+ d4Animation.translate(dx, dy);
+ }
+
+ public void show(Engagement e, Position position, float volume)
+ {
+ DiceAnimation.initSound(volume);
+ attack.write(String.format(" + %d + %d =", e.unitCount, e.flankBonus));
+ if (e.weatherDefense == 0)
+ defense.write(String.format("%d + %d =", e.unitDefense, e.terrainDefense));
+ else
+ defense.write(String.format("%d + %d + %d =", e.unitDefense, e.terrainDefense, e.weatherDefense));
+ attackR.write(String.format(" %2d", e.attackSum));
+ defenseR.write(String.format(" %2d", e.defenseSum));
+ if (e.success)
+ winner = ((e.attacker.getArmy() == Army.US) ? usFlag : geFlag);
+ else
+ winner = ((e.attacker.getArmy() == Army.US) ? geFlag : usFlag);
+
+ this.position = position;
+ placeElements();
+
+ state = State.ROLL1;
+ reroll = (e.d3 != 0);
+
+ d1Animation.set(e.d1);
+ d2Animation.set(e.d2);
+ if (reroll) {
+ d3Animation.set(e.d3);
+ d4Animation.set(e.d4);
+ }
+
+ visible = true;
+ }
+
+ private void placeElements()
+ {
+ float w = attackR.getWidth();
+ float w2 = defenseR.getWidth();
+ if (w2 > w)
+ w = w2;
+ float height = (okBtn.getHeight() + attackImg.getHeight() + defenseImg.getHeight() + (2 * VSPACING) + (2 * PADDING));
+ float width = (attackImg.getWidth() + (2 * d1Animation.getWidth()) + attack.getWidth() + w + (4 * HSPACING) + (2 * PADDING));
+ float x = position.getX(width);
+ float y = position.getY(height);
+ setPosition(x, y, width, height);
+
+ okBtn.setPosition((x + width - okBtn.getWidth() + OK_OFFSET), (y - OK_OFFSET));
+
+ x = getX() + PADDING;
+ y = getY() + PADDING;
+ winner.setPosition((getX() + (width / 2f) - (winner.getWidth() / 2f)), y);
+ y += (winner.getHeight() + VSPACING);
+
+ defenseImg.setPosition(x, y);
+ y = (y + (defenseImg.getHeight() / 2f) - (defense.getHeight() / 2f));
+ defenseR.setPosition((getX() + width - w - PADDING), y);
+ // x += (defenseImg.getWidth() + HSPACING);
+ defense.setPosition((defenseR.getX() - defense.getWidth() - HSPACING), y);
+
+ x = getX() + PADDING;
+ y += defenseImg.getHeight() + VSPACING;
+ attackImg.setPosition(x, y);
+ x += (attackImg.getWidth() + HSPACING);
+ d1Animation.setPosition(x, y);
+ d3Animation.setPosition(x, y);
+ x += (d1Animation.getWidth() + HSPACING);
+ d2Animation.setPosition(x, (y));
+ d4Animation.setPosition(x, y);
+ x += (d1Animation.getWidth() + HSPACING);
+ y = (y + (attackImg.getHeight() / 2f) - (attack.getHeight() / 2f));
+ attack.setPosition(x, y);
+ attackR.setPosition(defenseR.getX(), y);
+
+ rerollY = (d1Animation.getY() + d1Animation.getHeight() + VSPACING);
+ }
+
+ @Override
+ public boolean hit(float x, float y)
+ {
+ return rect.contains(x, y);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if (!visible) return true;
+ if (state == State.ROLL1) {
+ d1Animation.animate(delta);
+ d2Animation.animate(delta);
+ if (d1Animation.isDone() && d2Animation.isDone()) {
+ if (reroll)
+ state = State.MOVE;
+ else
+ state = State.RESULT;
+ }
+ }
+
+ if (state == State.MOVE) {
+ float y = (d1Animation.getY() + MOVE_STEP);
+ if (y >= rerollY) {
+ y = rerollY;
+ state = State.ROLL2;
+ }
+ setPosition(getX(), getY(), getWidth(), (y + d1Animation.getHeight() + VSPACING - getY()));
+ d1Animation.setPosition(d1Animation.getX(), y);
+ d2Animation.setPosition(d2Animation.getX(), y);
+ }
+
+ if (state == State.ROLL2) {
+ if (d1Animation.getY() < rerollY) {
+ d1Animation.setPosition(d1Animation.getX(), (d1Animation.getY() + d1Animation.getHeight() + VSPACING));
+ d2Animation.setPosition(d2Animation.getX(), (d2Animation.getY() + d2Animation.getHeight() + VSPACING));
+ } else {
+ d3Animation.animate(delta);
+ d4Animation.animate(delta);
+ if (d3Animation.isDone() && d4Animation.isDone())
+ state = State.RESULT;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ attack.dispose();
+ defense.dispose();
+ attackR.dispose();
+ defenseR.dispose();
+ d1Animation.dispose();
+ d2Animation.dispose();
+ d3Animation.dispose();
+ d4Animation.dispose();
+ okBtn.dispose();
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ super.draw(batch);
+ attackImg.draw(batch);
+ d1Animation.draw(batch);
+ d2Animation.draw(batch);
+ if ((state == State.ROLL2) || (reroll && (state == State.RESULT))) {
+ d3Animation.draw(batch);
+ d4Animation.draw(batch);
+ }
+ attack.draw(batch);
+ defenseImg.draw(batch);
+ defense.draw(batch);
+ defenseR.draw(batch);
+ okBtn.draw(batch);
+ if (state == State.RESULT) {
+ attackR.draw(batch);
+ winner.draw(batch);
+ }
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ if (!visible) return;
+ super.drawDebug(shapes);
+ attack.drawDebug(shapes);
+ defense.drawDebug(shapes);
+ attackR.drawDebug(shapes);
+ defenseR.drawDebug(shapes);
+ okBtn.drawDebug(shapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/hud/PlayerInfo.java b/core/src/ch/asynk/rustanddust/game/hud/PlayerInfo.java
new file mode 100644
index 0000000..dd77c8e
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/hud/PlayerInfo.java
@@ -0,0 +1,202 @@
+package ch.asynk.rustanddust.game.hud;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+
+import ch.asynk.rustanddust.game.State.StateType;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Hud;
+import ch.asynk.rustanddust.game.Army;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.ui.LabelImage;
+import ch.asynk.rustanddust.ui.Position;
+
+public class PlayerInfo implements Disposable, Drawable, Animation
+{
+ public static int PADDING = 5;
+
+ private final Ctrl ctrl;
+
+ private Object hit;
+
+ private Sprite flag;
+ private Sprite usFlag;
+ private Sprite geFlag;
+ private LabelImage turns;
+ private LabelImage aps;
+ private LabelImage reinforcement;
+ public UnitDock unitDock;
+ private Position position;
+
+ public PlayerInfo(Ctrl ctrl, BitmapFont font, TextureAtlas uiAtlas, TextureAtlas hudAtlas)
+ {
+ this.ctrl = ctrl;
+ this.position = Position.MIDDLE_CENTER;
+ usFlag = new Sprite(hudAtlas.findRegion("us-flag"));
+ geFlag = new Sprite(hudAtlas.findRegion("ge-flag"));
+ turns = new LabelImage(hudAtlas.findRegion("turns"), font, 5f);
+ aps = new LabelImage(hudAtlas.findRegion("aps"), font, 5f);
+ reinforcement = new LabelImage(hudAtlas.findRegion("reinforcement"), font, 5f);
+ unitDock = new UnitDock(ctrl, uiAtlas.findRegion("disabled"), hudAtlas.findRegion("reinforcement-selected"), 10f);
+ }
+
+ @Override
+ public void dispose()
+ {
+ turns.dispose();
+ aps.dispose();
+ reinforcement.dispose();
+ unitDock.dispose();
+ }
+
+ public void updatePosition()
+ {
+ float dx = (position.getX(usFlag.getWidth()) - usFlag.getX());
+ float dy = (position.getY(usFlag.getHeight()) - usFlag.getY());
+ usFlag.translate(dx, dy);
+ geFlag.translate(dx, dy);
+ turns.translate(dx, dy);
+ aps.translate(dx, dy);
+ reinforcement.translate(dx, dy);
+ unitDock.translate(dx, dy);
+ }
+
+ public void setPosition(Position position)
+ {
+ if (this.position == position)
+ return;
+ this.position = position;
+
+ float width = (usFlag.getWidth() + turns.getWidth() + aps.getWidth() + (2 * PADDING));
+ float height = (usFlag.getHeight() + reinforcement.getHeight() + (1 * PADDING));
+ float x = position.getX(width);
+ float y = position.getY(height);
+
+ if (position.isLeft()) {
+ reinforcement.setPosition(x, y);
+ y += (reinforcement.getHeight() + PADDING);
+ usFlag.setPosition(x, y);
+ geFlag.setPosition(x, y);
+ x += (usFlag.getWidth() + PADDING);
+ turns.setPosition(x, y);
+ x += (turns.getWidth() + PADDING);
+ aps.setPosition(x, y);
+ } else {
+ x = (x + width);
+ reinforcement.setPosition((x - reinforcement.getWidth()), y);
+ y += (reinforcement.getHeight() + PADDING);
+ x -= usFlag.getWidth();
+ usFlag.setPosition(x, y);
+ geFlag.setPosition(x, y);
+ x -= (turns.getWidth() + PADDING);
+ turns.setPosition(x, y);
+ x -= (aps.getWidth() + PADDING);
+ aps.setPosition(x, y);
+ }
+ aps.setLabelPosition(Position.TOP_RIGHT);
+ turns.setLabelPosition(Position.MIDDLE_CENTER);
+ reinforcement.setLabelPosition(Position.TOP_LEFT);
+ unitDock.setPosition(position, reinforcement.getY() - PADDING);
+ }
+
+ public void update(Player player, Position position)
+ {
+ unitDock.hide();
+ turns.write(String.format("%d", player.getCurrentTurn()));
+ aps.write(String.format("%d", player.getAp()));
+ int r = player.reinforcement();
+ if (r == 0) {
+ reinforcement.visible = false;
+ } else {
+ reinforcement.visible = true;
+ reinforcement.write(String.format("%d", r));
+ }
+
+ if (player.is(Army.GE))
+ flag = geFlag;
+ else
+ flag = usFlag;
+
+ setPosition(position);
+ }
+
+ public void blockEndOfTurn(boolean blocked)
+ {
+ turns.blocked = blocked;
+ }
+
+ public boolean touchDown(float x, float y)
+ {
+ hit = null;
+
+ if (reinforcement.hit(x, y))
+ hit = reinforcement;
+ else if (unitDock.hit(x, y))
+ hit = unitDock;
+ else if (turns.hit(x,y))
+ hit = turns;
+
+ return (hit != null);
+ }
+
+ public boolean touchUp(float x, float y)
+ {
+ if (hit == null)
+ return false;
+
+ if (hit == turns) {
+ if (turns.hit(x, y))
+ ctrl.hud.askEndOfTurn();
+ }
+ else if (hit == reinforcement) {
+ if (reinforcement.hit(x, y))
+ ctrl.reinforcementHit();
+ }
+ else if (hit == unitDock) {
+ if (unitDock.hit(x, y)) {
+ ctrl.hud.notify(unitDock.select(x, y).toString());
+ ctrl.stateTouchUp();
+ }
+ }
+
+ hit = null;
+
+ return true;
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ unitDock.animate(delta);
+ return false;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ flag.draw(batch);
+ turns.draw(batch);
+ aps.draw(batch);
+ reinforcement.draw(batch);
+ unitDock.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ turns.drawDebug(debugShapes);
+ aps.drawDebug(debugShapes);
+ reinforcement.drawDebug(debugShapes);
+ unitDock.drawDebug(debugShapes);
+ debugShapes.rect(flag.getX(), flag.getY(), flag.getWidth(), flag.getHeight());
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/hud/StatisticsPanel.java b/core/src/ch/asynk/rustanddust/game/hud/StatisticsPanel.java
new file mode 100644
index 0000000..2e6546b
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/hud/StatisticsPanel.java
@@ -0,0 +1,120 @@
+package ch.asynk.rustanddust.game.hud;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.game.Player;
+import ch.asynk.rustanddust.ui.Bg;
+import ch.asynk.rustanddust.ui.Label;
+import ch.asynk.rustanddust.ui.Patch;
+import ch.asynk.rustanddust.ui.Position;
+
+public class StatisticsPanel extends Patch
+{
+ public static int OK_OFFSET = 10;
+ public static int PADDING = 20;
+ public static int VSPACING = 10;
+ public static int HSPACING = 10;
+
+ private Label title;
+ private Label header;
+ private Label stats1;
+ private Label stats2;
+ private Bg okBtn;
+
+ public StatisticsPanel(BitmapFont font, TextureAtlas atlas)
+ {
+ super(atlas.createPatch("typewriter"));
+ this.title = new Label(font);
+ this.header = new Label(font);
+ this.stats1 = new Label(font);
+ this.stats2 = new Label(font);
+ this.okBtn = new Bg(atlas.findRegion("ok"));
+ this.visible = false;
+ this.header.write("\nActions\nUnits Left\nUnits Withrawed\nCasualties\nWon Attacks\nLost Attacks");
+ }
+
+ public void updatePosition()
+ {
+ if (!visible) return;
+ float dx = (position.getX(rect.width) - rect.x);
+ float dy = (position.getY(rect.height) - rect.y);
+ translate(dx, dy);
+ title.translate(dx, dy);
+ header.translate(dx, dy);
+ stats1.translate(dx, dy);
+ stats2.translate(dx, dy);
+ okBtn.translate(dx, dy);
+ }
+
+ public void show(Player winner, Player loser, Position position)
+ {
+ title.write(winner.getName() + " player won the battle in " + winner.getTurnDone() + " turns.");
+ stats1.write(winner.getStats());
+ stats2.write(loser.getStats());
+
+ float height = (title.getHeight() + header.getHeight() + (2 * PADDING) + (1 * VSPACING));
+ float width = (header.getWidth() + stats1.getWidth() + stats2.getWidth() + (2 * PADDING) + (4 * HSPACING));
+ float w2 = (title.getWidth() + (2 * PADDING));
+ if (w2 > width) width = w2;
+ float x = position.getX(width);
+ float y = position.getY(height);
+ setPosition(x, y, width, height);
+
+ okBtn.setPosition((x + width - okBtn.getWidth() + OK_OFFSET), (y - OK_OFFSET));
+
+ y += PADDING;
+ x += PADDING;
+ header.setPosition(x, y);
+ stats1.setPosition((x + header.getWidth() + (2 * HSPACING)), y);
+ stats2.setPosition((stats1.getX() + stats1.getWidth() + (2 * HSPACING)), y);
+ y += (header.getHeight() + VSPACING);
+ title.setPosition(x, y);
+ visible = true;
+ }
+
+ @Override
+ public boolean hit(float x, float y)
+ {
+ if (okBtn.hit(x, y))
+ return true;
+ return false;
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ title.dispose();
+ header.dispose();
+ stats1.dispose();
+ stats2.dispose();
+ okBtn.dispose();
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ super.draw(batch);
+ title.draw(batch);
+ header.draw(batch);
+ stats1.draw(batch);
+ stats2.draw(batch);
+ okBtn.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ if (!visible) return;
+ super.drawDebug(shapes);
+ title.drawDebug(shapes);
+ header.drawDebug(shapes);
+ stats1.drawDebug(shapes);
+ stats2.drawDebug(shapes);
+ okBtn.drawDebug(shapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/hud/UnitDock.java b/core/src/ch/asynk/rustanddust/game/hud/UnitDock.java
new file mode 100644
index 0000000..11895ba
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/hud/UnitDock.java
@@ -0,0 +1,226 @@
+package ch.asynk.rustanddust.game.hud;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.math.Matrix4;
+import com.badlogic.gdx.math.Rectangle;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.UnitList;
+import ch.asynk.rustanddust.ui.Bg;
+import ch.asynk.rustanddust.ui.Position;
+
+public class UnitDock extends Bg implements Animation
+{
+ private static final float SCALE = 0.4f;
+ private static final float STEP = 5f;
+ private final Ctrl ctrl;
+
+ private int n;
+ private float y;
+ private float to;
+ private float dx;
+ private float step;
+ private boolean show;
+ private boolean mvtDone;
+ public Unit selectedUnit;
+ private Sprite selected;
+ private UnitList units;
+ private Vector3 point;
+ private Matrix4 saved;
+ private Matrix4 transform;
+ private Rectangle scaledRect;
+
+ public UnitDock(Ctrl ctrl, TextureRegion region, TextureRegion selected, float padding)
+ {
+ super(region);
+ this.ctrl = ctrl;
+ this.padding = padding;
+ this.mvtDone = true;
+ this.point = new Vector3();
+ this.saved = new Matrix4();
+ this.transform = new Matrix4();
+ this.scaledRect = new Rectangle();
+ this.selected = new Sprite(selected);
+ this.visible = false;
+ this.dx = 0f;
+ }
+
+ @Override
+ public void translate(float _dx, float _dy)
+ {
+ this.y += _dy;
+ if (!visible) return;
+ super.translate(_dx, _dy);
+ for (Unit unit : units)
+ unit.translate(_dx, _dy);
+ to = position.getX(rect.width * SCALE);
+ transform.idt();
+ transform.translate((rect.x + dx), (rect.y + rect.height), 0).scale(SCALE, SCALE, 0).translate(-rect.x, - (rect.y + rect.height), 0);
+ point.set(rect.x, rect.y, 0).mul(transform);
+ scaledRect.x = point.x;
+ scaledRect.y = point.y;
+ point.set((rect.x + rect.width), (rect.y + rect.height), 0).mul(transform);
+ scaledRect.width = point.x - scaledRect.x;
+ scaledRect.height = point.y - scaledRect.y;
+ }
+
+ public void setPosition(Position position, float y)
+ {
+ if (this.position == position)
+ return;
+ this.position = position;
+ this.y = y;
+ this.step = (position.isLeft() ? STEP : -STEP);
+ this.mvtDone = true;
+ this.visible = false;
+ this.dx = 0f;
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ }
+
+ @Override
+ public boolean hit(float x, float y)
+ {
+ return (visible && scaledRect.contains(x, y));
+ }
+
+ public Unit select(float x, float y)
+ {
+ int i = (int) ((scaledRect.y + scaledRect.height - y) / (scaledRect.height / units.size()));
+ selectedUnit = units.get(i);
+ return selectedUnit;
+ }
+
+ public void hide()
+ {
+ if (!visible) return;
+ resize();
+ to = rect.x;
+
+ show = false;
+ mvtDone = false;
+ selectedUnit = null;
+ }
+
+ public void show()
+ {
+ if (!resize())
+ return;
+ to = position.getX(rect.width * SCALE);
+
+ show = true;
+ mvtDone = false;
+ selectedUnit = null;
+ visible = true;
+ }
+
+ private boolean resize()
+ {
+ int count = ctrl.player.reinforcement();
+ if (count == 0) {
+ n = 0;
+ return false;
+ }
+ if (count == n) return true;
+ n = count;
+
+ units = ctrl.player.reinforcement;
+ rect.width = units.get(0).getWidth() + (2 * padding);
+ rect.height = ((units.get(0).getHeight() * n) + ((n + 1) * padding));
+ float scaledWidth = (rect.width * SCALE);
+ to = position.getX(scaledWidth);
+ rect.x = to + (position.isLeft() ? -scaledWidth : scaledWidth);
+ rect.y = y - rect.height;
+
+ float px = rect.x;
+ float py = rect.y + rect.height;
+ float ph = units.get(0).getHeight();
+ for (Unit unit : units) {
+ py -= (ph + padding);
+ // unit.setPosition(px, py, Orientation.SOUTH.r());
+ unit.centerOn((px + (rect.width / 2)), py + (ph / 2));
+ unit.setRotation(position.isLeft() ? Orientation.NORTH.r() : Orientation.SOUTH.r());
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if (!visible) return true;
+ if (mvtDone) return true;
+
+ float x = (rect.x + dx);
+ if (show) {
+ if ((position.isLeft() && (x < to)) || (!position.isLeft() && x > to))
+ dx += step;
+ else {
+ dx = (to - rect.x);
+ mvtDone = true;
+ }
+ } else {
+ if ((position.isLeft() && (x > to)) || (!position.isLeft() && x < to))
+ dx -= step;
+ else {
+ dx = (to - rect.x);
+ mvtDone = true;
+ visible = false;
+ }
+ }
+
+ transform.idt();
+ transform.translate((rect.x + dx), (rect.y + rect.height), 0).scale(SCALE, SCALE, 0).translate(-rect.x, - (rect.y + rect.height), 0);
+ point.set(rect.x, rect.y, 0).mul(transform);
+ scaledRect.x = point.x;
+ scaledRect.y = point.y;
+ point.set((rect.x + rect.width), (rect.y + rect.height), 0).mul(transform);
+ scaledRect.width = point.x - scaledRect.x;
+ scaledRect.height = point.y - scaledRect.y;
+ return false;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+
+ saved.set(batch.getTransformMatrix());
+ batch.setTransformMatrix(transform);
+
+ super.draw(batch);
+ for (Unit unit : units) {
+ unit.draw(batch);
+ if (unit == selectedUnit) {
+ selected.setCenter((unit.getX() + (unit.getWidth() / 2)), (unit.getY() + (unit.getHeight() / 2)));
+ selected.draw(batch);
+ }
+ }
+
+ batch.setTransformMatrix(saved);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ if (!visible) return;
+
+ saved.set(shapes.getTransformMatrix());
+ shapes.setTransformMatrix(transform);
+
+ shapes.rect(rect.x, rect.y, rect.width, rect.height);
+
+ shapes.setTransformMatrix(saved);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateAnimation.java b/core/src/ch/asynk/rustanddust/game/states/StateAnimation.java
new file mode 100644
index 0000000..41831e0
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateAnimation.java
@@ -0,0 +1,37 @@
+package ch.asynk.rustanddust.game.states;
+
+public class StateAnimation extends StateCommon
+{
+ @Override
+ public void enter(StateType prevState)
+ {
+ ctrl.hud.actionButtons.hide();
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ }
+
+ @Override
+ public StateType abort()
+ {
+ return StateType.ABORT;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ return StateType.DONE;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateBreak.java b/core/src/ch/asynk/rustanddust/game/states/StateBreak.java
new file mode 100644
index 0000000..f1e40f6
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateBreak.java
@@ -0,0 +1,90 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public class StateBreak extends StateCommon
+{
+ private Orientation o = Orientation.KEEP;
+
+ @Override
+ public void enter(StateType prevState)
+ {
+ activeUnit = null;
+ ctrl.hud.actionButtons.show(Buttons.DONE.b);
+ ctrl.hud.pushNotify("Break move possible");
+ map.showBreakUnits();
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ map.hideBreakUnits();
+ map.hideMove(to);
+ map.hideDirections(to);
+ map.hideOrientation(to);
+ if (activeUnit != null) map.hideMove(activeUnit.getHex());
+ }
+
+ @Override
+ public StateType abort()
+ {
+ return StateType.ABORT;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ return StateType.DONE;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ // TODO : cancel preview move before showing rotation
+ if (activeUnit == null) {
+ Unit unit = upHex.getUnit();
+ if (map.breakUnits.contains(unit)) {
+ activeUnit = unit;
+ map.showMove(upHex);
+ map.showMove(to);
+ map.showDirections(to);
+ map.hideBreakUnits();
+ }
+ } else {
+ o = Orientation.fromAdj(to, upHex);
+
+ if (o == Orientation.KEEP) return;
+
+ if (ctrl.cfg.mustValidate) {
+ map.hideDirections(to);
+ map.showOrientation(to, o);
+ ctrl.hud.actionButtons.show(Buttons.DONE.b);
+ } else {
+ doRotation(o);
+ ctrl.setState(StateType.ANIMATION);
+ }
+ }
+ }
+
+ private void doRotation(Orientation o)
+ {
+ if (activeUnit == null) return;
+
+ map.pathBuilder.init(activeUnit);
+ if (map.pathBuilder.build(to) == 1) {
+ map.pathBuilder.orientation = o;
+ map.moveUnit(activeUnit);
+ ctrl.setAfterAnimationState(StateType.DONE);
+ } else
+ RustAndDust.debug("That's very wrong there should be only one path");
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateCommon.java b/core/src/ch/asynk/rustanddust/game/states/StateCommon.java
new file mode 100644
index 0000000..443182d
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateCommon.java
@@ -0,0 +1,68 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.State;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public abstract class StateCommon implements State
+{
+ protected static Ctrl ctrl;
+ protected static Map map;
+
+ protected static Hex selectedHex = null;
+ protected static Hex downHex = null;
+ protected static Hex upHex = null;
+ protected static Hex to = null;
+
+ protected boolean isEnemy;
+ protected static Unit activeUnit;
+ protected static Unit selectedUnit;
+
+ protected StateCommon()
+ {
+ }
+
+ public StateCommon(Ctrl ctrl, Map map)
+ {
+ this.ctrl = ctrl;
+ this.map = map;
+ }
+
+ @Override
+ public boolean downInMap(float x, float y)
+ {
+ downHex = map.getHexAt(x, y);
+ return (downHex != null);
+ }
+
+ @Override
+ public boolean upInMap(float x, float y)
+ {
+ upHex = map.getHexAt(x, y);
+ return (upHex != null);
+ }
+
+ protected boolean hasUnit()
+ {
+ return (selectedUnit != null);
+ }
+
+ protected void showPossibilities(Unit unit)
+ {
+ if (ctrl.cfg.showMoves && unit.canMove()) map.showPossibleMoves();
+ if (ctrl.cfg.showTargets && unit.canEngage()) map.showPossibleTargets();
+ if (ctrl.cfg.showMoveAssists && unit.canMove()) map.showMoveableUnits();
+ unit.enableOverlay(Unit.MOVE, false);
+ }
+
+ protected void hidePossibilities()
+ {
+ map.hidePossibleMoves();
+ map.hidePossibleTargets();
+ map.hideMoveableUnits();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateDeployment.java b/core/src/ch/asynk/rustanddust/game/states/StateDeployment.java
new file mode 100644
index 0000000..9528d2a
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateDeployment.java
@@ -0,0 +1,138 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.UnitList;
+import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public class StateDeployment extends StateCommon
+{
+ private boolean completed;
+ private Zone entryZone;
+ private UnitList deployedUnits = new UnitList(10);
+
+ @Override
+ public void enter(StateType prevState)
+ {
+ if (selectedHex != null)
+ map.unselectHex(selectedHex);
+ completed = false;
+ entryZone = null;
+ selectedHex = null;
+ selectedUnit = null;
+ ctrl.hud.actionButtons.hide();
+ ctrl.hud.playerInfo.unitDock.show();
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ selectedUnit = null;
+ if (selectedHex != null)
+ map.unselectHex(selectedHex);
+ if (entryZone != null)
+ entryZone.enable(Hex.AREA, false);
+ ctrl.hud.playerInfo.unitDock.hide();
+ }
+
+ @Override
+ public StateType abort()
+ {
+ if (activeUnit != null)
+ undo();
+ return StateType.DEPLOYMENT;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ deployedUnits.clear();
+ return StateType.DONE;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ Unit unit = ctrl.hud.playerInfo.unitDock.selectedUnit;
+ if (!completed && (unit != null) && (unit != activeUnit)) {
+ showEntryZone(unit);
+ } else if (selectedUnit != null) {
+ doRotation(Orientation.fromAdj(selectedHex, upHex));
+ } else if (!completed && (entryZone != null) && (upHex != null)) {
+ if (upHex.isEmpty() && entryZone.contains(upHex))
+ unitEnter(activeUnit);
+ } else {
+ unit = downHex.getUnit();
+ if (deployedUnits.contains(unit)) {
+ showRotation(unit, downHex);
+ activeUnit = unit;
+ }
+ }
+ }
+
+ private void showEntryZone(Unit unit)
+ {
+ activeUnit = unit;
+ if (entryZone != null) entryZone.enable(Hex.AREA, false);
+ entryZone = ctrl.battle.getEntryZone(activeUnit);
+ entryZone.enable(Hex.AREA, true);
+ }
+
+ private void undo()
+ {
+ map.unselectHex(selectedHex);
+ map.hideDirections(selectedHex);
+ map.revertEnter(activeUnit);
+ activeUnit = null;
+ selectedUnit = null;
+ ctrl.hud.update();
+ }
+
+ private void unitEnter(Unit unit)
+ {
+ selectedUnit = unit;
+ selectedHex = upHex;
+ ctrl.player.reinforcement.remove(unit);
+ map.showOnBoard(unit, upHex, entryZone.orientation);
+ deployedUnits.add(unit);
+ entryZone.enable(Hex.AREA, false);
+ showRotation(unit, upHex);
+ ctrl.hud.update();
+ }
+
+ private void showRotation(Unit unit, Hex hex)
+ {
+ selectedUnit = unit;
+ selectedHex = hex;
+ map.selectHex(selectedHex);
+ map.showDirections(selectedHex);
+ ctrl.hud.playerInfo.unitDock.hide();
+ ctrl.hud.actionButtons.show(Buttons.ABORT.b);
+ }
+
+ private void doRotation(Orientation o)
+ {
+ map.unselectHex(selectedHex);
+ map.hideDirections(selectedHex);
+
+ if (o != Orientation.KEEP)
+ map.setOnBoard(selectedUnit, selectedHex, o);
+
+ ctrl.hud.actionButtons.hide();
+ ctrl.hud.playerInfo.unitDock.show();
+ entryZone = null;
+ activeUnit = null;
+ selectedUnit = null;
+ if (ctrl.checkDeploymentDone())
+ completed = true;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateEngage.java b/core/src/ch/asynk/rustanddust/game/states/StateEngage.java
new file mode 100644
index 0000000..4588cb2
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateEngage.java
@@ -0,0 +1,105 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public class StateEngage extends StateCommon
+{
+ @Override
+ public void enter(StateType prevState)
+ {
+ map.possibleTargets.clear();
+ ctrl.hud.actionButtons.show(ctrl.cfg.canCancel ? Buttons.ABORT.b : 0);
+
+ // activeUnit is the target
+ if (prevState == StateType.SELECT) {
+ activeUnit = null;
+ // use selectedHex and selectedUnit
+ map.hidePossibleTargets();
+ map.collectPossibleTargets(selectedUnit, ctrl.opponent.units);
+ map.showPossibleTargets();
+ if (to != null) {
+ // quick fire -> replay touchUp
+ upHex = to;
+ touchUp();
+ }
+ selectedUnit.showAttack();
+ map.selectHex(selectedHex);
+ } else
+ RustAndDust.debug("should not happen");
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ selectedUnit.hideAttack();
+ map.hideAttackAssists();
+ map.hidePossibleTargets();
+ map.unselectHex(selectedHex);
+ if (to != null)
+ map.unselectHex(to);
+ }
+
+ @Override
+ public StateType abort()
+ {
+ map.activatedUnits.clear();
+ return StateType.ABORT;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ StateType nextState = StateType.DONE;
+ if (map.engageUnit(selectedUnit, activeUnit)) {
+ ctrl.player.wonEngagementCount += 1;
+ ctrl.opponent.casualty(activeUnit);
+ if (map.breakUnits.size() > 0) {
+ nextState = StateType.BREAK;
+ }
+ } else {
+ ctrl.player.lostEngagementCount += 1;
+ }
+
+ activeUnit.showTarget();
+ ctrl.setAfterAnimationState(nextState);
+ return StateType.ANIMATION;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ Unit unit = upHex.getUnit();
+
+ // activeUnit is the target, selectedTarget is the engagement leader
+ if (unit == selectedUnit) {
+ ctrl.setState(StateType.ABORT);
+ } else if ((activeUnit == null) && map.possibleTargets.contains(unit)) {
+ // ctrl.hud.notify("Engage " + unit);
+ map.hidePossibleTargets();
+ to = upHex;
+ activeUnit = unit;
+ activeUnit.showTarget();
+ map.collectAttackAssists(selectedUnit, activeUnit, ctrl.player.units);
+ map.showAttackAssists();
+ ctrl.hud.actionButtons.show((ctrl.cfg.mustValidate ? Buttons.DONE.b : 0) | (ctrl.cfg.canCancel ? Buttons.ABORT.b : 0));
+ }
+ else if (unit == activeUnit) {
+ ctrl.setState(StateType.DONE);
+ }
+ else if ((activeUnit != null) && map.engagementAssists.contains(unit)) {
+ map.toggleAttackAssist(unit);
+ // if(map.toggleAttackAssist(unit))
+ // ctrl.hud.notify(unit + " will fire");
+ // else
+ // ctrl.hud.notify(unit + " wont fire");
+ }
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateMove.java b/core/src/ch/asynk/rustanddust/game/states/StateMove.java
new file mode 100644
index 0000000..b59b133
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateMove.java
@@ -0,0 +1,193 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons;
+
+public class StateMove extends StateCommon
+{
+ @Override
+ public void enter(StateType prevState)
+ {
+ ctrl.hud.actionButtons.show(
+ ((map.activatedUnits.size() > 0) ? Buttons.DONE.b : 0)
+ | (ctrl.cfg.canCancel ? Buttons.ABORT.b : 0));
+
+ if (prevState == StateType.WITHDRAW) {
+ if (map.pathBuilder.size() == 1)
+ ctrl.setState(StateType.ROTATE);
+ return;
+ }
+
+ map.pathBuilder.clear();
+
+ if (prevState == StateType.SELECT) {
+ // use selectedHex and selectedUnit
+ activeUnit = selectedUnit;
+ activeUnit.showMoveable();
+ map.pathBuilder.init(activeUnit);
+ map.collectAndShowMovesAndAssits(activeUnit);
+ if (to != null) {
+ // quick move -> replay touchUp
+ upHex = to;
+ touchUp();
+ } else
+ checkExit(activeUnit, activeUnit.getHex());
+ } else {
+ // back from rotation -> chose next Pawn
+ if (selectedUnit.canMove()) {
+ changeUnit(selectedUnit);
+ } else {
+ changeUnit(map.moveableUnits.get(0));
+ }
+ }
+
+ activeUnit.enableOverlay(Unit.MOVE, false);
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ if (nextState == StateType.WITHDRAW)
+ return;
+
+ // hide all but assists : want them when in rotation
+ activeUnit.hideMoveable();
+ map.hidePossibleMoves();
+ map.unselectHex(activeUnit.getHex());
+ if (to != null)
+ map.hidePath(to);
+
+ if (nextState != StateType.SELECT) {
+ if (to == null)
+ to = activeUnit.getHex();
+ }
+ }
+
+ @Override
+ public StateType abort()
+ {
+ hideAssists();
+ if (activeUnit.justEntered()) {
+ map.revertEnter(activeUnit);
+ return StateType.ABORT;
+ }
+ int n = map.activatedUnits.size();
+ if (n == 0)
+ return StateType.ABORT;
+ map.revertMoves();
+ return StateType.ANIMATION;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ hideAssists();
+ // be sure that the hq is activated
+ if (selectedUnit.canMove() && (map.activatedUnits.size() > 0))
+ selectedUnit.setMoved();
+
+ return StateType.DONE;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ if (upHex == activeUnit.getHex()) {
+ if (to != null)
+ map.hidePath(to);
+ to = null;
+ map.pathBuilder.clear();
+ ctrl.setState(StateType.ROTATE);
+ return;
+ }
+
+ int s = map.pathBuilder.size();
+
+ Unit unit = upHex.getUnit();
+
+ if (map.moveableUnits.contains(unit)) {
+ if(unit != activeUnit)
+ changeUnit(unit);
+ } else if ((s == 0) && map.possibleMoves.contains(upHex)) {
+ s = collectPaths(upHex);
+ } else if (map.pathBuilder.contains(upHex)) {
+ s = togglePoint(downHex, s);
+ }
+
+ if (s == 1) {
+ if (!checkExit(activeUnit, upHex))
+ ctrl.setState(StateType.ROTATE);
+ }
+ }
+
+ private void hideAssists()
+ {
+ map.hideMoveableUnits();
+ }
+
+ private void changeUnit(Unit unit)
+ {
+ if (activeUnit != null ) {
+ map.unselectHex(activeUnit.getHex());
+ if (activeUnit.canMove())
+ activeUnit.enableOverlay(Unit.MOVE, true);
+ }
+ activeUnit = unit;
+ Hex hex = activeUnit.getHex();
+ map.pathBuilder.init(activeUnit, hex);
+ activeUnit.showMoveable();
+ map.hidePossibleMoves();
+ map.collectPossibleMoves(activeUnit);
+ map.showPossibleMoves();
+ map.selectHex(hex);
+ activeUnit.enableOverlay(Unit.MOVE, false);
+ ctrl.hud.notify(activeUnit.toString());
+ checkExit(activeUnit, hex);
+ }
+
+ private int collectPaths(Hex hex)
+ {
+ to = hex;
+ int s = map.pathBuilder.build(to);
+ map.showMove(to);
+ map.hidePossibleMoves();
+ map.showPathBuilder();
+ return s;
+ }
+
+ private int togglePoint(Hex hex, int s)
+ {
+ if (hex == activeUnit.getHex()) {
+ //
+ } else if (hex == to) {
+ //
+ } else {
+ map.hidePathBuilder();
+ map.togglePathOverlay(hex);
+ s = map.togglePathBuilderHex(hex);
+ map.showPathBuilder();
+ }
+
+ return s;
+ }
+
+ private boolean checkExit(Unit unit, Hex hex)
+ {
+ if ((hex == unit.getHex()) && (unit.justEntered()))
+ return false;
+ Zone exitZone = ctrl.battle.getExitZone(unit);
+ if ((exitZone == null) || !exitZone.contains(hex))
+ return false;
+ if ((unit.getHex() != hex) && !map.pathBuilder.canExit(exitZone.orientation))
+ return false;
+ ctrl.setState(StateType.WITHDRAW);
+ return true;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StatePromote.java b/core/src/ch/asynk/rustanddust/game/states/StatePromote.java
new file mode 100644
index 0000000..8543c89
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StatePromote.java
@@ -0,0 +1,42 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.game.Unit;
+
+public class StatePromote extends StateCommon
+{
+ @Override
+ public void enter(StateType prevState)
+ {
+ ctrl.setAfterAnimationState(StateType.DONE);
+ ctrl.setState(StateType.ANIMATION);
+ map.promoteUnit(selectedUnit);
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ map.unselectHex(selectedHex);
+ }
+
+ @Override
+ public StateType abort()
+ {
+ return StateType.ABORT;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ return StateType.DONE;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateReinforcement.java b/core/src/ch/asynk/rustanddust/game/states/StateReinforcement.java
new file mode 100644
index 0000000..77ff826
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateReinforcement.java
@@ -0,0 +1,87 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons;
+
+public class StateReinforcement extends StateCommon
+{
+ private Zone entryZone;
+
+ @Override
+ public void enter(StateType prevState)
+ {
+ map.clearAll();
+ if (selectedHex != null)
+ map.unselectHex(selectedHex);
+ entryZone = null;
+ selectedHex = null;
+ ctrl.hud.playerInfo.unitDock.show();
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ if (selectedHex != null)
+ map.unselectHex(selectedHex);
+ if (entryZone != null)
+ entryZone.enable(Hex.AREA, false);
+ ctrl.hud.playerInfo.unitDock.hide();
+ }
+
+ @Override
+ public StateType abort()
+ {
+ return StateType.ABORT;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ return StateType.DONE;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ Unit unit = ctrl.hud.playerInfo.unitDock.selectedUnit;
+ if ((unit != null) && (unit != activeUnit))
+ changeUnit(unit);
+ else if ((entryZone != null) && upHex.isEmpty() && entryZone.contains(upHex))
+ unitEnter(activeUnit);
+ else
+ ctrl.setState(StateType.SELECT);
+ }
+
+ private void changeUnit(Unit unit)
+ {
+ activeUnit = unit;
+ if (entryZone != null)
+ entryZone.enable(Hex.AREA, false);
+ entryZone = ctrl.battle.getEntryZone(activeUnit);
+ entryZone.enable(Hex.AREA, true);
+ ctrl.hud.actionButtons.show(((ctrl.cfg.canCancel) ? Buttons.ABORT.b : 0));
+ }
+
+ private void unitEnter(Unit unit)
+ {
+ selectedUnit = unit;
+ selectedHex = upHex;
+ map.selectHex(selectedHex);
+ entryZone.enable(Hex.AREA, false);
+ if (map.enterBoard(unit, upHex, entryZone.allowedMoves)) {
+ if (unit.getMovementPoints() > 0)
+ ctrl.setState(StateType.MOVE);
+ else
+ ctrl.setState(StateType.ROTATE);
+ } else {
+ ctrl.hud.notify("Can not enter the map at that position");
+ }
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateRotate.java b/core/src/ch/asynk/rustanddust/game/states/StateRotate.java
new file mode 100644
index 0000000..4d91740
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateRotate.java
@@ -0,0 +1,111 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.engine.Orientation;
+import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public class StateRotate extends StateCommon
+{
+ private boolean rotateOnly;
+ private boolean rotationSet;
+
+ @Override
+ public void enter(StateType prevState)
+ {
+ ctrl.hud.actionButtons.show((ctrl.cfg.canCancel && (map.moveableUnits.size() > 1))? Buttons.ABORT.b : 0);
+
+ if (activeUnit == null)
+ activeUnit = selectedUnit;
+ if (to == null)
+ to = activeUnit.getHex();
+
+ if (!map.pathBuilder.isSet()) {
+ map.pathBuilder.init(activeUnit);
+ map.pathBuilder.build(to);
+ }
+
+ if (map.pathBuilder.size() != 1)
+ RustAndDust.debug("ERROR: pathBuilder.size() == " + map.pathBuilder.size());
+
+ rotateOnly = (to == activeUnit.getHex());
+
+ if (!rotateOnly)
+ map.showPath(to);
+ map.selectHex(activeUnit.getHex());
+ map.showDirections(to);
+
+ rotationSet = false;
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ map.unselectHex(activeUnit.getHex());
+ map.hidePath(to);
+ map.hideDirections(to);
+ map.hideOrientation(to);
+ map.pathBuilder.clear();
+ to = null;
+ }
+
+ @Override
+ public StateType abort()
+ {
+ StateType nextState = StateType.ABORT;
+ ctrl.hud.actionButtons.hide();
+ if (activeUnit.justEntered()) {
+ map.revertEnter(activeUnit);
+ nextState = StateType.ABORT;
+ } else if (map.activatedUnits.size() == 0) {
+ map.hideMoveableUnits();
+ } else {
+ nextState = StateType.MOVE;
+ }
+ return nextState;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ StateType whenDone = StateType.DONE;
+
+ if (map.moveUnit(activeUnit) > 0)
+ whenDone = StateType.MOVE;
+
+ ctrl.setAfterAnimationState(whenDone);
+ return StateType.ANIMATION;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ if (rotationSet) return;
+
+ Orientation o = Orientation.fromAdj(to, upHex);
+ if (o == Orientation.KEEP) {
+ ctrl.setState(StateType.ABORT);
+ return;
+ }
+
+ if (!activeUnit.justEntered() && rotateOnly && (o == activeUnit.getOrientation()))
+ return;
+
+ map.pathBuilder.orientation = o;
+ rotationSet = true;
+
+ if (ctrl.cfg.mustValidate) {
+ map.hideDirections(to);
+ map.showOrientation(to, o);
+ ctrl.hud.actionButtons.show(Buttons.DONE.b | ((ctrl.cfg.canCancel) ? Buttons.ABORT.b : 0));
+ } else {
+ execute();
+ ctrl.setState(StateType.ANIMATION);
+ }
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateSelect.java b/core/src/ch/asynk/rustanddust/game/states/StateSelect.java
new file mode 100644
index 0000000..9161a6b
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateSelect.java
@@ -0,0 +1,132 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.game.Map;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.Unit;
+import ch.asynk.rustanddust.game.Ctrl;
+import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public class StateSelect extends StateCommon
+{
+ public StateSelect(Ctrl ctrl, Map map)
+ {
+ super(ctrl, map);
+ }
+
+ @Override
+ public void enter(StateType prevState)
+ {
+ to = null;
+ selectedHex = null;
+ selectedUnit = null;
+ activeUnit = null;
+ map.clearAll();
+ ctrl.hud.actionButtons.hide();
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ hidePossibilities();
+ }
+
+ @Override
+ public StateType abort()
+ {
+ if (selectedHex != null)
+ map.unselectHex(selectedHex);
+ hidePossibilities();
+ map.clearAll();
+ return StateType.ABORT;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ return StateType.DONE;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ if (!isEnemy) {
+ if (map.possibleMoves.contains(upHex)) {
+ // quick move
+ to = upHex;
+ ctrl.setState(StateType.MOVE);
+ return;
+ }
+ if (map.possibleTargets.contains(upHex.getUnit())) {
+ // quick fire
+ to = upHex;
+ ctrl.setState(StateType.ENGAGE);
+ return;
+ }
+ }
+
+ if (selectedHex != null)
+ map.unselectHex(selectedHex);
+
+ hidePossibilities();
+ if (upHex.isOffMap()) {
+ selectedUnit = null;
+ return;
+ }
+
+ Unit unit = upHex.getUnit();
+
+ if (unit == null) {
+ isEnemy = false;
+ ctrl.hud.actionButtons.hide();
+ map.clearAll();
+ selectedUnit = null;
+ return;
+ }
+
+ isEnemy = ctrl.player.isEnemy(unit);
+ if (!isEnemy && (unit == selectedUnit) && unit.canMove()) {
+ if (unit.isHq()) {
+ ctrl.hud.notify("HQ activation");
+ select(upHex, unit, isEnemy);
+ ctrl.setState(StateType.MOVE);
+ } else {
+ // quick rotate
+ to = upHex;
+ ctrl.setState(StateType.ROTATE);
+ }
+ } else {
+ select(upHex, unit, isEnemy);
+ ctrl.hud.notify(selectedUnit.toString());
+ }
+ }
+
+ private void select(Hex hex, Unit unit, boolean isEnemy)
+ {
+ selectedHex = hex;
+ selectedUnit = unit;
+
+ if (isEnemy && !ctrl.cfg.showEnemyPossibilities)
+ return;
+
+ int moves = map.collectPossibleMoves(selectedUnit);
+ int targets = map.collectPossibleTargets(selectedUnit, (isEnemy ? ctrl.player.units : ctrl.opponent.units));
+
+ if (moves > 0)
+ map.collectMoveableUnits(selectedUnit);
+
+ if ((moves > 0) || (targets > 0)) {
+ map.selectHex(selectedHex);
+ showPossibilities(selectedUnit);
+ }
+
+ ctrl.hud.actionButtons.show((ctrl.player.canPromote(selectedUnit)) ? Buttons.PROMOTE.b : 0 );
+ RustAndDust.debug("Select", selectedHex.toString() + " " + selectedUnit + (isEnemy ? " enemy " : " friend "));
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/game/states/StateWithdraw.java b/core/src/ch/asynk/rustanddust/game/states/StateWithdraw.java
new file mode 100644
index 0000000..f4f11a6
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/game/states/StateWithdraw.java
@@ -0,0 +1,71 @@
+package ch.asynk.rustanddust.game.states;
+
+import ch.asynk.rustanddust.game.Zone;
+import ch.asynk.rustanddust.game.Hex;
+import ch.asynk.rustanddust.game.Unit;
+
+public class StateWithdraw extends StateCommon
+{
+ @Override
+ public void enter(StateType prevState)
+ {
+ ctrl.hud.askExitBoard();
+ }
+
+ @Override
+ public void leave(StateType nextState)
+ {
+ }
+
+ @Override
+ public StateType abort()
+ {
+ return StateType.MOVE;
+ }
+
+ @Override
+ public StateType execute()
+ {
+ if (activeUnit == null)
+ activeUnit = selectedUnit;
+
+ ctrl.setAfterAnimationState(withdraw(activeUnit));
+ return StateType.ANIMATION;
+ }
+
+ @Override
+ public void touchDown()
+ {
+ }
+
+ @Override
+ public void touchUp()
+ {
+ }
+
+ private StateType withdraw(Unit unit)
+ {
+ Zone exitZone = ctrl.battle.getExitZone(unit);
+ Hex hex = unit.getHex();
+
+ // rotation
+ if (map.pathBuilder.to == null)
+ map.pathBuilder.build(hex);
+
+ Hex exitHex = (Hex) map.pathBuilder.to;
+ if (!exitZone.contains(exitHex))
+ throw new RuntimeException(String.format("%s not in exitZone", exitHex));
+
+ map.pathBuilder.setExit(exitZone.orientation);
+
+ unit.hideMoveable();
+ if (to != null)
+ map.hidePath(to);
+ map.hidePossibleMoves();
+ map.unselectHex(hex);
+
+ if (map.exitBoard(unit) > 0)
+ return StateType.MOVE;
+ return StateType.DONE;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/loading/LoadingBar.java b/core/src/ch/asynk/rustanddust/loading/LoadingBar.java
new file mode 100644
index 0000000..5a6181a
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/loading/LoadingBar.java
@@ -0,0 +1,32 @@
+package ch.asynk.rustanddust.loading;
+
+import com.badlogic.gdx.graphics.g2d.Animation;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.scenes.scene2d.Actor;
+
+public class LoadingBar extends Actor
+{
+ Animation animation;
+ TextureRegion reg;
+ float stateTime;
+
+ public LoadingBar(Animation animation)
+ {
+ this.animation = animation;
+ reg = animation.getKeyFrame(0);
+ }
+
+ @Override
+ public void act(float delta)
+ {
+ stateTime += delta;
+ reg = animation.getKeyFrame(stateTime);
+ }
+
+ @Override
+ public void draw(Batch batch, float parentAlpha)
+ {
+ batch.draw(reg, getX(), getY());
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/menu/MainMenu.java b/core/src/ch/asynk/rustanddust/menu/MainMenu.java
new file mode 100644
index 0000000..769adb3
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/menu/MainMenu.java
@@ -0,0 +1,67 @@
+package ch.asynk.rustanddust.menu;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.rustanddust.ui.Menu;
+
+public class MainMenu extends Menu
+{
+ public enum Items implements Menu.MenuItem
+ {
+ EXIT(0),
+ OPTIONS(1),
+ TUTORIALS(2),
+ SCENARIOS(3),
+ NONE(4);
+ public int i;
+ Items(int i)
+ {
+ this.i = i;
+ }
+ public int i() { return i; }
+ public int last() { return NONE.i; }
+ };
+
+ public MainMenu(BitmapFont font, TextureAtlas atlas)
+ {
+ super(Items.NONE, font, atlas.createPatch("typewriter"));
+
+ label(Items.OPTIONS).write("Options");
+ label(Items.TUTORIALS).write("Tutorials");
+ label(Items.SCENARIOS).write("Scenarios");
+ label(Items.EXIT).write("Exit");
+
+ this.visible = true;
+ }
+
+ public Items getMenu()
+ {
+ return (Items) menuItem;
+ }
+
+ @Override
+ public boolean hit(float x, float y)
+ {
+ boolean ret = false;
+ menuItem = Items.NONE;
+
+ if (!visible) return ret;
+
+ if (label(Items.SCENARIOS).hit(x, y)) {
+ menuItem = Items.SCENARIOS;
+ ret = true;
+ } else if (label(Items.TUTORIALS).hit(x, y)) {
+ menuItem = Items.TUTORIALS;
+ ret = true;
+ } else if (label(Items.OPTIONS).hit(x, y)) {
+ menuItem = Items.OPTIONS;
+ ret = true;
+ } else if (label(Items.EXIT).hit(x, y)) {
+ Gdx.app.exit();
+ }
+
+ return ret;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/menu/OptionsMenu.java b/core/src/ch/asynk/rustanddust/menu/OptionsMenu.java
new file mode 100644
index 0000000..9a7c675
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/menu/OptionsMenu.java
@@ -0,0 +1,257 @@
+package ch.asynk.rustanddust.menu;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.GlyphLayout;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.rustanddust.ui.Label;
+import ch.asynk.rustanddust.ui.Bg;
+import ch.asynk.rustanddust.ui.Patch;
+import ch.asynk.rustanddust.ui.OkCancel;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+public class OptionsMenu extends Patch
+{
+ public static int PADDING = 40;
+ public static int OK_PADDING = 10;
+ public static int TITLE_PADDING = 30;
+ public static int VSPACING = 5;
+ public static int HSPACING = 30;
+ public static String CHECK = "#";
+
+ private final RustAndDust game;
+ private final BitmapFont font;
+
+ private String [] checkStrings = {
+ "Debug",
+ "Must Validate",
+ "Can Cancel",
+ "Show Enemy Possibilities",
+ "Show Moves Assists",
+ "Show Targets",
+ "Show Moves",
+ };
+ private String [] fxStrings = { "OFF", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "ON" };
+
+ private float checkDy;
+ private Label title;
+ private Label fxVolume;
+ private Label fxVolumeValue;
+ private Label graphics;
+ private Label graphicsValue;
+ private Label gameMode;
+ private Label gameModeValue;
+ private Label [] checkLabels;
+ private boolean [] checkValues;
+ private OkCancel okCancel;
+ protected Bg okBtn;
+
+ public OptionsMenu(RustAndDust game, BitmapFont font, TextureAtlas atlas)
+ {
+ super(atlas.createPatch("typewriter"));
+ this.game = game;
+ this.font = font;
+ this.okCancel = new OkCancel(font, atlas);
+ this.okBtn = new Bg(atlas.findRegion("ok"));
+ this.title = new Label(font);
+ this.title.write("- Options");
+ this.fxVolume = new Label(font);
+ this.fxVolume.write("Fx Volume");
+ this.fxVolumeValue = new Label(font);
+ this.fxVolumeValue.write(fxStrings[(int) (game.config.fxVolume * 10)]);
+ this.graphics = new Label(font);
+ this.graphics.write("Graphics");
+ this.graphicsValue = new Label(font);
+ this.graphicsValue.write(game.config.graphics.s);
+ this.gameMode = new Label(font);
+ this.gameMode.write("Game mode");
+ this.gameModeValue = new Label(font);
+ this.gameModeValue.write(game.config.gameMode.s);
+ this.checkValues = new boolean[checkStrings.length];
+ this.checkLabels = new Label[checkStrings.length];
+ for (int i = 0; i < checkLabels.length; i++) {
+ Label l = new Label(font, 5f);
+ l.write(checkStrings[i]);
+ this.checkLabels[i] = l;
+ }
+ getValues();
+ GlyphLayout layout = new GlyphLayout();
+ layout.setText(font, CHECK);
+ checkDy = layout.height + 5;
+
+ this.visible = false;
+ }
+
+ private void getValues()
+ {
+ checkValues[6] = game.config.showMoves;
+ checkValues[5] = game.config.showTargets;
+ checkValues[4] = game.config.showMoveAssists;
+ checkValues[3] = game.config.showEnemyPossibilities;
+ checkValues[2] = game.config.canCancel;
+ checkValues[1] = game.config.mustValidate;
+ checkValues[0] = game.config.debug;
+ }
+
+ private boolean apply()
+ {
+ game.config.showMoves = checkValues[6];
+ game.config.showTargets = checkValues[5];
+ game.config.showMoveAssists = checkValues[4];
+ game.config.showEnemyPossibilities = checkValues[3];
+ game.config.canCancel = checkValues[2];
+ game.config.mustValidate = checkValues[1];
+ game.config.debug = checkValues[0];
+ if (!game.config.gameModeImplemented()) {
+ this.visible = false;
+ okCancel.show(String.format("'%s' Game Mode not implemented yet.", game.config.gameMode.s));
+ okCancel.noCancel();
+ return false;
+ }
+ return true;
+ }
+
+ private void cycleFxVolume()
+ {
+ int i = (int) (game.config.fxVolume * 10) + 1;
+ if (i > 10) i = 0;
+ float fx = fxVolumeValue.getX();
+ float fy = fxVolumeValue.getY();
+ fxVolumeValue.write(fxStrings[i]);
+ fxVolumeValue.setPosition(fx, fy);
+ game.config.fxVolume = (i / 10f);
+ }
+
+ private void cycleGraphics()
+ {
+ game.config.graphics = game.config.graphics.next();
+ float fx = graphicsValue.getX();
+ float fy = graphicsValue.getY();
+ graphicsValue.write(game.config.graphics.s);
+ graphicsValue.setPosition(fx, fy);
+ }
+
+ private void cycleGameMode()
+ {
+ game.config.gameMode = game.config.gameMode.next();
+ float fx = gameModeValue.getX();
+ float fy = gameModeValue.getY();
+ gameModeValue.write(game.config.gameMode.s);
+ gameModeValue.setPosition(fx, fy);
+ }
+
+ public void setPosition()
+ {
+ float h = (title.getHeight() + TITLE_PADDING + ((checkLabels.length - 1) * VSPACING) + (2 * PADDING));
+ for (int i = 0; i < checkLabels.length; i++)
+ h += checkLabels[i].getHeight();
+ h += (graphics.getHeight() + VSPACING);
+ h += (gameMode.getHeight() + VSPACING);
+ h += (fxVolume.getHeight() + VSPACING);
+
+ float w = title.getWidth();
+ for (int i = 0; i < checkLabels.length; i++) {
+ float t = checkLabels[i].getWidth();
+ if (t > w)
+ w = t;
+ }
+ w += (2 * PADDING) + HSPACING;
+
+ float x = position.getX(w);
+ float y = position.getY(h);
+ setPosition(x, y, w, h);
+
+ okBtn.setPosition((x + w - okBtn.getWidth() + OK_PADDING), (y - OK_PADDING));
+
+ y += PADDING;
+ x += PADDING + HSPACING;
+ float dy = (VSPACING + checkLabels[0].getHeight());
+
+ graphics.setPosition(x, y);
+ graphicsValue.setPosition((x + graphics.getWidth() + 10), y);
+ y += dy;
+ gameMode.setPosition(x, y);
+ gameModeValue.setPosition((x + gameMode.getWidth() + 10), y);
+ y += dy;
+ fxVolume.setPosition(x, y);
+ fxVolumeValue.setPosition((x + fxVolume.getWidth() + 10), y);
+ y += dy;
+ for (int i = 0; i < checkLabels.length; i++) {
+ checkLabels[i].setPosition(x, y);
+ y += dy;
+ }
+ y += (TITLE_PADDING - VSPACING);
+ title.setPosition(x, y);
+ }
+
+ @Override
+ public boolean hit(float x, float y)
+ {
+ if (okCancel.hit(x, y)) {
+ this.visible = true;
+ okCancel.visible = false;
+ return false;
+ }
+
+ if (!visible) return false;
+
+ if (okBtn.hit(x, y)) {
+ return apply();
+ } else if (fxVolume.hit(x, y) || fxVolumeValue.hit(x, y)) {
+ cycleFxVolume();
+ } else if (graphics.hit(x, y) || graphicsValue.hit(x, y)) {
+ cycleGraphics();
+ } else if (gameMode.hit(x, y) || gameModeValue.hit(x, y)) {
+ cycleGameMode();
+ } else {
+ for (int i = 0; i < checkLabels.length; i++) {
+ if (checkLabels[i].hit(x, y))
+ checkValues[i] =! checkValues[i];
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ title.dispose();
+ okBtn.dispose();
+ okCancel.dispose();
+ fxVolume.dispose();
+ fxVolumeValue.dispose();
+ graphics.dispose();
+ graphicsValue.dispose();
+ gameMode.dispose();
+ gameModeValue.dispose();
+ for (int i = 0; i < checkLabels.length; i++)
+ checkLabels[i].dispose();
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ okCancel.draw(batch);
+
+ if (!visible) return;
+ super.draw(batch);
+ title.draw(batch);
+ okBtn.draw(batch);
+ fxVolume.draw(batch);
+ fxVolumeValue.draw(batch);
+ graphics.draw(batch);
+ graphicsValue.draw(batch);
+ gameMode.draw(batch);
+ gameModeValue.draw(batch);
+ for (int i = 0; i < checkLabels.length; i++) {
+ Label l = checkLabels[i];
+ l.draw(batch);
+ if (checkValues[i])
+ font.draw(batch, CHECK, (l.getX() - HSPACING) , l.getY() + checkDy);
+ }
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/menu/ScenariosMenu.java b/core/src/ch/asynk/rustanddust/menu/ScenariosMenu.java
new file mode 100644
index 0000000..d5b99ed
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/menu/ScenariosMenu.java
@@ -0,0 +1,141 @@
+package ch.asynk.rustanddust.menu;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.GlyphLayout;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.rustanddust.ui.Label;
+import ch.asynk.rustanddust.ui.Bg;
+import ch.asynk.rustanddust.ui.Patch;
+import ch.asynk.rustanddust.RustAndDust;
+import ch.asynk.rustanddust.game.Battle;
+
+public class ScenariosMenu extends Patch
+{
+ public static int PADDING = 40;
+ public static int BTN_PADDING = 10;
+ public static int TITLE_PADDING = 30;
+ public static int VSPACING = 5;
+ public static int HSPACING = 30;
+ public static String CHECK = "#";
+
+ private final RustAndDust game;
+ private final BitmapFont font;
+
+ private float checkDy;
+ private Label title;
+ protected Bg okBtn;
+ protected Bg cancelBtn;
+ private Label [] battleLabels;
+
+ public boolean launch;
+
+ public ScenariosMenu(RustAndDust game, BitmapFont font, TextureAtlas atlas)
+ {
+ super(atlas.createPatch("typewriter"));
+ this.game = game;
+ this.font = font;
+ this.okBtn = new Bg(atlas.findRegion("ok"));
+ this.cancelBtn = new Bg(atlas.findRegion("cancel"));
+ this.title = new Label(font);
+ this.title.write("- Scenarios");
+ this.battleLabels = new Label[game.factory.battles.length];
+ for (int i = 0; i < battleLabels.length; i++) {
+ Label l = new Label(font, 8f);
+ l.write(game.factory.battles[i].getName());
+ battleLabels[i] = l;
+ }
+ GlyphLayout layout = new GlyphLayout();
+ layout.setText(font, CHECK);
+ checkDy = layout.height + 9;
+
+ this.visible = false;
+ this.launch = false;
+ }
+
+ public void setPosition()
+ {
+ float h = (title.getHeight() + TITLE_PADDING + ((battleLabels.length - 1) * VSPACING) + (2 * PADDING));
+ for (int i = 0; i < battleLabels.length; i++)
+ h += battleLabels[i].getHeight();
+
+ float w = title.getWidth();
+ for (int i = 0; i < battleLabels.length; i++) {
+ float t = battleLabels[i].getWidth();
+ if (t > w)
+ w = t;
+ }
+ w += (2 * PADDING) + HSPACING;
+
+ float x = position.getX(w);
+ float y = position.getY(h);
+ setPosition(x, y, w, h);
+
+ okBtn.setPosition((x + w - okBtn.getWidth() + BTN_PADDING), (y - BTN_PADDING));
+ cancelBtn.setPosition((x - BTN_PADDING), okBtn.getY());
+
+ y += PADDING;
+ x += PADDING + HSPACING;
+ float dy = (VSPACING + battleLabels[0].getHeight());
+
+ for (int i = (battleLabels.length - 1); i > -1; i--) {
+ battleLabels[i].setPosition(x, y);
+ y += dy;
+ }
+ y += (TITLE_PADDING - VSPACING);
+ title.setPosition(x, y);
+ }
+
+ @Override
+ public boolean hit(float x, float y)
+ {
+ if (!visible) return false;
+
+ if (okBtn.hit(x, y)) {
+ this.launch = (game.config.battle != null);
+ return true;
+ } else if (cancelBtn.hit(x, y)) {
+ this.launch = false;
+ return true;
+ } else {
+ for (int i = 0; i <battleLabels.length; i++) {
+ if (battleLabels[i].hit(x, y)) {
+ if (game.config.battle == game.factory.battles[i])
+ game.config.battle = null;
+ else
+ game.config.battle = game.factory.battles[i];
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ title.dispose();
+ okBtn.dispose();
+ cancelBtn.dispose();
+ for (int i = 0; i < battleLabels.length; i++)
+ battleLabels[i].dispose();
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ super.draw(batch);
+ title.draw(batch);
+ okBtn.draw(batch);
+ cancelBtn.draw(batch);
+ for (int i = 0; i < battleLabels.length; i++) {
+ Label l = battleLabels[i];
+ l.draw(batch);
+ if (game.config.battle == game.factory.battles[i])
+ font.draw(batch, CHECK, (l.getX() - HSPACING) , l.getY() + checkDy);
+ }
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/menu/TutorialsMenu.java b/core/src/ch/asynk/rustanddust/menu/TutorialsMenu.java
new file mode 100644
index 0000000..7f54aad
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/menu/TutorialsMenu.java
@@ -0,0 +1,94 @@
+package ch.asynk.rustanddust.menu;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.rustanddust.ui.Label;
+import ch.asynk.rustanddust.ui.Bg;
+import ch.asynk.rustanddust.ui.Patch;
+import ch.asynk.rustanddust.RustAndDust;
+
+public class TutorialsMenu extends Patch
+{
+ public static int PADDING = 40;
+ public static int OK_PADDING = 10;
+ public static int TITLE_PADDING = 30;
+ public static int VSPACING = 20;
+
+ private final RustAndDust game;
+ private final BitmapFont font;
+
+ private Label title;
+ private Label msg;
+ protected Bg okBtn;
+
+ public TutorialsMenu(RustAndDust game, BitmapFont font, TextureAtlas atlas)
+ {
+ super(atlas.createPatch("typewriter"));
+ this.game = game;
+ this.font = font;
+ this.okBtn = new Bg(atlas.findRegion("ok"));
+ this.title = new Label(font);
+ this.title.write("- Tutorials");
+ this.msg = new Label(font);
+ this.msg.write("Not implemented yet.");
+
+ this.visible = false;
+ }
+
+ public void setPosition()
+ {
+ float h = (title.getHeight() + TITLE_PADDING + (2 * PADDING));
+ h += msg.getHeight();
+
+ float w = title.getWidth();
+ if (msg.getWidth() > w)
+ w = msg.getWidth();
+ w += (2 * PADDING);
+
+ float x = position.getX(w);
+ float y = position.getY(h);
+ setPosition(x, y, w, h);
+
+ okBtn.setPosition((x + w - okBtn.getWidth() + OK_PADDING), (y - OK_PADDING));
+
+ y += PADDING;
+ x += PADDING;
+
+ msg.setPosition(x, y);
+
+ y += (msg.getHeight() + TITLE_PADDING);
+ title.setPosition(x, y);
+ }
+
+ @Override
+ public boolean hit(float x, float y)
+ {
+ if (!visible) return false;
+
+ if (okBtn.hit(x, y))
+ return true;
+
+ return false;
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ title.dispose();
+ msg.dispose();
+ okBtn.dispose();
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ super.draw(batch);
+ title.draw(batch);
+ msg.draw(batch);
+ okBtn.draw(batch);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/screens/GameCamera.java b/core/src/ch/asynk/rustanddust/screens/GameCamera.java
new file mode 100644
index 0000000..0dbcb50
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/screens/GameCamera.java
@@ -0,0 +1,212 @@
+package ch.asynk.rustanddust.screens;
+
+import com.badlogic.gdx.Gdx;
+
+import com.badlogic.gdx.graphics.OrthographicCamera;
+
+import com.badlogic.gdx.math.Rectangle;
+import com.badlogic.gdx.math.Matrix4;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.math.MathUtils;
+
+public class GameCamera extends OrthographicCamera
+{
+ private static final float ZEROF = 0.01f;
+
+ private int screenWidth;
+ private int screenHeight;
+ private float zoomOut;
+ private float zoomIn;
+ private float viewportAspect;
+ private float widthFactor;
+ private float heightFactor;
+ private Rectangle virtual;
+ private Rectangle window;
+ private Rectangle hud;
+ private Matrix4 hudMatrix;
+ private Matrix4 hudInvProjMatrix;
+ private int hudCorrection;
+ private int hudLeft;
+ private int hudBottom;
+ private boolean fixedHud;
+
+ public GameCamera(float virtualWidth, float virtualHeight, float zoomOut, float zoomIn, int hudCorrection, boolean fixedHud)
+ {
+ super(virtualWidth, virtualHeight);
+ this.zoomOut = zoomOut;
+ this.zoomIn = zoomIn;
+ this.viewportAspect = (viewportWidth / viewportHeight);
+ this.virtual = new Rectangle();
+ this.virtual.set(0, 0, virtualWidth, virtualHeight);
+ this.window = new Rectangle();
+ this.hud = new Rectangle();
+ this.hudMatrix = new Matrix4();
+ this.hudInvProjMatrix = new Matrix4();
+ this.hudCorrection = hudCorrection;
+ this.fixedHud = fixedHud;
+ }
+
+ public void updateViewport(int screenWidth, int screenHeight)
+ {
+ this.screenWidth = screenWidth;
+ this.screenHeight = screenHeight;
+
+ float aspect = (screenWidth / (float) screenHeight);
+ float diff = (viewportAspect - aspect);
+
+ if (diff < -ZEROF) {
+ // wider than tall
+ window.width = java.lang.Math.min((screenHeight * viewportAspect / zoom), screenWidth);
+ window.height = screenHeight;
+ window.x = ((screenWidth - window.width) / 2f);
+ window.y = 0f;
+ viewportWidth = (viewportHeight * (window.width / window.height));
+ hud.y = hudCorrection;
+ hud.x = (hud.y * viewportWidth / viewportHeight);
+ } else if (diff > ZEROF) {
+ // taller than wide
+ // FIXME fix hud vertical position
+ window.width = screenWidth;
+ window.height = java.lang.Math.min((screenWidth * viewportAspect / zoom), screenHeight);
+ window.x = 0f;
+ window.y = ((screenHeight - window.height) / 2f);
+ viewportHeight = (viewportWidth * (window.height / window.width));
+ hud.x = hudCorrection;
+ hud.y = (hud.x * viewportHeight / viewportWidth);
+ }
+
+ if (fixedHud) {
+ hud.x = 0;
+ hud.y = 0;
+ hud.width = screenWidth;
+ hud.height = screenHeight;
+ } else {
+ hud.width = (window.width - (2 * hud.x));
+ hud.height = (window.height - (2 * hud.y));
+ }
+
+ widthFactor = (viewportWidth / screenWidth);
+ heightFactor = (viewportHeight / screenHeight);
+
+ clampPosition();
+ update(true);
+ hudMatrix.setToOrtho2D(hud.x, hud.y, hud.width, hud.height);
+ hudInvProjMatrix.set(hudMatrix);
+ Matrix4.inv(hudInvProjMatrix.val);
+ }
+
+ public void applyMapViewport()
+ {
+ Gdx.gl.glViewport((int)window.x, (int)window.y, (int)window.width, (int)window.height);
+ }
+
+ public void applyHudViewport()
+ {
+ if (fixedHud)
+ Gdx.gl.glViewport(0, 0, screenWidth, screenHeight);
+ }
+
+ public Matrix4 getHudMatrix()
+ {
+ return hudMatrix;
+ }
+
+ public int getScreenWidth()
+ {
+ return screenWidth;
+ }
+
+ public int getScreenHeight()
+ {
+ return screenHeight;
+ }
+
+ public int getHudLeft()
+ {
+ return (int) hud.x;
+ }
+
+ public int getHudBottom()
+ {
+ return (int) hud.y;
+ }
+
+ public int getHudWidth()
+ {
+ return (int) hud.width;
+ }
+
+ public int getHudHeight()
+ {
+ return (int) hud.height;
+ }
+
+ public void centerOnWorld()
+ {
+ position.set((viewportWidth / 2f), (viewportHeight / 2f), 0f);
+ }
+
+ public void zoom(float dz)
+ {
+ zoom += dz;
+ clampZoom();
+ updateViewport(screenWidth, screenHeight);
+ }
+
+ public void translate(float dx, float dy)
+ {
+ float deltaX = (dx * zoom * widthFactor);
+ float deltaY = (dy * zoom * heightFactor);
+ translate(deltaX, -deltaY, 0);
+ clampPosition();
+ update(true);
+ }
+
+ public void clampZoom()
+ {
+ zoom = MathUtils.clamp(zoom, zoomIn, zoomOut);
+ }
+
+ public void clampPosition()
+ {
+ float cx = (viewportWidth * zoom);
+ float cy = (viewportHeight * zoom);
+
+ if ((virtual.width - cx) > ZEROF) {
+ cx /= 2f;
+ position.x = MathUtils.clamp(position.x, cx, (virtual.width - cx));
+ } else
+ position.x = (virtual.width / 2f);
+
+ if ((virtual.height - cy) > ZEROF) {
+ cy /= 2f;
+ position.y = MathUtils.clamp(position.y, cy, (virtual.height - cy));
+ } else
+ position.y = (virtual.height / 2f);
+ }
+
+ public void debug()
+ {
+ System.err.println(String.format(" VIEWPORT: %dx%d * %.2f -> %dx%d", (int)viewportWidth, (int)viewportHeight,
+ zoom, (int)(viewportWidth * zoom), (int)(viewportHeight * zoom)));
+ System.err.println(String.format(" WINDOW: %d;%d %dx%d", (int)window.x, (int)window.y, (int)window.width, (int)window.height));
+ System.err.println("MATRIX:" + combined.toString());
+ }
+
+ public void unproject(int x, int y, Vector3 v)
+ {
+ unproject(v.set(x, y, 0), window.x, window.y, window.width, window.height);
+ }
+
+ public void unprojectHud(float x, float y, Vector3 v)
+ {
+ Rectangle r = (fixedHud ? hud : window);
+ x = x - r.x;
+ y = Gdx.graphics.getHeight() - y - 1;
+ y = y - r.y;
+ v.x = (2 * x) / r.width - 1;
+ v.y = (2 * y) / r.height - 1;
+ v.z = 2 * v.z - 1;
+ v.prj(hudInvProjMatrix);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/screens/GameScreen.java b/core/src/ch/asynk/rustanddust/screens/GameScreen.java
new file mode 100644
index 0000000..c369989
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/screens/GameScreen.java
@@ -0,0 +1,221 @@
+package ch.asynk.rustanddust.screens;
+
+import com.badlogic.gdx.Gdx;
+
+import com.badlogic.gdx.Screen;
+import com.badlogic.gdx.Input;
+import com.badlogic.gdx.InputAdapter;
+import com.badlogic.gdx.InputMultiplexer;
+import com.badlogic.gdx.input.GestureDetector;
+import com.badlogic.gdx.input.GestureDetector.GestureAdapter;
+
+import com.badlogic.gdx.graphics.GL20;
+import com.badlogic.gdx.graphics.g2d.SpriteBatch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import com.badlogic.gdx.math.Vector2;
+
+import ch.asynk.rustanddust.RustAndDust;
+
+import ch.asynk.rustanddust.game.Ctrl;
+
+public class GameScreen implements Screen
+{
+ private static boolean DEBUG = false;
+
+ private static final boolean FIXED_HUD = true;
+ private static final float INPUT_DELAY = 0.1f;
+ private static final float ZOOM_IN_MAX = 0.3f;
+ private static final float ZOOM_OUT_MAX = 1f;
+ private static final float ZOOM_GESTURE_FACTOR = .01f;
+ private static final float ZOOM_SCROLL_FACTOR = .1f;
+ private static final int DRAGGED_Z_INDEX = 10;
+ private static final int DRAG_THRESHOLD = 6;
+
+ private final GameCamera cam;
+
+ private final SpriteBatch batch;
+ private ShapeRenderer debugShapes = null;
+
+ private final RustAndDust game;
+ private Ctrl ctrl;
+
+ private int dragged;
+ private boolean blocked;
+ private float inputDelay = 0f;
+ private Vector2 dragPos = new Vector2();
+
+ public GameScreen(final RustAndDust game)
+ {
+ DEBUG = game.config.debug;
+
+ this.game = game;
+ this.dragged = 0;
+ this.blocked = false;
+
+ this.batch = new SpriteBatch();
+ this.ctrl = new Ctrl(game, game.config.battle);
+ this.cam = new GameCamera(ctrl.map.getWidth(), ctrl.map.getHeight(), ZOOM_OUT_MAX, ZOOM_IN_MAX, game.hudCorrection, FIXED_HUD);
+
+ if (DEBUG) this.debugShapes = new ShapeRenderer();
+
+ Gdx.input.setInputProcessor(getMultiplexer());
+ }
+
+
+ private InputMultiplexer getMultiplexer()
+ {
+ final InputMultiplexer multiplexer = new InputMultiplexer();
+ multiplexer.addProcessor(new GestureDetector(new GestureAdapter() {
+ @Override
+ public boolean zoom(float initialDistance, float distance)
+ {
+ if (initialDistance > distance)
+ cam.zoom(ZOOM_GESTURE_FACTOR);
+ else
+ cam.zoom(-ZOOM_GESTURE_FACTOR);
+ ctrl.hud.resize(cam.getHudLeft(), cam.getHudBottom(), cam.getHudWidth(), cam.getHudHeight());
+ blocked = true;
+ inputDelay = INPUT_DELAY;
+ return true;
+ }
+ }));
+ multiplexer.addProcessor(new InputAdapter() {
+ @Override
+ public boolean touchDragged(int x, int y, int pointer)
+ {
+ dragged += 1;
+ cam.translate((dragPos.x - x), (dragPos.y - y));
+ dragPos.set(x, y);
+ return true;
+ }
+ @Override
+ public boolean touchDown(int x, int y, int pointer, int button)
+ {
+ if (blocked) return true;
+ if (button == Input.Buttons.LEFT) {
+ dragPos.set(x, y);
+ cam.unproject(x, y, ctrl.mapTouch);
+ cam.unprojectHud(x, y, ctrl.hudTouch);
+ ctrl.touchDown();
+ }
+ return true;
+ }
+ @Override
+ public boolean touchUp(int x, int y, int pointer, int button)
+ {
+ if (blocked) return true;
+ if (dragged > DRAG_THRESHOLD) {
+ dragged = 0;
+ return true;
+ }
+ dragged = 0;
+ if (button == Input.Buttons.LEFT) {
+ cam.unproject(x, y, ctrl.mapTouch);
+ cam.unprojectHud(x, y, ctrl.hudTouch);
+ ctrl.touchUp();
+ }
+ return true;
+ }
+ @Override
+ public boolean scrolled(int amount)
+ {
+ cam.zoom(amount * ZOOM_SCROLL_FACTOR);
+ ctrl.hud.resize(cam.getHudLeft(), cam.getHudBottom(), cam.getHudWidth(), cam.getHudHeight());
+ return true;
+ }
+ });
+
+ return multiplexer;
+ }
+
+ @Override
+ public void render(float delta)
+ {
+ if (inputDelay > 0f) {
+ inputDelay -= delta;
+ if (inputDelay <= 0f)
+ blocked = false;
+ }
+
+ ctrl.hud.animate(delta);
+ ctrl.map.animate(delta);
+
+ Gdx.gl.glClearColor(0, 0, 0, 1);
+ Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
+
+ // cam.update();
+ cam.applyMapViewport();
+ batch.setProjectionMatrix(cam.combined);
+ batch.begin();
+ ctrl.map.draw(batch);
+ batch.end();
+
+
+ if (DEBUG) {
+ Gdx.gl.glEnable(GL20.GL_BLEND);
+ debugShapes.setAutoShapeType(true);
+ debugShapes.setProjectionMatrix(cam.combined);
+ debugShapes.begin();
+ ctrl.map.drawDebug(debugShapes);
+ debugShapes.end();
+ }
+
+ cam.applyHudViewport();
+ batch.setProjectionMatrix(cam.getHudMatrix());
+ batch.begin();
+ ctrl.hud.draw(batch, DEBUG);
+ batch.end();
+
+ if (DEBUG) {
+ Gdx.gl.glEnable(GL20.GL_BLEND);
+ debugShapes.setAutoShapeType(true);
+ debugShapes.setProjectionMatrix(cam.getHudMatrix());
+ debugShapes.begin();
+ debugShapes.rect(cam.getHudLeft(), cam.getHudBottom(), cam.getHudWidth(), cam.getHudHeight());
+ ctrl.hud.drawDebug(debugShapes);
+ debugShapes.end();
+ }
+ }
+
+ @Override
+ public void resize(int width, int height)
+ {
+ // RustAndDust.debug("GameScreen", "resize (" + width + "," + height + ")");
+ cam.updateViewport(width, height);
+ ctrl.hud.resize(cam.getHudLeft(), cam.getHudBottom(), cam.getHudWidth(), cam.getHudHeight());
+ }
+
+ @Override
+ public void dispose()
+ {
+ // RustAndDust.debug("GameScreen", "dispose()");
+ batch.dispose();
+ ctrl.dispose();
+ if (DEBUG) debugShapes.dispose();
+ }
+
+ @Override
+ public void show()
+ {
+ // RustAndDust.debug("GameScreen", "show()");
+ }
+
+ @Override
+ public void hide()
+ {
+ // RustAndDust.debug("GameScreen", "hide()");
+ }
+
+ @Override
+ public void pause()
+ {
+ // RustAndDust.debug("GameScreen", "pause()");
+ }
+
+ @Override
+ public void resume()
+ {
+ // RustAndDust.debug("GameScreen", "resume()");
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/screens/MenuCamera.java b/core/src/ch/asynk/rustanddust/screens/MenuCamera.java
new file mode 100644
index 0000000..be8341a
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/screens/MenuCamera.java
@@ -0,0 +1,112 @@
+package ch.asynk.rustanddust.screens;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.OrthographicCamera;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.math.Matrix4;
+import com.badlogic.gdx.math.Rectangle;
+
+public class MenuCamera extends OrthographicCamera
+{
+ private static final float ZEROF = 0.01f;
+
+ private float virtualAspect;
+ private final Rectangle virtual;
+ private final Rectangle window;
+ private int hudLeft;
+ private int hudBottom;
+ private int hudCorrection;
+
+ private Matrix4 uiMatrix;
+ private Matrix4 uiInvProjMatrix;
+
+ public MenuCamera(int cx, int cy, int width, int height, int hudCorrection)
+ {
+ super(width, height);
+ this.virtual = new Rectangle();
+ this.virtual.set(cx, cy, width, height);
+ this.virtualAspect = (virtual.width / virtual.height);
+ this.window = new Rectangle();
+ this.window.set(0, 0, 0, 0);
+ this.position.set(virtual.x, virtual.y, 0f);
+ this.hudLeft = 0;
+ this.hudBottom = 0;
+ this.hudCorrection = hudCorrection;
+
+ this.uiMatrix = new Matrix4();
+ this.uiInvProjMatrix = new Matrix4();
+ }
+
+ public void updateViewport(int screenWidth, int screenHeight)
+ {
+ float aspect = (screenWidth / (float) screenHeight);
+ float diff = (virtualAspect - aspect);
+
+ if (diff < -ZEROF) {
+ viewportWidth = (virtual.height * aspect);
+ viewportHeight = virtual.height;
+ } else if (diff > ZEROF) {
+ viewportWidth = virtual.width;
+ viewportHeight = (virtual.width / aspect);
+ }
+
+ window.width = screenWidth;
+ window.height = screenHeight;
+ hudLeft = hudCorrection;
+ hudBottom = (int) (hudLeft / aspect);
+
+ Gdx.gl.glViewport((int)window.x, (int)window.y, (int)window.width, (int)window.height);
+
+ update(true);
+
+ uiMatrix.setToOrtho2D(getHudLeft(), getHudBottom(), getHudWidth(), getHudHeight());
+ uiInvProjMatrix.set(uiMatrix);
+ Matrix4.inv(uiInvProjMatrix.val);
+ }
+
+ public float getScreenWidth()
+ {
+ return window.width;
+ }
+
+ public float getScreenHeight()
+ {
+ return window.height;
+ }
+
+ public int getHudLeft()
+ {
+ return hudLeft;
+ }
+
+ public int getHudBottom()
+ {
+ return hudBottom;
+ }
+
+ public int getHudWidth()
+ {
+ return (int) window.width - (2 * getHudLeft());
+ }
+
+ public int getHudHeight()
+ {
+ return (int) window.height - (2 * getHudBottom());
+ }
+
+ public void uiUnproject(float x, float y, Vector3 v)
+ {
+ x = x - window.x;
+ y = Gdx.graphics.getHeight() - y - 1;
+ y = y - window.y;
+ v.x = (2 * x) / window.width - 1;
+ v.y = (2 * y) / window.height - 1;
+ v.z = 2 * v.z - 1;
+ v.prj(uiInvProjMatrix);
+ }
+
+ public Matrix4 uiCombined()
+ {
+ return uiMatrix;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/screens/MenuScreen.java b/core/src/ch/asynk/rustanddust/screens/MenuScreen.java
new file mode 100644
index 0000000..0ca8a63
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/screens/MenuScreen.java
@@ -0,0 +1,258 @@
+package ch.asynk.rustanddust.screens;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.Screen;
+import com.badlogic.gdx.InputAdapter;
+import com.badlogic.gdx.graphics.GL20;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.SpriteBatch;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.math.Interpolation;
+
+import ch.asynk.rustanddust.RustAndDust;
+import ch.asynk.rustanddust.ui.Position;
+import ch.asynk.rustanddust.menu.MainMenu;
+import ch.asynk.rustanddust.menu.OptionsMenu;
+import ch.asynk.rustanddust.menu.ScenariosMenu;
+import ch.asynk.rustanddust.menu.TutorialsMenu;
+
+public class MenuScreen implements Screen
+{
+ private final RustAndDust game;
+
+ private final int OFFSET = 20;
+ private final int V_WIDTH = 1600;
+ private final int V_HEIGHT = 1125;
+ private final int V_CENTER_X = 1000;
+ private final int V_CENTER_Y = 890;
+
+ private float percent;
+ private float delay = 0.0f;
+ private float dx;
+ private float dy;
+ private int[] xPath = { 369, 558, 747, 936, 1125, 1030, 936, 1125, 1314, 1408, 1597};
+ private int[] yPath = { 565, 565, 565, 565, 565, 729, 892, 892, 892, 1056, 1056};
+ private int n = xPath.length;
+
+ private boolean ready;
+ private boolean gameAssetsLoading;
+ private Texture bg;
+
+ private Sprite unit;
+ private Sprite move;
+ private Sprite from;
+ private Sprite to;
+ private Sprite geFlag;
+ private Sprite usFlag;
+
+ private MainMenu mainMenu;
+ private OptionsMenu optionsMenu;
+ private ScenariosMenu scenariosMenu;
+ private TutorialsMenu tutorialsMenu;
+
+ private final MenuCamera camera;
+ private final SpriteBatch batch;
+ private Vector3 touch = new Vector3();
+
+ public MenuScreen(final RustAndDust game)
+ {
+ this.game = game;
+ this.batch = new SpriteBatch();
+
+ float width = Gdx.graphics.getWidth();
+ float height = Gdx.graphics.getHeight();
+
+ this.camera = new MenuCamera(V_CENTER_X, V_CENTER_Y, V_WIDTH, V_HEIGHT, game.hudCorrection);
+
+ this.gameAssetsLoading = false;
+
+ this.bg = game.manager.get("data/map_a.png", Texture.class);
+
+ this.unit = new Sprite(game.menuAtlas.findRegion("unit"));
+ this.move = new Sprite(game.menuAtlas.findRegion("move"));
+ this.from = new Sprite(game.menuAtlas.findRegion("from"));
+ this.to = new Sprite(game.menuAtlas.findRegion("to"));
+ this.usFlag = new Sprite(game.menuAtlas.findRegion("us-flag"));
+ this.geFlag = new Sprite(game.menuAtlas.findRegion("ge-flag"));
+
+ this.mainMenu = new MainMenu(game.fontB, game.uiAtlas);
+ this.optionsMenu = new OptionsMenu(game, game.fontB, game.uiAtlas);
+ this.scenariosMenu = new ScenariosMenu(game, game.fontB, game.uiAtlas);
+ this.tutorialsMenu = new TutorialsMenu(game, game.fontB, game.uiAtlas);
+
+ this.game.config.battle = null;
+
+ Gdx.input.setInputProcessor(new InputAdapter() {
+ @Override
+ public boolean touchDown(int x, int y, int pointer, int button)
+ {
+ camera.uiUnproject(x, y, touch);
+ return hit(touch.x, touch.y);
+ }
+ });
+ }
+
+ private boolean hit(float x, float y)
+ {
+ if (mainMenu.hit(x, y)) {
+ mainMenu.visible = false;
+ showNextMenu();
+ return true;
+ } else if (optionsMenu.hit(x, y)) {
+ mainMenu.visible = true;
+ optionsMenu.visible = false;
+ return true;
+ } else if (scenariosMenu.hit(x, y)) {
+ mainMenu.visible = true;
+ scenariosMenu.visible = false;
+ if (scenariosMenu.launch)
+ startLoading();
+ return true;
+ } else if (tutorialsMenu.hit(x, y)) {
+ mainMenu.visible = true;
+ tutorialsMenu.visible = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ private void showNextMenu()
+ {
+ MainMenu.Items item = mainMenu.getMenu();
+
+ if (item == MainMenu.Items.OPTIONS)
+ optionsMenu.visible = true;
+ else if (item == MainMenu.Items.SCENARIOS)
+ scenariosMenu.visible = true;
+ else if (item == MainMenu.Items.TUTORIALS)
+ tutorialsMenu.visible = true;
+ }
+
+ private void startLoading()
+ {
+ mainMenu.visible = false;
+ game.loadGameAssets();
+ gameAssetsLoading = true;
+ }
+
+ private void gameAssetsLoadingCompleted()
+ {
+ RustAndDust.debug("LoadScreen", "assets ready : " + (Gdx.app.getJavaHeap()/1024.0f) + "KB");
+ game.switchToGame();
+ dispose();
+ }
+
+ @Override
+ public void render(float delta)
+ {
+ float x = xPath[0];
+ float y = yPath[0];
+ if (gameAssetsLoading) {
+ if (game.manager.update()) {
+ delay += delta;
+ if (delay >= 0.6f)
+ gameAssetsLoadingCompleted();
+ }
+
+ percent = Interpolation.linear.apply(percent, game.manager.getProgress(), 0.1f);
+ int idx = (int) (percent * 10);
+ float fraction = ((percent * 100 ) % 10 / 10);
+ x = (xPath[idx] + ((xPath[idx + 1] - xPath[idx]) * fraction));
+ y = (yPath[idx] + ((yPath[idx + 1] - yPath[idx]) * fraction));
+ }
+
+ Gdx.gl.glClearColor(0, 0, 0, 1);
+ Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
+
+ batch.setProjectionMatrix(camera.combined);
+ batch.begin();
+ batch.draw(bg, 0, 0);
+ from.draw(batch);
+ to.draw(batch);
+ usFlag.draw(batch);
+ geFlag.draw(batch);
+ for (int i = 1; i < (n - 1); i++)
+ drawCentered(batch, move, xPath[i], yPath[i]);
+ drawCentered(batch, unit, (int) (x + dx), (int) (y + dy));
+ batch.end();
+
+ batch.setProjectionMatrix(camera.uiCombined());
+ batch.begin();
+ mainMenu.draw(batch);
+ optionsMenu.draw(batch);
+ scenariosMenu.draw(batch);
+ tutorialsMenu.draw(batch);
+ batch.end();
+ }
+
+ private void drawCentered(SpriteBatch batch, TextureRegion region, int x, int y)
+ {
+ batch.draw(region, (x - (region.getRegionWidth() / 2f)), (y - (region.getRegionHeight() / 2f)));
+ }
+
+ private void setCenteredPosition(Sprite sprite, int x, int y)
+ {
+ sprite.setPosition((x - (sprite.getWidth() / 2f)), (y - (sprite.getHeight() / 2f)));
+ }
+
+ private void update(int width, int height)
+ {
+ camera.updateViewport(width, height);
+ Position.update(camera.getHudLeft(), camera.getHudBottom(), camera.getHudWidth(), camera.getHudHeight());
+
+ setCenteredPosition(from, xPath[0], yPath[0]);
+ setCenteredPosition(to, xPath[n - 1], yPath[n - 1]);
+ setCenteredPosition(usFlag, xPath[0], yPath[0]);
+ setCenteredPosition(geFlag, xPath[n - 1], yPath[n - 1]);
+
+ mainMenu.setPosition();
+ optionsMenu.setPosition();
+ scenariosMenu.setPosition();
+ tutorialsMenu.setPosition();
+ }
+
+ @Override
+ public void resize(int width, int height)
+ {
+ update(width, height);
+ }
+
+ @Override
+ public void dispose()
+ {
+ mainMenu.dispose();
+ optionsMenu.dispose();
+ scenariosMenu.dispose();
+ tutorialsMenu.dispose();
+ }
+
+ @Override
+ public void show()
+ {
+ int width = (int) Gdx.graphics.getWidth();
+ int height = (int) Gdx.graphics.getHeight();
+ update(width, height);
+ }
+
+ @Override
+ public void hide()
+ {
+ // RustAndDust.debug("MenuScreen", "hide()");
+ }
+
+ @Override
+ public void pause()
+ {
+ // RustAndDust.debug("MenuScreen", "pause()");
+ }
+
+ @Override
+ public void resume()
+ {
+ // RustAndDust.debug("MenuScreen", "resume()");
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/Bg.java b/core/src/ch/asynk/rustanddust/ui/Bg.java
new file mode 100644
index 0000000..cac3ddc
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/Bg.java
@@ -0,0 +1,28 @@
+package ch.asynk.rustanddust.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+
+public class Bg extends Widget
+{
+ private TextureRegion region;
+
+ public Bg(TextureRegion region)
+ {
+ super();
+ this.region = region;
+ setPosition(0, 0, region.getRegionWidth(), region.getRegionHeight());
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ batch.draw(region, rect.x, rect.y, rect.width, rect.height);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/Label.java b/core/src/ch/asynk/rustanddust/ui/Label.java
new file mode 100644
index 0000000..6a6d809
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/Label.java
@@ -0,0 +1,72 @@
+package ch.asynk.rustanddust.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.GlyphLayout;
+
+public class Label extends Widget
+{
+ private BitmapFont font;
+ private GlyphLayout layout;
+ private float dx;
+ private float dy;
+ protected String text;
+
+ public Label(BitmapFont font)
+ {
+ this(font, 0f);
+ }
+
+ public Label(BitmapFont font, float padding)
+ {
+ this(font, padding, Position.MIDDLE_CENTER);
+ }
+
+ public Label(BitmapFont font, float padding, Position position)
+ {
+ super();
+ this.font = font;
+ this.padding = padding;
+ this.position = position;
+ this.layout = new GlyphLayout();
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ @Override
+ public void translate(float dx, float dy)
+ {
+ setPosition((rect.x + dx), (rect.y + dy));
+ }
+
+ @Override
+ public void setPosition(float x, float y)
+ {
+ this.layout.setText(font, (text == null) ? "" : text);
+ setPosition(x, y, (layout.width + (2 * padding)), (layout.height + (2 * padding)));
+ this.dx = (x + padding);
+ this.dy = (y + padding + layout.height);
+ }
+
+ public void write(String text)
+ {
+ this.text = text;
+ setPosition(position);
+ }
+
+ public void write(String text, float x, float y)
+ {
+ this.text = text;
+ setPosition(x, y);
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ font.draw(batch, layout, dx, dy);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/LabelImage.java b/core/src/ch/asynk/rustanddust/ui/LabelImage.java
new file mode 100644
index 0000000..0fb6ecb
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/LabelImage.java
@@ -0,0 +1,72 @@
+package ch.asynk.rustanddust.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public class LabelImage extends Bg
+{
+ private Label label;
+
+ public LabelImage(TextureRegion region, BitmapFont font)
+ {
+ this(region, font, 0f);
+ }
+
+ public LabelImage(TextureRegion region, BitmapFont font, float padding)
+ {
+ this(region, font, padding, Position.MIDDLE_CENTER);
+ }
+
+ public LabelImage(TextureRegion region, BitmapFont font, float padding, Position position)
+ {
+ super(region);
+ this.label = new Label(font, padding, position);
+ }
+
+ @Override
+ public void dispose()
+ {
+ label.dispose();
+ }
+
+ @Override
+ public void translate(float dx, float dy)
+ {
+ super.translate(dx, dy);
+ label.translate(dx, dy);
+ }
+
+ public void setPosition(float x, float y)
+ {
+ super.setPosition(x, y);
+ label.setPosition(x, y);
+ }
+
+ public void setLabelPosition(Position position)
+ {
+ label.setPosition(position, this);
+ }
+
+ public void write(String text)
+ {
+ this.label.write(text);
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ super.draw(batch);
+ label.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ if (!visible) return;
+ super.drawDebug(shapes);
+ label.drawDebug(shapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/LabelStack.java b/core/src/ch/asynk/rustanddust/ui/LabelStack.java
new file mode 100644
index 0000000..92933e3
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/LabelStack.java
@@ -0,0 +1,72 @@
+package ch.asynk.rustanddust.ui;
+
+import java.util.ArrayDeque;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class LabelStack extends Label implements Animation
+{
+ class MsgInfo
+ {
+ String text;
+ float duration;
+ Position position;
+ MsgInfo(String text, float duration, Position position)
+ {
+ this.text = text;
+ this.duration = duration;
+ this.position = position;
+ }
+ }
+
+ private float duration;
+ private float elapsed;
+ private ArrayDeque<MsgInfo> stack;
+
+ public LabelStack(BitmapFont font, float padding)
+ {
+ super(font, padding);
+ this.visible = false;
+ this.stack = new ArrayDeque<MsgInfo>();
+ }
+
+ public void pushWrite(String text, float duration, Position position)
+ {
+ if (visible)
+ stack.push(new MsgInfo(text, duration, position));
+ else
+ write(text, duration, position);
+ }
+
+ public void write(String text, float duration, Position position)
+ {
+ this.position = position;
+ write(text, duration);
+ }
+
+ public void write(String text, float duration)
+ {
+ this.duration = duration;
+ this.visible = true;
+ this.elapsed = 0f;
+ write(text);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if (!visible) return true;
+ elapsed += delta;
+ if (elapsed >= duration) {
+ visible = false;
+ if (stack.size() > 0) {
+ MsgInfo info = stack.pop();
+ write(info.text, info.duration, info.position);
+ }
+ }
+ return false;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/Menu.java b/core/src/ch/asynk/rustanddust/ui/Menu.java
new file mode 100644
index 0000000..2fe93a7
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/Menu.java
@@ -0,0 +1,93 @@
+package ch.asynk.rustanddust.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.NinePatch;
+
+public class Menu extends Patch
+{
+ public static int PADDING = 40;
+ public static int VSPACING = 8;
+
+ protected Label []labels;
+
+ public interface MenuItem
+ {
+ public int last();
+ public int i();
+ };
+
+ protected MenuItem menuItem;
+
+ public Menu(MenuItem menuItem, BitmapFont font, NinePatch ninePatch)
+ {
+ super(ninePatch);
+ this.menuItem = menuItem;
+ this.labels = new Label[menuItem.last()];
+ for (int i = 0; i< menuItem.last(); i ++)
+ labels[i] = new Label(font, 10);
+ }
+
+ protected Label label(MenuItem m)
+ {
+ return labels[m.i()];
+ }
+
+ protected float widestLabel()
+ {
+ float w = 0f;
+ for (int i = 0; i< menuItem.last(); i ++) {
+ float t = labels[i].getWidth();
+ if (t> w)
+ w = t;
+ }
+ return w;
+ }
+
+ protected float highestLabel()
+ {
+ float h = 0f;
+ for (int i = 0; i< menuItem.last(); i ++) {
+ float t = labels[i].getHeight();
+ if (t> h)
+ h = t;
+ }
+ return h;
+ }
+
+ public void setPosition()
+ {
+ float lh = highestLabel();
+ float h = ((menuItem.last() * lh) + (2 * PADDING) + ((menuItem.last() - 1) * VSPACING));
+ float w = (widestLabel() + (2 * PADDING));
+ float x = position.getX(w);
+ float y = position.getY(h);
+ setPosition(x, y, w, h);
+
+ y += PADDING;
+ x += PADDING;
+ float dy = (VSPACING + lh);
+
+ for (int i = 0; i< menuItem.last(); i ++) {
+ labels[i].setPosition(x, y);
+ y += dy;
+ }
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ for (int i = 0; i < menuItem.last(); i ++)
+ labels[i].dispose();
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ super.draw(batch);
+ for (int i = 0; i < menuItem.last(); i ++)
+ labels[i].draw(batch);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/Msg.java b/core/src/ch/asynk/rustanddust/ui/Msg.java
new file mode 100644
index 0000000..e1e7c13
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/Msg.java
@@ -0,0 +1,79 @@
+package ch.asynk.rustanddust.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public class Msg extends Patch
+{
+ private LabelStack label;
+
+ public Msg(BitmapFont font, TextureAtlas atlas)
+ {
+ super(atlas.createPatch("typewriter"));
+ label = new LabelStack(font, 20f);
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ label.dispose();
+ }
+
+ public void updatePosition()
+ {
+ if (!visible) return;
+ float dx = (position.getX(rect.width) - rect.x);
+ float dy = (position.getY(rect.height) - rect.y);
+ translate(dx, dy);
+ label.translate(dx, dy);
+ }
+
+ public void write(String text, float duration)
+ {
+ label.write(text, duration);
+ resize();
+ }
+
+ public void write(String text, float duration, Position position)
+ {
+ this.position = position;
+ label.write(text, duration, position);
+ resize();
+ }
+
+ public void pushWrite(String text, float duration, Position position)
+ {
+ this.position = position;
+ label.pushWrite(text, duration, position);
+ resize();
+ }
+
+ private void resize()
+ {
+ setPosition(label.getX(), label.getY(), label.getWidth(), label.getHeight());
+ }
+
+ public boolean animate(float delta)
+ {
+ return label.animate(delta);
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!label.visible) return;
+ super.draw(batch);
+ label.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ if (!label.visible) return;
+ super.drawDebug(shapes);
+ label.drawDebug(shapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/OkCancel.java b/core/src/ch/asynk/rustanddust/ui/OkCancel.java
new file mode 100644
index 0000000..f30a65b
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/OkCancel.java
@@ -0,0 +1,115 @@
+package ch.asynk.rustanddust.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public class OkCancel extends Patch
+{
+ public static int PADDING = 20;
+ public static int VSPACING = 10;
+ public static int HSPACING = 10;
+
+ public boolean ok;
+ protected Label label;
+ protected Bg okBtn;
+ protected Bg cancelBtn;
+
+ public OkCancel(BitmapFont font, TextureAtlas atlas)
+ {
+ super(atlas.createPatch("typewriter"));
+ this.label = new Label(font);
+ this.okBtn = new Bg(atlas.findRegion("ok"));
+ this.cancelBtn = new Bg(atlas.findRegion("cancel"));
+ this.visible = false;
+ }
+
+ public void updatePosition()
+ {
+ if (!visible) return;
+ float dx = (position.getX(rect.width) - rect.x);
+ float dy = (position.getY(rect.height) - rect.y);
+ translate(dx, dy);
+ label.translate(dx, dy);
+ okBtn.translate(dx, dy);
+ cancelBtn.translate(dx, dy);
+ }
+
+ public void show(String msg)
+ {
+ show(msg, Position.MIDDLE_CENTER);
+ }
+
+ public void show(String msg, Position position)
+ {
+ label.write(msg);
+
+ float height = (label.getHeight() + okBtn.getHeight() + (2 * PADDING) + (2 * VSPACING));
+ float width = (label.getWidth() + (2 * PADDING));
+ float w2 = (okBtn.getWidth() + cancelBtn.getWidth() + (2 * PADDING) + (1 * HSPACING));
+ if (w2 > width)
+ width = w2;
+ float x = position.getX(width);
+ float y = position.getY(height);
+ setPosition(x, y, width, height);
+
+ okBtn.setPosition((x + width - okBtn.getWidth() - PADDING), (y + PADDING));
+ cancelBtn.setPosition((x + PADDING), okBtn.getY());
+ label.setPosition((x + PADDING), (y + PADDING + okBtn.getHeight() + VSPACING));
+ cancelBtn.visible = true;
+ visible = true;
+ ok = false;
+ }
+
+ public void noCancel()
+ {
+ cancelBtn.visible = false;
+ }
+
+ @Override
+ public boolean hit(float x, float y)
+ {
+ if (!cancelBtn.visible && super.hit(x, y)) {
+ ok = true;
+ return true;
+ }
+ if (okBtn.hit(x, y)) {
+ ok = true;
+ return true;
+ } else if (cancelBtn.hit(x, y)) {
+ ok = false;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ label.dispose();
+ okBtn.dispose();
+ cancelBtn.dispose();
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ super.draw(batch);
+ label.draw(batch);
+ okBtn.draw(batch);
+ cancelBtn.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ if (!visible) return;
+ super.drawDebug(shapes);
+ label.drawDebug(shapes);
+ okBtn.drawDebug(shapes);
+ cancelBtn.drawDebug(shapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/Patch.java b/core/src/ch/asynk/rustanddust/ui/Patch.java
new file mode 100644
index 0000000..acff791
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/Patch.java
@@ -0,0 +1,28 @@
+package ch.asynk.rustanddust.ui;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.NinePatch;
+
+public class Patch extends Widget
+{
+ private NinePatch patch;
+
+ public Patch(NinePatch patch)
+ {
+ super();
+ this.patch = patch;
+ setPosition(0, 0, patch.getTotalWidth(), patch.getTotalHeight());
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (!visible) return;
+ patch.draw(batch, rect.x, rect.y, rect.width, rect.height);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/Position.java b/core/src/ch/asynk/rustanddust/ui/Position.java
new file mode 100644
index 0000000..d8c6096
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/Position.java
@@ -0,0 +1,239 @@
+package ch.asynk.rustanddust.ui;
+
+import com.badlogic.gdx.Gdx;
+import ch.asynk.rustanddust.game.Hud;
+
+public enum Position
+{
+ TOP_LEFT,
+ TOP_RIGHT,
+ TOP_CENTER,
+ MIDDLE_LEFT,
+ MIDDLE_RIGHT,
+ MIDDLE_CENTER,
+ BOTTOM_LEFT,
+ BOTTOM_RIGHT,
+ BOTTOM_CENTER;
+
+ public Position verticalMirror()
+ {
+ Position p = this;
+ switch(this) {
+ case TOP_LEFT:
+ p = TOP_RIGHT;
+ break;
+ case MIDDLE_LEFT:
+ p = MIDDLE_RIGHT;
+ break;
+ case BOTTOM_LEFT:
+ p = BOTTOM_RIGHT;
+ break;
+ case TOP_RIGHT:
+ p = TOP_LEFT;
+ break;
+ case MIDDLE_RIGHT:
+ p = MIDDLE_LEFT;
+ break;
+ case BOTTOM_RIGHT:
+ p = BOTTOM_LEFT;
+ break;
+ }
+ return p;
+ }
+
+ public Position horizontalMirror()
+ {
+ Position p = this;
+ switch(this) {
+ case TOP_LEFT:
+ p = BOTTOM_LEFT;
+ break;
+ case TOP_CENTER:
+ p = BOTTOM_CENTER;
+ break;
+ case TOP_RIGHT:
+ p = BOTTOM_RIGHT;
+ break;
+ case BOTTOM_LEFT:
+ p = TOP_LEFT;
+ break;
+ case BOTTOM_CENTER:
+ p = TOP_CENTER;
+ break;
+ case BOTTOM_RIGHT:
+ p = TOP_RIGHT;
+ break;
+ }
+ return p;
+ }
+
+ public boolean isLeft()
+ {
+ boolean r = false;
+ switch(this) {
+ case TOP_LEFT:
+ case MIDDLE_LEFT:
+ case BOTTOM_LEFT:
+ r = true;
+ break;
+ default:
+ r = false;
+ break;
+ }
+ return r;
+ }
+
+ public boolean isRight()
+ {
+ boolean r = false;
+ switch(this) {
+ case TOP_RIGHT:
+ case MIDDLE_RIGHT:
+ case BOTTOM_RIGHT:
+ r = true;
+ break;
+ default:
+ r = false;
+ break;
+ }
+ return r;
+ }
+
+ public boolean isCenter()
+ {
+ boolean r = false;
+ switch(this) {
+ case TOP_CENTER:
+ case MIDDLE_CENTER:
+ case BOTTOM_CENTER:
+ r = true;
+ break;
+ default:
+ r = false;
+ break;
+ }
+ return r;
+ }
+
+ private static int hudLeft = 0;
+ private static int hudBottom = 0;
+ private static int hudWidth = Gdx.graphics.getWidth();
+ private static int hudHeight = Gdx.graphics.getHeight();
+
+ public static void update(int width, int height)
+ {
+ update(0, 0, width, height);
+ }
+
+ public static void update(int left, int bottom, int width, int height)
+ {
+ hudLeft = left;
+ hudBottom = bottom;
+ hudWidth = width;
+ hudHeight = height;
+ }
+
+ public float getX(float width)
+ {
+ float x = hudLeft;
+ switch(this) {
+ case TOP_LEFT:
+ case MIDDLE_LEFT:
+ case BOTTOM_LEFT:
+ x += Hud.OFFSET;
+ break;
+ case TOP_CENTER:
+ case MIDDLE_CENTER:
+ case BOTTOM_CENTER:
+ x += ((hudWidth - width) / 2);
+ break;
+ case TOP_RIGHT:
+ case MIDDLE_RIGHT:
+ case BOTTOM_RIGHT:
+ x += (hudWidth - width - Hud.OFFSET);
+ break;
+ default:
+ x += ((hudWidth - width) / 2);
+ break;
+ }
+ return x;
+ }
+
+ public float getY(float height)
+ {
+ float y = hudBottom;
+ switch(this) {
+ case TOP_LEFT:
+ case TOP_CENTER:
+ case TOP_RIGHT:
+ y += (hudHeight - height - Hud.OFFSET);
+ break;
+ case MIDDLE_LEFT:
+ case MIDDLE_CENTER:
+ case MIDDLE_RIGHT:
+ y += ((hudHeight - height) / 2);
+ break;
+ case BOTTOM_LEFT:
+ case BOTTOM_CENTER:
+ case BOTTOM_RIGHT:
+ y += Hud.OFFSET;
+ break;
+ default:
+ y += ((hudHeight - height) / 2);
+ break;
+ }
+ return y;
+ }
+
+ public float getX(Widget widget, float width)
+ {
+ float x = 0;
+ switch(this) {
+ case TOP_LEFT:
+ case MIDDLE_LEFT:
+ case BOTTOM_LEFT:
+ x = widget.getX();
+ break;
+ case TOP_CENTER:
+ case MIDDLE_CENTER:
+ case BOTTOM_CENTER:
+ x = (widget.getX() + ((widget.getWidth() - width) / 2));
+ break;
+ case TOP_RIGHT:
+ case MIDDLE_RIGHT:
+ case BOTTOM_RIGHT:
+ x = (widget.getX() + widget.getWidth() - width);
+ break;
+ default:
+ x = (widget.getX() + ((widget.getWidth() - width) / 2));
+ break;
+ }
+ return x;
+ }
+
+ public float getY(Widget widget, float height)
+ {
+ float y = 0;
+ switch(this) {
+ case TOP_LEFT:
+ case TOP_CENTER:
+ case TOP_RIGHT:
+ y = (widget.getY() + widget.getHeight() - height);
+ break;
+ case MIDDLE_LEFT:
+ case MIDDLE_CENTER:
+ case MIDDLE_RIGHT:
+ y = (widget.getY() + ((widget.getHeight() - height) / 2));
+ break;
+ case BOTTOM_LEFT:
+ case BOTTOM_CENTER:
+ case BOTTOM_RIGHT:
+ y = widget.getY();
+ break;
+ default:
+ y = (widget.getY() + ((widget.getHeight() - height) / 2));
+ break;
+ }
+ return y;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/ui/Widget.java b/core/src/ch/asynk/rustanddust/ui/Widget.java
new file mode 100644
index 0000000..4ae8afd
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/ui/Widget.java
@@ -0,0 +1,93 @@
+package ch.asynk.rustanddust.ui;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.math.Rectangle;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+
+public abstract class Widget implements Disposable, Drawable
+{
+ public boolean blocked;
+ public boolean visible;
+ protected float padding;
+ protected Rectangle rect;
+ protected Position position;
+ protected Widget parent;
+
+ protected Widget()
+ {
+ this.parent = null;
+ this.blocked = false;
+ this.visible = true;
+ this.padding = 0f;
+ this.rect = new Rectangle(0, 0, 0, 0);
+ this.position = Position.MIDDLE_CENTER;
+ }
+
+ public float getX() { return rect.x; }
+ public float getY() { return rect.y; }
+ public float getWidth() { return rect.width; }
+ public float getHeight() { return rect.height; }
+
+ public void translate(float dx, float dy)
+ {
+ rect.x += dx;
+ rect.y += dy;
+ }
+
+ public void setPosition(Rectangle r)
+ {
+ rect.set(r);
+ }
+
+ public void setPosition(float x, float y)
+ {
+ rect.x = x;
+ rect.y = y;
+ }
+
+ public void setPosition(float x, float y, float w, float h)
+ {
+ rect.set(x, y, w, h);
+ }
+
+ public void setPosition(Position position)
+ {
+ this.position = position;
+ setParent(this.parent);
+ }
+
+ public void setPosition(Position position, Widget parent)
+ {
+ this.position = position;
+ setParent(parent);
+ }
+
+ public void setParent(Widget parent)
+ {
+ this.parent = parent;
+ if (parent == null) {
+ rect.x = position.getX(rect.width);
+ rect.y = position.getY(rect.height);
+ } else {
+ rect.x = position.getX(parent, rect.width);
+ rect.y = position.getY(parent, rect.height);
+ }
+ // might trigger something if overriden
+ setPosition(rect.x, rect.y);
+ }
+
+ public boolean hit(float x, float y)
+ {
+ if (blocked || !visible) return false;
+ return rect.contains(x, y);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ if (!visible) return;
+ shapes.rect(rect.x, rect.y, rect.width, rect.height);
+ }
+}