summaryrefslogtreecommitdiffstats
path: root/core/src/ch/asynk/rustanddust/engine
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/ch/asynk/rustanddust/engine')
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Attack.java28
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Board.java554
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Faction.java6
-rw-r--r--core/src/ch/asynk/rustanddust/engine/HeadedPawn.java88
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Meteorology.java24
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Move.java158
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Objective.java49
-rw-r--r--core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java78
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Order.java16
-rw-r--r--core/src/ch/asynk/rustanddust/engine/OrderList.java64
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Orientation.java134
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Path.java62
-rw-r--r--core/src/ch/asynk/rustanddust/engine/PathBuilder.java266
-rw-r--r--core/src/ch/asynk/rustanddust/engine/PathIterator.java73
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Pawn.java365
-rw-r--r--core/src/ch/asynk/rustanddust/engine/SearchBoard.java545
-rw-r--r--core/src/ch/asynk/rustanddust/engine/SelectedTile.java80
-rw-r--r--core/src/ch/asynk/rustanddust/engine/Tile.java175
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/Animation.java8
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java10
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java16
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java99
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java76
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java62
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java141
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java87
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java222
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java126
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java98
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java67
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java83
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java76
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java38
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java196
-rw-r--r--core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java48
35 files changed, 4218 insertions, 0 deletions
diff --git a/core/src/ch/asynk/rustanddust/engine/Attack.java b/core/src/ch/asynk/rustanddust/engine/Attack.java
new file mode 100644
index 0000000..e64399a
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Attack.java
@@ -0,0 +1,28 @@
+package ch.asynk.rustanddust.engine;
+
+public class Attack
+{
+ public Pawn attacker;
+ public Pawn target;
+ public int distance;
+ public boolean isClear;
+ public boolean isFlank;
+
+ public Attack(Pawn attacker)
+ {
+ this.attacker = attacker;
+ }
+
+ public String toString()
+ {
+ return String.format("attack : %s -> %s dist:%d clear:%b flank:%b", attacker, target, distance, isClear, isFlank);
+ }
+
+ public void reset()
+ {
+ target = null;
+ distance = 0;;
+ isClear = false;
+ isFlank = false;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Board.java b/core/src/ch/asynk/rustanddust/engine/Board.java
new file mode 100644
index 0000000..e4aba6d
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Board.java
@@ -0,0 +1,554 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.Iterator;
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+
+import com.badlogic.gdx.Gdx;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import com.badlogic.gdx.math.Matrix4;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+import ch.asynk.rustanddust.engine.gfx.animations.AnimationSequence;
+import ch.asynk.rustanddust.engine.gfx.animations.RunnableAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.MoveToAnimation.MoveToAnimationCb;
+
+public abstract class Board implements Disposable, Animation
+{
+ private int cols;
+ private int rows;
+ private final Tile neighbours[] = new Tile[6];
+
+ public interface TileBuilder
+ {
+ public Tile getNewTile(float x, float y, int col, int row, boolean offmap);
+ }
+
+ public static class Config
+ {
+ public int cols;
+ public int rows;
+ public int x0; // bottom left x offset
+ public int y0; // bottom left y offset
+ public int w; // hex width
+ public int dw; // half hex : w/2
+ public int s; // hex side
+ public float dh; // hex top : s/2
+ public float h; // square height : s + dh
+ public float slope; // north-west side slope : (dh / (float) dw)
+ }
+
+ private Config cfg;
+ private Tile[] tiles;
+ private SearchBoard searchBoard;
+ private Sprite board;
+ private Orientation sides[];
+
+ private boolean transform;
+ private Matrix4 prevTransform;
+ private Matrix4 nextTransform;
+
+ private int tileCount = 0;
+ private int pawnCount = 0;
+ private int animationCount = 0;
+ private final ArrayList<Animation> animations = new ArrayList<Animation>(2);
+ private final ArrayList<Animation> nextAnimations = new ArrayList<Animation>(2);
+ private final LinkedHashSet<Tile> tilesToDraw = new LinkedHashSet<Tile>();
+
+ protected SelectedTile selectedTile;
+
+ protected Board(int cols, int rows)
+ {
+ // add a frame of OFFMAP Tiles
+ this.cols = (cols + 2);
+ this.rows = (rows + 2);
+ searchBoard = new SearchBoard(this, cols, rows);
+ initSides();
+ }
+
+ public Board(TileBuilder tileBuilder, Config cfg, Texture boardTexture, SelectedTile selectedTile)
+ {
+ board = new Sprite(boardTexture);
+ this.cfg = cfg;
+ // add a frame of OFFMAP Tiles
+ this.cols = (cfg.cols + 2);
+ this.rows = (cfg.rows + 2);
+ this.tiles = new Tile[this.cols * this.rows];
+ searchBoard = new SearchBoard(this, cfg.cols, cfg.rows);
+
+ int idx = 0;
+ boolean evenRow = false;
+ float y = cfg.y0 - cfg.dh + cfg.s - cfg.h;
+ for (int i = -1; i < (cfg.rows + 1); i++) {
+ float x = cfg.x0 + cfg.dw - cfg.w;
+ if (!evenRow) x += cfg.dw;
+ for ( int j = -1; j < (cfg.cols + 1); j ++) {
+ boolean offmap = ((j < 0) || (i < 0) || (j >= cfg.cols) || (i >= cfg.rows));
+ this.tiles[idx] = tileBuilder.getNewTile(x, y, (j + ((i + 1) / 2)), i, offmap);
+ idx += 1;
+ x += cfg.w;
+ }
+ y += cfg.h;
+ evenRow = !evenRow;
+ }
+
+ initSides();
+
+ this.selectedTile = selectedTile;
+ }
+
+ private void initSides()
+ {
+ this.sides = new Orientation[6];
+ sides[0] = Orientation.NORTH;
+ sides[1] = Orientation.NORTH_EAST;
+ sides[2] = Orientation.SOUTH_EAST;
+ sides[3] = Orientation.SOUTH;
+ sides[4] = Orientation.SOUTH_WEST;
+ sides[5] = Orientation.NORTH_WEST;
+ }
+
+ @Override
+ public void dispose()
+ {
+ for (int i = 0; i < (this.cols * this.rows); i++)
+ tiles[i].dispose();
+ tilesToDraw.clear();
+ for (int i = 0, n = nextAnimations.size(); i < n; i++)
+ nextAnimations.get(i).dispose();
+ animations.clear();
+ for (int i = 0, n = animations.size(); i < n; i++)
+ animations.get(i).dispose();
+ animations.clear();
+ if (selectedTile != null)
+ selectedTile.dispose();
+ Move.clearPool();
+ Path.clearPool();
+ }
+
+ public float getWidth()
+ {
+ return board.getWidth();
+ }
+
+ public float getHeight()
+ {
+ return board.getHeight();
+ }
+
+ public void setPosition(float x, float y)
+ {
+ board.setPosition(x, y);
+ if ((x != 0.0f) || (y != 0.0f)) {
+ transform = true;
+ prevTransform = new Matrix4();
+ nextTransform = new Matrix4();
+ nextTransform.translate(x, y, 0);
+ } else
+ transform = false;
+ }
+
+ public Orientation getSide(int i)
+ {
+ return sides[i];
+ }
+
+ protected int getTileOffset(int col, int row)
+ {
+ col = (col + 1 - ((row + 1) / 2));
+ row = (row + 1);
+ if ((col < 0) || (row < 0) || (row >= this.rows) || (col >= this.cols))
+ return -1;
+
+ return (col + (row * this.cols));
+ }
+
+ protected Tile getTile(int col, int row)
+ {
+ int offset = getTileOffset(col, row);
+ if (offset < 0)
+ return null;
+ return tiles[offset];
+ }
+
+ public void setAdjacentTiles(Tile tile, Tile tiles[])
+ {
+ tiles[0] = getAdjTileAt(tile, sides[0].opposite());
+ tiles[1] = getAdjTileAt(tile, sides[1].opposite());
+ tiles[2] = getAdjTileAt(tile, sides[2].opposite());
+ tiles[3] = getAdjTileAt(tile, sides[3].opposite());
+ tiles[4] = getAdjTileAt(tile, sides[4].opposite());
+ tiles[5] = getAdjTileAt(tile, sides[5].opposite());
+ }
+
+ public Tile getAdjTileAt(Tile tile, Orientation o)
+ {
+ Tile t = null;
+ switch(o) {
+ case NORTH:
+ t = getTile((tile.col + 1), tile.row);
+ break;
+ case NORTH_EAST:
+ t = getTile(tile.col, (tile.row - 1));
+ break;
+ case SOUTH_EAST:
+ t = getTile((tile.col - 1), (tile.row - 1));
+ break;
+ case SOUTH:
+ t = getTile((tile.col - 1), tile.row);
+ break;
+ case SOUTH_WEST:
+ t = getTile(tile.col, (tile.row + 1));
+ break;
+ case NORTH_WEST:
+ t = getTile((tile.col + 1), (tile.row + 1));
+ break;
+ }
+ return t;
+ }
+
+ protected abstract void animationsOver();
+
+ protected void addAnimation(Animation a)
+ {
+ nextAnimations.add(a);
+ }
+
+ public int animationCount()
+ {
+ return animations.size();
+ }
+
+ private void stats()
+ {
+ boolean print = false;
+
+ if (tileCount != tilesToDraw.size()) {
+ tileCount = tilesToDraw.size();
+ print = true;
+ }
+
+ if (animationCount != animations.size()) {
+ animationCount = animations.size();
+ print = true;
+ }
+
+ if (print)
+ Gdx.app.debug("Board", " tiles:" + tileCount + " pawns:" + pawnCount + " animations:" + animationCount);
+ }
+
+ public boolean animate(float delta)
+ {
+ boolean over = (animations.size() > 0);
+ Iterator<Animation> iter = animations.iterator();
+ while (iter.hasNext()) {
+ Animation a = iter.next();
+ if (a.animate(delta))
+ iter.remove();
+ }
+ if (over && (animations.size() == 0))
+ animationsOver();
+
+ for (int i = 0, n = nextAnimations.size(); i < n; i++)
+ animations.add(nextAnimations.get(i));
+ nextAnimations.clear();
+
+ selectedTile.animate(delta);
+
+ return true;
+ }
+
+ public void draw(Batch batch)
+ {
+ board.draw(batch);
+
+ if (transform) {
+ prevTransform.set(batch.getTransformMatrix());
+ batch.setTransformMatrix(nextTransform);
+ }
+
+ Iterator<Tile> tileIter = tilesToDraw.iterator();
+ while (tileIter.hasNext())
+ tileIter.next().draw(batch);
+
+ Iterator<Animation> animationIter = animations.iterator();
+ while (animationIter.hasNext())
+ animationIter.next().draw(batch);
+
+ selectedTile.draw(batch);
+
+ if (transform)
+ batch.setTransformMatrix(prevTransform);
+ }
+
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ stats();
+ if (transform) {
+ prevTransform.set(debugShapes.getTransformMatrix());
+ debugShapes.setTransformMatrix(nextTransform);
+ }
+
+ Iterator<Tile> iter = tilesToDraw.iterator();
+ while (iter.hasNext())
+ iter.next().drawDebug(debugShapes);
+
+ Iterator<Animation> animationIter = animations.iterator();
+ while (animationIter.hasNext())
+ animationIter.next().drawDebug(debugShapes);
+
+ if (transform)
+ debugShapes.setTransformMatrix(prevTransform);
+ }
+
+ protected int collectPossibleMoves(Pawn pawn, Collection<Tile> moves)
+ {
+ return searchBoard.possibleMovesFrom(pawn, moves);
+ }
+
+ protected int collectPossibleTargets(Pawn pawn, Collection<Pawn> targets)
+ {
+ return searchBoard.possibleTargetsFrom(pawn, targets);
+ }
+
+ protected int collectPossibleTargets(Pawn pawn, Collection<Pawn> units, Collection<Pawn> targets)
+ {
+ targets.clear();
+ for (Pawn target : units) {
+ if (pawn.canEngage(target) && searchBoard.canAttack(pawn, target, true))
+ targets.add(target);
+ }
+
+ return targets.size();
+ }
+
+ protected int collectMoveAssists(Pawn pawn, Collection<Pawn> assists)
+ {
+ assists.clear();
+ setAdjacentTiles(pawn.getTile(), neighbours);
+ for (int i = 0; i < 6; i++) {
+ Tile tile = neighbours[i];
+ if (tile != null) {
+ Iterator<Pawn> pawns = tile.iterator();
+ while(pawns.hasNext()) {
+ Pawn p = pawns.next();
+ if (!pawn.isEnemy(p) && p.canMove())
+ assists.add(p);
+ }
+ }
+ }
+ return assists.size();
+ }
+
+ protected int collectAttackAssists(Pawn pawn, Pawn target, Collection<Pawn> units, Collection<Pawn> assists)
+ {
+ assists.clear();
+ for (Pawn p : units) {
+ if ((p != pawn) && p.canEngage(target) && searchBoard.canAttack(p, target, !p.canAssistEngagementWithoutLos()))
+ assists.add(p);
+ }
+
+ return assists.size();
+ }
+
+ public Orientation findBestEntry(Pawn pawn, Tile to, int allowedMoves)
+ {
+ Orientation entry = Orientation.KEEP;
+ int cost = Integer.MAX_VALUE;
+ boolean road = false;
+
+ setAdjacentTiles(to, neighbours);
+ for (int i = 0; i < 6; i++) {
+ Tile t = neighbours[i];
+ if (t.isOffMap()) {
+ Orientation o = Orientation.fromAdj(t.col, t.row, to.col, to.row);
+ if (o.isInSides(allowedMoves)) {
+ o = o.opposite();
+ boolean r = to.road(o);
+ int c = to.costFrom(pawn, o);
+ if ((c < cost) || (r && (c == cost))) {
+ entry = o;
+ cost = c;
+ road = r;
+ }
+ }
+ }
+ }
+
+ return entry.opposite();
+ }
+
+ public void enableOverlayOn(Tile tile, int i, boolean enable)
+ {
+ if(tile.enableOverlay(i, enable))
+ tilesToDraw.add(tile);
+ else
+ tilesToDraw.remove(tile);
+ }
+
+ public void enableOverlayOn(Tile tile, int i, Orientation o, boolean enable)
+ {
+ if(tile.enableOverlay(i, enable, o.r()))
+ tilesToDraw.add(tile);
+ else
+ tilesToDraw.remove(tile);
+ }
+
+ private int pushPawnOnto(Pawn pawn, Tile tile)
+ {
+ if (!tile.isOffMap())
+ tilesToDraw.add(tile);
+ return tile.push(pawn);
+ }
+
+ public int removePawn(Pawn pawn)
+ {
+ Tile tile = pawn.getTile();
+ if (tile == null)
+ return 0;
+ int n = tile.remove(pawn);
+ if (!tile.mustBeDrawn())
+ tilesToDraw.remove(tile);
+ return n;
+ }
+
+ public Pawn setPawnOnto(Pawn pawn, Move move)
+ {
+ pawn.move(move);
+ return setPawnOnto(pawn, move.to, move.orientation);
+ }
+
+ public Pawn setPawnOnto(Pawn pawn, Tile tile, Orientation o)
+ {
+ pawn.setOnTile(tile, o.r());
+ pushPawnOnto(pawn, tile);
+ return pawn;
+ }
+
+ private RunnableAnimation getSetPawnOntoAnimation(final Pawn pawn)
+ {
+ return RunnableAnimation.get(pawn, new Runnable() {
+ @Override
+ public void run() {
+ Tile to = pawn.move.to;
+ if (!to.isOffMap())
+ setPawnOnto(pawn, to, pawn.move.orientation);
+ }
+ });
+ }
+
+ protected void movePawn(final Pawn pawn, Move move, MoveToAnimationCb cb)
+ {
+ pawn.move(move);
+ removePawn(pawn);
+
+ AnimationSequence seq = pawn.getMoveAnimation(move.iterator(), (move.steps() + 1), cb);
+ seq.addAnimation(getSetPawnOntoAnimation(pawn));
+ addAnimation(seq);
+ }
+
+ protected void enterPawn(final Pawn pawn, Move move)
+ {
+ pawn.move(move);
+ setPawnOnto(pawn, move.to, move.orientation);
+ }
+
+ protected void revertLastPawnMove(final Pawn pawn)
+ {
+ removePawn(pawn);
+
+ addAnimation(RunnableAnimation.get(pawn, new Runnable() {
+ @Override
+ public void run() {
+ pushPawnOnto(pawn, pawn.getTile());
+ }
+ }));
+
+ pawn.revertLastMove();
+ }
+
+ public void attack(final Pawn pawn, final Pawn target, boolean clearVisibility)
+ {
+ if (!pawn.canEngage(target) || !searchBoard.canAttack(pawn, target, clearVisibility))
+ throw new RuntimeException(String.format("%s cannot attack %s", pawn, target));
+ }
+
+ public Tile getTileAt(float mx, float my)
+ {
+ // compute row
+ float y = (my - cfg.y0);
+ int row = (int) (y / cfg.h);
+ boolean oddRow = ((row % 2) == 1);
+ if (y < 0.f) {
+ row = -1;
+ oddRow = true;
+ }
+
+ // compute col
+ float x = (mx - cfg.x0);
+ if (oddRow) x -= cfg.dw;
+ int col = (int) (x / cfg.w);
+ if (x < 0.f)
+ col = -1;
+
+ int colOffset = ((row + 1) / 2);
+
+ // check upper boundaries
+ float dy = (y - (row * cfg.h));
+ if (dy > cfg.s) {
+ dy -= cfg.s;
+ float dx = (x - (col * cfg.w));
+ col += colOffset;
+ if (dx < cfg.dw) {
+ if ((dx * cfg.slope) < dy) {
+ // upper left corner
+ row += 1;
+ colOffset = ((row +1) / 2);
+ }
+ } else {
+ if (((cfg.w - dx) * cfg.slope) < dy) {
+ // upper right corner
+ row += 1;
+ col += 1;
+ colOffset = ((row +1) / 2);
+ }
+ }
+ } else
+ col += colOffset;
+
+ return getTile(col, row);
+ }
+
+ public int distance(Tile from, Tile to)
+ {
+ return distance(from.col, from.row, to.col, to.row);
+ }
+
+ public int distance(int col0, int row0, int col1, int row1)
+ {
+ int dx = Math.abs(col1 - col0);
+ int dy = Math.abs(row1 - row0);
+ int dz = Math.abs((col0 - row0) - (col1 - row1));
+
+ if (dx > dy) {
+ if (dx > dz)
+ return dx;
+ else
+ return dz;
+ } else {
+ if (dy > dz)
+ return dy;
+ else
+ return dz;
+ }
+ }
+}
+
diff --git a/core/src/ch/asynk/rustanddust/engine/Faction.java b/core/src/ch/asynk/rustanddust/engine/Faction.java
new file mode 100644
index 0000000..e108fc4
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Faction.java
@@ -0,0 +1,6 @@
+package ch.asynk.rustanddust.engine;
+
+public interface Faction
+{
+ public boolean isEnemy(Faction other);
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/HeadedPawn.java b/core/src/ch/asynk/rustanddust/engine/HeadedPawn.java
new file mode 100644
index 0000000..67e1d44
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/HeadedPawn.java
@@ -0,0 +1,88 @@
+package ch.asynk.rustanddust.engine;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import com.badlogic.gdx.math.Vector3;
+
+public abstract class HeadedPawn extends Pawn
+{
+ private Sprite head;
+ protected Orientation orientation;
+
+ public HeadedPawn(Faction faction, String pawn, String head, TextureAtlas pawns, TextureAtlas overlays)
+ {
+ super(faction, pawn, pawns, overlays);
+ this.head = new Sprite(pawns.findRegion(head));
+ this.orientation = Orientation.KEEP;
+ this.descr += " " + orientation;
+ }
+
+ @Override
+ public void dispose()
+ {
+ super.dispose();
+ }
+
+ @Override
+ public void setAlpha(float alpha)
+ {
+ super.setAlpha(alpha);
+ head.setAlpha(alpha);
+ }
+
+ @Override
+ public float getRotation()
+ {
+ return orientation.r();
+ }
+
+ @Override
+ public Orientation getOrientation()
+ {
+ return orientation;
+ }
+
+ @Override
+ public void setPosition(float x, float y)
+ {
+ super.setPosition(x, y);
+ float cx = x + (getWidth() / 2f);
+ float cy = y + (getHeight() / 2f);
+ head.setPosition((cx - (head.getWidth() / 2f)), (cy - (head.getHeight() / 2f)));
+ }
+
+ @Override
+ public void setRotation(float z)
+ {
+ getPosition().z = z;
+ head.setRotation(z);
+ this.orientation = Orientation.fromRotation(z);
+ }
+
+ @Override
+ public void setPosition(float x, float y, float z)
+ {
+ setPosition(x, y);
+ setRotation(z);
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ head.draw(batch);
+ super.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ float w = head.getWidth();
+ float h = head.getHeight();
+ debugShapes.rect(head.getX(), head.getY(), (w / 2f), (h / 2f), w, h, head.getScaleX(), head.getScaleY(), head.getRotation());
+ super.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Meteorology.java b/core/src/ch/asynk/rustanddust/engine/Meteorology.java
new file mode 100644
index 0000000..9addf63
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Meteorology.java
@@ -0,0 +1,24 @@
+package ch.asynk.rustanddust.engine;
+
+public class Meteorology
+{
+ public enum Day { DAY, NIGHT };
+ public enum Season { SUMMER, SPRING, WINTER, FALL };
+ public enum Weather { CLEAR, RAIN, SNOW, WIND };
+
+ public Day day;
+ public Season season;
+ public Weather weather;
+
+ public Meteorology()
+ {
+ clear();
+ }
+
+ public void clear()
+ {
+ day = Day.DAY;
+ season = Season.SUMMER;
+ weather = Weather.CLEAR;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Move.java b/core/src/ch/asynk/rustanddust/engine/Move.java
new file mode 100644
index 0000000..077823f
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Move.java
@@ -0,0 +1,158 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.Iterator;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.math.Vector3;
+
+public class Move extends Path implements Iterable<Vector3>
+{
+ public enum MoveType
+ {
+ REGULAR,
+ SET,
+ ENTER,
+ EXIT;
+ }
+
+ private static final Pool<Move> movePool = new Pool<Move>()
+ {
+ @Override
+ protected Move newObject() {
+ return new Move();
+ }
+ };
+
+ public static Move get(Pawn pawn, Tile from, Tile to, Orientation orientation, Path path)
+ {
+ Move m = movePool.obtain();
+ m.pawn = pawn;
+ m.from = from;
+ m.to = to;
+ m.orientation = orientation;
+ if (path != null) {
+ m.init(path.tiles.size());
+ m.cost = path.cost;
+ m.roadMarch = path.roadMarch;
+ for (Tile tile : path.tiles)
+ m.tiles.add(tile);
+ } else {
+ m.init(0);
+ }
+
+ return m;
+ }
+
+ public static void clearPool()
+ {
+ movePool.clear();
+ }
+
+ public static Move getEnter(Pawn pawn, Tile to, Orientation orientation)
+ {
+ Move m = get(pawn, null, to, orientation, null);
+ m.type = MoveType.ENTER;
+ m.cost = to.costFrom(pawn, orientation);
+ return m;
+ }
+
+ public static Move getSet(Pawn pawn, Tile to, Orientation orientation)
+ {
+ Move m = get(pawn, null, to, orientation, null);
+ m.type = MoveType.SET;
+ m.cost = 0;
+ return m;
+ }
+
+ public Pawn pawn;
+ public Tile from;
+ public Tile to;
+ public Orientation orientation;
+ public MoveType type;
+
+ public Move()
+ {
+ super();
+ this.pawn = null;
+ this.from = null;
+ this.to = null;
+ this.orientation = Orientation.KEEP;
+ this.type = MoveType.REGULAR;
+ }
+
+ @Override
+ public void reset()
+ {
+ pawn = null;
+ from = null;
+ to = null;
+ orientation = Orientation.KEEP;
+ type = MoveType.REGULAR;
+ super.reset();
+ }
+
+ @Override
+ public void dispose()
+ {
+ tiles.clear();
+ movePool.free(this);
+ }
+
+ public boolean isSet()
+ {
+ return (type == MoveType.SET);
+ }
+
+ public boolean isEnter()
+ {
+ return (type == MoveType.ENTER);
+ }
+
+ public boolean isRegular()
+ {
+ return (type == MoveType.REGULAR);
+ }
+
+ public boolean isFinal()
+ {
+ return (type != MoveType.ENTER);
+ }
+
+ public int steps()
+ {
+ int steps = 0;
+
+ Tile tile = from;
+ Orientation o = pawn.getOrientation();
+ for (Tile next : tiles) {
+ Orientation nextO = Orientation.fromMove(tile.col, tile.row, next.col, next.row);
+ if (nextO != o) {
+ steps += 2;
+ o = nextO;
+ } else
+ steps += 1;
+ tile = next;
+ }
+ if (orientation != Orientation.fromMove(tile.col, tile.row, to.col, to.row))
+ steps += 2;
+ else
+ steps +=1;
+
+ return steps;
+ }
+
+ @Override
+ public String toString()
+ {
+ if (from == null)
+ return String.format("%s %s c:%d", to.toShort(), orientation, cost);
+ else
+ return String.format("%s->%s %s c:%d", from.toShort(), to.toShort(), orientation, cost);
+ }
+
+ @Override
+ public Iterator<Vector3> iterator()
+ {
+ return new PathIterator(pawn, from, to, orientation, tiles);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Objective.java b/core/src/ch/asynk/rustanddust/engine/Objective.java
new file mode 100644
index 0000000..de1c7d3
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Objective.java
@@ -0,0 +1,49 @@
+package ch.asynk.rustanddust.engine;
+
+public class Objective
+{
+ protected Faction curFaction;
+ protected Faction prevFaction;
+ private boolean persistent;
+
+ public Objective(Faction faction, boolean persistent)
+ {
+ this.curFaction = faction;
+ this.prevFaction = faction;
+ this.persistent = persistent;
+ }
+
+ public boolean is(Faction faction)
+ {
+ return (curFaction == faction);
+ }
+
+ public Faction faction()
+ {
+ return curFaction;
+ }
+
+ public boolean set(Faction faction)
+ {
+ if (faction == curFaction)
+ return false;
+
+ prevFaction = curFaction;
+ curFaction = faction;
+ return true;
+ }
+
+ public boolean unset()
+ {
+ if (persistent)
+ return false;
+ revert();
+ return true;
+ }
+
+ public Faction revert()
+ {
+ curFaction = prevFaction;
+ return curFaction;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java b/core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java
new file mode 100644
index 0000000..5618a9d
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java
@@ -0,0 +1,78 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.Collection;
+import java.util.HashMap;
+
+public class ObjectiveSet extends HashMap<Tile, Objective>
+{
+ public interface ObjectiveCb
+ {
+ public void showObjective(Tile tile, Faction faction);
+ }
+
+ private final Board board;
+ private final HashMap<Objective, Tile> modified;
+
+ public ObjectiveSet(Board board, int n)
+ {
+ super(n);
+ this.board = board;
+ this.modified = new HashMap<Objective, Tile>(10);
+ }
+
+ public void add(Tile tile, Faction faction, boolean persistent)
+ {
+ put(tile, new Objective(faction, persistent));
+ }
+
+ public int count(Faction faction)
+ {
+ int n = 0;
+ for (Objective objective : values()) {
+ if (objective.is(faction))
+ n += 1;
+ }
+ return n;
+ }
+
+ public Faction claim(Tile tile, Faction faction)
+ {
+ Objective objective = get(tile);
+ if (objective == null)
+ return null;
+
+ if (objective.set(faction))
+ modified.put(objective, tile);
+ return objective.faction();
+ }
+
+ public Faction unclaim(Tile tile)
+ {
+ Objective objective = get(tile);
+ if (objective == null)
+ return null;
+
+ if (objective.unset())
+ modified.remove(objective);
+ return objective.faction();
+ }
+
+ public void forget()
+ {
+ modified.clear();
+ }
+
+ public int modifiedCount()
+ {
+ return modified.size();
+ }
+
+ public void revert(ObjectiveCb cb)
+ {
+ for (Objective objective : modified.keySet()) {
+ objective.revert();
+ cb.showObjective(modified.get(objective), objective.faction());
+ }
+ modified.clear();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Order.java b/core/src/ch/asynk/rustanddust/engine/Order.java
new file mode 100644
index 0000000..960b126
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Order.java
@@ -0,0 +1,16 @@
+package ch.asynk.rustanddust.engine;
+
+import java.lang.Comparable;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.utils.Json;
+
+public abstract class Order implements Disposable, Pool.Poolable, Json.Serializable, Comparable<Pawn>
+{
+ public interface OrderType
+ {
+ }
+
+ public abstract boolean isA(OrderType type);
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/OrderList.java b/core/src/ch/asynk/rustanddust/engine/OrderList.java
new file mode 100644
index 0000000..4488cfe
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/OrderList.java
@@ -0,0 +1,64 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.LinkedList;
+import java.util.Iterator;
+
+import com.badlogic.gdx.utils.Json;
+import com.badlogic.gdx.utils.JsonValue;
+import com.badlogic.gdx.utils.JsonWriter.OutputType;
+
+public class OrderList extends LinkedList<Order> implements Json.Serializable
+{
+ public void dispose(Pawn pawn)
+ {
+ Iterator<Order> it = iterator();
+ while(it.hasNext()) {
+ Order order = it.next();
+ if (order.compareTo(pawn) == 0) {
+ it.remove();
+ order.dispose();
+ }
+ }
+ }
+
+ public void dispose(Pawn pawn, Order.OrderType type)
+ {
+ Iterator<Order> it = iterator();
+ while(it.hasNext()) {
+ Order order = it.next();
+ if ((order.compareTo(pawn) == 0) && (order.isA(type))) {
+ it.remove();
+ order.dispose();
+ }
+ }
+ }
+
+ public void dispose()
+ {
+ for (Order o : this)
+ o.dispose();
+ clear();
+ }
+
+ public String toJson()
+ {
+ Json json = new Json();
+ json.setOutputType(OutputType.json);
+ return json.toJson(this);
+ }
+
+ @Override
+ public void write(Json json)
+ {
+ json.writeArrayStart("commands");
+ for (Order o : this)
+ json.writeValue(o);
+ json.writeArrayEnd();
+ }
+
+ @Override
+ public void read(Json json, JsonValue jsonMap)
+ {
+ // TODO read(Json json, JsonValue jsonMap)
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Orientation.java b/core/src/ch/asynk/rustanddust/engine/Orientation.java
new file mode 100644
index 0000000..2c3ef58
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Orientation.java
@@ -0,0 +1,134 @@
+package ch.asynk.rustanddust.engine;
+
+public enum Orientation
+{
+ ALL(0, 63),
+ KEEP(0, 0),
+ NORTH(270, 1),
+ NORTH_EAST(210, 2),
+ SOUTH_EAST(150, 4),
+ SOUTH(90, 8),
+ SOUTH_WEST (30, 16),
+ NORTH_WEST(330, 32);
+
+ public static int offset = 0;
+ public static float delta = 5f;
+ private final int r;
+ public final int s;
+
+ Orientation(int r, int s) { this.r = r; this.s = s; }
+
+ public float r() { return offset + r; }
+
+ public boolean isInSides(int sides)
+ {
+ return ((sides & s) == s);
+ }
+
+ public Orientation left()
+ {
+ if (this == NORTH) return NORTH_WEST;
+ else return fromSide(s >> 1);
+ }
+
+ public Orientation right()
+ {
+ if (this == NORTH_WEST) return NORTH;
+ else return fromSide(s << 1);
+ }
+
+ public Orientation opposite()
+ {
+ return left().left().left();
+ }
+
+ public int allBut()
+ {
+ return ALL.s & (s ^ 0xFFFF);
+ }
+
+ public int getFrontSides()
+ {
+ return s | left().s | right().s;
+ }
+
+ public int getBackSides()
+ {
+ return opposite().getFrontSides();
+ }
+
+ public static Orientation fromSide(int s)
+ {
+ if (s == NORTH.s) return NORTH;
+ else if (s == NORTH_EAST.s) return NORTH_EAST;
+ else if (s == SOUTH_EAST.s) return SOUTH_EAST;
+ else if (s == SOUTH.s) return SOUTH;
+ else if (s == SOUTH_WEST.s) return SOUTH_WEST;
+ else if (s == NORTH_WEST.s) return NORTH_WEST;
+ else return KEEP;
+ }
+
+ public static Orientation fromRotation(float r)
+ {
+ if (r < 0) r += 360f;
+ if ((r > (NORTH.r - 5f)) && (r < (NORTH.r + 5f))) return NORTH;
+ else if ((r > (NORTH_EAST.r - delta)) && (r < (NORTH_EAST.r + delta))) return NORTH_EAST;
+ else if ((r > (SOUTH_EAST.r - delta)) && (r < (SOUTH_EAST.r + delta))) return SOUTH_EAST;
+ else if ((r > (SOUTH.r - delta)) && (r < (SOUTH.r + delta))) return SOUTH;
+ else if ((r > (SOUTH_WEST.r - delta)) && (r < (SOUTH_WEST.r + delta))) return SOUTH_WEST;
+ else if ((r > (NORTH_WEST.r - delta)) && (r < (NORTH_WEST.r + delta))) return NORTH_WEST;
+ else return KEEP;
+ }
+
+ public static Orientation fromMove(int col0, int row0, int col1, int row1)
+ {
+ int dx = col1 - col0;
+ int dy = row1 - row0;
+
+ if (dy == 0) {
+ if (dx == 0) return KEEP;
+ if (dx > 0) return NORTH;
+ return SOUTH;
+ }
+ if (dy > 0) {
+ if (dx > 0) return NORTH_WEST;
+ return SOUTH_WEST;
+ } else {
+ if (dx < 0) return SOUTH_EAST;
+ return NORTH_EAST;
+ }
+ }
+
+ public static Orientation fromAdj(Tile from, Tile to)
+ {
+ return fromAdj(from.col, from.row, to.col, to.row);
+ }
+
+ public static Orientation fromAdj(int col0, int row0, int col1, int row1)
+ {
+ Orientation o = KEEP;
+
+ if (row1 == row0) {
+ if (col1 == (col0 - 1)) {
+ o = SOUTH;
+ } else if (col1 == (col0 + 1)) {
+ o = NORTH;
+ }
+ } else if (row1 == (row0 - 1)) {
+ if (col1 == (col0 - 1)) {
+ o = SOUTH_EAST;
+ } else if (col1 == col0) {
+ o = NORTH_EAST;
+ }
+
+ } else if (row1 == (row0 + 1)) {
+ if (col1 == col0) {
+ o = SOUTH_WEST;
+ } else if (col1 == (col0 + 1)) {
+ o = NORTH_WEST;
+ }
+ }
+
+ return o;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Path.java b/core/src/ch/asynk/rustanddust/engine/Path.java
new file mode 100644
index 0000000..2ddf3e5
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Path.java
@@ -0,0 +1,62 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.ArrayList;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.utils.Disposable;
+
+public class Path implements Disposable, Pool.Poolable
+{
+ private static final Pool<Path> pathPool = new Pool<Path>() {
+ @Override
+ protected Path newObject() {
+ return new Path();
+ }
+ };
+
+ public static Path get(int size)
+ {
+ Path p = pathPool.obtain();
+ p.init(size);
+ return p;
+ }
+
+ public static void clearPool()
+ {
+ pathPool.clear();
+ }
+
+ public int cost;
+ public boolean roadMarch;
+ public ArrayList<Tile> tiles;
+
+ public Path()
+ {
+ this.cost = -1;
+ this.roadMarch = true;
+ this.tiles = null;
+ }
+
+ protected void init(int size)
+ {
+ if (tiles == null)
+ tiles = new ArrayList<Tile>(size);
+ else
+ tiles. ensureCapacity(size);
+ }
+
+ @Override
+ public void reset()
+ {
+ cost = -1;
+ roadMarch = true;
+ tiles.clear();
+ }
+
+ @Override
+ public void dispose()
+ {
+ tiles.clear();
+ pathPool.free(this);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/PathBuilder.java b/core/src/ch/asynk/rustanddust/engine/PathBuilder.java
new file mode 100644
index 0000000..3692a91
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/PathBuilder.java
@@ -0,0 +1,266 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.math.Vector3;
+
+public class PathBuilder implements Disposable
+{
+ private final Board board;
+
+ public Pawn pawn;
+ public Tile from;
+ public Tile to;
+ public int distance;
+ public Orientation orientation;
+ private List<Tile> stack;
+ private List<Tile> ctrlTiles;
+ private List<Path> paths;
+ private List<Path> filteredPaths;
+ private HashSet<Tile> tiles;
+
+ public PathBuilder(Board board, int tSize, int stSize, int ftSize, int vectSize)
+ {
+ this.board = board;
+ this.tiles = new LinkedHashSet<Tile>(tSize);
+ this.stack = new ArrayList<Tile>(stSize);
+ this.ctrlTiles = new ArrayList<Tile>(ftSize);
+ this.paths = new LinkedList<Path>();
+ this.filteredPaths = new LinkedList<Path>();
+ this.to = null;
+ this.pawn = null;
+ this.orientation = Orientation.KEEP;
+ }
+
+ public void init(Pawn pawn, Tile from)
+ {
+ this.pawn = pawn;
+ this.from = from;
+ }
+
+ public void init(Pawn pawn)
+ {
+ init(pawn, pawn.getTile());
+ }
+
+ public void initRotation(Pawn pawn, Orientation o)
+ {
+ init(pawn, pawn.getTile());
+ build(pawn.getTile());
+ orientation = o;
+ }
+
+ public boolean isSet()
+ {
+ return (to != null);
+ }
+
+ @Override
+ public void dispose()
+ {
+ clear();
+ }
+
+ public void clear()
+ {
+ this.to = null;
+ this.distance = -1;
+ this.orientation = Orientation.KEEP;
+ for (Path path : this.paths) path.dispose();
+ this.tiles.clear();
+ this.stack.clear();
+ this.ctrlTiles.clear();
+ this.paths.clear();
+ this.filteredPaths.clear();
+ }
+
+ public int size()
+ {
+ if (ctrlTiles.size() == 0)
+ return paths.size();
+ return filteredPaths.size();
+ }
+
+ public boolean contains(Tile tile)
+ {
+ return tiles.contains(tile);
+ }
+
+ public void enable(int i, boolean enable)
+ {
+ for (Tile tile : tiles)
+ board.enableOverlayOn(tile, i, enable);
+ }
+
+ public int build(Tile to)
+ {
+ clear();
+ this.to = to;
+ // from and to are not part of the path
+ this.distance = board.distance(from, to);
+ if (distance < 2) {
+ Orientation o = Orientation.fromMove(to.col, to.row, from.col, from.row);
+ Path path = Path.get(0);
+ path.roadMarch = to.road(o);
+ path.cost = to.costFrom(pawn, o);
+ paths.add(path);
+ } else {
+ findAllPaths(from, pawn.getMovementPoints(), true);
+ }
+
+ // printToErr("paths", paths);
+ stack.clear();
+ return paths.size();
+ }
+
+ private void findAllPaths(Tile from, int mvtLeft, boolean roadMarch)
+ {
+ Tile moves[] = new Tile[6];
+ board.setAdjacentTiles(from, moves);
+
+ for(int i = 0; i < 6; i++) {
+ Tile next = moves[i];
+ if ((next == null) || next.isOffMap()) continue;
+
+ Orientation o = board.getSide(i);
+ int m = (mvtLeft - next.costFrom(pawn, o));
+ boolean r = roadMarch & next.road(o);
+
+ int l = (m + (r ? pawn.getRoadMarchBonus() : 0));
+
+ if ((board.distance(next, to) <= l)) {
+ if (next == to) {
+ Path path = Path.get(stack.size() + 1);
+ for (Tile t: stack) {
+ path.tiles.add(t);
+ tiles.add(t);
+ }
+ path.roadMarch = r;
+ path.cost = (pawn.getMovementPoints() - m);
+ paths.add(path);
+ } else {
+ stack.add(next);
+ findAllPaths(next, m, r);
+ stack.remove(stack.size() - 1);
+ }
+ }
+ }
+ }
+
+ public int toggleCtrlTile(Tile tile)
+ {
+ if (ctrlTiles.contains(tile))
+ ctrlTiles.remove(tile);
+ else
+ ctrlTiles.add(tile);
+ return filterPaths();
+ }
+
+ private int filterPaths()
+ {
+ int s = ctrlTiles.size();
+
+ tiles.clear();
+ filteredPaths.clear();
+ for (Path path : paths) {
+ int ok = 0;
+ for (Tile filter : ctrlTiles) {
+ if (path.tiles.contains(filter))
+ ok += 1;
+ }
+ if (ok == s) {
+ if (path.tiles.size() == (s + 0)) { // from and to are not part of the path
+ filteredPaths.clear();
+ filteredPaths.add(path);
+ tiles.clear();
+ for (Tile tile : path.tiles) tiles.add(tile);
+ break;
+ } else {
+ filteredPaths.add(path);
+ for (Tile tile : path.tiles) tiles.add(tile);
+ }
+ }
+ }
+
+ // printToErr("filteredPaths", filteredPaths);
+ return filteredPaths.size();
+ }
+
+ public int pathCost(int i)
+ {
+ return paths.get(i).cost;
+ }
+
+ public Move getMove()
+ {
+ if (size() != 1) {
+ System.err.println("ask for only move but they are many");
+ return null;
+ }
+
+ return Move.get(pawn, from, to, orientation, getPath(0));
+ }
+
+ public Move getExitMove()
+ {
+ Move move = getMove();
+ move.type = Move.MoveType.EXIT;
+ return move;
+ }
+
+ public boolean canExit(Orientation o)
+ {
+ List<Path> ps;
+ if (ctrlTiles.size() == 0)
+ ps = paths;
+ else
+ ps = filteredPaths;
+
+ int mvt = pawn.getMovementPoints();
+ int rBonus = pawn.getRoadMarchBonus();
+ boolean road = to.road(o);
+ int cost = to.exitCost();
+
+ for (Path p : ps) {
+ int c = (p.cost + cost);
+ if ((c <= mvt) || (p.roadMarch && road && (c <= (mvt + rBonus))))
+ return true;
+ }
+ return false;
+ }
+
+ public Path getPath(int i)
+ {
+ if (ctrlTiles.size() == 0)
+ return paths.get(i);
+ return filteredPaths.get(i);
+ }
+
+ public void setExit(Orientation o)
+ {
+ orientation = o;
+ Path path = getPath(0);
+ if (from != to) {
+ path.cost += 1;
+ path.tiles.add(to);
+ }
+ to = board.getAdjTileAt(to, o);
+ }
+
+ private void printToErr(String what, List<Path> paths)
+ {
+ System.err.println(what + pawn + " ("+paths.size()+") " + from + " -> " + to);
+ for (Path path : paths) {
+ System.err.println(String.format(" - path (l:%d c:%d r:%b)", path.tiles.size(), path.cost, path.roadMarch));
+ for(Tile tile : path.tiles)
+ System.err.println(" " + tile.toString());
+ }
+ System.err.println();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/PathIterator.java b/core/src/ch/asynk/rustanddust/engine/PathIterator.java
new file mode 100644
index 0000000..53f2f82
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/PathIterator.java
@@ -0,0 +1,73 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.List;
+import java.util.Iterator;
+
+import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.math.Vector3;
+
+public class PathIterator implements Iterator<Vector3>
+{
+ private Pawn pawn;
+ private Tile to;
+ private Orientation o;
+ private Orientation orientation;
+ private Tile tile;
+ private Vector2 pos = new Vector2();
+ private Vector3 v = new Vector3();
+ private int i;
+ private List<Tile> path;
+
+ public PathIterator(Pawn pawn, Tile from, Tile to, Orientation orientation, List<Tile> path)
+ {
+ this.pawn = pawn;
+ this.to = to;
+ this.tile = from;
+ this.orientation = orientation;
+ this.path = path;
+ this.o = pawn.getOrientation();
+ this.v.set(pawn.getPosition().x, pawn.getPosition().y, o.r());
+ this.i = 0;
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ if ((tile == to) && (o == orientation))
+ return false;
+ return true;
+ }
+
+ @Override
+ public Vector3 next()
+ {
+ if (tile == to) {
+ v.z = orientation.r();
+ o = orientation;
+ return v;
+ }
+ Tile nextTile;
+ if (i < path.size())
+ nextTile = path.get(i);
+ else
+ nextTile = to;
+ Orientation nextO = Orientation.fromMove(tile.col, tile.row, nextTile.col, nextTile.row);
+ if (nextO != o) {
+ v.z = nextO.r();
+ o = nextO;
+ return v;
+ }
+ pawn.getPosAt(nextTile, pos);
+ v.x = pos.x;
+ v.y = pos.y;
+ tile = nextTile;
+ i += 1;
+ return v;
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Pawn.java b/core/src/ch/asynk/rustanddust/engine/Pawn.java
new file mode 100644
index 0000000..f95347f
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Pawn.java
@@ -0,0 +1,365 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.Iterator;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.math.Vector3;
+
+import ch.asynk.rustanddust.engine.gfx.Moveable;
+import ch.asynk.rustanddust.engine.gfx.StackedImages;
+import ch.asynk.rustanddust.engine.gfx.animations.MoveToAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.RunnableAnimation;
+import ch.asynk.rustanddust.engine.gfx.animations.AnimationSequence;
+
+public abstract class Pawn implements Moveable, Disposable
+{
+ public interface PawnType
+ {
+ }
+
+ public interface PawnId
+ {
+ }
+
+ private static final float MOVE_TIME = 0.4f;
+
+ private Vector3 position;
+ private Vector3 prevPosition;
+ private Tile tile;
+ private Tile prevTile;
+ protected Faction faction;
+ protected String descr;
+ private Sprite sprite;
+ private StackedImages overlays;
+ protected Attack attack;
+ protected Move move;
+
+ public abstract int getMovementPoints();
+ public abstract int getRoadMarchBonus();
+ public abstract int getAngleOfAttack();
+ public abstract int getFlankSides();
+ public abstract int getEngagementRangeFrom(Tile tile);
+ public abstract int getDefense(Tile tile);
+
+ public abstract boolean isUnit();
+ public abstract boolean isA(PawnId id);
+ public abstract boolean isA(PawnType type);
+ public abstract boolean isHq();
+ public abstract boolean isHqOf(Pawn other);
+ public abstract boolean isHardTarget();
+
+ public abstract boolean canMove();
+ public abstract boolean canRotate();
+ public abstract boolean canEngage();
+ public abstract boolean canEngage(Pawn other);
+ public abstract boolean canAssistEngagementWithoutLos();
+
+ public abstract void move();
+ public abstract void engage();
+
+ public abstract void revertLastMove();
+
+ protected Pawn()
+ {
+ this.tile = null;
+ this.prevTile = null;
+ this.position = new Vector3(0f, 0f, 0f);
+ this.prevPosition = new Vector3(0f, 0f, 0f);
+ this.attack = new Attack(this);
+ }
+
+ public Pawn(Faction faction, String name, TextureAtlas pawns, TextureAtlas overlays)
+ {
+ this();
+ this.faction = faction;
+ this.descr = descr;
+ this.sprite = new Sprite(pawns.findRegion(name));
+ this.overlays = new StackedImages(overlays);
+ }
+
+ @Override
+ public String toString()
+ {
+ return descr;
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ @Override
+ public Faction getFaction()
+ {
+ return faction;
+ }
+
+ public void reset()
+ {
+ move = null;
+ attack.reset();
+ }
+
+ public void move(Move move)
+ {
+ switch(move.type)
+ {
+ case REGULAR:
+ if ((this.move != null) && (!this.move.isEnter()))
+ throw new RuntimeException("try to override an existing move instance");
+ break;
+ case ENTER:
+ if (this.move != null)
+ throw new RuntimeException("try to override an existing move instance");
+ break;
+ case SET:
+ break;
+ default:
+ throw new RuntimeException("unsupported MoveType");
+ }
+
+ this.move = move;
+ move();
+ }
+
+ public void setAttack(Pawn target, int distance)
+ {
+ attack.reset();
+ attack.target = target;
+ attack.distance = distance;
+ }
+
+ public boolean justEntered()
+ {
+ return ((move != null) && move.isEnter());
+ }
+
+ public boolean is(Faction faction)
+ {
+ return (this.faction == faction);
+ }
+
+ public boolean isEnemy(Faction other)
+ {
+ return faction.isEnemy(other);
+ }
+
+ public boolean isEnemy(Pawn other)
+ {
+ return faction.isEnemy(other.faction);
+ }
+
+ public boolean isFlankAttack()
+ {
+ return (attack.isClear && attack.isFlank);
+ }
+
+ public int attackDistance()
+ {
+ return attack.distance;
+ }
+
+ public Tile getTile()
+ {
+ return tile;
+ }
+
+ public Tile getPreviousTile()
+ {
+ return prevTile;
+ }
+
+ public Vector3 getPosition()
+ {
+ return position;
+ }
+
+ public Vector3 getPreviousPosition()
+ {
+ return prevPosition;
+ }
+
+ private void revertPosition()
+ {
+ this.tile = this.prevTile;
+ this.prevTile = null;
+ position.set(prevPosition);
+ prevPosition.set(0f, 0f, 0f);
+ setPosition(position.x, position.y, position.z);
+ }
+
+ public float getCenterX()
+ {
+ return (getX() + (getWidth() / 2f));
+ }
+
+ public float getCenterY()
+ {
+ return (getY() + (getHeight() / 2f));
+ }
+
+ public Vector2 getPosAt(Tile tile, Vector2 pos)
+ {
+ float x = (tile.getX() - (getWidth() / 2f));
+ float y = (tile.getY() - (getHeight() / 2f));
+ if (pos == null)
+ return new Vector2(x, y);
+ else
+ pos.set(x, y);
+ return pos;
+ }
+
+ public void setOnTile(Tile tile, float z)
+ {
+ this.prevTile = this.tile;
+ this.tile = tile;
+ float x = (tile.getX() - (getWidth() / 2f));
+ float y = (tile.getY() - (getHeight() / 2f));
+ setPosition(x, y, z);
+ }
+
+ @Override
+ public void setAlpha(float alpha)
+ {
+ sprite.setAlpha(alpha);
+ overlays.setAlpha(alpha);
+ }
+
+ @Override
+ public float getX()
+ {
+ return sprite.getX();
+ }
+
+ @Override
+ public float getY()
+ {
+ return sprite.getY();
+ }
+
+ @Override
+ public float getWidth()
+ {
+ return sprite.getWidth();
+ }
+
+ @Override
+ public float getHeight()
+ {
+ return sprite.getHeight();
+ }
+
+ @Override
+ public float getRotation()
+ {
+ return sprite.getRotation();
+ }
+
+ public Orientation getOrientation()
+ {
+ return Orientation.fromRotation(getRotation());
+ }
+
+ public void translate(float dx, float dy)
+ {
+ setPosition((getX() + dx), (getY() + dy));
+ }
+
+ public void centerOn(float x, float y)
+ {
+ setPosition((x - (getWidth() / 2f)), (y - (getHeight() / 2f)));
+ }
+
+ @Override
+ public void setPosition(float x, float y)
+ {
+ position.set(x, y, 0f);
+ sprite.setPosition(x, y);
+ float cx = x + (getWidth() / 2f);
+ float cy = y + (getHeight() / 2f);
+ overlays.centerOn(cx, cy);
+ }
+
+ public void setRotation(float z)
+ {
+ position.z = z;
+ sprite.setRotation(z);
+ overlays.setRotation(z);
+ }
+
+ @Override
+ public void setPosition(float x, float y, float z)
+ {
+ setPosition(x, y);
+ setRotation(z);
+ }
+
+ public boolean hasOverlayEnabled()
+ {
+ return overlays.isEnabled();
+ }
+
+ public boolean enableOverlay(int i, boolean enable)
+ {
+ overlays.enable(i, enable);
+ if (enable) return true;
+ return hasOverlayEnabled();
+ }
+
+ public AnimationSequence getRotateAnimation(float z, int size)
+ {
+ prevPosition.set(position);
+ AnimationSequence seq = AnimationSequence.get(1 + size);
+ seq.addAnimation(MoveToAnimation.get(this, position.x, position.y, z, MOVE_TIME));
+
+ return seq;
+ }
+
+ public AnimationSequence getMoveAnimation(Iterator<Vector3> vectors, int size, MoveToAnimation.MoveToAnimationCb cb)
+ {
+ prevPosition.set(position);
+ AnimationSequence seq = AnimationSequence.get(size);
+ while (vectors.hasNext())
+ seq.addAnimation(MoveToAnimation.get(this, vectors.next(), MOVE_TIME, cb));
+
+ return seq;
+ }
+
+ public AnimationSequence getRevertLastMoveAnimation(int size)
+ {
+ AnimationSequence seq = AnimationSequence.get(2 + size);
+ seq.addAnimation(MoveToAnimation.get(this, prevPosition, MOVE_TIME));
+ seq.addAnimation(RunnableAnimation.get(this, new Runnable() {
+ @Override
+ public void run() {
+ revertPosition();
+ }
+ }));
+
+ return seq;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ sprite.draw(batch);
+ overlays.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ float w = sprite.getWidth();
+ float h = sprite.getHeight();
+ debugShapes.rect(sprite.getX(), sprite.getY(), (w / 2f), (h / 2f), w, h, sprite.getScaleX(), sprite.getScaleY(), sprite.getRotation());
+ overlays.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/SearchBoard.java b/core/src/ch/asynk/rustanddust/engine/SearchBoard.java
new file mode 100644
index 0000000..1d8ed88
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/SearchBoard.java
@@ -0,0 +1,545 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.Collection;
+
+public class SearchBoard
+{
+ public class Node
+ {
+ public int col;
+ public int row;
+ public int search;
+ public int remaining;
+ public Node parent;
+ public boolean roadMarch;
+
+ public Node(int col, int row)
+ {
+ this.col = col;
+ this.row = row;
+ }
+
+ @Override
+ public String toString()
+ {
+ return col + ";" + row;
+ }
+ }
+
+ private int cols;
+ private int rows;
+ private Board board;
+ private int searchCount;
+ private Node nodes[];
+
+ private ArrayDeque<Node> stack;
+ private LinkedList<Node> queue;
+ private ArrayDeque<Node> roadMarch;
+ private List<Node> los;
+
+ public SearchBoard(Board board, int cols, int rows)
+ {
+ this.cols = cols;
+ this.rows = rows;
+ this.board = board;
+ this.searchCount = 0;
+
+ this.nodes = new Node[cols * rows];
+ for (int j = 0; j < rows; j++) {
+ int dx = ((j + 1) / 2);
+ for (int i = 0; i < cols; i++)
+ nodes[i + (j * cols)] = new Node((i + dx), j);
+ }
+
+ this.queue = new LinkedList<Node>();
+ this.stack = new ArrayDeque<Node>(20);
+ this.roadMarch = new ArrayDeque<Node>(5);
+ this.los = new ArrayList<Node>(10);
+ }
+
+ private boolean inMap(int col, int row)
+ {
+ if ((row < 0) || (row >= rows))
+ return false;
+
+ int colOffset = ((row + 1) / 2);
+ if ((col < colOffset) || ((col - colOffset) >= cols))
+ return false;
+
+ return true;
+ }
+
+ private Tile getTile(Node node)
+ {
+ return board.getTile(node.col, node.row);
+ }
+
+ private Node getNode(Tile tile)
+ {
+ return getNode(tile.col, tile.row);
+ }
+
+ protected Node getNode(int col, int row)
+ {
+ int colOffset = ((row + 1) / 2);
+ if ((col < colOffset) || (row < 0) || (row >= rows) || ((col - colOffset) >= cols))
+ return null;
+
+ return nodes[((col - colOffset)) + (row * cols)];
+ }
+
+ public int distance(Node from, Node to)
+ {
+ return board.distance(from.col, from.row, to.col, to.row);
+ }
+
+ public void adjacentMoves(Node src, Node a[])
+ {
+ // move to enter dst by sides[i]
+ a[0] = getNode((src.col - 1), src.row);
+ a[1] = getNode(src.col, (src.row + 1));
+ a[2] = getNode((src.col + 1), (src.row + 1));
+ a[3] = getNode((src.col + 1), src.row);
+ a[4] = getNode(src.col, (src.row - 1));
+ a[5] = getNode((src.col - 1), (src.row - 1));
+ }
+
+ public int possibleMovesFrom(Pawn pawn, Collection<Tile> moves)
+ {
+ moves.clear();
+ searchCount += 1;
+
+ Node adjacents[] = new Node[6];
+
+ Node from = getNode(pawn.getTile());
+ from.parent = null;
+ from.search = searchCount;
+ from.remaining = pawn.getMovementPoints();
+ from.roadMarch = true;
+
+ if (from.remaining <= 0)
+ return moves.size();
+
+ int roadMarchBonus = pawn.getRoadMarchBonus();
+ boolean first = true;
+
+ stack.push(from);
+
+ while (stack.size() != 0) {
+ Node src = stack.pop();
+
+ if (src.remaining < 0)
+ continue;
+ if (src.remaining == 0) {
+ if (src.roadMarch)
+ roadMarch.push(src);
+ continue;
+ }
+
+ adjacentMoves(src, adjacents);
+
+ for(int i = 0; i < 6; i++) {
+ Node dst = adjacents[i];
+ if (dst != null) {
+
+ Tile t = getTile(dst);
+ int cost = t.costFrom(pawn, board.getSide(i));
+ boolean mayMoveOne = first && t.atLeastOneMove(pawn);
+ int r = src.remaining - cost;
+ boolean roadMarch = (src.roadMarch && t.road(board.getSide(i)));
+
+ if (dst.search == searchCount) {
+ if ((r >= 0) && ((r > dst.remaining) || (roadMarch && ((r + roadMarchBonus) >= dst.remaining)))) {
+ dst.remaining = r;
+ dst.parent = src;
+ dst.roadMarch = roadMarch;
+ stack.push(dst);
+ moves.add(getTile(dst));
+ }
+ } else {
+ dst.search = searchCount;
+ if ((r >= 0) || mayMoveOne) {
+ dst.parent = src;
+ dst.remaining = r;
+ dst.roadMarch = roadMarch;
+ stack.push(dst);
+ moves.add(getTile(dst));
+ } else {
+ dst.parent = null;
+ dst.remaining = -1;
+ }
+ }
+ }
+ }
+ first = false;
+ }
+
+ for (Node n : roadMarch) n.remaining = roadMarchBonus;
+ while(roadMarch.size() != 0) {
+ Node src = roadMarch.pop();
+
+ adjacentMoves(src, adjacents);
+
+ for(int i = 0; i < 6; i++) {
+ Node dst = adjacents[i];
+ if (dst != null) {
+
+ Tile t = getTile(dst);
+ if (!t.road(board.getSide(i)))
+ continue;
+ int cost = t.costFrom(pawn, board.getSide(i));
+ int r = src.remaining - cost;
+
+ if (dst.search == searchCount) {
+ if ((r >= 0) && (r > dst.remaining)) {
+ dst.remaining = r;
+ dst.parent = src;
+ dst.roadMarch = true;
+ roadMarch.push(dst);
+ moves.add(getTile(dst));
+ }
+ } else {
+ dst.search = searchCount;
+ if (r >= 0) {
+ dst.parent = src;
+ dst.remaining = r;
+ dst.roadMarch = true;
+ roadMarch.push(dst);
+ moves.add(getTile(dst));
+ } else {
+ dst.parent = null;
+ dst.remaining = -1;
+ }
+ }
+ }
+ }
+ }
+
+ return moves.size();
+ }
+
+ private void adjacentTargets(Node src, int angle, Node a[])
+ {
+ // move in allowed directions
+ if (Orientation.NORTH.isInSides(angle))
+ a[0] = getNode((src.col + 1), src.row);
+ else
+ a[0] = null;
+
+ if (Orientation.NORTH_EAST.isInSides(angle))
+ a[1] = getNode(src.col, (src.row - 1));
+ else
+ a[1] = null;
+
+ if (Orientation.SOUTH_EAST.isInSides(angle))
+ a[2] = getNode((src.col - 1), (src.row - 1));
+ else
+ a[2] = null;
+
+ if (Orientation.SOUTH.isInSides(angle))
+ a[3] = getNode((src.col - 1), src.row);
+ else
+ a[3] = null;
+
+ if (Orientation.SOUTH_WEST.isInSides(angle))
+ a[4] = getNode(src.col, (src.row + 1));
+ else
+ a[4] = null;
+
+ if (Orientation.NORTH_WEST.isInSides(angle))
+ a[5] = getNode((src.col + 1), (src.row + 1));
+ else
+ a[5] = null;
+ }
+
+ public int possibleTargetsFrom(Pawn pawn, Collection<Pawn> targets)
+ {
+ targets.clear();
+ searchCount += 1;
+
+ Node adjacents[] = new Node[6];
+
+ int range = pawn.getEngagementRangeFrom(pawn.getTile());
+ int angle = pawn.getAngleOfAttack();
+ int extendedAngle = pawn.getOrientation().opposite().allBut();
+
+ Node from = getNode(pawn.getTile());
+ from.search = searchCount;
+ from.remaining = range;
+
+ if (range <= 0)
+ return targets.size();
+
+ queue.add(from);
+
+ boolean first = true;
+ while (queue.size() != 0) {
+ Node src = queue.remove();
+
+ if (src.remaining <= 0)
+ continue;
+
+ if (!first && (((range - src.remaining) % 2) == 0))
+ adjacentTargets(src, extendedAngle, adjacents);
+ else
+ adjacentTargets(src, angle, adjacents);
+
+ first = false;
+ int rangeLeft = src.remaining - 1;
+
+ for(int i = 0; i < 6; i++) {
+ Node dst = adjacents[i];
+ if (dst != null) {
+ if (dst.search == searchCount) {
+ if ((rangeLeft > dst.remaining))
+ dst.remaining = rangeLeft;
+ } else {
+ dst.search = searchCount;
+ dst.remaining = rangeLeft;
+ queue.add(dst);
+ Tile t = getTile(dst);
+ if (hasClearLineOfSight(from, dst, angle)) {
+ Iterator<Pawn> it = t.iterator();
+ while (it.hasNext()) {
+ Pawn target = it.next();
+ if (pawn.canEngage(target))
+ targets.add(target);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return targets.size();
+ }
+
+ public boolean canAttack(Pawn pawn, Pawn target, boolean clearVisibility)
+ {
+ Node from = getNode(pawn.getTile());
+ Node to = getNode(target.getTile());
+
+ pawn.setAttack(target, distance(from, to));
+
+ if (pawn.attack.distance > pawn.getEngagementRangeFrom(pawn.getTile()))
+ return false;
+
+ List<Node> los = lineOfSight(from.col, from.row, to.col, to.row, clearVisibility);
+ Node last = los.get(los.size() -1);
+ if (last != to)
+ return false;
+
+ if (!validatePathAngle(pawn.getAngleOfAttack(), los))
+ return false;
+
+ pawn.attack.isClear = isClearAttack(getTile(from), los);
+ pawn.attack.isFlank = isFlankAttack(target.getFlankSides(), los);
+
+ return true;
+ }
+
+ private boolean hasClearLineOfSight(Node from, Node to, int angleOfAttack)
+ {
+ List<Node> los = lineOfSight(from.col, from.row, to.col, to.row, true);
+ Node last = los.get(los.size() -1);
+ if ((last.col != to.col) || (last.row != to.row))
+ return false;
+ return validatePathAngle(angleOfAttack, los);
+ }
+
+ private boolean isFlankAttack(int angle, List<Node> los)
+ {
+ Node from = los.get(los.size() - 2);
+ Node to = los.get(los.size() - 1);
+ Orientation o = Orientation.fromMove(to.col, to.row, from.col, from.row);
+ return o.isInSides(angle);
+ }
+
+ private boolean isClearAttack(Tile from, List<Node> los)
+ {
+ int n = los.size() - 1;
+ for (int i = 1; i < n; i++) {
+ if (getTile(los.get(i)).blockLineOfSightFrom(from))
+ return false;
+ }
+ return true;
+ }
+
+ private boolean validatePathAngle(int angle, List<Node> los)
+ {
+ int forth = 0;
+ Node prev = null;
+ for (Node next : los) {
+ if (prev != null) {
+ Orientation o = Orientation.fromMove(prev.col, prev.row, next.col, next.row);
+ if (!o.isInSides(angle)) {
+ forth -= 1;
+ if (forth < 0)
+ return false;
+ }
+ forth += 1;
+ }
+ prev = next;
+ }
+
+ return true;
+ }
+
+ public List<Node> lineOfSight(int x0, int y0, int x1, int y1, boolean clearVisibility)
+ {
+ los.clear();
+ Tile from = board.getTile(x0, y0);
+
+ // orthogonal axis
+ int ox0 = x0 - ((y0 +1) / 2);
+ int ox1 = x1 - ((y1 +1) / 2);
+
+ int dy = y1 - y0;
+ int dx = ox1 - ox0;
+
+ int xs = 1;
+ int ys = 1;
+ if (dx < 0) xs = -1;
+ if (dy < 0) ys = -1;
+ boolean sig = !(((dx < 0) && (dy >= 0)) || ((dx >= 0) && (dy < 0)));
+
+ dy = Math.abs(dy);
+ dx = Math.abs(2 * dx);
+ if ((dy % 2) == 1) {
+ if ((y0 % 2) == 0) dx += xs;
+ else {
+ dx -= xs;
+ Math.abs(dx);
+ }
+ }
+
+ if (dx == 0)
+ return verticalLineOfSight(x0, y0, x1, y1, clearVisibility);
+ if (dx == (3 * dy))
+ return diagonalLineOfSight(x0, y0, x1, y1, clearVisibility);
+
+ int dx3 = 3 * dx;
+ int dy3 = 3 * dy;
+
+ int x = x0;
+ int y = y0;
+ int e = -2 * dx;
+
+ boolean flat = (dx > (3 * dy));
+ boolean diag = (dx == (3 * dy));
+
+ los.add(getNode(x, y));
+ while((x != x1) || (y != y1)) {
+ if (e > 0) {
+ e -= (dy3 + dx3);
+ y += ys;
+ if (!sig)
+ x -= xs;
+ } else {
+ e += dy3;
+ if ((e > -dx) || (!flat && (e == -dx))) {
+ e -= dx3;
+ y += ys;
+ if (sig)
+ x += xs;
+ } else if ((e < -dx3) || (diag && (e == -dx3))) {
+ e += dx3;
+ y -= ys;
+ if (!sig)
+ x += xs;
+ } else {
+ e += dy3;
+ x += xs;
+ }
+ }
+ los.add(getNode(x, y));
+ if(clearVisibility && board.getTile(x, y).blockLineOfSightFrom(from)) return los;
+ }
+
+ return los;
+ }
+
+ private List<Node> verticalLineOfSight(int x0, int y0, int x1, int y1, boolean clearVisibility)
+ {
+ Tile from = board.getTile(x0, y0);
+
+ int d = ( (y1 > y0) ? 1 : -1);
+ int x = x0;
+ int y = y0;
+
+ Tile t = null;
+ los.add(getNode(x, y));
+ while((x != x1) || (y != y1)) {
+ boolean ok = false;
+
+ y += d;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ if (!clearVisibility || !t.blockLineOfSightFrom(from))
+ ok = true;
+
+ x += d;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ if (!clearVisibility || !t.blockLineOfSightFrom(from))
+ ok = true;
+
+ if (!ok)
+ return los;
+
+ y += d;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ }
+
+ return los;
+ }
+
+ private List<Node> diagonalLineOfSight(int x0, int y0, int x1, int y1, boolean clearVisibility)
+ {
+ Tile from = board.getTile(x0, y0);
+
+ int dy = ( (y1 > y0) ? 1 : -1);
+ int dx = ( (x1 > x0) ? 1 : -1);
+ boolean sig = !(((dx < 0) && (dy >= 0)) || ((dx >= 0) && (dy < 0)));
+
+ int x = x0;
+ int y = y0;
+
+ Tile t = null;
+ los.add(getNode(x, y));
+ while((x != x1) || (y != y1)) {
+ boolean ok = false;
+
+ x += dx;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ if (!clearVisibility || !t.blockLineOfSightFrom(from))
+ ok = true;
+
+ y += dy;
+ if (!sig)
+ x -= dx;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ if (!clearVisibility || !t.blockLineOfSightFrom(from))
+ ok = true;
+
+ if (!ok)
+ return los;
+
+ x += dx;
+ t = board.getTile(x, y);
+ if (!t.isOffMap()) los.add(getNode(x, y));
+ }
+
+ return los;
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/SelectedTile.java b/core/src/ch/asynk/rustanddust/engine/SelectedTile.java
new file mode 100644
index 0000000..0e1d8ac
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/SelectedTile.java
@@ -0,0 +1,80 @@
+package ch.asynk.rustanddust.engine;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+import ch.asynk.rustanddust.engine.gfx.animations.Sprites;
+
+public class SelectedTile implements Disposable, Drawable, Animation
+{
+ private Sprites sprites;
+ public Tile tile;
+ public boolean visible;
+ public float x;
+ public float y;
+ private float elapsed;
+ private int frame;
+ private float[] seq;
+
+ public SelectedTile(Texture texture, float[] seq)
+ {
+ this.sprites = new Sprites(texture, seq.length, 1);
+ this.visible = false;
+ this.tile = null;
+ this.elapsed = 0f;
+ this.seq = seq;
+ }
+
+ public void hide()
+ {
+ tile = null;
+ visible = false;
+ }
+
+ public void set(Tile tile)
+ {
+ this.visible = true;
+ this.tile = tile;
+ this.frame = 0;
+ this.elapsed = 0f;
+ this.x = (tile.getX() - (sprites.width / 2f));
+ this.y = (tile.getY() - (sprites.height / 2f));
+ }
+
+ public void dispose()
+ {
+ sprites.dispose();
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if (visible) {
+ elapsed += delta;
+ if (elapsed > seq[frame]) {
+ frame = ((frame + 1) % sprites.frames.length);
+ elapsed = 0f;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (visible)
+ batch.draw(sprites.frames[frame], x, y);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ if (visible)
+ debugShapes.rect(x, y, sprites.width, sprites.height);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/Tile.java b/core/src/ch/asynk/rustanddust/engine/Tile.java
new file mode 100644
index 0000000..f44e763
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/Tile.java
@@ -0,0 +1,175 @@
+package ch.asynk.rustanddust.engine;
+
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayDeque;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.math.Vector2;
+
+import ch.asynk.rustanddust.engine.Board;
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.StackedImages;
+
+public abstract class Tile implements Drawable, Disposable, Iterable<Pawn>
+{
+ public interface TileTerrain
+ {
+ }
+
+ protected int col;
+ protected int row;
+ protected float x;
+ protected float y;
+ private StackedImages overlays;
+ protected ArrayDeque<Pawn> stack;
+
+ public abstract int defense();
+ public abstract int exitCost();
+ public abstract int costFrom(Pawn pawn, Orientation side);
+
+ public abstract boolean isOffMap();
+ public abstract boolean isA(TileTerrain terrain);
+ public abstract boolean road(Orientation side);
+ public abstract boolean atLeastOneMove(Pawn pawn);
+ public abstract boolean blockLineOfSightFrom(Tile tile);
+
+ protected Tile(int col, int row)
+ {
+ this.col = col;
+ this.row = row;
+ }
+
+ public Tile(float x, float y, int col, int row, TextureAtlas atlas)
+ {
+ this.stack = new ArrayDeque<Pawn>();
+ this.x = x;
+ this.y = y;
+ this.col = col;
+ this.row = row;
+ this.overlays = new StackedImages(atlas);
+ this.overlays.centerOn(x, y);
+ }
+
+ public float getX() { return x; }
+ public float getY() { return y; }
+ public int getCol() { return col; }
+ public int getRow() { return row; }
+
+ @Override
+ public String toString()
+ {
+ return String.format("(%d;%d) %s", col, row, (isOffMap() ? "x" : ""));
+ }
+
+ public String toShort()
+ {
+ return String.format("(%d;%d)", col, row);
+ }
+
+ @Override
+ public void dispose()
+ {
+ stack.clear();
+ overlays.dispose();
+ }
+
+ public boolean isEmpty()
+ {
+ return stack.isEmpty();
+ }
+
+ public Iterator<Pawn> iterator()
+ {
+ return stack.iterator();
+ }
+
+ public int push(Pawn pawn)
+ {
+ if (!stack.contains(pawn))
+ stack.push(pawn);
+ return stack.size();
+ }
+
+ public int remove(Pawn pawn)
+ {
+ stack.remove(pawn);
+ return stack.size();
+ }
+
+ private Pawn getTopPawn()
+ {
+ if (isEmpty()) return null;
+ return stack.getFirst();
+ }
+
+ public boolean hasUnits()
+ {
+ if (isEmpty()) return false;
+ Iterator<Pawn> itr = iterator();
+ while(itr.hasNext()) {
+ if (itr.next().isUnit())
+ return true;
+ }
+ return false;
+ }
+
+ public boolean mustBeDrawn()
+ {
+ if (!isEmpty()) return true;
+ return hasOverlayEnabled();
+ }
+
+ public boolean disableOverlays()
+ {
+ overlays.disableAll();
+ return !isEmpty();
+ }
+
+ public boolean hasOverlayEnabled()
+ {
+ return overlays.isEnabled();
+ }
+
+ public boolean isOverlayEnabled(int i)
+ {
+ return overlays.isEnabled(i);
+ }
+
+ public boolean enableOverlay(int i, boolean enable)
+ {
+ overlays.enable(i, enable);
+ if (enable) return true;
+ return mustBeDrawn();
+ }
+
+ public boolean enableOverlay(int i, boolean enable, float r)
+ {
+ overlays.enable(i, enable);
+ overlays.rotate(i, r);
+ if (enable) return true;
+ return mustBeDrawn();
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ overlays.draw(batch);
+ Pawn pawn = getTopPawn();
+ if (pawn != null)
+ pawn.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ overlays.drawDebug(debugShapes);
+ Pawn pawn = getTopPawn();
+ if (pawn != null)
+ pawn.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/Animation.java b/core/src/ch/asynk/rustanddust/engine/gfx/Animation.java
new file mode 100644
index 0000000..eb973de
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/Animation.java
@@ -0,0 +1,8 @@
+package ch.asynk.rustanddust.engine.gfx;
+
+import com.badlogic.gdx.utils.Disposable;
+
+public interface Animation extends Disposable, Drawable
+{
+ public boolean animate(float delta);
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java b/core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java
new file mode 100644
index 0000000..d405faa
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java
@@ -0,0 +1,10 @@
+package ch.asynk.rustanddust.engine.gfx;
+
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public interface Drawable
+{
+ public void draw(Batch batch);
+ public void drawDebug(ShapeRenderer debugShapes);
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java b/core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java
new file mode 100644
index 0000000..e8790ab
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java
@@ -0,0 +1,16 @@
+package ch.asynk.rustanddust.engine.gfx;
+
+import ch.asynk.rustanddust.engine.Faction;
+
+public interface Moveable extends Drawable
+{
+ public void setAlpha(float alpha);
+ public float getX();
+ public float getY();
+ public float getWidth();
+ public float getHeight();
+ public float getRotation();
+ public void setPosition(float x, float y);
+ public void setPosition(float x, float y, float r);
+ public Faction getFaction();
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java b/core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java
new file mode 100644
index 0000000..6d4fd1f
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java
@@ -0,0 +1,99 @@
+package ch.asynk.rustanddust.engine.gfx;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.g2d.Sprite;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.utils.Array;
+
+public class StackedImages implements Drawable, Disposable
+{
+ private boolean enabled[];
+ private Array<Sprite> sprites;
+
+ public StackedImages(TextureAtlas atlas)
+ {
+ this.sprites = atlas.createSprites();
+ this.enabled = new boolean[sprites.size];
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ public void disableAll()
+ {
+ for (int i = 0; i < sprites.size; i++)
+ enabled[i] = false;
+ }
+
+ public void enable(int i, boolean enable)
+ {
+ enabled[i] = enable;
+ }
+
+ public boolean isEnabled(int i)
+ {
+ return enabled[i];
+ }
+
+ public boolean isEnabled()
+ {
+ for (int i = 0; i < sprites.size; i++)
+ if (enabled[i]) return true;
+ return false;
+ }
+
+ public void setAlpha(float alpha)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++)
+ sprites.get(i).setAlpha(alpha);
+ }
+
+ public void rotate(int i, float r)
+ {
+ sprites.get(i).setRotation(r);
+ }
+
+ public void setRotation(float r)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++)
+ sprites.get(i).setRotation(r);
+ }
+
+ public void translate(float dx, float dy)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++)
+ sprites.get(i).translate(dx, dy);
+ }
+
+ public void centerOn(float cx, float cy)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++) {
+ float x = (cx - (sprites.get(i).getWidth() / 2f));
+ float y = (cy - (sprites.get(i).getHeight() / 2f));
+ sprites.get(i).setPosition(x, y);
+ }
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ for (int i = 0, n = sprites.size; i < n; i++) {
+ if (enabled[i])
+ sprites.get(i).draw(batch);
+ }
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer shapes)
+ {
+ Sprite sprite = sprites.get(0);
+ float w = sprite.getWidth();
+ float h = sprite.getHeight();
+ shapes.rect(sprite.getX(), sprite.getY(), (w / 2f), (h / 2f), w, h, sprite.getScaleX(), sprite.getScaleY(), sprite.getRotation());
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java
new file mode 100644
index 0000000..fdd1e80
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java
@@ -0,0 +1,76 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.ArrayList;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class AnimationSequence implements Animation, Pool.Poolable
+{
+ private ArrayList<Animation> animations;
+
+ private static final Pool<AnimationSequence> animationSequencePool = new Pool<AnimationSequence>() {
+ @Override
+ protected AnimationSequence newObject() {
+ return new AnimationSequence();
+ }
+ };
+
+ public static AnimationSequence get(int capacity)
+ {
+ AnimationSequence seq = animationSequencePool.obtain();
+ if (seq.animations == null)
+ seq.animations = new ArrayList<Animation>(capacity);
+ else
+ seq.animations.ensureCapacity(capacity);
+
+ return seq;
+ }
+
+ @Override
+ public void reset()
+ {
+ for (int i = 0, n = animations.size(); i < n; i++)
+ animations.get(i).dispose();
+ animations.clear();
+ }
+
+ @Override
+ public void dispose()
+ {
+ animationSequencePool.free(this);
+ }
+
+ public void addAnimation(Animation animation)
+ {
+ animations.add(animation);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if(animations.isEmpty()) return true;
+
+ Animation animation = animations.get(0);
+ if (animation.animate(delta)) {
+ animations.remove(0);
+ }
+
+ return (animations.isEmpty());
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ animations.get(0).draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ animations.get(0).drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java
new file mode 100644
index 0000000..d1fc1bb
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java
@@ -0,0 +1,62 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Moveable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class DestroyAnimation implements Disposable, Animation
+{
+ private static final float DELAY = 1.5f;
+ private static final float DURATION = 1.5f;
+
+ private Moveable moveable;
+ private float x;
+ private float y;
+ private int alphaP;
+ private float elapsed;
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ public void set(float duration, Moveable moveable)
+ {
+ this.moveable = moveable;
+ this.alphaP = 0;
+ this.elapsed = 0f;
+ this.x = (moveable.getX() + (moveable.getWidth() / 2f));
+ this.y = (moveable.getY() + (moveable.getHeight() / 2f));
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+ if (elapsed < DELAY)
+ return false;
+
+ int a = (int) (((elapsed - DELAY) / DURATION) * 10);
+ if (a != alphaP) {
+ alphaP = a;
+ moveable.setAlpha(1f - (alphaP / 10f));
+ }
+
+ return (elapsed >= (DELAY + DURATION));
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ moveable.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ moveable.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java
new file mode 100644
index 0000000..1a0a3bb
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java
@@ -0,0 +1,141 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class DiceAnimation implements Animation, Drawable
+{
+ private static final float DURATION = 0.7f;
+ private static final float DURATION_SCATTERING = 0.5f;
+ private static final int DICE_DIMENSION = 24;
+
+ private static Random random = new Random();
+ private static Sprites dice;
+ private static Sound sound;
+ private static double sndId;
+ private static float volume;
+ private static int[][] rolls = new int[][]{
+ { 25, 40, 55, 70, 85, 100, 115, 99, 83, 67, 51, 36, 37, 52, 67, 66, 65, 64 },
+ { 58, 74, 59, 60, 45, 62, 78, 94, 109, 108, 123, 106, 89, 71, 70, 69, 68 },
+ { 106, 121, 120, 103, 86, 70, 54, 37, 20, 19, 18, 34, 50, 51, 52, 69, 86, 103, 119, 128 },
+ { 95, 79, 93, 92, 91, 90, 104, 103, 102, 85, 84, 67, 66, 65, 49, 32, 16, 0 },
+ { 22, 39, 56, 73, 90, 107, 124, 128, 113, 98, 83, 68, 53, 38, 23, 0, 25, 42, 59, 76 },
+ { 79, 78, 61, 76, 91, 106, 121, 120, 119, 102, 101, 84, 68, 52, 37, 38, 39, 40, 41, 58, 75, 74, 73, 72 },
+ };
+
+ private float x;
+ private float y;
+ private int frame;
+ private int[] roll;
+ private float elapsed;
+ private float duration;
+ // public boolean stop;
+
+ public static void init(Texture texture, int cols, int rows, Sound s)
+ {
+ dice = new Sprites(texture, cols, rows);
+ sound = s;
+ sndId = -1;
+ }
+
+ public static void initSound(float v)
+ {
+ sndId = -1;
+ volume = v;
+ }
+
+ public static void free()
+ {
+ sound.dispose();
+ dice.dispose();
+ }
+
+ public void translate(float dx, float dy)
+ {
+ x += dx;
+ y += dy;
+ }
+
+ public float getX()
+ {
+ return x;
+ }
+
+ public float getY()
+ {
+ return y;
+ }
+
+ public int getWidth()
+ {
+ return DICE_DIMENSION;
+ }
+
+ public int getHeight()
+ {
+ return DICE_DIMENSION;
+ }
+
+ public void setPosition(float x, float y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+
+ public void set(int result)
+ {
+ this.frame = 0;
+ this.elapsed = 0f;
+ this.roll = rolls[result - 1];
+ this.duration = DURATION + (DURATION_SCATTERING * random.nextFloat());
+ // this.stop = false;
+ }
+
+ public boolean isDone()
+ {
+ return (elapsed >= duration);
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ // if (stop)
+ // return true;
+ elapsed += delta;
+ if (elapsed < duration) {
+ int idx = (int) (roll.length * elapsed / duration);
+ if (idx >= roll.length)
+ idx = (roll.length -1);
+ frame = roll[idx];
+ }
+ if (sndId == -1)
+ sndId = sound.play(volume);
+
+ return false;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ batch.draw(dice.frames[frame], x, y, DICE_DIMENSION, DICE_DIMENSION);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ debugShapes.rect(x, y, dice.frames[frame].getRegionWidth(), dice.frames[frame].getRegionHeight());
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java
new file mode 100644
index 0000000..5835525
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java
@@ -0,0 +1,87 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.Texture;
+
+public class FireAnimation
+{
+ public static Random random = new Random();
+
+ public static Sprites infantryFire;
+ public static Sprites tankFire;
+ public static Sprites explosion;
+
+ public static Sound infantryFireSnd;
+ public static Sound tankFireSnd;
+ public static Sound tankFireSndLong;
+ public static Sound explosionSnd;
+ public static Sound explosionSndLong;
+
+ public static double infantryFireSndLongId;
+ public static double tankFireSndLongId;
+ public static double explosionSndLongId;
+
+ public static void init(
+ Texture infantryFireT, int iCols, int iRows,
+ Texture tankFireT, int sCols, int sRows,
+ Texture explosionT, int eCols, int eRows,
+ Sound infantryFireS,
+ Sound tankFireS,
+ Sound tankFireLongS,
+ Sound explosionS,
+ Sound explosionLongS)
+ {
+ infantryFire = new Sprites(infantryFireT, iCols, iRows);
+ tankFire = new Sprites(tankFireT, sCols, sRows);
+ explosion = new Sprites(explosionT, eCols, eRows);
+ infantryFireSnd = infantryFireS;
+ tankFireSnd = tankFireS;
+ tankFireSndLong = tankFireLongS;
+ explosionSnd = explosionS;
+ explosionSndLong = explosionLongS;
+
+ reset();
+ }
+
+ public static void reset()
+ {
+ infantryFireSndLongId = -1;
+ tankFireSndLongId = -1;
+ explosionSndLongId = -1;
+ }
+
+ public static void free()
+ {
+ tankFire.dispose();
+ explosion.dispose();
+
+ tankFireSnd.dispose();
+ tankFireSndLong.dispose();
+ explosionSnd.dispose();
+ explosionSndLong.dispose();
+ }
+
+ public static void infantryFireSndPlay(float volume)
+ {
+ if (infantryFireSndLongId == -1)
+ infantryFireSndLongId = infantryFireSnd.play(volume);
+ }
+
+ public static void tankFireSndPlay(float volume)
+ {
+ if (tankFireSndLongId == -1)
+ tankFireSndLongId = tankFireSndLong.play(volume);
+ else
+ tankFireSnd.play(volume);
+ }
+
+ public static void explosionSndPlay(float volume)
+ {
+ if (explosionSndLongId == -1)
+ explosionSndLongId = explosionSndLong.play(volume);
+ else
+ explosionSnd.play(volume);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java
new file mode 100644
index 0000000..233305a
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java
@@ -0,0 +1,222 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.utils.Pool;
+
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class InfantryFireAnimation implements Disposable, Animation, Pool.Poolable
+{
+ class Shot
+ {
+ public TextureRegion fireRegion;
+ public float fire_a;
+ public float fire_x;
+ public float fire_y;
+ public float fire_w;
+ public float fire_dx;
+ public float fire_dy;
+ public float fire_dw;
+
+ public boolean fired;
+ public boolean hit;
+ public boolean completed;
+
+ public float fire_time;
+ public float hit_time;
+ public float end_time;
+
+ public int hit_frame;
+
+ public Shot(TextureRegion region)
+ {
+ this.fireRegion = region;
+ }
+
+ public void set(float delay, float x0, float y0, float x1, float y1, float w, float a)
+ {
+ float dx = (x1 - x0);
+ float dy = (y1 - y0);
+
+ // timing
+ float fire_duration = ((FireAnimation.random.nextFloat() * TIME_SCATTERING) + (w / SHOT_SPEED));
+ float hit_duration = (FireAnimation.infantryFire.rows * HIT_FRAME_DURATION);
+
+ this.fired = false;
+ this.fire_time = delay;
+ this.hit_time = (this.fire_time + fire_duration);
+ this.end_time = (this.hit_time + hit_duration);
+
+ // fire vars
+ this.fire_a = a;
+ this.fire_x = x0;
+ this.fire_y = y0;
+ this.fire_w = 0;
+ this.fire_dx = (dx / fire_duration);
+ this.fire_dy = (dy / fire_duration);
+ this.fire_dw = (w / fire_duration);
+ this.hit_frame = 0;
+ }
+
+ public boolean animate(float delta)
+ {
+ if (!fired && (elapsed < fire_time))
+ return false;
+
+ if (!fired) {
+ fired = true;
+ FireAnimation.infantryFireSndPlay(volume);
+ }
+
+ if (!hit && (elapsed < hit_time)) {
+ fire_w += (fire_dw * delta);
+ fire_x += (fire_dx * delta);
+ fire_y += (fire_dy * delta);
+ fireRegion.setRegionWidth((int) fire_w);
+ return false;
+ }
+
+ if (!hit)
+ hit = true;
+
+ if (elapsed < end_time) {
+ int frame = (int) ((elapsed - hit_time) / HIT_FRAME_DURATION);
+ if (frame != hit_frame) {
+ hit_frame = frame;
+ fireRegion.setRegion(FireAnimation.infantryFire.frames[hit_frame]);
+ fireRegion.setRegionWidth((int) fire_w);
+ }
+ return false;
+ }
+
+ completed = true;
+ return true;
+ }
+
+ public void draw(Batch batch)
+ {
+ if (fired && !completed)
+ batch.draw(fireRegion, fire_x, fire_y, 0, 0, fireRegion.getRegionWidth(), fireRegion.getRegionHeight(), 1f, 1f, fire_a);
+ }
+ }
+
+ private static final int SHOT_COUNT = 19;
+ private static final float SHOT_DELAY = (1.6f / SHOT_COUNT);
+ private static final float SHOT_SCATTERING = 40f;
+ private static final float TIME_SCATTERING = 0.6f;
+ private static final float START_DELAY = 0.8f;
+ private static final float SHOT_SPEED = 1000f;
+ private static final float HIT_FRAME_DURATION = 0.05f;
+
+ private Shot[] shots;
+
+ private float elapsed;
+
+ private float volume;
+
+ private static final Pool<InfantryFireAnimation> fireAnimationPool = new Pool<InfantryFireAnimation>() {
+ @Override
+ protected InfantryFireAnimation newObject() {
+ return new InfantryFireAnimation();
+ }
+ };
+
+ public static InfantryFireAnimation get(float volume, float x0, float y0, float x1, float y1, float halfWidth)
+ {
+ InfantryFireAnimation a = fireAnimationPool.obtain();
+ a.set(volume, x0, y0, x1, y1, halfWidth);
+ return a;
+ }
+
+ public InfantryFireAnimation()
+ {
+ this.shots = new Shot[SHOT_COUNT];
+ for (int i = 0; i < shots.length; i++)
+ shots[i] = new Shot(new TextureRegion(FireAnimation.infantryFire.frames[0]));
+ }
+
+ private void set(float volume, float x0, float y0, float x1, float y1, float halfWidth)
+ {
+ this.volume = volume;
+ this.elapsed = 0f;
+
+ float delay = START_DELAY + (FireAnimation.random.nextFloat() * TIME_SCATTERING);
+
+ y0 -= (FireAnimation.infantryFire.height / 2.0f);
+ double r = Math.atan2((y0 - y1), (x0 - x1));
+ x0 -= ((float) (Math.cos(r) * halfWidth));
+ y0 -= ((float) (Math.sin(r) * halfWidth));
+
+ float dx = (x1 - x0);
+ float dy = (y1 - y0);
+ float w = (float) Math.sqrt((dx * dx) + (dy * dy));
+ double dr = (Math.atan2(halfWidth, w) / 2f);
+
+ double a = (r + (dr / 2f));
+ double da = (dr / (float) SHOT_COUNT);
+
+ for (Shot shot : shots) {
+ float x = (float) (x0 - (Math.cos(a) * w));
+ float y = (float) (y0 - (Math.sin(a) * w));
+
+ shot.set(delay, x0, y0, x, y, w, (float) Math.toDegrees(a));
+
+ delay += SHOT_DELAY;
+ a -= 2 * (da * FireAnimation.random.nextFloat());
+ }
+ }
+
+ @Override
+ public void reset()
+ {
+ }
+
+ @Override
+ public void dispose()
+ {
+ fireAnimationPool.free(this);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+
+ boolean completed = true;
+ for (Shot shot : shots)
+ completed &= shot.animate(delta);
+
+ return completed;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ for (Shot shot : shots)
+ shot.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ // debugShapes.end();
+ // debugShapes.begin(ShapeRenderer.ShapeType.Line);
+ // debugShapes.identity();
+ // debugShapes.translate(fire_x, fire_y, 0);
+ // debugShapes.rotate(0, 0, 1, fire_a);
+ // debugShapes.translate(-fire_x, -fire_y, 0);
+ // debugShapes.rect(fire_x, fire_y, fire_w, FireAnimation.infantryFire.height);
+ // debugShapes.end();
+ // debugShapes.begin(ShapeRenderer.ShapeType.Line);
+ // debugShapes.identity();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java
new file mode 100644
index 0000000..f6380bc
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java
@@ -0,0 +1,126 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Moveable;
+
+public class MoveToAnimation extends TimedAnimation
+{
+ public interface MoveToAnimationCb {
+ void moveToAnimationLeave(Moveable moveable, float x, float y, float r);
+ void moveToAnimationEnter(Moveable moveable, float x, float y, float r);
+ void moveToAnimationDone(Moveable moveable, float x, float y, float r);
+ }
+
+ private Moveable moveable;
+ private float fromX;
+ private float fromY;
+ private float fromR;
+ private float toX;
+ private float toY;
+ private float toR;
+ private float rDelta;
+ private boolean notified;
+ private MoveToAnimationCb cb;
+
+ private static final Pool<MoveToAnimation> moveToAnimationPool = new Pool<MoveToAnimation>() {
+ @Override
+ protected MoveToAnimation newObject() {
+ return new MoveToAnimation();
+ }
+ };
+
+ public static MoveToAnimation get(Moveable moveable, Vector3 v, float duration)
+ {
+ return get(moveable, v.x, v.y, v.z, duration);
+ }
+
+ public static MoveToAnimation get(Moveable moveable, Vector3 v, float duration, MoveToAnimationCb cb)
+ {
+ return get(moveable, v.x, v.y, v.z, duration, cb);
+ }
+
+ public static MoveToAnimation get(Moveable moveable, float x, float y, float r, float duration)
+ {
+ return get(moveable, x, y, r, duration, null);
+ }
+
+ public static MoveToAnimation get(Moveable moveable, float x, float y, float r, float duration, MoveToAnimationCb cb)
+ {
+ MoveToAnimation a = moveToAnimationPool.obtain();
+
+ a.moveable = moveable;
+ a.toX = x;
+ a.toY = y;
+ a.toR = r;
+ a.duration = duration;
+ a.cb = cb;
+ a.rDelta = 0;
+ a.notified = false;
+
+ return a;
+ }
+
+ @Override
+ public void dispose()
+ {
+ moveToAnimationPool.free(this);
+ }
+
+ @Override
+ protected void begin()
+ {
+ fromX = moveable.getX();
+ fromY = moveable.getY();
+ fromR = moveable.getRotation();
+ notified = ((fromX == toX) && (fromY == toY));
+
+ if (Math.abs(toR - fromR) <= 180.f)
+ rDelta = (toR - fromR);
+ else {
+ if (toR > fromR)
+ rDelta = (toR - 360 - fromR);
+ else
+ rDelta = (toR + 360 - fromR);
+ }
+ }
+
+ @Override
+ protected void end()
+ {
+ if (cb != null)
+ cb.moveToAnimationDone(moveable, (toX + (moveable.getWidth() / 2)), (toY + (moveable.getHeight() / 2)), toR);
+ dispose();
+ }
+
+ @Override
+ protected void update(float percent)
+ {
+ if ((cb != null) && !notified && (percent >= 0.5)) {
+ float dw = (moveable.getWidth() / 2);
+ float dh = (moveable.getHeight() / 2);
+ cb.moveToAnimationLeave(moveable, (fromX + dw), (fromY + dh), fromR);
+ cb.moveToAnimationEnter(moveable, (toX + dw), (toY + dh), toR);
+ notified = true;
+ }
+ if (percent == 1f)
+ moveable.setPosition(toX, toY, (int) toR);
+ else
+ moveable.setPosition(fromX + ((toX - fromX) * percent), fromY + ((toY - fromY) * percent), (fromR + (rDelta * percent)));
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ moveable.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ moveable.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java
new file mode 100644
index 0000000..24eac18
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java
@@ -0,0 +1,98 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.lang.Math;
+
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class PromoteAnimation implements Animation, Drawable
+{
+ private static PromoteAnimation instance = new PromoteAnimation();
+
+ private static final float DURATION = 0.3f;
+ private static final float MAX_SCALE = 2f;
+
+ private static Sound usSound;
+ private static Sound geSound;
+ private static Sound snd;
+ private static TextureRegion region;
+
+ private float x0;
+ private float y0;
+ private float x;
+ private float y;
+ private float scale;
+ private float step;
+ private float volume;
+ private float elapsed;
+
+ public static void init(TextureAtlas atlas, Sound usSnd, Sound geSnd)
+ {
+ region = atlas.findRegion("stars");
+ usSound = usSnd;
+ geSound = geSnd;
+ }
+
+ public static void free()
+ {
+ }
+
+ protected void PromoteAnimation()
+ {
+ }
+
+ public static PromoteAnimation get(boolean us, float x, float y, float v)
+ {
+ x = (x - (region.getRegionWidth() / 2.0f));
+ y = (y - (region.getRegionHeight() / 2.0f));
+
+ instance.volume = v;
+ instance.x0 = x;
+ instance.y0 = y;
+ instance.scale = 0f;
+ instance.elapsed = 0f;
+ snd = (us ? usSound : geSound);
+
+ return instance;
+ }
+
+ @Override
+ public void dispose()
+ {
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+ if (elapsed >= DURATION) {
+ snd.play(volume);
+ return true;
+ }
+
+ float s = MAX_SCALE * (float) Math.sin(Math.PI / DURATION * elapsed);
+ scale = 1f + s;
+ x = x0 - ((region.getRegionWidth() * scale) / 4f);
+ y = y0 - ((region.getRegionHeight() * scale) / 4f);
+
+ return false;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ batch.draw(region, x, y, 0, 0, region.getRegionWidth(), region.getRegionHeight(), scale, scale, 0f);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ debugShapes.rect(x, y, region.getRegionWidth(), region.getRegionHeight());
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java
new file mode 100644
index 0000000..231f859
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java
@@ -0,0 +1,67 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Moveable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class RunnableAnimation implements Animation, Pool.Poolable
+{
+ private Runnable runnable;
+ private Moveable moveable;
+ private boolean ran;
+
+ private static final Pool<RunnableAnimation> runnableAnimationPool = new Pool<RunnableAnimation>() {
+ @Override
+ protected RunnableAnimation newObject() {
+ return new RunnableAnimation();
+ }
+ };
+
+ public static RunnableAnimation get(Moveable moveable, Runnable runnable)
+ {
+ RunnableAnimation a = runnableAnimationPool.obtain();
+ a.runnable = runnable;
+ a.moveable = moveable;
+ return a;
+ }
+
+ @Override
+ public void reset()
+ {
+ ran = false;
+ }
+
+ @Override
+ public void dispose()
+ {
+ runnableAnimationPool.free(this);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if (ran) return true;
+
+ runnable.run();
+ runnable = null;
+ ran = true;
+ dispose();
+
+ return true;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ moveable.draw(batch);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ moveable.drawDebug(debugShapes);
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java
new file mode 100644
index 0000000..7e83f38
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java
@@ -0,0 +1,83 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Pool;
+import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+public class SoundAnimation extends TimedAnimation
+{
+ public enum Action
+ {
+ FADE_IN,
+ FADE_OUT
+ };
+
+ private Sound sound;
+ private long soundId;
+ private Action action;
+ private float volume;
+
+ private static final Pool<SoundAnimation> soundAnimationPool = new Pool<SoundAnimation>() {
+ @Override
+ protected SoundAnimation newObject() {
+ return new SoundAnimation();
+ }
+ };
+
+ public static SoundAnimation get(Action action, Sound sound, long soundId, float volume, float duration)
+ {
+ SoundAnimation a = soundAnimationPool.obtain();
+
+ a.action = action;
+ a.sound = sound;
+ a.soundId = soundId;
+ a.volume = volume;
+ a.duration = duration;
+
+ return a;
+ }
+
+ @Override
+ public void dispose()
+ {
+ soundAnimationPool.free(this);
+ }
+
+ @Override
+ protected void begin()
+ {
+ }
+
+ @Override
+ protected void end()
+ {
+ dispose();
+ }
+
+ @Override
+ protected void update(float percent)
+ {
+ float v;
+ switch(action) {
+ case FADE_IN:
+ v = ( volume * percent);
+ sound.setVolume(soundId, v);
+ break;
+ case FADE_OUT:
+ v = (volume - ( volume * percent));
+ sound.setVolume(soundId, v);
+ break;
+ }
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java
new file mode 100644
index 0000000..4d10210
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java
@@ -0,0 +1,76 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.utils.Disposable;
+
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class SpriteAnimation implements Disposable, Animation
+{
+ private static Random random = new Random();
+ private Sprites sprites;
+ private float duration;
+ private float frameDuration;
+ private float elapsed;
+ private float x0;
+ private float y0;
+ private float x1;
+ private float y1;
+ private int randFreq;
+
+ public SpriteAnimation(Texture texture, int cols, int rows, int randFreq)
+ {
+ this.sprites = new Sprites(texture, cols, rows);
+ this.randFreq = randFreq;
+ }
+
+ @Override
+ public void dispose()
+ {
+ sprites.dispose();
+ }
+
+ public void init(float duration, float x, float y)
+ {
+ this.duration = duration;
+ this.frameDuration = (duration / (float) sprites.frames.length);
+ this.x0 = x - (sprites.width / 2f);
+ this.y0 = y - (sprites.height / 2f);
+ this.elapsed = 0f;
+ randPos();
+ }
+
+ private void randPos()
+ {
+ this.x1 = this.x0 + (random.nextInt(sprites.width) - (sprites.width / 2));
+ this.y1 = this.y0 + (random.nextInt(sprites.height) - (sprites.height / 2));
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+ return (elapsed >= duration);
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ int n = (((int)(elapsed / frameDuration)) % sprites.frames.length);
+ if ((n > 0) && (n % randFreq) == 0)
+ randPos();
+ batch.draw(sprites.frames[n], x1, y1);
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java
new file mode 100644
index 0000000..2d2a0c1
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java
@@ -0,0 +1,38 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+
+public class Sprites implements Disposable
+{
+ public Texture texture;
+ public TextureRegion[] frames;
+ public final int width;
+ public final int height;
+ public final int cols;
+ public final int rows;
+
+ public Sprites(Texture texture, int cols, int rows)
+ {
+ this.cols = cols;
+ this.rows = rows;
+ this.width = (texture.getWidth() / cols);
+ this.height = (texture.getHeight() / rows);
+ this.texture = texture;
+ TextureRegion[][] tmp = TextureRegion.split(texture, width, height);
+ frames = new TextureRegion[cols * rows];
+ int idx = 0;
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ frames[idx++] = tmp[i][j];
+ }
+ }
+ }
+
+ @Override
+ public void dispose()
+ {
+ texture.dispose();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java
new file mode 100644
index 0000000..82a87fd
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java
@@ -0,0 +1,196 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import java.util.Random;
+
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.utils.Pool;
+
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+
+import ch.asynk.rustanddust.engine.gfx.Drawable;
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public class TankFireAnimation implements Disposable, Animation, Pool.Poolable
+{
+ private static final float SHOT_SCATTERING = 60f;
+ private static final float TIME_SCATTERING = 0.6f;
+ private static final float START_DELAY = 0.8f;
+ private static final float SHOT_SPEED = 900f;
+ private static final float EXPLOSION_FRAME_DURATION = 0.07f;
+
+ private TextureRegion fireRegion;
+ private float fire_a;
+ private float fire_x;
+ private float fire_y;
+ private float fire_w;
+ private float fire_dx;
+ private float fire_dy;
+ private float fire_dw;
+
+ private float smoke_df;
+ private int smoke_frame;
+
+ private float explosion_x;
+ private float explosion_y;
+ private float explosion_df;
+ private int explosion_frame;
+
+ private boolean fired;
+ private boolean hit;
+ private float elapsed;
+ private float fire_time;
+ private float hit_time;
+ private float end_time;
+
+ private float volume;
+
+ private static final Pool<TankFireAnimation> fireAnimationPool = new Pool<TankFireAnimation>() {
+ @Override
+ protected TankFireAnimation newObject() {
+ return new TankFireAnimation();
+ }
+ };
+
+ public static TankFireAnimation get(float volume, float x0, float y0, float x1, float y1, float halfWidth)
+ {
+ TankFireAnimation a = fireAnimationPool.obtain();
+ a.set(volume, x0, y0, x1, y1, halfWidth);
+ return a;
+ }
+
+ public TankFireAnimation()
+ {
+ this.fireRegion = new TextureRegion(FireAnimation.tankFire.frames[0]);
+ }
+
+ private void set(float volume, float x0, float y0, float x1, float y1, float halfWidth)
+ {
+ this.fired = false;
+ this.hit = false;
+ this.volume = volume;
+
+ // fire geometry
+ y0 -= (FireAnimation.tankFire.height / 2.0f);
+ x1 += ((SHOT_SCATTERING * FireAnimation.random.nextFloat()) - (SHOT_SCATTERING / 2f));
+ y1 += ((SHOT_SCATTERING * FireAnimation.random.nextFloat()) - (SHOT_SCATTERING / 2f));
+
+ double r = Math.atan2((y0 - y1), (x0 - x1));
+ float xadj = (float) (Math.cos(r) * halfWidth);
+ float yadj = (float) (Math.sin(r) * halfWidth);
+ x0 -= xadj;
+ y0 -= yadj;
+
+ float a = (float) Math.toDegrees(r);
+ float dx = (x1 - x0);
+ float dy = (y1 - y0);
+ float w = (float) Math.sqrt((dx * dx) + (dy * dy));
+
+ // timing
+ float delay = START_DELAY + (FireAnimation.random.nextFloat() * TIME_SCATTERING);
+ float fire_duration = ((FireAnimation.random.nextFloat() * TIME_SCATTERING) + (w / SHOT_SPEED));
+ float explosion_duration = (FireAnimation.explosion.cols * EXPLOSION_FRAME_DURATION);
+
+ this.elapsed = 0f;
+ this.fire_time = delay;
+ this.hit_time = (fire_time + fire_duration);
+ this.end_time = (hit_time + explosion_duration);
+
+ // fire vars
+ this.fire_a = a;
+ this.fire_x = x0;
+ this.fire_y = y0;
+ this.fire_w = 0;
+ this.fire_dx = (dx / fire_duration);
+ this.fire_dy = (dy / fire_duration);
+ this.fire_dw = (w / fire_duration);
+
+ // smoke var
+ this.smoke_df = (FireAnimation.tankFire.rows / explosion_duration);
+ this.smoke_frame = 0;
+
+ // explosion vars
+ this.explosion_x = (x1 - (FireAnimation.explosion.width / 2.0f));
+ this.explosion_y = (y1 - (FireAnimation.explosion.height / 2.0f));
+ this.explosion_df = (FireAnimation.explosion.cols / explosion_duration);
+ this.explosion_frame = (FireAnimation.random.nextInt(FireAnimation.explosion.rows) * FireAnimation.explosion.cols);
+ }
+
+ @Override
+ public void reset()
+ {
+ }
+
+ @Override
+ public void dispose()
+ {
+ fireAnimationPool.free(this);
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ elapsed += delta;
+
+ if (!fired && (elapsed < fire_time))
+ return false;
+
+ if (!fired) {
+ fired = true;
+ FireAnimation.tankFireSndPlay(volume);
+ }
+
+ if (!hit && (elapsed < hit_time)) {
+ fire_w += (fire_dw * delta);
+ fire_x += (fire_dx * delta);
+ fire_y += (fire_dy * delta);
+ fireRegion.setRegionWidth((int) fire_w);
+ return false;
+ }
+
+ if (!hit) {
+ hit = true;
+ FireAnimation.explosionSndPlay(volume);
+ }
+
+ if (elapsed < end_time) {
+ int frame = (int) ((elapsed - hit_time) * smoke_df);
+ if (frame != smoke_frame) {
+ smoke_frame = frame;
+ fireRegion.setRegion(FireAnimation.tankFire.frames[smoke_frame]);
+ fireRegion.setRegionWidth((int) fire_w);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void draw(Batch batch)
+ {
+ if (fired)
+ batch.draw(fireRegion, fire_x, fire_y, 0, 0, fireRegion.getRegionWidth(), fireRegion.getRegionHeight(), 1f, 1f, fire_a);
+
+ if (hit) {
+ int frame = (explosion_frame + (int) ((elapsed - hit_time) * explosion_df));
+ batch.draw(FireAnimation.explosion.frames[frame], explosion_x, explosion_y);
+ }
+ }
+
+ @Override
+ public void drawDebug(ShapeRenderer debugShapes)
+ {
+ debugShapes.end();
+ debugShapes.begin(ShapeRenderer.ShapeType.Line);
+ debugShapes.identity();
+ debugShapes.translate(fire_x, fire_y, 0);
+ debugShapes.rotate(0, 0, 1, fire_a);
+ debugShapes.translate(-fire_x, -fire_y, 0);
+ debugShapes.rect(fire_x, fire_y, fire_w, FireAnimation.tankFire.height);
+ debugShapes.end();
+ debugShapes.begin(ShapeRenderer.ShapeType.Line);
+ debugShapes.identity();
+ }
+}
diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java
new file mode 100644
index 0000000..0c0d14d
--- /dev/null
+++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java
@@ -0,0 +1,48 @@
+package ch.asynk.rustanddust.engine.gfx.animations;
+
+import com.badlogic.gdx.utils.Pool;
+
+import ch.asynk.rustanddust.engine.gfx.Animation;
+
+public abstract class TimedAnimation implements Animation, Pool.Poolable
+{
+ private float time;
+ private boolean began;
+ private boolean completed;
+ protected float duration;
+
+ abstract protected void begin();
+ abstract protected void end();
+ abstract protected void update(float percent);
+
+ @Override
+ public void reset()
+ {
+ time = 0f;
+ began = false;
+ completed = false;
+ }
+
+ @Override
+ public boolean animate(float delta)
+ {
+ if (completed) return true;
+
+ if (!began) {
+ begin();
+ began = true;
+ }
+
+ time += delta;
+ completed = (time >= duration);
+
+ if (!completed) {
+ update(time / duration);
+ return false;
+ }
+
+ update(1);
+ end();
+ return true;
+ }
+}