summaryrefslogtreecommitdiffstats
path: root/core/src/ch/asynk/creepingarmor/game
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/ch/asynk/creepingarmor/game')
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Army.java30
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Battle.java40
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Command.java225
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Config.java76
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Ctrl.java336
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Engagement.java114
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Hex.java138
-rw-r--r--core/src/ch/asynk/creepingarmor/game/HexSet.java29
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Hud.java307
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Map.java659
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Player.java213
-rw-r--r--core/src/ch/asynk/creepingarmor/game/State.java35
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Unit.java361
-rw-r--r--core/src/ch/asynk/creepingarmor/game/UnitList.java20
-rw-r--r--core/src/ch/asynk/creepingarmor/game/Zone.java14
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/BattleCommon.java151
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/BattleCounterAttack.java150
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/BattleFrontalAssault.java124
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/BattleHeadToHead.java118
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/BattleLastStand.java136
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/BattleNightAction.java153
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/BattleStabToTheFlank.java143
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/BattleTest.java128
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/Factory.java192
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/MapA.java77
-rw-r--r--core/src/ch/asynk/creepingarmor/game/battles/MapB.java75
-rw-r--r--core/src/ch/asynk/creepingarmor/game/hud/ActionButtons.java185
-rw-r--r--core/src/ch/asynk/creepingarmor/game/hud/EngagementPanel.java255
-rw-r--r--core/src/ch/asynk/creepingarmor/game/hud/PlayerInfo.java202
-rw-r--r--core/src/ch/asynk/creepingarmor/game/hud/StatisticsPanel.java120
-rw-r--r--core/src/ch/asynk/creepingarmor/game/hud/UnitDock.java226
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateAnimation.java37
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateBreak.java90
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateCommon.java68
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateDeployment.java138
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateEngage.java105
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateMove.java193
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StatePromote.java42
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateReinforcement.java87
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateRotate.java111
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateSelect.java132
-rw-r--r--core/src/ch/asynk/creepingarmor/game/states/StateWithdraw.java71
42 files changed, 6106 insertions, 0 deletions
diff --git a/core/src/ch/asynk/creepingarmor/game/Army.java b/core/src/ch/asynk/creepingarmor/game/Army.java
new file mode 100644
index 0000000..dbf5656
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Army.java
@@ -0,0 +1,30 @@
+package ch.asynk.creepingarmor.game;
+
+import ch.asynk.creepingarmor.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/creepingarmor/game/Battle.java b/core/src/ch/asynk/creepingarmor/game/Battle.java
new file mode 100644
index 0000000..9a4a0c3
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Battle.java
@@ -0,0 +1,40 @@
+package ch.asynk.creepingarmor.game;
+
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.battles.Factory.MapType;
+import ch.asynk.creepingarmor.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/creepingarmor/game/Command.java b/core/src/ch/asynk/creepingarmor/game/Command.java
new file mode 100644
index 0000000..34af71b
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Command.java
@@ -0,0 +1,225 @@
+package ch.asynk.creepingarmor.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.creepingarmor.engine.Order;
+import ch.asynk.creepingarmor.engine.Move;
+import ch.asynk.creepingarmor.engine.Pawn;
+import ch.asynk.creepingarmor.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/creepingarmor/game/Config.java b/core/src/ch/asynk/creepingarmor/game/Config.java
new file mode 100644
index 0000000..88a7fe2
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Config.java
@@ -0,0 +1,76 @@
+package ch.asynk.creepingarmor.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/creepingarmor/game/Ctrl.java b/core/src/ch/asynk/creepingarmor/game/Ctrl.java
new file mode 100644
index 0000000..f4696f1
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Ctrl.java
@@ -0,0 +1,336 @@
+package ch.asynk.creepingarmor.game;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.math.Vector3;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+import ch.asynk.creepingarmor.ui.Position;
+import ch.asynk.creepingarmor.game.State.StateType;
+import ch.asynk.creepingarmor.game.states.StateSelect;
+import ch.asynk.creepingarmor.game.states.StateMove;
+import ch.asynk.creepingarmor.game.states.StateRotate;
+import ch.asynk.creepingarmor.game.states.StatePromote;
+import ch.asynk.creepingarmor.game.states.StateEngage;
+import ch.asynk.creepingarmor.game.states.StateBreak;
+import ch.asynk.creepingarmor.game.states.StateAnimation;
+import ch.asynk.creepingarmor.game.states.StateReinforcement;
+import ch.asynk.creepingarmor.game.states.StateDeployment;
+import ch.asynk.creepingarmor.game.states.StateWithdraw;
+
+public class Ctrl implements Disposable
+{
+ private final CreepingArmor 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 CreepingArmor 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) {
+ CreepingArmor.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);
+
+ CreepingArmor.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/creepingarmor/game/Engagement.java b/core/src/ch/asynk/creepingarmor/game/Engagement.java
new file mode 100644
index 0000000..1468181
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Engagement.java
@@ -0,0 +1,114 @@
+package ch.asynk.creepingarmor.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/creepingarmor/game/Hex.java b/core/src/ch/asynk/creepingarmor/game/Hex.java
new file mode 100644
index 0000000..cca4fad
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Hex.java
@@ -0,0 +1,138 @@
+package ch.asynk.creepingarmor.game;
+
+import java.util.List;
+import java.util.Iterator;
+
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.creepingarmor.engine.Board;
+import ch.asynk.creepingarmor.engine.Pawn;
+import ch.asynk.creepingarmor.engine.Tile;
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.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/creepingarmor/game/HexSet.java b/core/src/ch/asynk/creepingarmor/game/HexSet.java
new file mode 100644
index 0000000..bcbfa2d
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/HexSet.java
@@ -0,0 +1,29 @@
+package ch.asynk.creepingarmor.game;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+import ch.asynk.creepingarmor.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/creepingarmor/game/Hud.java b/core/src/ch/asynk/creepingarmor/game/Hud.java
new file mode 100644
index 0000000..1220a2d
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Hud.java
@@ -0,0 +1,307 @@
+package ch.asynk.creepingarmor.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.creepingarmor.engine.gfx.Animation;
+import ch.asynk.creepingarmor.game.State.StateType;
+import ch.asynk.creepingarmor.ui.Position;
+import ch.asynk.creepingarmor.ui.Msg;
+import ch.asynk.creepingarmor.ui.OkCancel;
+import ch.asynk.creepingarmor.ui.Widget;
+import ch.asynk.creepingarmor.game.hud.PlayerInfo;
+import ch.asynk.creepingarmor.game.hud.ActionButtons;
+import ch.asynk.creepingarmor.game.hud.StatisticsPanel;
+import ch.asynk.creepingarmor.game.hud.EngagementPanel;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+
+public class Hud implements Disposable, Animation
+{
+ public static final float OFFSET = 10f;
+ public static final float NOTIFY_DURATION = 2f;
+
+ private final CreepingArmor 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 CreepingArmor 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/creepingarmor/game/Map.java b/core/src/ch/asynk/creepingarmor/game/Map.java
new file mode 100644
index 0000000..00d720b
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Map.java
@@ -0,0 +1,659 @@
+package ch.asynk.creepingarmor.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.creepingarmor.CreepingArmor;
+import ch.asynk.creepingarmor.engine.Pawn;
+import ch.asynk.creepingarmor.engine.Board;
+import ch.asynk.creepingarmor.engine.Tile;
+import ch.asynk.creepingarmor.engine.Faction;
+import ch.asynk.creepingarmor.engine.Move;
+import ch.asynk.creepingarmor.engine.SelectedTile;
+import ch.asynk.creepingarmor.engine.ObjectiveSet;
+import ch.asynk.creepingarmor.engine.OrderList;
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.engine.Meteorology;
+import ch.asynk.creepingarmor.engine.PathBuilder;
+import ch.asynk.creepingarmor.engine.gfx.Moveable;
+import ch.asynk.creepingarmor.engine.gfx.Animation;
+import ch.asynk.creepingarmor.engine.gfx.animations.AnimationSequence;
+import ch.asynk.creepingarmor.engine.gfx.animations.DiceAnimation;
+import ch.asynk.creepingarmor.engine.gfx.animations.FireAnimation;
+import ch.asynk.creepingarmor.engine.gfx.animations.TankFireAnimation;
+import ch.asynk.creepingarmor.engine.gfx.animations.InfantryFireAnimation;
+import ch.asynk.creepingarmor.engine.gfx.animations.PromoteAnimation;
+import ch.asynk.creepingarmor.engine.gfx.animations.DestroyAnimation;
+import ch.asynk.creepingarmor.engine.gfx.animations.SoundAnimation;
+import ch.asynk.creepingarmor.engine.gfx.animations.RunnableAnimation;
+import ch.asynk.creepingarmor.engine.gfx.animations.MoveToAnimation.MoveToAnimationCb;
+
+import ch.asynk.creepingarmor.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 CreepingArmor 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)
+ {
+ CreepingArmor.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)
+ {
+ CreepingArmor.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()
+ {
+ CreepingArmor.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) {
+ CreepingArmor.debug(" revertMove() " + unit);
+ revertLastPawnMove(unit);
+ commands.dispose(unit, Command.CommandType.MOVE);
+ }
+ activatedUnits.clear();
+ objectives.revert(this);
+ }
+
+ public void revertEnter(final Unit unit)
+ {
+ CreepingArmor.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/creepingarmor/game/Player.java b/core/src/ch/asynk/creepingarmor/game/Player.java
new file mode 100644
index 0000000..020118a
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Player.java
@@ -0,0 +1,213 @@
+package ch.asynk.creepingarmor.game;
+
+import java.util.Random;
+import java.util.List;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+
+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 CreepingArmor 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) CreepingArmor.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/creepingarmor/game/State.java b/core/src/ch/asynk/creepingarmor/game/State.java
new file mode 100644
index 0000000..a0af2e2
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/State.java
@@ -0,0 +1,35 @@
+package ch.asynk.creepingarmor.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/creepingarmor/game/Unit.java b/core/src/ch/asynk/creepingarmor/game/Unit.java
new file mode 100644
index 0000000..f209ea2
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Unit.java
@@ -0,0 +1,361 @@
+package ch.asynk.creepingarmor.game;
+
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.creepingarmor.engine.Pawn;
+import ch.asynk.creepingarmor.engine.Tile;
+import ch.asynk.creepingarmor.engine.HeadedPawn;
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.game.Hex.Terrain;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+
+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)
+ CreepingArmor.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/creepingarmor/game/UnitList.java b/core/src/ch/asynk/creepingarmor/game/UnitList.java
new file mode 100644
index 0000000..2e4ee27
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/UnitList.java
@@ -0,0 +1,20 @@
+package ch.asynk.creepingarmor.game;
+
+import java.util.Collection;
+import java.util.ArrayList;
+
+import ch.asynk.creepingarmor.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/creepingarmor/game/Zone.java b/core/src/ch/asynk/creepingarmor/game/Zone.java
new file mode 100644
index 0000000..f7c0a82
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/Zone.java
@@ -0,0 +1,14 @@
+package ch.asynk.creepingarmor.game;
+
+import ch.asynk.creepingarmor.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/creepingarmor/game/battles/BattleCommon.java b/core/src/ch/asynk/creepingarmor/game/battles/BattleCommon.java
new file mode 100644
index 0000000..fb1ab53
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/BattleCommon.java
@@ -0,0 +1,151 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import java.util.Random;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Battle;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.HexSet;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Unit.UnitId;
+import ch.asynk.creepingarmor.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/creepingarmor/game/battles/BattleCounterAttack.java b/core/src/ch/asynk/creepingarmor/game/battles/BattleCounterAttack.java
new file mode 100644
index 0000000..a06d7cd
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/BattleCounterAttack.java
@@ -0,0 +1,150 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Unit.UnitId;
+import ch.asynk.creepingarmor.ui.Position;
+import ch.asynk.creepingarmor.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/creepingarmor/game/battles/BattleFrontalAssault.java b/core/src/ch/asynk/creepingarmor/game/battles/BattleFrontalAssault.java
new file mode 100644
index 0000000..a7f5726
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/BattleFrontalAssault.java
@@ -0,0 +1,124 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.HexSet;
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Unit.UnitId;
+import ch.asynk.creepingarmor.ui.Position;
+import ch.asynk.creepingarmor.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/creepingarmor/game/battles/BattleHeadToHead.java b/core/src/ch/asynk/creepingarmor/game/battles/BattleHeadToHead.java
new file mode 100644
index 0000000..26d2b1d
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/BattleHeadToHead.java
@@ -0,0 +1,118 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.HexSet;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Unit.UnitId;
+import ch.asynk.creepingarmor.ui.Position;
+import ch.asynk.creepingarmor.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/creepingarmor/game/battles/BattleLastStand.java b/core/src/ch/asynk/creepingarmor/game/battles/BattleLastStand.java
new file mode 100644
index 0000000..6d7e035
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/BattleLastStand.java
@@ -0,0 +1,136 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.HexSet;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Unit.UnitId;
+import ch.asynk.creepingarmor.ui.Position;
+import ch.asynk.creepingarmor.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/creepingarmor/game/battles/BattleNightAction.java b/core/src/ch/asynk/creepingarmor/game/battles/BattleNightAction.java
new file mode 100644
index 0000000..965327a
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/BattleNightAction.java
@@ -0,0 +1,153 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.HexSet;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Unit.UnitId;
+import ch.asynk.creepingarmor.ui.Position;
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.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/creepingarmor/game/battles/BattleStabToTheFlank.java b/core/src/ch/asynk/creepingarmor/game/battles/BattleStabToTheFlank.java
new file mode 100644
index 0000000..d2d1457
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/BattleStabToTheFlank.java
@@ -0,0 +1,143 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.HexSet;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Unit.UnitId;
+import ch.asynk.creepingarmor.ui.Position;
+import ch.asynk.creepingarmor.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/creepingarmor/game/battles/BattleTest.java b/core/src/ch/asynk/creepingarmor/game/battles/BattleTest.java
new file mode 100644
index 0000000..6c1d6c1
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/BattleTest.java
@@ -0,0 +1,128 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.HexSet;
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Unit.UnitId;
+import ch.asynk.creepingarmor.ui.Position;
+import ch.asynk.creepingarmor.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/creepingarmor/game/battles/Factory.java b/core/src/ch/asynk/creepingarmor/game/battles/Factory.java
new file mode 100644
index 0000000..4af2536
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/Factory.java
@@ -0,0 +1,192 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+import ch.asynk.creepingarmor.engine.Board;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Unit.UnitId;
+import ch.asynk.creepingarmor.game.Unit.UnitType;
+import ch.asynk.creepingarmor.game.Battle;
+import ch.asynk.creepingarmor.game.battles.BattleHeadToHead;
+import ch.asynk.creepingarmor.game.battles.BattleFrontalAssault;
+import ch.asynk.creepingarmor.game.battles.BattleLastStand;
+import ch.asynk.creepingarmor.game.battles.BattleCounterAttack;
+import ch.asynk.creepingarmor.game.battles.BattleStabToTheFlank;
+import ch.asynk.creepingarmor.game.battles.BattleNightAction;
+import ch.asynk.creepingarmor.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 CreepingArmor game;
+
+ public Factory(final CreepingArmor 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/creepingarmor/game/battles/MapA.java b/core/src/ch/asynk/creepingarmor/game/battles/MapA.java
new file mode 100644
index 0000000..11c2ddd
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/MapA.java
@@ -0,0 +1,77 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+import ch.asynk.creepingarmor.engine.Board;
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+
+public class MapA extends Map
+{
+ public MapA(final CreepingArmor 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/creepingarmor/game/battles/MapB.java b/core/src/ch/asynk/creepingarmor/game/battles/MapB.java
new file mode 100644
index 0000000..1248cea
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/battles/MapB.java
@@ -0,0 +1,75 @@
+package ch.asynk.creepingarmor.game.battles;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+import ch.asynk.creepingarmor.engine.Board;
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+
+public class MapB extends Map
+{
+ public MapB(final CreepingArmor 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/creepingarmor/game/hud/ActionButtons.java b/core/src/ch/asynk/creepingarmor/game/hud/ActionButtons.java
new file mode 100644
index 0000000..1e85a5e
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/hud/ActionButtons.java
@@ -0,0 +1,185 @@
+package ch.asynk.creepingarmor.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.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.State.StateType;
+import ch.asynk.creepingarmor.ui.Widget;
+import ch.asynk.creepingarmor.ui.Bg;
+import ch.asynk.creepingarmor.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/creepingarmor/game/hud/EngagementPanel.java b/core/src/ch/asynk/creepingarmor/game/hud/EngagementPanel.java
new file mode 100644
index 0000000..5471dfb
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/hud/EngagementPanel.java
@@ -0,0 +1,255 @@
+package ch.asynk.creepingarmor.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.creepingarmor.game.Engagement;
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.engine.gfx.Animation;
+import ch.asynk.creepingarmor.engine.gfx.animations.DiceAnimation;
+import ch.asynk.creepingarmor.ui.Bg;
+import ch.asynk.creepingarmor.ui.Label;
+import ch.asynk.creepingarmor.ui.Patch;
+import ch.asynk.creepingarmor.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/creepingarmor/game/hud/PlayerInfo.java b/core/src/ch/asynk/creepingarmor/game/hud/PlayerInfo.java
new file mode 100644
index 0000000..fbd843b
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/hud/PlayerInfo.java
@@ -0,0 +1,202 @@
+package ch.asynk.creepingarmor.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.creepingarmor.engine.gfx.Animation;
+import ch.asynk.creepingarmor.engine.gfx.Drawable;
+
+import ch.asynk.creepingarmor.game.State.StateType;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Hud;
+import ch.asynk.creepingarmor.game.Army;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.ui.LabelImage;
+import ch.asynk.creepingarmor.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/creepingarmor/game/hud/StatisticsPanel.java b/core/src/ch/asynk/creepingarmor/game/hud/StatisticsPanel.java
new file mode 100644
index 0000000..e4a70ce
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/hud/StatisticsPanel.java
@@ -0,0 +1,120 @@
+package ch.asynk.creepingarmor.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.creepingarmor.game.Player;
+import ch.asynk.creepingarmor.ui.Bg;
+import ch.asynk.creepingarmor.ui.Label;
+import ch.asynk.creepingarmor.ui.Patch;
+import ch.asynk.creepingarmor.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/creepingarmor/game/hud/UnitDock.java b/core/src/ch/asynk/creepingarmor/game/hud/UnitDock.java
new file mode 100644
index 0000000..b4a2f6f
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/hud/UnitDock.java
@@ -0,0 +1,226 @@
+package ch.asynk.creepingarmor.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.creepingarmor.engine.gfx.Animation;
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.UnitList;
+import ch.asynk.creepingarmor.ui.Bg;
+import ch.asynk.creepingarmor.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/creepingarmor/game/states/StateAnimation.java b/core/src/ch/asynk/creepingarmor/game/states/StateAnimation.java
new file mode 100644
index 0000000..512169a
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateAnimation.java
@@ -0,0 +1,37 @@
+package ch.asynk.creepingarmor.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/creepingarmor/game/states/StateBreak.java b/core/src/ch/asynk/creepingarmor/game/states/StateBreak.java
new file mode 100644
index 0000000..3b474a5
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateBreak.java
@@ -0,0 +1,90 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+
+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
+ CreepingArmor.debug("That's very wrong there should be only one path");
+ }
+}
diff --git a/core/src/ch/asynk/creepingarmor/game/states/StateCommon.java b/core/src/ch/asynk/creepingarmor/game/states/StateCommon.java
new file mode 100644
index 0000000..8ac4ab7
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateCommon.java
@@ -0,0 +1,68 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.State;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+
+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/creepingarmor/game/states/StateDeployment.java b/core/src/ch/asynk/creepingarmor/game/states/StateDeployment.java
new file mode 100644
index 0000000..891156f
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateDeployment.java
@@ -0,0 +1,138 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.UnitList;
+import ch.asynk.creepingarmor.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+
+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/creepingarmor/game/states/StateEngage.java b/core/src/ch/asynk/creepingarmor/game/states/StateEngage.java
new file mode 100644
index 0000000..a9dc074
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateEngage.java
@@ -0,0 +1,105 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+
+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
+ CreepingArmor.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/creepingarmor/game/states/StateMove.java b/core/src/ch/asynk/creepingarmor/game/states/StateMove.java
new file mode 100644
index 0000000..9a586e3
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateMove.java
@@ -0,0 +1,193 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.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/creepingarmor/game/states/StatePromote.java b/core/src/ch/asynk/creepingarmor/game/states/StatePromote.java
new file mode 100644
index 0000000..92a0d7b
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StatePromote.java
@@ -0,0 +1,42 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.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/creepingarmor/game/states/StateReinforcement.java b/core/src/ch/asynk/creepingarmor/game/states/StateReinforcement.java
new file mode 100644
index 0000000..4d0cce4
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateReinforcement.java
@@ -0,0 +1,87 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.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/creepingarmor/game/states/StateRotate.java b/core/src/ch/asynk/creepingarmor/game/states/StateRotate.java
new file mode 100644
index 0000000..4f8cf39
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateRotate.java
@@ -0,0 +1,111 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.engine.Orientation;
+import ch.asynk.creepingarmor.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+
+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)
+ CreepingArmor.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/creepingarmor/game/states/StateSelect.java b/core/src/ch/asynk/creepingarmor/game/states/StateSelect.java
new file mode 100644
index 0000000..a0863e1
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateSelect.java
@@ -0,0 +1,132 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.game.Map;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.game.Unit;
+import ch.asynk.creepingarmor.game.Ctrl;
+import ch.asynk.creepingarmor.game.hud.ActionButtons.Buttons;
+
+import ch.asynk.creepingarmor.CreepingArmor;
+
+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 );
+ CreepingArmor.debug("Select", selectedHex.toString() + " " + selectedUnit + (isEnemy ? " enemy " : " friend "));
+ }
+}
diff --git a/core/src/ch/asynk/creepingarmor/game/states/StateWithdraw.java b/core/src/ch/asynk/creepingarmor/game/states/StateWithdraw.java
new file mode 100644
index 0000000..3d0dfdd
--- /dev/null
+++ b/core/src/ch/asynk/creepingarmor/game/states/StateWithdraw.java
@@ -0,0 +1,71 @@
+package ch.asynk.creepingarmor.game.states;
+
+import ch.asynk.creepingarmor.game.Zone;
+import ch.asynk.creepingarmor.game.Hex;
+import ch.asynk.creepingarmor.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;
+ }
+}