diff options
author | Jérémy Zurcher <jeremy@asynk.ch> | 2015-07-19 13:20:33 +0200 |
---|---|---|
committer | Jérémy Zurcher <jeremy@asynk.ch> | 2015-07-19 13:20:33 +0200 |
commit | de0463bcf0f76ef8b07f2719679c9e0d72745c5d (patch) | |
tree | 9a33df947ceeea16a3e20b400585b1d3c304e77e /core/src/ch/asynk/rustanddust | |
parent | e66f9f2a61d3dab4545e996046486de0d44e2901 (diff) | |
download | RustAndDust-de0463bcf0f76ef8b07f2719679c9e0d72745c5d.zip RustAndDust-de0463bcf0f76ef8b07f2719679c9e0d72745c5d.tar.gz |
welcome RustAndDust
Diffstat (limited to 'core/src/ch/asynk/rustanddust')
97 files changed, 12831 insertions, 0 deletions
diff --git a/core/src/ch/asynk/rustanddust/RustAndDust.java b/core/src/ch/asynk/rustanddust/RustAndDust.java new file mode 100644 index 0000000..52732a3 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/RustAndDust.java @@ -0,0 +1,222 @@ +package ch.asynk.rustanddust; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Game; +import com.badlogic.gdx.assets.AssetManager; +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; + +import ch.asynk.rustanddust.screens.MenuScreen; +import ch.asynk.rustanddust.screens.GameScreen; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Config; +import ch.asynk.rustanddust.game.battles.Factory; + +public class RustAndDust extends Game +{ + public AssetManager manager; + public Factory factory; + public Ctrl ctrl; + public Config config; + public int hudCorrection; + + public TextureAtlas uiAtlas; + public TextureAtlas menuAtlas; + public BitmapFont fontB; + public BitmapFont fontW; + + public enum State + { + MENU, + GAME, + NONE + } + private State state; + + public static void debug(String msg) + { + debug("", msg); + } + + public static void debug(String dom, String msg) + { + Gdx.app.debug(dom, msg); + } + + @Override + public void create () + { + Gdx.app.setLogLevel(Gdx.app.LOG_DEBUG); + this.hudCorrection = ((int) (125 * Gdx.graphics.getDensity()) - 75); + debug("RustAndDust", "create() [" + Gdx.graphics.getWidth() + ";" + Gdx.graphics.getHeight() + "] " + Gdx.graphics.getDensity() + " -> " + hudCorrection); + + manager = new AssetManager(); + factory = new Factory(this); + config = new Config(); + + state = State.NONE; + loadUiAssets(); + switchToMenu(); + } + + public void switchToMenu() + { + if (state == State.GAME) { + unloadGameAssets(); + factory.dispose(); + ctrl.dispose(); + getScreen().dispose(); + } + loadMenuAssets(); + state = State.MENU; + setScreen(new MenuScreen(this)); + } + + public void switchToGame() + { + unloadMenuAssets(); + getScreen().dispose(); + factory.assetsLoaded(); + state = State.GAME; + setScreen(new GameScreen(this)); + } + + public void loadGameAssets() + { + if (config.battle.getMapType() == Factory.MapType.MAP_A) + manager.load("data/map_a.png", Texture.class); + if (config.battle.getMapType() == Factory.MapType.MAP_B) + manager.load("data/map_b.png", Texture.class); + int i = config.graphics.i; + manager.load(String.format("data/units%d.atlas",i), TextureAtlas.class); + manager.load(String.format("data/unit-overlays%d.atlas", i), TextureAtlas.class); + manager.load("data/hex.png", Texture.class); + manager.load("data/hud.atlas", TextureAtlas.class); + manager.load("data/hex-overlays.atlas", TextureAtlas.class); + manager.load("data/dice.png", Texture.class); + manager.load("data/infantry_fire.png", Texture.class); + manager.load("data/tank_fire.png", Texture.class); + manager.load("data/explosions.png", Texture.class); + manager.load("sounds/dice.mp3", Sound.class); + manager.load("sounds/tank_move.mp3", Sound.class); + manager.load("sounds/infantry_move.mp3", Sound.class); + manager.load("sounds/infantry_fire.mp3", Sound.class); + manager.load("sounds/tank_fire.mp3", Sound.class); + manager.load("sounds/tank_fire_short.mp3", Sound.class); + manager.load("sounds/explosion.mp3", Sound.class); + manager.load("sounds/explosion_short.mp3", Sound.class); + manager.load("sounds/promote_us.mp3", Sound.class); + manager.load("sounds/promote_ge.mp3", Sound.class); + debug("RustAndDust", " assets loaded : " + (Gdx.app.getJavaHeap()/1024.0f) + "KB"); + } + + private void unloadGameAssets() + { + if (config.battle.getMapType() == Factory.MapType.MAP_A) + manager.unload("data/map_a.png"); + if (config.battle.getMapType() == Factory.MapType.MAP_B) + manager.unload("data/map_b.png"); + int i = config.graphics.i; + manager.unload(String.format("data/units%d.atlas",i)); + manager.unload(String.format("data/unit-overlays%d.atlas", i)); + manager.unload("data/hex.png"); + manager.unload("data/hud.atlas"); + manager.unload("data/hex-overlays.atlas"); + manager.unload("data/dice.png"); + manager.unload("data/infantry_fire.png"); + manager.unload("data/tank_fire.png"); + manager.unload("data/explosions.png"); + manager.unload("sounds/dice.mp3"); + manager.unload("sounds/tank_move.mp3"); + manager.unload("sounds/infantry_move.mp3"); + manager.unload("sounds/infantry_fire.mp3"); + manager.unload("sounds/tank_fire.mp3"); + manager.unload("sounds/tank_fire_short.mp3"); + manager.unload("sounds/explosion.mp3"); + manager.unload("sounds/explosion_short.mp3"); + manager.unload("sounds/promote_us.mp3"); + manager.unload("sounds/promote_ge.mp3"); + debug("RustAndDust", " assets unloaded : " + (Gdx.app.getJavaHeap()/1024.0f) + "KB"); + } + + private void loadUiAssets() + { + manager.load("data/ui.atlas", TextureAtlas.class); + manager.finishLoading(); + uiAtlas = manager.get("data/ui.atlas", TextureAtlas.class); + fontB = new BitmapFont(Gdx.files.internal("skin/veteran.fnt"), uiAtlas.findRegion("veteran-black")); + fontW = new BitmapFont(Gdx.files.internal("skin/veteran.fnt"), uiAtlas.findRegion("veteran-white")); + } + + private void unloadUiAssets() + { + fontB.dispose(); + fontW.dispose(); + manager.unload("data/ui.atlas"); + } + + private void loadMenuAssets() + { + manager.load("data/map_a.png", Texture.class); + manager.load("data/menu.atlas", TextureAtlas.class); + manager.finishLoading(); + menuAtlas = manager.get("data/menu.atlas", TextureAtlas.class); + } + + private void unloadMenuAssets() + { + manager.unload("data/map_a.png"); + manager.unload("data/menu.atlas"); + } + + // @Override + // public void render () + // { + // Gdx.gl.glClearColor(0, 0, 0, 1); + // Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + // super.render(); + // } + + // @Override + // public void resize(int width, int height) + // { + // debug("RustAndDust", "resize(" + width + ", " + height + ")"); + // super.resize(width, height); + // } + + @Override + public void dispose() + { + debug("RustAndDust", "dispose()"); + debug("RustAndDust", "diagnostics:\n" + manager.getDiagnostics() ); + getScreen().dispose(); + unloadUiAssets(); + switch(state) { + case MENU: + unloadMenuAssets(); + break; + case GAME: + unloadGameAssets(); + factory.dispose(); + ctrl.dispose(); + break; + } + debug("RustAndDust", "diagnostics:\n" + manager.getDiagnostics() ); + manager.clear(); + manager.dispose(); + } + + // @Override + // public void pause() + // { + // debug("RustAndDust", "pause()"); + // } + + // @Override + // public void resume() + // { + // debug("RustAndDust", "resume()"); + // } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Attack.java b/core/src/ch/asynk/rustanddust/engine/Attack.java new file mode 100644 index 0000000..e64399a --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Attack.java @@ -0,0 +1,28 @@ +package ch.asynk.rustanddust.engine; + +public class Attack +{ + public Pawn attacker; + public Pawn target; + public int distance; + public boolean isClear; + public boolean isFlank; + + public Attack(Pawn attacker) + { + this.attacker = attacker; + } + + public String toString() + { + return String.format("attack : %s -> %s dist:%d clear:%b flank:%b", attacker, target, distance, isClear, isFlank); + } + + public void reset() + { + target = null; + distance = 0;; + isClear = false; + isFlank = false; + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Board.java b/core/src/ch/asynk/rustanddust/engine/Board.java new file mode 100644 index 0000000..e4aba6d --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Board.java @@ -0,0 +1,554 @@ +package ch.asynk.rustanddust.engine; + +import java.util.Iterator; +import java.util.Collection; +import java.util.ArrayList; +import java.util.LinkedHashSet; + +import com.badlogic.gdx.Gdx; + +import com.badlogic.gdx.utils.Disposable; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import com.badlogic.gdx.math.Matrix4; + +import ch.asynk.rustanddust.engine.gfx.Animation; +import ch.asynk.rustanddust.engine.gfx.animations.AnimationSequence; +import ch.asynk.rustanddust.engine.gfx.animations.RunnableAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.MoveToAnimation.MoveToAnimationCb; + +public abstract class Board implements Disposable, Animation +{ + private int cols; + private int rows; + private final Tile neighbours[] = new Tile[6]; + + public interface TileBuilder + { + public Tile getNewTile(float x, float y, int col, int row, boolean offmap); + } + + public static class Config + { + public int cols; + public int rows; + public int x0; // bottom left x offset + public int y0; // bottom left y offset + public int w; // hex width + public int dw; // half hex : w/2 + public int s; // hex side + public float dh; // hex top : s/2 + public float h; // square height : s + dh + public float slope; // north-west side slope : (dh / (float) dw) + } + + private Config cfg; + private Tile[] tiles; + private SearchBoard searchBoard; + private Sprite board; + private Orientation sides[]; + + private boolean transform; + private Matrix4 prevTransform; + private Matrix4 nextTransform; + + private int tileCount = 0; + private int pawnCount = 0; + private int animationCount = 0; + private final ArrayList<Animation> animations = new ArrayList<Animation>(2); + private final ArrayList<Animation> nextAnimations = new ArrayList<Animation>(2); + private final LinkedHashSet<Tile> tilesToDraw = new LinkedHashSet<Tile>(); + + protected SelectedTile selectedTile; + + protected Board(int cols, int rows) + { + // add a frame of OFFMAP Tiles + this.cols = (cols + 2); + this.rows = (rows + 2); + searchBoard = new SearchBoard(this, cols, rows); + initSides(); + } + + public Board(TileBuilder tileBuilder, Config cfg, Texture boardTexture, SelectedTile selectedTile) + { + board = new Sprite(boardTexture); + this.cfg = cfg; + // add a frame of OFFMAP Tiles + this.cols = (cfg.cols + 2); + this.rows = (cfg.rows + 2); + this.tiles = new Tile[this.cols * this.rows]; + searchBoard = new SearchBoard(this, cfg.cols, cfg.rows); + + int idx = 0; + boolean evenRow = false; + float y = cfg.y0 - cfg.dh + cfg.s - cfg.h; + for (int i = -1; i < (cfg.rows + 1); i++) { + float x = cfg.x0 + cfg.dw - cfg.w; + if (!evenRow) x += cfg.dw; + for ( int j = -1; j < (cfg.cols + 1); j ++) { + boolean offmap = ((j < 0) || (i < 0) || (j >= cfg.cols) || (i >= cfg.rows)); + this.tiles[idx] = tileBuilder.getNewTile(x, y, (j + ((i + 1) / 2)), i, offmap); + idx += 1; + x += cfg.w; + } + y += cfg.h; + evenRow = !evenRow; + } + + initSides(); + + this.selectedTile = selectedTile; + } + + private void initSides() + { + this.sides = new Orientation[6]; + sides[0] = Orientation.NORTH; + sides[1] = Orientation.NORTH_EAST; + sides[2] = Orientation.SOUTH_EAST; + sides[3] = Orientation.SOUTH; + sides[4] = Orientation.SOUTH_WEST; + sides[5] = Orientation.NORTH_WEST; + } + + @Override + public void dispose() + { + for (int i = 0; i < (this.cols * this.rows); i++) + tiles[i].dispose(); + tilesToDraw.clear(); + for (int i = 0, n = nextAnimations.size(); i < n; i++) + nextAnimations.get(i).dispose(); + animations.clear(); + for (int i = 0, n = animations.size(); i < n; i++) + animations.get(i).dispose(); + animations.clear(); + if (selectedTile != null) + selectedTile.dispose(); + Move.clearPool(); + Path.clearPool(); + } + + public float getWidth() + { + return board.getWidth(); + } + + public float getHeight() + { + return board.getHeight(); + } + + public void setPosition(float x, float y) + { + board.setPosition(x, y); + if ((x != 0.0f) || (y != 0.0f)) { + transform = true; + prevTransform = new Matrix4(); + nextTransform = new Matrix4(); + nextTransform.translate(x, y, 0); + } else + transform = false; + } + + public Orientation getSide(int i) + { + return sides[i]; + } + + protected int getTileOffset(int col, int row) + { + col = (col + 1 - ((row + 1) / 2)); + row = (row + 1); + if ((col < 0) || (row < 0) || (row >= this.rows) || (col >= this.cols)) + return -1; + + return (col + (row * this.cols)); + } + + protected Tile getTile(int col, int row) + { + int offset = getTileOffset(col, row); + if (offset < 0) + return null; + return tiles[offset]; + } + + public void setAdjacentTiles(Tile tile, Tile tiles[]) + { + tiles[0] = getAdjTileAt(tile, sides[0].opposite()); + tiles[1] = getAdjTileAt(tile, sides[1].opposite()); + tiles[2] = getAdjTileAt(tile, sides[2].opposite()); + tiles[3] = getAdjTileAt(tile, sides[3].opposite()); + tiles[4] = getAdjTileAt(tile, sides[4].opposite()); + tiles[5] = getAdjTileAt(tile, sides[5].opposite()); + } + + public Tile getAdjTileAt(Tile tile, Orientation o) + { + Tile t = null; + switch(o) { + case NORTH: + t = getTile((tile.col + 1), tile.row); + break; + case NORTH_EAST: + t = getTile(tile.col, (tile.row - 1)); + break; + case SOUTH_EAST: + t = getTile((tile.col - 1), (tile.row - 1)); + break; + case SOUTH: + t = getTile((tile.col - 1), tile.row); + break; + case SOUTH_WEST: + t = getTile(tile.col, (tile.row + 1)); + break; + case NORTH_WEST: + t = getTile((tile.col + 1), (tile.row + 1)); + break; + } + return t; + } + + protected abstract void animationsOver(); + + protected void addAnimation(Animation a) + { + nextAnimations.add(a); + } + + public int animationCount() + { + return animations.size(); + } + + private void stats() + { + boolean print = false; + + if (tileCount != tilesToDraw.size()) { + tileCount = tilesToDraw.size(); + print = true; + } + + if (animationCount != animations.size()) { + animationCount = animations.size(); + print = true; + } + + if (print) + Gdx.app.debug("Board", " tiles:" + tileCount + " pawns:" + pawnCount + " animations:" + animationCount); + } + + public boolean animate(float delta) + { + boolean over = (animations.size() > 0); + Iterator<Animation> iter = animations.iterator(); + while (iter.hasNext()) { + Animation a = iter.next(); + if (a.animate(delta)) + iter.remove(); + } + if (over && (animations.size() == 0)) + animationsOver(); + + for (int i = 0, n = nextAnimations.size(); i < n; i++) + animations.add(nextAnimations.get(i)); + nextAnimations.clear(); + + selectedTile.animate(delta); + + return true; + } + + public void draw(Batch batch) + { + board.draw(batch); + + if (transform) { + prevTransform.set(batch.getTransformMatrix()); + batch.setTransformMatrix(nextTransform); + } + + Iterator<Tile> tileIter = tilesToDraw.iterator(); + while (tileIter.hasNext()) + tileIter.next().draw(batch); + + Iterator<Animation> animationIter = animations.iterator(); + while (animationIter.hasNext()) + animationIter.next().draw(batch); + + selectedTile.draw(batch); + + if (transform) + batch.setTransformMatrix(prevTransform); + } + + public void drawDebug(ShapeRenderer debugShapes) + { + stats(); + if (transform) { + prevTransform.set(debugShapes.getTransformMatrix()); + debugShapes.setTransformMatrix(nextTransform); + } + + Iterator<Tile> iter = tilesToDraw.iterator(); + while (iter.hasNext()) + iter.next().drawDebug(debugShapes); + + Iterator<Animation> animationIter = animations.iterator(); + while (animationIter.hasNext()) + animationIter.next().drawDebug(debugShapes); + + if (transform) + debugShapes.setTransformMatrix(prevTransform); + } + + protected int collectPossibleMoves(Pawn pawn, Collection<Tile> moves) + { + return searchBoard.possibleMovesFrom(pawn, moves); + } + + protected int collectPossibleTargets(Pawn pawn, Collection<Pawn> targets) + { + return searchBoard.possibleTargetsFrom(pawn, targets); + } + + protected int collectPossibleTargets(Pawn pawn, Collection<Pawn> units, Collection<Pawn> targets) + { + targets.clear(); + for (Pawn target : units) { + if (pawn.canEngage(target) && searchBoard.canAttack(pawn, target, true)) + targets.add(target); + } + + return targets.size(); + } + + protected int collectMoveAssists(Pawn pawn, Collection<Pawn> assists) + { + assists.clear(); + setAdjacentTiles(pawn.getTile(), neighbours); + for (int i = 0; i < 6; i++) { + Tile tile = neighbours[i]; + if (tile != null) { + Iterator<Pawn> pawns = tile.iterator(); + while(pawns.hasNext()) { + Pawn p = pawns.next(); + if (!pawn.isEnemy(p) && p.canMove()) + assists.add(p); + } + } + } + return assists.size(); + } + + protected int collectAttackAssists(Pawn pawn, Pawn target, Collection<Pawn> units, Collection<Pawn> assists) + { + assists.clear(); + for (Pawn p : units) { + if ((p != pawn) && p.canEngage(target) && searchBoard.canAttack(p, target, !p.canAssistEngagementWithoutLos())) + assists.add(p); + } + + return assists.size(); + } + + public Orientation findBestEntry(Pawn pawn, Tile to, int allowedMoves) + { + Orientation entry = Orientation.KEEP; + int cost = Integer.MAX_VALUE; + boolean road = false; + + setAdjacentTiles(to, neighbours); + for (int i = 0; i < 6; i++) { + Tile t = neighbours[i]; + if (t.isOffMap()) { + Orientation o = Orientation.fromAdj(t.col, t.row, to.col, to.row); + if (o.isInSides(allowedMoves)) { + o = o.opposite(); + boolean r = to.road(o); + int c = to.costFrom(pawn, o); + if ((c < cost) || (r && (c == cost))) { + entry = o; + cost = c; + road = r; + } + } + } + } + + return entry.opposite(); + } + + public void enableOverlayOn(Tile tile, int i, boolean enable) + { + if(tile.enableOverlay(i, enable)) + tilesToDraw.add(tile); + else + tilesToDraw.remove(tile); + } + + public void enableOverlayOn(Tile tile, int i, Orientation o, boolean enable) + { + if(tile.enableOverlay(i, enable, o.r())) + tilesToDraw.add(tile); + else + tilesToDraw.remove(tile); + } + + private int pushPawnOnto(Pawn pawn, Tile tile) + { + if (!tile.isOffMap()) + tilesToDraw.add(tile); + return tile.push(pawn); + } + + public int removePawn(Pawn pawn) + { + Tile tile = pawn.getTile(); + if (tile == null) + return 0; + int n = tile.remove(pawn); + if (!tile.mustBeDrawn()) + tilesToDraw.remove(tile); + return n; + } + + public Pawn setPawnOnto(Pawn pawn, Move move) + { + pawn.move(move); + return setPawnOnto(pawn, move.to, move.orientation); + } + + public Pawn setPawnOnto(Pawn pawn, Tile tile, Orientation o) + { + pawn.setOnTile(tile, o.r()); + pushPawnOnto(pawn, tile); + return pawn; + } + + private RunnableAnimation getSetPawnOntoAnimation(final Pawn pawn) + { + return RunnableAnimation.get(pawn, new Runnable() { + @Override + public void run() { + Tile to = pawn.move.to; + if (!to.isOffMap()) + setPawnOnto(pawn, to, pawn.move.orientation); + } + }); + } + + protected void movePawn(final Pawn pawn, Move move, MoveToAnimationCb cb) + { + pawn.move(move); + removePawn(pawn); + + AnimationSequence seq = pawn.getMoveAnimation(move.iterator(), (move.steps() + 1), cb); + seq.addAnimation(getSetPawnOntoAnimation(pawn)); + addAnimation(seq); + } + + protected void enterPawn(final Pawn pawn, Move move) + { + pawn.move(move); + setPawnOnto(pawn, move.to, move.orientation); + } + + protected void revertLastPawnMove(final Pawn pawn) + { + removePawn(pawn); + + addAnimation(RunnableAnimation.get(pawn, new Runnable() { + @Override + public void run() { + pushPawnOnto(pawn, pawn.getTile()); + } + })); + + pawn.revertLastMove(); + } + + public void attack(final Pawn pawn, final Pawn target, boolean clearVisibility) + { + if (!pawn.canEngage(target) || !searchBoard.canAttack(pawn, target, clearVisibility)) + throw new RuntimeException(String.format("%s cannot attack %s", pawn, target)); + } + + public Tile getTileAt(float mx, float my) + { + // compute row + float y = (my - cfg.y0); + int row = (int) (y / cfg.h); + boolean oddRow = ((row % 2) == 1); + if (y < 0.f) { + row = -1; + oddRow = true; + } + + // compute col + float x = (mx - cfg.x0); + if (oddRow) x -= cfg.dw; + int col = (int) (x / cfg.w); + if (x < 0.f) + col = -1; + + int colOffset = ((row + 1) / 2); + + // check upper boundaries + float dy = (y - (row * cfg.h)); + if (dy > cfg.s) { + dy -= cfg.s; + float dx = (x - (col * cfg.w)); + col += colOffset; + if (dx < cfg.dw) { + if ((dx * cfg.slope) < dy) { + // upper left corner + row += 1; + colOffset = ((row +1) / 2); + } + } else { + if (((cfg.w - dx) * cfg.slope) < dy) { + // upper right corner + row += 1; + col += 1; + colOffset = ((row +1) / 2); + } + } + } else + col += colOffset; + + return getTile(col, row); + } + + public int distance(Tile from, Tile to) + { + return distance(from.col, from.row, to.col, to.row); + } + + public int distance(int col0, int row0, int col1, int row1) + { + int dx = Math.abs(col1 - col0); + int dy = Math.abs(row1 - row0); + int dz = Math.abs((col0 - row0) - (col1 - row1)); + + if (dx > dy) { + if (dx > dz) + return dx; + else + return dz; + } else { + if (dy > dz) + return dy; + else + return dz; + } + } +} + diff --git a/core/src/ch/asynk/rustanddust/engine/Faction.java b/core/src/ch/asynk/rustanddust/engine/Faction.java new file mode 100644 index 0000000..e108fc4 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Faction.java @@ -0,0 +1,6 @@ +package ch.asynk.rustanddust.engine; + +public interface Faction +{ + public boolean isEnemy(Faction other); +} diff --git a/core/src/ch/asynk/rustanddust/engine/HeadedPawn.java b/core/src/ch/asynk/rustanddust/engine/HeadedPawn.java new file mode 100644 index 0000000..67e1d44 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/HeadedPawn.java @@ -0,0 +1,88 @@ +package ch.asynk.rustanddust.engine; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import com.badlogic.gdx.math.Vector3; + +public abstract class HeadedPawn extends Pawn +{ + private Sprite head; + protected Orientation orientation; + + public HeadedPawn(Faction faction, String pawn, String head, TextureAtlas pawns, TextureAtlas overlays) + { + super(faction, pawn, pawns, overlays); + this.head = new Sprite(pawns.findRegion(head)); + this.orientation = Orientation.KEEP; + this.descr += " " + orientation; + } + + @Override + public void dispose() + { + super.dispose(); + } + + @Override + public void setAlpha(float alpha) + { + super.setAlpha(alpha); + head.setAlpha(alpha); + } + + @Override + public float getRotation() + { + return orientation.r(); + } + + @Override + public Orientation getOrientation() + { + return orientation; + } + + @Override + public void setPosition(float x, float y) + { + super.setPosition(x, y); + float cx = x + (getWidth() / 2f); + float cy = y + (getHeight() / 2f); + head.setPosition((cx - (head.getWidth() / 2f)), (cy - (head.getHeight() / 2f))); + } + + @Override + public void setRotation(float z) + { + getPosition().z = z; + head.setRotation(z); + this.orientation = Orientation.fromRotation(z); + } + + @Override + public void setPosition(float x, float y, float z) + { + setPosition(x, y); + setRotation(z); + } + + @Override + public void draw(Batch batch) + { + head.draw(batch); + super.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + float w = head.getWidth(); + float h = head.getHeight(); + debugShapes.rect(head.getX(), head.getY(), (w / 2f), (h / 2f), w, h, head.getScaleX(), head.getScaleY(), head.getRotation()); + super.drawDebug(debugShapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Meteorology.java b/core/src/ch/asynk/rustanddust/engine/Meteorology.java new file mode 100644 index 0000000..9addf63 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Meteorology.java @@ -0,0 +1,24 @@ +package ch.asynk.rustanddust.engine; + +public class Meteorology +{ + public enum Day { DAY, NIGHT }; + public enum Season { SUMMER, SPRING, WINTER, FALL }; + public enum Weather { CLEAR, RAIN, SNOW, WIND }; + + public Day day; + public Season season; + public Weather weather; + + public Meteorology() + { + clear(); + } + + public void clear() + { + day = Day.DAY; + season = Season.SUMMER; + weather = Weather.CLEAR; + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Move.java b/core/src/ch/asynk/rustanddust/engine/Move.java new file mode 100644 index 0000000..077823f --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Move.java @@ -0,0 +1,158 @@ +package ch.asynk.rustanddust.engine; + +import java.util.Iterator; + +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.math.Vector3; + +public class Move extends Path implements Iterable<Vector3> +{ + public enum MoveType + { + REGULAR, + SET, + ENTER, + EXIT; + } + + private static final Pool<Move> movePool = new Pool<Move>() + { + @Override + protected Move newObject() { + return new Move(); + } + }; + + public static Move get(Pawn pawn, Tile from, Tile to, Orientation orientation, Path path) + { + Move m = movePool.obtain(); + m.pawn = pawn; + m.from = from; + m.to = to; + m.orientation = orientation; + if (path != null) { + m.init(path.tiles.size()); + m.cost = path.cost; + m.roadMarch = path.roadMarch; + for (Tile tile : path.tiles) + m.tiles.add(tile); + } else { + m.init(0); + } + + return m; + } + + public static void clearPool() + { + movePool.clear(); + } + + public static Move getEnter(Pawn pawn, Tile to, Orientation orientation) + { + Move m = get(pawn, null, to, orientation, null); + m.type = MoveType.ENTER; + m.cost = to.costFrom(pawn, orientation); + return m; + } + + public static Move getSet(Pawn pawn, Tile to, Orientation orientation) + { + Move m = get(pawn, null, to, orientation, null); + m.type = MoveType.SET; + m.cost = 0; + return m; + } + + public Pawn pawn; + public Tile from; + public Tile to; + public Orientation orientation; + public MoveType type; + + public Move() + { + super(); + this.pawn = null; + this.from = null; + this.to = null; + this.orientation = Orientation.KEEP; + this.type = MoveType.REGULAR; + } + + @Override + public void reset() + { + pawn = null; + from = null; + to = null; + orientation = Orientation.KEEP; + type = MoveType.REGULAR; + super.reset(); + } + + @Override + public void dispose() + { + tiles.clear(); + movePool.free(this); + } + + public boolean isSet() + { + return (type == MoveType.SET); + } + + public boolean isEnter() + { + return (type == MoveType.ENTER); + } + + public boolean isRegular() + { + return (type == MoveType.REGULAR); + } + + public boolean isFinal() + { + return (type != MoveType.ENTER); + } + + public int steps() + { + int steps = 0; + + Tile tile = from; + Orientation o = pawn.getOrientation(); + for (Tile next : tiles) { + Orientation nextO = Orientation.fromMove(tile.col, tile.row, next.col, next.row); + if (nextO != o) { + steps += 2; + o = nextO; + } else + steps += 1; + tile = next; + } + if (orientation != Orientation.fromMove(tile.col, tile.row, to.col, to.row)) + steps += 2; + else + steps +=1; + + return steps; + } + + @Override + public String toString() + { + if (from == null) + return String.format("%s %s c:%d", to.toShort(), orientation, cost); + else + return String.format("%s->%s %s c:%d", from.toShort(), to.toShort(), orientation, cost); + } + + @Override + public Iterator<Vector3> iterator() + { + return new PathIterator(pawn, from, to, orientation, tiles); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Objective.java b/core/src/ch/asynk/rustanddust/engine/Objective.java new file mode 100644 index 0000000..de1c7d3 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Objective.java @@ -0,0 +1,49 @@ +package ch.asynk.rustanddust.engine; + +public class Objective +{ + protected Faction curFaction; + protected Faction prevFaction; + private boolean persistent; + + public Objective(Faction faction, boolean persistent) + { + this.curFaction = faction; + this.prevFaction = faction; + this.persistent = persistent; + } + + public boolean is(Faction faction) + { + return (curFaction == faction); + } + + public Faction faction() + { + return curFaction; + } + + public boolean set(Faction faction) + { + if (faction == curFaction) + return false; + + prevFaction = curFaction; + curFaction = faction; + return true; + } + + public boolean unset() + { + if (persistent) + return false; + revert(); + return true; + } + + public Faction revert() + { + curFaction = prevFaction; + return curFaction; + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java b/core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java new file mode 100644 index 0000000..5618a9d --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/ObjectiveSet.java @@ -0,0 +1,78 @@ +package ch.asynk.rustanddust.engine; + +import java.util.Collection; +import java.util.HashMap; + +public class ObjectiveSet extends HashMap<Tile, Objective> +{ + public interface ObjectiveCb + { + public void showObjective(Tile tile, Faction faction); + } + + private final Board board; + private final HashMap<Objective, Tile> modified; + + public ObjectiveSet(Board board, int n) + { + super(n); + this.board = board; + this.modified = new HashMap<Objective, Tile>(10); + } + + public void add(Tile tile, Faction faction, boolean persistent) + { + put(tile, new Objective(faction, persistent)); + } + + public int count(Faction faction) + { + int n = 0; + for (Objective objective : values()) { + if (objective.is(faction)) + n += 1; + } + return n; + } + + public Faction claim(Tile tile, Faction faction) + { + Objective objective = get(tile); + if (objective == null) + return null; + + if (objective.set(faction)) + modified.put(objective, tile); + return objective.faction(); + } + + public Faction unclaim(Tile tile) + { + Objective objective = get(tile); + if (objective == null) + return null; + + if (objective.unset()) + modified.remove(objective); + return objective.faction(); + } + + public void forget() + { + modified.clear(); + } + + public int modifiedCount() + { + return modified.size(); + } + + public void revert(ObjectiveCb cb) + { + for (Objective objective : modified.keySet()) { + objective.revert(); + cb.showObjective(modified.get(objective), objective.faction()); + } + modified.clear(); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Order.java b/core/src/ch/asynk/rustanddust/engine/Order.java new file mode 100644 index 0000000..960b126 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Order.java @@ -0,0 +1,16 @@ +package ch.asynk.rustanddust.engine; + +import java.lang.Comparable; + +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.utils.Json; + +public abstract class Order implements Disposable, Pool.Poolable, Json.Serializable, Comparable<Pawn> +{ + public interface OrderType + { + } + + public abstract boolean isA(OrderType type); +} diff --git a/core/src/ch/asynk/rustanddust/engine/OrderList.java b/core/src/ch/asynk/rustanddust/engine/OrderList.java new file mode 100644 index 0000000..4488cfe --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/OrderList.java @@ -0,0 +1,64 @@ +package ch.asynk.rustanddust.engine; + +import java.util.LinkedList; +import java.util.Iterator; + +import com.badlogic.gdx.utils.Json; +import com.badlogic.gdx.utils.JsonValue; +import com.badlogic.gdx.utils.JsonWriter.OutputType; + +public class OrderList extends LinkedList<Order> implements Json.Serializable +{ + public void dispose(Pawn pawn) + { + Iterator<Order> it = iterator(); + while(it.hasNext()) { + Order order = it.next(); + if (order.compareTo(pawn) == 0) { + it.remove(); + order.dispose(); + } + } + } + + public void dispose(Pawn pawn, Order.OrderType type) + { + Iterator<Order> it = iterator(); + while(it.hasNext()) { + Order order = it.next(); + if ((order.compareTo(pawn) == 0) && (order.isA(type))) { + it.remove(); + order.dispose(); + } + } + } + + public void dispose() + { + for (Order o : this) + o.dispose(); + clear(); + } + + public String toJson() + { + Json json = new Json(); + json.setOutputType(OutputType.json); + return json.toJson(this); + } + + @Override + public void write(Json json) + { + json.writeArrayStart("commands"); + for (Order o : this) + json.writeValue(o); + json.writeArrayEnd(); + } + + @Override + public void read(Json json, JsonValue jsonMap) + { + // TODO read(Json json, JsonValue jsonMap) + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Orientation.java b/core/src/ch/asynk/rustanddust/engine/Orientation.java new file mode 100644 index 0000000..2c3ef58 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Orientation.java @@ -0,0 +1,134 @@ +package ch.asynk.rustanddust.engine; + +public enum Orientation +{ + ALL(0, 63), + KEEP(0, 0), + NORTH(270, 1), + NORTH_EAST(210, 2), + SOUTH_EAST(150, 4), + SOUTH(90, 8), + SOUTH_WEST (30, 16), + NORTH_WEST(330, 32); + + public static int offset = 0; + public static float delta = 5f; + private final int r; + public final int s; + + Orientation(int r, int s) { this.r = r; this.s = s; } + + public float r() { return offset + r; } + + public boolean isInSides(int sides) + { + return ((sides & s) == s); + } + + public Orientation left() + { + if (this == NORTH) return NORTH_WEST; + else return fromSide(s >> 1); + } + + public Orientation right() + { + if (this == NORTH_WEST) return NORTH; + else return fromSide(s << 1); + } + + public Orientation opposite() + { + return left().left().left(); + } + + public int allBut() + { + return ALL.s & (s ^ 0xFFFF); + } + + public int getFrontSides() + { + return s | left().s | right().s; + } + + public int getBackSides() + { + return opposite().getFrontSides(); + } + + public static Orientation fromSide(int s) + { + if (s == NORTH.s) return NORTH; + else if (s == NORTH_EAST.s) return NORTH_EAST; + else if (s == SOUTH_EAST.s) return SOUTH_EAST; + else if (s == SOUTH.s) return SOUTH; + else if (s == SOUTH_WEST.s) return SOUTH_WEST; + else if (s == NORTH_WEST.s) return NORTH_WEST; + else return KEEP; + } + + public static Orientation fromRotation(float r) + { + if (r < 0) r += 360f; + if ((r > (NORTH.r - 5f)) && (r < (NORTH.r + 5f))) return NORTH; + else if ((r > (NORTH_EAST.r - delta)) && (r < (NORTH_EAST.r + delta))) return NORTH_EAST; + else if ((r > (SOUTH_EAST.r - delta)) && (r < (SOUTH_EAST.r + delta))) return SOUTH_EAST; + else if ((r > (SOUTH.r - delta)) && (r < (SOUTH.r + delta))) return SOUTH; + else if ((r > (SOUTH_WEST.r - delta)) && (r < (SOUTH_WEST.r + delta))) return SOUTH_WEST; + else if ((r > (NORTH_WEST.r - delta)) && (r < (NORTH_WEST.r + delta))) return NORTH_WEST; + else return KEEP; + } + + public static Orientation fromMove(int col0, int row0, int col1, int row1) + { + int dx = col1 - col0; + int dy = row1 - row0; + + if (dy == 0) { + if (dx == 0) return KEEP; + if (dx > 0) return NORTH; + return SOUTH; + } + if (dy > 0) { + if (dx > 0) return NORTH_WEST; + return SOUTH_WEST; + } else { + if (dx < 0) return SOUTH_EAST; + return NORTH_EAST; + } + } + + public static Orientation fromAdj(Tile from, Tile to) + { + return fromAdj(from.col, from.row, to.col, to.row); + } + + public static Orientation fromAdj(int col0, int row0, int col1, int row1) + { + Orientation o = KEEP; + + if (row1 == row0) { + if (col1 == (col0 - 1)) { + o = SOUTH; + } else if (col1 == (col0 + 1)) { + o = NORTH; + } + } else if (row1 == (row0 - 1)) { + if (col1 == (col0 - 1)) { + o = SOUTH_EAST; + } else if (col1 == col0) { + o = NORTH_EAST; + } + + } else if (row1 == (row0 + 1)) { + if (col1 == col0) { + o = SOUTH_WEST; + } else if (col1 == (col0 + 1)) { + o = NORTH_WEST; + } + } + + return o; + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Path.java b/core/src/ch/asynk/rustanddust/engine/Path.java new file mode 100644 index 0000000..2ddf3e5 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Path.java @@ -0,0 +1,62 @@ +package ch.asynk.rustanddust.engine; + +import java.util.ArrayList; + +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.utils.Disposable; + +public class Path implements Disposable, Pool.Poolable +{ + private static final Pool<Path> pathPool = new Pool<Path>() { + @Override + protected Path newObject() { + return new Path(); + } + }; + + public static Path get(int size) + { + Path p = pathPool.obtain(); + p.init(size); + return p; + } + + public static void clearPool() + { + pathPool.clear(); + } + + public int cost; + public boolean roadMarch; + public ArrayList<Tile> tiles; + + public Path() + { + this.cost = -1; + this.roadMarch = true; + this.tiles = null; + } + + protected void init(int size) + { + if (tiles == null) + tiles = new ArrayList<Tile>(size); + else + tiles. ensureCapacity(size); + } + + @Override + public void reset() + { + cost = -1; + roadMarch = true; + tiles.clear(); + } + + @Override + public void dispose() + { + tiles.clear(); + pathPool.free(this); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/PathBuilder.java b/core/src/ch/asynk/rustanddust/engine/PathBuilder.java new file mode 100644 index 0000000..3692a91 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/PathBuilder.java @@ -0,0 +1,266 @@ +package ch.asynk.rustanddust.engine; + +import java.util.ArrayList; +import java.util.List; +import java.util.LinkedList; +import java.util.HashSet; +import java.util.LinkedHashSet; + +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; + +public class PathBuilder implements Disposable +{ + private final Board board; + + public Pawn pawn; + public Tile from; + public Tile to; + public int distance; + public Orientation orientation; + private List<Tile> stack; + private List<Tile> ctrlTiles; + private List<Path> paths; + private List<Path> filteredPaths; + private HashSet<Tile> tiles; + + public PathBuilder(Board board, int tSize, int stSize, int ftSize, int vectSize) + { + this.board = board; + this.tiles = new LinkedHashSet<Tile>(tSize); + this.stack = new ArrayList<Tile>(stSize); + this.ctrlTiles = new ArrayList<Tile>(ftSize); + this.paths = new LinkedList<Path>(); + this.filteredPaths = new LinkedList<Path>(); + this.to = null; + this.pawn = null; + this.orientation = Orientation.KEEP; + } + + public void init(Pawn pawn, Tile from) + { + this.pawn = pawn; + this.from = from; + } + + public void init(Pawn pawn) + { + init(pawn, pawn.getTile()); + } + + public void initRotation(Pawn pawn, Orientation o) + { + init(pawn, pawn.getTile()); + build(pawn.getTile()); + orientation = o; + } + + public boolean isSet() + { + return (to != null); + } + + @Override + public void dispose() + { + clear(); + } + + public void clear() + { + this.to = null; + this.distance = -1; + this.orientation = Orientation.KEEP; + for (Path path : this.paths) path.dispose(); + this.tiles.clear(); + this.stack.clear(); + this.ctrlTiles.clear(); + this.paths.clear(); + this.filteredPaths.clear(); + } + + public int size() + { + if (ctrlTiles.size() == 0) + return paths.size(); + return filteredPaths.size(); + } + + public boolean contains(Tile tile) + { + return tiles.contains(tile); + } + + public void enable(int i, boolean enable) + { + for (Tile tile : tiles) + board.enableOverlayOn(tile, i, enable); + } + + public int build(Tile to) + { + clear(); + this.to = to; + // from and to are not part of the path + this.distance = board.distance(from, to); + if (distance < 2) { + Orientation o = Orientation.fromMove(to.col, to.row, from.col, from.row); + Path path = Path.get(0); + path.roadMarch = to.road(o); + path.cost = to.costFrom(pawn, o); + paths.add(path); + } else { + findAllPaths(from, pawn.getMovementPoints(), true); + } + + // printToErr("paths", paths); + stack.clear(); + return paths.size(); + } + + private void findAllPaths(Tile from, int mvtLeft, boolean roadMarch) + { + Tile moves[] = new Tile[6]; + board.setAdjacentTiles(from, moves); + + for(int i = 0; i < 6; i++) { + Tile next = moves[i]; + if ((next == null) || next.isOffMap()) continue; + + Orientation o = board.getSide(i); + int m = (mvtLeft - next.costFrom(pawn, o)); + boolean r = roadMarch & next.road(o); + + int l = (m + (r ? pawn.getRoadMarchBonus() : 0)); + + if ((board.distance(next, to) <= l)) { + if (next == to) { + Path path = Path.get(stack.size() + 1); + for (Tile t: stack) { + path.tiles.add(t); + tiles.add(t); + } + path.roadMarch = r; + path.cost = (pawn.getMovementPoints() - m); + paths.add(path); + } else { + stack.add(next); + findAllPaths(next, m, r); + stack.remove(stack.size() - 1); + } + } + } + } + + public int toggleCtrlTile(Tile tile) + { + if (ctrlTiles.contains(tile)) + ctrlTiles.remove(tile); + else + ctrlTiles.add(tile); + return filterPaths(); + } + + private int filterPaths() + { + int s = ctrlTiles.size(); + + tiles.clear(); + filteredPaths.clear(); + for (Path path : paths) { + int ok = 0; + for (Tile filter : ctrlTiles) { + if (path.tiles.contains(filter)) + ok += 1; + } + if (ok == s) { + if (path.tiles.size() == (s + 0)) { // from and to are not part of the path + filteredPaths.clear(); + filteredPaths.add(path); + tiles.clear(); + for (Tile tile : path.tiles) tiles.add(tile); + break; + } else { + filteredPaths.add(path); + for (Tile tile : path.tiles) tiles.add(tile); + } + } + } + + // printToErr("filteredPaths", filteredPaths); + return filteredPaths.size(); + } + + public int pathCost(int i) + { + return paths.get(i).cost; + } + + public Move getMove() + { + if (size() != 1) { + System.err.println("ask for only move but they are many"); + return null; + } + + return Move.get(pawn, from, to, orientation, getPath(0)); + } + + public Move getExitMove() + { + Move move = getMove(); + move.type = Move.MoveType.EXIT; + return move; + } + + public boolean canExit(Orientation o) + { + List<Path> ps; + if (ctrlTiles.size() == 0) + ps = paths; + else + ps = filteredPaths; + + int mvt = pawn.getMovementPoints(); + int rBonus = pawn.getRoadMarchBonus(); + boolean road = to.road(o); + int cost = to.exitCost(); + + for (Path p : ps) { + int c = (p.cost + cost); + if ((c <= mvt) || (p.roadMarch && road && (c <= (mvt + rBonus)))) + return true; + } + return false; + } + + public Path getPath(int i) + { + if (ctrlTiles.size() == 0) + return paths.get(i); + return filteredPaths.get(i); + } + + public void setExit(Orientation o) + { + orientation = o; + Path path = getPath(0); + if (from != to) { + path.cost += 1; + path.tiles.add(to); + } + to = board.getAdjTileAt(to, o); + } + + private void printToErr(String what, List<Path> paths) + { + System.err.println(what + pawn + " ("+paths.size()+") " + from + " -> " + to); + for (Path path : paths) { + System.err.println(String.format(" - path (l:%d c:%d r:%b)", path.tiles.size(), path.cost, path.roadMarch)); + for(Tile tile : path.tiles) + System.err.println(" " + tile.toString()); + } + System.err.println(); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/PathIterator.java b/core/src/ch/asynk/rustanddust/engine/PathIterator.java new file mode 100644 index 0000000..53f2f82 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/PathIterator.java @@ -0,0 +1,73 @@ +package ch.asynk.rustanddust.engine; + +import java.util.List; +import java.util.Iterator; + +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; + +public class PathIterator implements Iterator<Vector3> +{ + private Pawn pawn; + private Tile to; + private Orientation o; + private Orientation orientation; + private Tile tile; + private Vector2 pos = new Vector2(); + private Vector3 v = new Vector3(); + private int i; + private List<Tile> path; + + public PathIterator(Pawn pawn, Tile from, Tile to, Orientation orientation, List<Tile> path) + { + this.pawn = pawn; + this.to = to; + this.tile = from; + this.orientation = orientation; + this.path = path; + this.o = pawn.getOrientation(); + this.v.set(pawn.getPosition().x, pawn.getPosition().y, o.r()); + this.i = 0; + } + + @Override + public boolean hasNext() + { + if ((tile == to) && (o == orientation)) + return false; + return true; + } + + @Override + public Vector3 next() + { + if (tile == to) { + v.z = orientation.r(); + o = orientation; + return v; + } + Tile nextTile; + if (i < path.size()) + nextTile = path.get(i); + else + nextTile = to; + Orientation nextO = Orientation.fromMove(tile.col, tile.row, nextTile.col, nextTile.row); + if (nextO != o) { + v.z = nextO.r(); + o = nextO; + return v; + } + pawn.getPosAt(nextTile, pos); + v.x = pos.x; + v.y = pos.y; + tile = nextTile; + i += 1; + return v; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Pawn.java b/core/src/ch/asynk/rustanddust/engine/Pawn.java new file mode 100644 index 0000000..f95347f --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Pawn.java @@ -0,0 +1,365 @@ +package ch.asynk.rustanddust.engine; + +import java.util.Iterator; + +import com.badlogic.gdx.utils.Disposable; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; + +import ch.asynk.rustanddust.engine.gfx.Moveable; +import ch.asynk.rustanddust.engine.gfx.StackedImages; +import ch.asynk.rustanddust.engine.gfx.animations.MoveToAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.RunnableAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.AnimationSequence; + +public abstract class Pawn implements Moveable, Disposable +{ + public interface PawnType + { + } + + public interface PawnId + { + } + + private static final float MOVE_TIME = 0.4f; + + private Vector3 position; + private Vector3 prevPosition; + private Tile tile; + private Tile prevTile; + protected Faction faction; + protected String descr; + private Sprite sprite; + private StackedImages overlays; + protected Attack attack; + protected Move move; + + public abstract int getMovementPoints(); + public abstract int getRoadMarchBonus(); + public abstract int getAngleOfAttack(); + public abstract int getFlankSides(); + public abstract int getEngagementRangeFrom(Tile tile); + public abstract int getDefense(Tile tile); + + public abstract boolean isUnit(); + public abstract boolean isA(PawnId id); + public abstract boolean isA(PawnType type); + public abstract boolean isHq(); + public abstract boolean isHqOf(Pawn other); + public abstract boolean isHardTarget(); + + public abstract boolean canMove(); + public abstract boolean canRotate(); + public abstract boolean canEngage(); + public abstract boolean canEngage(Pawn other); + public abstract boolean canAssistEngagementWithoutLos(); + + public abstract void move(); + public abstract void engage(); + + public abstract void revertLastMove(); + + protected Pawn() + { + this.tile = null; + this.prevTile = null; + this.position = new Vector3(0f, 0f, 0f); + this.prevPosition = new Vector3(0f, 0f, 0f); + this.attack = new Attack(this); + } + + public Pawn(Faction faction, String name, TextureAtlas pawns, TextureAtlas overlays) + { + this(); + this.faction = faction; + this.descr = descr; + this.sprite = new Sprite(pawns.findRegion(name)); + this.overlays = new StackedImages(overlays); + } + + @Override + public String toString() + { + return descr; + } + + @Override + public void dispose() + { + } + + @Override + public Faction getFaction() + { + return faction; + } + + public void reset() + { + move = null; + attack.reset(); + } + + public void move(Move move) + { + switch(move.type) + { + case REGULAR: + if ((this.move != null) && (!this.move.isEnter())) + throw new RuntimeException("try to override an existing move instance"); + break; + case ENTER: + if (this.move != null) + throw new RuntimeException("try to override an existing move instance"); + break; + case SET: + break; + default: + throw new RuntimeException("unsupported MoveType"); + } + + this.move = move; + move(); + } + + public void setAttack(Pawn target, int distance) + { + attack.reset(); + attack.target = target; + attack.distance = distance; + } + + public boolean justEntered() + { + return ((move != null) && move.isEnter()); + } + + public boolean is(Faction faction) + { + return (this.faction == faction); + } + + public boolean isEnemy(Faction other) + { + return faction.isEnemy(other); + } + + public boolean isEnemy(Pawn other) + { + return faction.isEnemy(other.faction); + } + + public boolean isFlankAttack() + { + return (attack.isClear && attack.isFlank); + } + + public int attackDistance() + { + return attack.distance; + } + + public Tile getTile() + { + return tile; + } + + public Tile getPreviousTile() + { + return prevTile; + } + + public Vector3 getPosition() + { + return position; + } + + public Vector3 getPreviousPosition() + { + return prevPosition; + } + + private void revertPosition() + { + this.tile = this.prevTile; + this.prevTile = null; + position.set(prevPosition); + prevPosition.set(0f, 0f, 0f); + setPosition(position.x, position.y, position.z); + } + + public float getCenterX() + { + return (getX() + (getWidth() / 2f)); + } + + public float getCenterY() + { + return (getY() + (getHeight() / 2f)); + } + + public Vector2 getPosAt(Tile tile, Vector2 pos) + { + float x = (tile.getX() - (getWidth() / 2f)); + float y = (tile.getY() - (getHeight() / 2f)); + if (pos == null) + return new Vector2(x, y); + else + pos.set(x, y); + return pos; + } + + public void setOnTile(Tile tile, float z) + { + this.prevTile = this.tile; + this.tile = tile; + float x = (tile.getX() - (getWidth() / 2f)); + float y = (tile.getY() - (getHeight() / 2f)); + setPosition(x, y, z); + } + + @Override + public void setAlpha(float alpha) + { + sprite.setAlpha(alpha); + overlays.setAlpha(alpha); + } + + @Override + public float getX() + { + return sprite.getX(); + } + + @Override + public float getY() + { + return sprite.getY(); + } + + @Override + public float getWidth() + { + return sprite.getWidth(); + } + + @Override + public float getHeight() + { + return sprite.getHeight(); + } + + @Override + public float getRotation() + { + return sprite.getRotation(); + } + + public Orientation getOrientation() + { + return Orientation.fromRotation(getRotation()); + } + + public void translate(float dx, float dy) + { + setPosition((getX() + dx), (getY() + dy)); + } + + public void centerOn(float x, float y) + { + setPosition((x - (getWidth() / 2f)), (y - (getHeight() / 2f))); + } + + @Override + public void setPosition(float x, float y) + { + position.set(x, y, 0f); + sprite.setPosition(x, y); + float cx = x + (getWidth() / 2f); + float cy = y + (getHeight() / 2f); + overlays.centerOn(cx, cy); + } + + public void setRotation(float z) + { + position.z = z; + sprite.setRotation(z); + overlays.setRotation(z); + } + + @Override + public void setPosition(float x, float y, float z) + { + setPosition(x, y); + setRotation(z); + } + + public boolean hasOverlayEnabled() + { + return overlays.isEnabled(); + } + + public boolean enableOverlay(int i, boolean enable) + { + overlays.enable(i, enable); + if (enable) return true; + return hasOverlayEnabled(); + } + + public AnimationSequence getRotateAnimation(float z, int size) + { + prevPosition.set(position); + AnimationSequence seq = AnimationSequence.get(1 + size); + seq.addAnimation(MoveToAnimation.get(this, position.x, position.y, z, MOVE_TIME)); + + return seq; + } + + public AnimationSequence getMoveAnimation(Iterator<Vector3> vectors, int size, MoveToAnimation.MoveToAnimationCb cb) + { + prevPosition.set(position); + AnimationSequence seq = AnimationSequence.get(size); + while (vectors.hasNext()) + seq.addAnimation(MoveToAnimation.get(this, vectors.next(), MOVE_TIME, cb)); + + return seq; + } + + public AnimationSequence getRevertLastMoveAnimation(int size) + { + AnimationSequence seq = AnimationSequence.get(2 + size); + seq.addAnimation(MoveToAnimation.get(this, prevPosition, MOVE_TIME)); + seq.addAnimation(RunnableAnimation.get(this, new Runnable() { + @Override + public void run() { + revertPosition(); + } + })); + + return seq; + } + + @Override + public void draw(Batch batch) + { + sprite.draw(batch); + overlays.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + float w = sprite.getWidth(); + float h = sprite.getHeight(); + debugShapes.rect(sprite.getX(), sprite.getY(), (w / 2f), (h / 2f), w, h, sprite.getScaleX(), sprite.getScaleY(), sprite.getRotation()); + overlays.drawDebug(debugShapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/SearchBoard.java b/core/src/ch/asynk/rustanddust/engine/SearchBoard.java new file mode 100644 index 0000000..1d8ed88 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/SearchBoard.java @@ -0,0 +1,545 @@ +package ch.asynk.rustanddust.engine; + +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.ArrayDeque; +import java.util.Iterator; +import java.util.Collection; + +public class SearchBoard +{ + public class Node + { + public int col; + public int row; + public int search; + public int remaining; + public Node parent; + public boolean roadMarch; + + public Node(int col, int row) + { + this.col = col; + this.row = row; + } + + @Override + public String toString() + { + return col + ";" + row; + } + } + + private int cols; + private int rows; + private Board board; + private int searchCount; + private Node nodes[]; + + private ArrayDeque<Node> stack; + private LinkedList<Node> queue; + private ArrayDeque<Node> roadMarch; + private List<Node> los; + + public SearchBoard(Board board, int cols, int rows) + { + this.cols = cols; + this.rows = rows; + this.board = board; + this.searchCount = 0; + + this.nodes = new Node[cols * rows]; + for (int j = 0; j < rows; j++) { + int dx = ((j + 1) / 2); + for (int i = 0; i < cols; i++) + nodes[i + (j * cols)] = new Node((i + dx), j); + } + + this.queue = new LinkedList<Node>(); + this.stack = new ArrayDeque<Node>(20); + this.roadMarch = new ArrayDeque<Node>(5); + this.los = new ArrayList<Node>(10); + } + + private boolean inMap(int col, int row) + { + if ((row < 0) || (row >= rows)) + return false; + + int colOffset = ((row + 1) / 2); + if ((col < colOffset) || ((col - colOffset) >= cols)) + return false; + + return true; + } + + private Tile getTile(Node node) + { + return board.getTile(node.col, node.row); + } + + private Node getNode(Tile tile) + { + return getNode(tile.col, tile.row); + } + + protected Node getNode(int col, int row) + { + int colOffset = ((row + 1) / 2); + if ((col < colOffset) || (row < 0) || (row >= rows) || ((col - colOffset) >= cols)) + return null; + + return nodes[((col - colOffset)) + (row * cols)]; + } + + public int distance(Node from, Node to) + { + return board.distance(from.col, from.row, to.col, to.row); + } + + public void adjacentMoves(Node src, Node a[]) + { + // move to enter dst by sides[i] + a[0] = getNode((src.col - 1), src.row); + a[1] = getNode(src.col, (src.row + 1)); + a[2] = getNode((src.col + 1), (src.row + 1)); + a[3] = getNode((src.col + 1), src.row); + a[4] = getNode(src.col, (src.row - 1)); + a[5] = getNode((src.col - 1), (src.row - 1)); + } + + public int possibleMovesFrom(Pawn pawn, Collection<Tile> moves) + { + moves.clear(); + searchCount += 1; + + Node adjacents[] = new Node[6]; + + Node from = getNode(pawn.getTile()); + from.parent = null; + from.search = searchCount; + from.remaining = pawn.getMovementPoints(); + from.roadMarch = true; + + if (from.remaining <= 0) + return moves.size(); + + int roadMarchBonus = pawn.getRoadMarchBonus(); + boolean first = true; + + stack.push(from); + + while (stack.size() != 0) { + Node src = stack.pop(); + + if (src.remaining < 0) + continue; + if (src.remaining == 0) { + if (src.roadMarch) + roadMarch.push(src); + continue; + } + + adjacentMoves(src, adjacents); + + for(int i = 0; i < 6; i++) { + Node dst = adjacents[i]; + if (dst != null) { + + Tile t = getTile(dst); + int cost = t.costFrom(pawn, board.getSide(i)); + boolean mayMoveOne = first && t.atLeastOneMove(pawn); + int r = src.remaining - cost; + boolean roadMarch = (src.roadMarch && t.road(board.getSide(i))); + + if (dst.search == searchCount) { + if ((r >= 0) && ((r > dst.remaining) || (roadMarch && ((r + roadMarchBonus) >= dst.remaining)))) { + dst.remaining = r; + dst.parent = src; + dst.roadMarch = roadMarch; + stack.push(dst); + moves.add(getTile(dst)); + } + } else { + dst.search = searchCount; + if ((r >= 0) || mayMoveOne) { + dst.parent = src; + dst.remaining = r; + dst.roadMarch = roadMarch; + stack.push(dst); + moves.add(getTile(dst)); + } else { + dst.parent = null; + dst.remaining = -1; + } + } + } + } + first = false; + } + + for (Node n : roadMarch) n.remaining = roadMarchBonus; + while(roadMarch.size() != 0) { + Node src = roadMarch.pop(); + + adjacentMoves(src, adjacents); + + for(int i = 0; i < 6; i++) { + Node dst = adjacents[i]; + if (dst != null) { + + Tile t = getTile(dst); + if (!t.road(board.getSide(i))) + continue; + int cost = t.costFrom(pawn, board.getSide(i)); + int r = src.remaining - cost; + + if (dst.search == searchCount) { + if ((r >= 0) && (r > dst.remaining)) { + dst.remaining = r; + dst.parent = src; + dst.roadMarch = true; + roadMarch.push(dst); + moves.add(getTile(dst)); + } + } else { + dst.search = searchCount; + if (r >= 0) { + dst.parent = src; + dst.remaining = r; + dst.roadMarch = true; + roadMarch.push(dst); + moves.add(getTile(dst)); + } else { + dst.parent = null; + dst.remaining = -1; + } + } + } + } + } + + return moves.size(); + } + + private void adjacentTargets(Node src, int angle, Node a[]) + { + // move in allowed directions + if (Orientation.NORTH.isInSides(angle)) + a[0] = getNode((src.col + 1), src.row); + else + a[0] = null; + + if (Orientation.NORTH_EAST.isInSides(angle)) + a[1] = getNode(src.col, (src.row - 1)); + else + a[1] = null; + + if (Orientation.SOUTH_EAST.isInSides(angle)) + a[2] = getNode((src.col - 1), (src.row - 1)); + else + a[2] = null; + + if (Orientation.SOUTH.isInSides(angle)) + a[3] = getNode((src.col - 1), src.row); + else + a[3] = null; + + if (Orientation.SOUTH_WEST.isInSides(angle)) + a[4] = getNode(src.col, (src.row + 1)); + else + a[4] = null; + + if (Orientation.NORTH_WEST.isInSides(angle)) + a[5] = getNode((src.col + 1), (src.row + 1)); + else + a[5] = null; + } + + public int possibleTargetsFrom(Pawn pawn, Collection<Pawn> targets) + { + targets.clear(); + searchCount += 1; + + Node adjacents[] = new Node[6]; + + int range = pawn.getEngagementRangeFrom(pawn.getTile()); + int angle = pawn.getAngleOfAttack(); + int extendedAngle = pawn.getOrientation().opposite().allBut(); + + Node from = getNode(pawn.getTile()); + from.search = searchCount; + from.remaining = range; + + if (range <= 0) + return targets.size(); + + queue.add(from); + + boolean first = true; + while (queue.size() != 0) { + Node src = queue.remove(); + + if (src.remaining <= 0) + continue; + + if (!first && (((range - src.remaining) % 2) == 0)) + adjacentTargets(src, extendedAngle, adjacents); + else + adjacentTargets(src, angle, adjacents); + + first = false; + int rangeLeft = src.remaining - 1; + + for(int i = 0; i < 6; i++) { + Node dst = adjacents[i]; + if (dst != null) { + if (dst.search == searchCount) { + if ((rangeLeft > dst.remaining)) + dst.remaining = rangeLeft; + } else { + dst.search = searchCount; + dst.remaining = rangeLeft; + queue.add(dst); + Tile t = getTile(dst); + if (hasClearLineOfSight(from, dst, angle)) { + Iterator<Pawn> it = t.iterator(); + while (it.hasNext()) { + Pawn target = it.next(); + if (pawn.canEngage(target)) + targets.add(target); + } + } + } + } + } + } + + return targets.size(); + } + + public boolean canAttack(Pawn pawn, Pawn target, boolean clearVisibility) + { + Node from = getNode(pawn.getTile()); + Node to = getNode(target.getTile()); + + pawn.setAttack(target, distance(from, to)); + + if (pawn.attack.distance > pawn.getEngagementRangeFrom(pawn.getTile())) + return false; + + List<Node> los = lineOfSight(from.col, from.row, to.col, to.row, clearVisibility); + Node last = los.get(los.size() -1); + if (last != to) + return false; + + if (!validatePathAngle(pawn.getAngleOfAttack(), los)) + return false; + + pawn.attack.isClear = isClearAttack(getTile(from), los); + pawn.attack.isFlank = isFlankAttack(target.getFlankSides(), los); + + return true; + } + + private boolean hasClearLineOfSight(Node from, Node to, int angleOfAttack) + { + List<Node> los = lineOfSight(from.col, from.row, to.col, to.row, true); + Node last = los.get(los.size() -1); + if ((last.col != to.col) || (last.row != to.row)) + return false; + return validatePathAngle(angleOfAttack, los); + } + + private boolean isFlankAttack(int angle, List<Node> los) + { + Node from = los.get(los.size() - 2); + Node to = los.get(los.size() - 1); + Orientation o = Orientation.fromMove(to.col, to.row, from.col, from.row); + return o.isInSides(angle); + } + + private boolean isClearAttack(Tile from, List<Node> los) + { + int n = los.size() - 1; + for (int i = 1; i < n; i++) { + if (getTile(los.get(i)).blockLineOfSightFrom(from)) + return false; + } + return true; + } + + private boolean validatePathAngle(int angle, List<Node> los) + { + int forth = 0; + Node prev = null; + for (Node next : los) { + if (prev != null) { + Orientation o = Orientation.fromMove(prev.col, prev.row, next.col, next.row); + if (!o.isInSides(angle)) { + forth -= 1; + if (forth < 0) + return false; + } + forth += 1; + } + prev = next; + } + + return true; + } + + public List<Node> lineOfSight(int x0, int y0, int x1, int y1, boolean clearVisibility) + { + los.clear(); + Tile from = board.getTile(x0, y0); + + // orthogonal axis + int ox0 = x0 - ((y0 +1) / 2); + int ox1 = x1 - ((y1 +1) / 2); + + int dy = y1 - y0; + int dx = ox1 - ox0; + + int xs = 1; + int ys = 1; + if (dx < 0) xs = -1; + if (dy < 0) ys = -1; + boolean sig = !(((dx < 0) && (dy >= 0)) || ((dx >= 0) && (dy < 0))); + + dy = Math.abs(dy); + dx = Math.abs(2 * dx); + if ((dy % 2) == 1) { + if ((y0 % 2) == 0) dx += xs; + else { + dx -= xs; + Math.abs(dx); + } + } + + if (dx == 0) + return verticalLineOfSight(x0, y0, x1, y1, clearVisibility); + if (dx == (3 * dy)) + return diagonalLineOfSight(x0, y0, x1, y1, clearVisibility); + + int dx3 = 3 * dx; + int dy3 = 3 * dy; + + int x = x0; + int y = y0; + int e = -2 * dx; + + boolean flat = (dx > (3 * dy)); + boolean diag = (dx == (3 * dy)); + + los.add(getNode(x, y)); + while((x != x1) || (y != y1)) { + if (e > 0) { + e -= (dy3 + dx3); + y += ys; + if (!sig) + x -= xs; + } else { + e += dy3; + if ((e > -dx) || (!flat && (e == -dx))) { + e -= dx3; + y += ys; + if (sig) + x += xs; + } else if ((e < -dx3) || (diag && (e == -dx3))) { + e += dx3; + y -= ys; + if (!sig) + x += xs; + } else { + e += dy3; + x += xs; + } + } + los.add(getNode(x, y)); + if(clearVisibility && board.getTile(x, y).blockLineOfSightFrom(from)) return los; + } + + return los; + } + + private List<Node> verticalLineOfSight(int x0, int y0, int x1, int y1, boolean clearVisibility) + { + Tile from = board.getTile(x0, y0); + + int d = ( (y1 > y0) ? 1 : -1); + int x = x0; + int y = y0; + + Tile t = null; + los.add(getNode(x, y)); + while((x != x1) || (y != y1)) { + boolean ok = false; + + y += d; + t = board.getTile(x, y); + if (!t.isOffMap()) los.add(getNode(x, y)); + if (!clearVisibility || !t.blockLineOfSightFrom(from)) + ok = true; + + x += d; + t = board.getTile(x, y); + if (!t.isOffMap()) los.add(getNode(x, y)); + if (!clearVisibility || !t.blockLineOfSightFrom(from)) + ok = true; + + if (!ok) + return los; + + y += d; + t = board.getTile(x, y); + if (!t.isOffMap()) los.add(getNode(x, y)); + } + + return los; + } + + private List<Node> diagonalLineOfSight(int x0, int y0, int x1, int y1, boolean clearVisibility) + { + Tile from = board.getTile(x0, y0); + + int dy = ( (y1 > y0) ? 1 : -1); + int dx = ( (x1 > x0) ? 1 : -1); + boolean sig = !(((dx < 0) && (dy >= 0)) || ((dx >= 0) && (dy < 0))); + + int x = x0; + int y = y0; + + Tile t = null; + los.add(getNode(x, y)); + while((x != x1) || (y != y1)) { + boolean ok = false; + + x += dx; + t = board.getTile(x, y); + if (!t.isOffMap()) los.add(getNode(x, y)); + if (!clearVisibility || !t.blockLineOfSightFrom(from)) + ok = true; + + y += dy; + if (!sig) + x -= dx; + t = board.getTile(x, y); + if (!t.isOffMap()) los.add(getNode(x, y)); + if (!clearVisibility || !t.blockLineOfSightFrom(from)) + ok = true; + + if (!ok) + return los; + + x += dx; + t = board.getTile(x, y); + if (!t.isOffMap()) los.add(getNode(x, y)); + } + + return los; + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/SelectedTile.java b/core/src/ch/asynk/rustanddust/engine/SelectedTile.java new file mode 100644 index 0000000..0e1d8ac --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/SelectedTile.java @@ -0,0 +1,80 @@ +package ch.asynk.rustanddust.engine; + +import com.badlogic.gdx.utils.Disposable; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Drawable; +import ch.asynk.rustanddust.engine.gfx.Animation; +import ch.asynk.rustanddust.engine.gfx.animations.Sprites; + +public class SelectedTile implements Disposable, Drawable, Animation +{ + private Sprites sprites; + public Tile tile; + public boolean visible; + public float x; + public float y; + private float elapsed; + private int frame; + private float[] seq; + + public SelectedTile(Texture texture, float[] seq) + { + this.sprites = new Sprites(texture, seq.length, 1); + this.visible = false; + this.tile = null; + this.elapsed = 0f; + this.seq = seq; + } + + public void hide() + { + tile = null; + visible = false; + } + + public void set(Tile tile) + { + this.visible = true; + this.tile = tile; + this.frame = 0; + this.elapsed = 0f; + this.x = (tile.getX() - (sprites.width / 2f)); + this.y = (tile.getY() - (sprites.height / 2f)); + } + + public void dispose() + { + sprites.dispose(); + } + + @Override + public boolean animate(float delta) + { + if (visible) { + elapsed += delta; + if (elapsed > seq[frame]) { + frame = ((frame + 1) % sprites.frames.length); + elapsed = 0f; + } + } + return false; + } + + @Override + public void draw(Batch batch) + { + if (visible) + batch.draw(sprites.frames[frame], x, y); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + if (visible) + debugShapes.rect(x, y, sprites.width, sprites.height); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/Tile.java b/core/src/ch/asynk/rustanddust/engine/Tile.java new file mode 100644 index 0000000..f44e763 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/Tile.java @@ -0,0 +1,175 @@ +package ch.asynk.rustanddust.engine; + +import java.util.List; +import java.util.Iterator; +import java.util.ArrayDeque; + +import com.badlogic.gdx.utils.Disposable; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.math.Vector2; + +import ch.asynk.rustanddust.engine.Board; +import ch.asynk.rustanddust.engine.gfx.Drawable; +import ch.asynk.rustanddust.engine.gfx.StackedImages; + +public abstract class Tile implements Drawable, Disposable, Iterable<Pawn> +{ + public interface TileTerrain + { + } + + protected int col; + protected int row; + protected float x; + protected float y; + private StackedImages overlays; + protected ArrayDeque<Pawn> stack; + + public abstract int defense(); + public abstract int exitCost(); + public abstract int costFrom(Pawn pawn, Orientation side); + + public abstract boolean isOffMap(); + public abstract boolean isA(TileTerrain terrain); + public abstract boolean road(Orientation side); + public abstract boolean atLeastOneMove(Pawn pawn); + public abstract boolean blockLineOfSightFrom(Tile tile); + + protected Tile(int col, int row) + { + this.col = col; + this.row = row; + } + + public Tile(float x, float y, int col, int row, TextureAtlas atlas) + { + this.stack = new ArrayDeque<Pawn>(); + this.x = x; + this.y = y; + this.col = col; + this.row = row; + this.overlays = new StackedImages(atlas); + this.overlays.centerOn(x, y); + } + + public float getX() { return x; } + public float getY() { return y; } + public int getCol() { return col; } + public int getRow() { return row; } + + @Override + public String toString() + { + return String.format("(%d;%d) %s", col, row, (isOffMap() ? "x" : "")); + } + + public String toShort() + { + return String.format("(%d;%d)", col, row); + } + + @Override + public void dispose() + { + stack.clear(); + overlays.dispose(); + } + + public boolean isEmpty() + { + return stack.isEmpty(); + } + + public Iterator<Pawn> iterator() + { + return stack.iterator(); + } + + public int push(Pawn pawn) + { + if (!stack.contains(pawn)) + stack.push(pawn); + return stack.size(); + } + + public int remove(Pawn pawn) + { + stack.remove(pawn); + return stack.size(); + } + + private Pawn getTopPawn() + { + if (isEmpty()) return null; + return stack.getFirst(); + } + + public boolean hasUnits() + { + if (isEmpty()) return false; + Iterator<Pawn> itr = iterator(); + while(itr.hasNext()) { + if (itr.next().isUnit()) + return true; + } + return false; + } + + public boolean mustBeDrawn() + { + if (!isEmpty()) return true; + return hasOverlayEnabled(); + } + + public boolean disableOverlays() + { + overlays.disableAll(); + return !isEmpty(); + } + + public boolean hasOverlayEnabled() + { + return overlays.isEnabled(); + } + + public boolean isOverlayEnabled(int i) + { + return overlays.isEnabled(i); + } + + public boolean enableOverlay(int i, boolean enable) + { + overlays.enable(i, enable); + if (enable) return true; + return mustBeDrawn(); + } + + public boolean enableOverlay(int i, boolean enable, float r) + { + overlays.enable(i, enable); + overlays.rotate(i, r); + if (enable) return true; + return mustBeDrawn(); + } + + @Override + public void draw(Batch batch) + { + overlays.draw(batch); + Pawn pawn = getTopPawn(); + if (pawn != null) + pawn.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + overlays.drawDebug(debugShapes); + Pawn pawn = getTopPawn(); + if (pawn != null) + pawn.drawDebug(debugShapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/Animation.java b/core/src/ch/asynk/rustanddust/engine/gfx/Animation.java new file mode 100644 index 0000000..eb973de --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/Animation.java @@ -0,0 +1,8 @@ +package ch.asynk.rustanddust.engine.gfx; + +import com.badlogic.gdx.utils.Disposable; + +public interface Animation extends Disposable, Drawable +{ + public boolean animate(float delta); +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java b/core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java new file mode 100644 index 0000000..d405faa --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/Drawable.java @@ -0,0 +1,10 @@ +package ch.asynk.rustanddust.engine.gfx; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +public interface Drawable +{ + public void draw(Batch batch); + public void drawDebug(ShapeRenderer debugShapes); +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java b/core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java new file mode 100644 index 0000000..e8790ab --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/Moveable.java @@ -0,0 +1,16 @@ +package ch.asynk.rustanddust.engine.gfx; + +import ch.asynk.rustanddust.engine.Faction; + +public interface Moveable extends Drawable +{ + public void setAlpha(float alpha); + public float getX(); + public float getY(); + public float getWidth(); + public float getHeight(); + public float getRotation(); + public void setPosition(float x, float y); + public void setPosition(float x, float y, float r); + public Faction getFaction(); +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java b/core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java new file mode 100644 index 0000000..6d4fd1f --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/StackedImages.java @@ -0,0 +1,99 @@ +package ch.asynk.rustanddust.engine.gfx; + +import com.badlogic.gdx.utils.Disposable; + +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.utils.Array; + +public class StackedImages implements Drawable, Disposable +{ + private boolean enabled[]; + private Array<Sprite> sprites; + + public StackedImages(TextureAtlas atlas) + { + this.sprites = atlas.createSprites(); + this.enabled = new boolean[sprites.size]; + } + + @Override + public void dispose() + { + } + + public void disableAll() + { + for (int i = 0; i < sprites.size; i++) + enabled[i] = false; + } + + public void enable(int i, boolean enable) + { + enabled[i] = enable; + } + + public boolean isEnabled(int i) + { + return enabled[i]; + } + + public boolean isEnabled() + { + for (int i = 0; i < sprites.size; i++) + if (enabled[i]) return true; + return false; + } + + public void setAlpha(float alpha) + { + for (int i = 0, n = sprites.size; i < n; i++) + sprites.get(i).setAlpha(alpha); + } + + public void rotate(int i, float r) + { + sprites.get(i).setRotation(r); + } + + public void setRotation(float r) + { + for (int i = 0, n = sprites.size; i < n; i++) + sprites.get(i).setRotation(r); + } + + public void translate(float dx, float dy) + { + for (int i = 0, n = sprites.size; i < n; i++) + sprites.get(i).translate(dx, dy); + } + + public void centerOn(float cx, float cy) + { + for (int i = 0, n = sprites.size; i < n; i++) { + float x = (cx - (sprites.get(i).getWidth() / 2f)); + float y = (cy - (sprites.get(i).getHeight() / 2f)); + sprites.get(i).setPosition(x, y); + } + } + + @Override + public void draw(Batch batch) + { + for (int i = 0, n = sprites.size; i < n; i++) { + if (enabled[i]) + sprites.get(i).draw(batch); + } + } + + @Override + public void drawDebug(ShapeRenderer shapes) + { + Sprite sprite = sprites.get(0); + float w = sprite.getWidth(); + float h = sprite.getHeight(); + shapes.rect(sprite.getX(), sprite.getY(), (w / 2f), (h / 2f), w, h, sprite.getScaleX(), sprite.getScaleY(), sprite.getRotation()); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java new file mode 100644 index 0000000..fdd1e80 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/AnimationSequence.java @@ -0,0 +1,76 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import java.util.ArrayList; + +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Animation; + +public class AnimationSequence implements Animation, Pool.Poolable +{ + private ArrayList<Animation> animations; + + private static final Pool<AnimationSequence> animationSequencePool = new Pool<AnimationSequence>() { + @Override + protected AnimationSequence newObject() { + return new AnimationSequence(); + } + }; + + public static AnimationSequence get(int capacity) + { + AnimationSequence seq = animationSequencePool.obtain(); + if (seq.animations == null) + seq.animations = new ArrayList<Animation>(capacity); + else + seq.animations.ensureCapacity(capacity); + + return seq; + } + + @Override + public void reset() + { + for (int i = 0, n = animations.size(); i < n; i++) + animations.get(i).dispose(); + animations.clear(); + } + + @Override + public void dispose() + { + animationSequencePool.free(this); + } + + public void addAnimation(Animation animation) + { + animations.add(animation); + } + + @Override + public boolean animate(float delta) + { + if(animations.isEmpty()) return true; + + Animation animation = animations.get(0); + if (animation.animate(delta)) { + animations.remove(0); + } + + return (animations.isEmpty()); + } + + @Override + public void draw(Batch batch) + { + animations.get(0).draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + animations.get(0).drawDebug(debugShapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java new file mode 100644 index 0000000..d1fc1bb --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DestroyAnimation.java @@ -0,0 +1,62 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Moveable; +import ch.asynk.rustanddust.engine.gfx.Animation; + +public class DestroyAnimation implements Disposable, Animation +{ + private static final float DELAY = 1.5f; + private static final float DURATION = 1.5f; + + private Moveable moveable; + private float x; + private float y; + private int alphaP; + private float elapsed; + + @Override + public void dispose() + { + } + + public void set(float duration, Moveable moveable) + { + this.moveable = moveable; + this.alphaP = 0; + this.elapsed = 0f; + this.x = (moveable.getX() + (moveable.getWidth() / 2f)); + this.y = (moveable.getY() + (moveable.getHeight() / 2f)); + } + + @Override + public boolean animate(float delta) + { + elapsed += delta; + if (elapsed < DELAY) + return false; + + int a = (int) (((elapsed - DELAY) / DURATION) * 10); + if (a != alphaP) { + alphaP = a; + moveable.setAlpha(1f - (alphaP / 10f)); + } + + return (elapsed >= (DELAY + DURATION)); + } + + @Override + public void draw(Batch batch) + { + moveable.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + moveable.drawDebug(debugShapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java new file mode 100644 index 0000000..1a0a3bb --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/DiceAnimation.java @@ -0,0 +1,141 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import java.util.Random; + +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Drawable; +import ch.asynk.rustanddust.engine.gfx.Animation; + +public class DiceAnimation implements Animation, Drawable +{ + private static final float DURATION = 0.7f; + private static final float DURATION_SCATTERING = 0.5f; + private static final int DICE_DIMENSION = 24; + + private static Random random = new Random(); + private static Sprites dice; + private static Sound sound; + private static double sndId; + private static float volume; + private static int[][] rolls = new int[][]{ + { 25, 40, 55, 70, 85, 100, 115, 99, 83, 67, 51, 36, 37, 52, 67, 66, 65, 64 }, + { 58, 74, 59, 60, 45, 62, 78, 94, 109, 108, 123, 106, 89, 71, 70, 69, 68 }, + { 106, 121, 120, 103, 86, 70, 54, 37, 20, 19, 18, 34, 50, 51, 52, 69, 86, 103, 119, 128 }, + { 95, 79, 93, 92, 91, 90, 104, 103, 102, 85, 84, 67, 66, 65, 49, 32, 16, 0 }, + { 22, 39, 56, 73, 90, 107, 124, 128, 113, 98, 83, 68, 53, 38, 23, 0, 25, 42, 59, 76 }, + { 79, 78, 61, 76, 91, 106, 121, 120, 119, 102, 101, 84, 68, 52, 37, 38, 39, 40, 41, 58, 75, 74, 73, 72 }, + }; + + private float x; + private float y; + private int frame; + private int[] roll; + private float elapsed; + private float duration; + // public boolean stop; + + public static void init(Texture texture, int cols, int rows, Sound s) + { + dice = new Sprites(texture, cols, rows); + sound = s; + sndId = -1; + } + + public static void initSound(float v) + { + sndId = -1; + volume = v; + } + + public static void free() + { + sound.dispose(); + dice.dispose(); + } + + public void translate(float dx, float dy) + { + x += dx; + y += dy; + } + + public float getX() + { + return x; + } + + public float getY() + { + return y; + } + + public int getWidth() + { + return DICE_DIMENSION; + } + + public int getHeight() + { + return DICE_DIMENSION; + } + + public void setPosition(float x, float y) + { + this.x = x; + this.y = y; + } + + public void set(int result) + { + this.frame = 0; + this.elapsed = 0f; + this.roll = rolls[result - 1]; + this.duration = DURATION + (DURATION_SCATTERING * random.nextFloat()); + // this.stop = false; + } + + public boolean isDone() + { + return (elapsed >= duration); + } + + @Override + public void dispose() + { + } + + @Override + public boolean animate(float delta) + { + // if (stop) + // return true; + elapsed += delta; + if (elapsed < duration) { + int idx = (int) (roll.length * elapsed / duration); + if (idx >= roll.length) + idx = (roll.length -1); + frame = roll[idx]; + } + if (sndId == -1) + sndId = sound.play(volume); + + return false; + } + + @Override + public void draw(Batch batch) + { + batch.draw(dice.frames[frame], x, y, DICE_DIMENSION, DICE_DIMENSION); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + debugShapes.rect(x, y, dice.frames[frame].getRegionWidth(), dice.frames[frame].getRegionHeight()); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java new file mode 100644 index 0000000..5835525 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/FireAnimation.java @@ -0,0 +1,87 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import java.util.Random; + +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.graphics.Texture; + +public class FireAnimation +{ + public static Random random = new Random(); + + public static Sprites infantryFire; + public static Sprites tankFire; + public static Sprites explosion; + + public static Sound infantryFireSnd; + public static Sound tankFireSnd; + public static Sound tankFireSndLong; + public static Sound explosionSnd; + public static Sound explosionSndLong; + + public static double infantryFireSndLongId; + public static double tankFireSndLongId; + public static double explosionSndLongId; + + public static void init( + Texture infantryFireT, int iCols, int iRows, + Texture tankFireT, int sCols, int sRows, + Texture explosionT, int eCols, int eRows, + Sound infantryFireS, + Sound tankFireS, + Sound tankFireLongS, + Sound explosionS, + Sound explosionLongS) + { + infantryFire = new Sprites(infantryFireT, iCols, iRows); + tankFire = new Sprites(tankFireT, sCols, sRows); + explosion = new Sprites(explosionT, eCols, eRows); + infantryFireSnd = infantryFireS; + tankFireSnd = tankFireS; + tankFireSndLong = tankFireLongS; + explosionSnd = explosionS; + explosionSndLong = explosionLongS; + + reset(); + } + + public static void reset() + { + infantryFireSndLongId = -1; + tankFireSndLongId = -1; + explosionSndLongId = -1; + } + + public static void free() + { + tankFire.dispose(); + explosion.dispose(); + + tankFireSnd.dispose(); + tankFireSndLong.dispose(); + explosionSnd.dispose(); + explosionSndLong.dispose(); + } + + public static void infantryFireSndPlay(float volume) + { + if (infantryFireSndLongId == -1) + infantryFireSndLongId = infantryFireSnd.play(volume); + } + + public static void tankFireSndPlay(float volume) + { + if (tankFireSndLongId == -1) + tankFireSndLongId = tankFireSndLong.play(volume); + else + tankFireSnd.play(volume); + } + + public static void explosionSndPlay(float volume) + { + if (explosionSndLongId == -1) + explosionSndLongId = explosionSndLong.play(volume); + else + explosionSnd.play(volume); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java new file mode 100644 index 0000000..233305a --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/InfantryFireAnimation.java @@ -0,0 +1,222 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import java.util.Random; + +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.Pool; + +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Drawable; +import ch.asynk.rustanddust.engine.gfx.Animation; + +public class InfantryFireAnimation implements Disposable, Animation, Pool.Poolable +{ + class Shot + { + public TextureRegion fireRegion; + public float fire_a; + public float fire_x; + public float fire_y; + public float fire_w; + public float fire_dx; + public float fire_dy; + public float fire_dw; + + public boolean fired; + public boolean hit; + public boolean completed; + + public float fire_time; + public float hit_time; + public float end_time; + + public int hit_frame; + + public Shot(TextureRegion region) + { + this.fireRegion = region; + } + + public void set(float delay, float x0, float y0, float x1, float y1, float w, float a) + { + float dx = (x1 - x0); + float dy = (y1 - y0); + + // timing + float fire_duration = ((FireAnimation.random.nextFloat() * TIME_SCATTERING) + (w / SHOT_SPEED)); + float hit_duration = (FireAnimation.infantryFire.rows * HIT_FRAME_DURATION); + + this.fired = false; + this.fire_time = delay; + this.hit_time = (this.fire_time + fire_duration); + this.end_time = (this.hit_time + hit_duration); + + // fire vars + this.fire_a = a; + this.fire_x = x0; + this.fire_y = y0; + this.fire_w = 0; + this.fire_dx = (dx / fire_duration); + this.fire_dy = (dy / fire_duration); + this.fire_dw = (w / fire_duration); + this.hit_frame = 0; + } + + public boolean animate(float delta) + { + if (!fired && (elapsed < fire_time)) + return false; + + if (!fired) { + fired = true; + FireAnimation.infantryFireSndPlay(volume); + } + + if (!hit && (elapsed < hit_time)) { + fire_w += (fire_dw * delta); + fire_x += (fire_dx * delta); + fire_y += (fire_dy * delta); + fireRegion.setRegionWidth((int) fire_w); + return false; + } + + if (!hit) + hit = true; + + if (elapsed < end_time) { + int frame = (int) ((elapsed - hit_time) / HIT_FRAME_DURATION); + if (frame != hit_frame) { + hit_frame = frame; + fireRegion.setRegion(FireAnimation.infantryFire.frames[hit_frame]); + fireRegion.setRegionWidth((int) fire_w); + } + return false; + } + + completed = true; + return true; + } + + public void draw(Batch batch) + { + if (fired && !completed) + batch.draw(fireRegion, fire_x, fire_y, 0, 0, fireRegion.getRegionWidth(), fireRegion.getRegionHeight(), 1f, 1f, fire_a); + } + } + + private static final int SHOT_COUNT = 19; + private static final float SHOT_DELAY = (1.6f / SHOT_COUNT); + private static final float SHOT_SCATTERING = 40f; + private static final float TIME_SCATTERING = 0.6f; + private static final float START_DELAY = 0.8f; + private static final float SHOT_SPEED = 1000f; + private static final float HIT_FRAME_DURATION = 0.05f; + + private Shot[] shots; + + private float elapsed; + + private float volume; + + private static final Pool<InfantryFireAnimation> fireAnimationPool = new Pool<InfantryFireAnimation>() { + @Override + protected InfantryFireAnimation newObject() { + return new InfantryFireAnimation(); + } + }; + + public static InfantryFireAnimation get(float volume, float x0, float y0, float x1, float y1, float halfWidth) + { + InfantryFireAnimation a = fireAnimationPool.obtain(); + a.set(volume, x0, y0, x1, y1, halfWidth); + return a; + } + + public InfantryFireAnimation() + { + this.shots = new Shot[SHOT_COUNT]; + for (int i = 0; i < shots.length; i++) + shots[i] = new Shot(new TextureRegion(FireAnimation.infantryFire.frames[0])); + } + + private void set(float volume, float x0, float y0, float x1, float y1, float halfWidth) + { + this.volume = volume; + this.elapsed = 0f; + + float delay = START_DELAY + (FireAnimation.random.nextFloat() * TIME_SCATTERING); + + y0 -= (FireAnimation.infantryFire.height / 2.0f); + double r = Math.atan2((y0 - y1), (x0 - x1)); + x0 -= ((float) (Math.cos(r) * halfWidth)); + y0 -= ((float) (Math.sin(r) * halfWidth)); + + float dx = (x1 - x0); + float dy = (y1 - y0); + float w = (float) Math.sqrt((dx * dx) + (dy * dy)); + double dr = (Math.atan2(halfWidth, w) / 2f); + + double a = (r + (dr / 2f)); + double da = (dr / (float) SHOT_COUNT); + + for (Shot shot : shots) { + float x = (float) (x0 - (Math.cos(a) * w)); + float y = (float) (y0 - (Math.sin(a) * w)); + + shot.set(delay, x0, y0, x, y, w, (float) Math.toDegrees(a)); + + delay += SHOT_DELAY; + a -= 2 * (da * FireAnimation.random.nextFloat()); + } + } + + @Override + public void reset() + { + } + + @Override + public void dispose() + { + fireAnimationPool.free(this); + } + + @Override + public boolean animate(float delta) + { + elapsed += delta; + + boolean completed = true; + for (Shot shot : shots) + completed &= shot.animate(delta); + + return completed; + } + + @Override + public void draw(Batch batch) + { + for (Shot shot : shots) + shot.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + // debugShapes.end(); + // debugShapes.begin(ShapeRenderer.ShapeType.Line); + // debugShapes.identity(); + // debugShapes.translate(fire_x, fire_y, 0); + // debugShapes.rotate(0, 0, 1, fire_a); + // debugShapes.translate(-fire_x, -fire_y, 0); + // debugShapes.rect(fire_x, fire_y, fire_w, FireAnimation.infantryFire.height); + // debugShapes.end(); + // debugShapes.begin(ShapeRenderer.ShapeType.Line); + // debugShapes.identity(); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java new file mode 100644 index 0000000..f6380bc --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/MoveToAnimation.java @@ -0,0 +1,126 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Moveable; + +public class MoveToAnimation extends TimedAnimation +{ + public interface MoveToAnimationCb { + void moveToAnimationLeave(Moveable moveable, float x, float y, float r); + void moveToAnimationEnter(Moveable moveable, float x, float y, float r); + void moveToAnimationDone(Moveable moveable, float x, float y, float r); + } + + private Moveable moveable; + private float fromX; + private float fromY; + private float fromR; + private float toX; + private float toY; + private float toR; + private float rDelta; + private boolean notified; + private MoveToAnimationCb cb; + + private static final Pool<MoveToAnimation> moveToAnimationPool = new Pool<MoveToAnimation>() { + @Override + protected MoveToAnimation newObject() { + return new MoveToAnimation(); + } + }; + + public static MoveToAnimation get(Moveable moveable, Vector3 v, float duration) + { + return get(moveable, v.x, v.y, v.z, duration); + } + + public static MoveToAnimation get(Moveable moveable, Vector3 v, float duration, MoveToAnimationCb cb) + { + return get(moveable, v.x, v.y, v.z, duration, cb); + } + + public static MoveToAnimation get(Moveable moveable, float x, float y, float r, float duration) + { + return get(moveable, x, y, r, duration, null); + } + + public static MoveToAnimation get(Moveable moveable, float x, float y, float r, float duration, MoveToAnimationCb cb) + { + MoveToAnimation a = moveToAnimationPool.obtain(); + + a.moveable = moveable; + a.toX = x; + a.toY = y; + a.toR = r; + a.duration = duration; + a.cb = cb; + a.rDelta = 0; + a.notified = false; + + return a; + } + + @Override + public void dispose() + { + moveToAnimationPool.free(this); + } + + @Override + protected void begin() + { + fromX = moveable.getX(); + fromY = moveable.getY(); + fromR = moveable.getRotation(); + notified = ((fromX == toX) && (fromY == toY)); + + if (Math.abs(toR - fromR) <= 180.f) + rDelta = (toR - fromR); + else { + if (toR > fromR) + rDelta = (toR - 360 - fromR); + else + rDelta = (toR + 360 - fromR); + } + } + + @Override + protected void end() + { + if (cb != null) + cb.moveToAnimationDone(moveable, (toX + (moveable.getWidth() / 2)), (toY + (moveable.getHeight() / 2)), toR); + dispose(); + } + + @Override + protected void update(float percent) + { + if ((cb != null) && !notified && (percent >= 0.5)) { + float dw = (moveable.getWidth() / 2); + float dh = (moveable.getHeight() / 2); + cb.moveToAnimationLeave(moveable, (fromX + dw), (fromY + dh), fromR); + cb.moveToAnimationEnter(moveable, (toX + dw), (toY + dh), toR); + notified = true; + } + if (percent == 1f) + moveable.setPosition(toX, toY, (int) toR); + else + moveable.setPosition(fromX + ((toX - fromX) * percent), fromY + ((toY - fromY) * percent), (fromR + (rDelta * percent))); + } + + @Override + public void draw(Batch batch) + { + moveable.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + moveable.drawDebug(debugShapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java new file mode 100644 index 0000000..24eac18 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/PromoteAnimation.java @@ -0,0 +1,98 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import java.lang.Math; + +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Drawable; +import ch.asynk.rustanddust.engine.gfx.Animation; + +public class PromoteAnimation implements Animation, Drawable +{ + private static PromoteAnimation instance = new PromoteAnimation(); + + private static final float DURATION = 0.3f; + private static final float MAX_SCALE = 2f; + + private static Sound usSound; + private static Sound geSound; + private static Sound snd; + private static TextureRegion region; + + private float x0; + private float y0; + private float x; + private float y; + private float scale; + private float step; + private float volume; + private float elapsed; + + public static void init(TextureAtlas atlas, Sound usSnd, Sound geSnd) + { + region = atlas.findRegion("stars"); + usSound = usSnd; + geSound = geSnd; + } + + public static void free() + { + } + + protected void PromoteAnimation() + { + } + + public static PromoteAnimation get(boolean us, float x, float y, float v) + { + x = (x - (region.getRegionWidth() / 2.0f)); + y = (y - (region.getRegionHeight() / 2.0f)); + + instance.volume = v; + instance.x0 = x; + instance.y0 = y; + instance.scale = 0f; + instance.elapsed = 0f; + snd = (us ? usSound : geSound); + + return instance; + } + + @Override + public void dispose() + { + } + + @Override + public boolean animate(float delta) + { + elapsed += delta; + if (elapsed >= DURATION) { + snd.play(volume); + return true; + } + + float s = MAX_SCALE * (float) Math.sin(Math.PI / DURATION * elapsed); + scale = 1f + s; + x = x0 - ((region.getRegionWidth() * scale) / 4f); + y = y0 - ((region.getRegionHeight() * scale) / 4f); + + return false; + } + + @Override + public void draw(Batch batch) + { + batch.draw(region, x, y, 0, 0, region.getRegionWidth(), region.getRegionHeight(), scale, scale, 0f); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + debugShapes.rect(x, y, region.getRegionWidth(), region.getRegionHeight()); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java new file mode 100644 index 0000000..231f859 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/RunnableAnimation.java @@ -0,0 +1,67 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Moveable; +import ch.asynk.rustanddust.engine.gfx.Animation; + +public class RunnableAnimation implements Animation, Pool.Poolable +{ + private Runnable runnable; + private Moveable moveable; + private boolean ran; + + private static final Pool<RunnableAnimation> runnableAnimationPool = new Pool<RunnableAnimation>() { + @Override + protected RunnableAnimation newObject() { + return new RunnableAnimation(); + } + }; + + public static RunnableAnimation get(Moveable moveable, Runnable runnable) + { + RunnableAnimation a = runnableAnimationPool.obtain(); + a.runnable = runnable; + a.moveable = moveable; + return a; + } + + @Override + public void reset() + { + ran = false; + } + + @Override + public void dispose() + { + runnableAnimationPool.free(this); + } + + @Override + public boolean animate(float delta) + { + if (ran) return true; + + runnable.run(); + runnable = null; + ran = true; + dispose(); + + return true; + } + + @Override + public void draw(Batch batch) + { + moveable.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + moveable.drawDebug(debugShapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java new file mode 100644 index 0000000..7e83f38 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SoundAnimation.java @@ -0,0 +1,83 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +public class SoundAnimation extends TimedAnimation +{ + public enum Action + { + FADE_IN, + FADE_OUT + }; + + private Sound sound; + private long soundId; + private Action action; + private float volume; + + private static final Pool<SoundAnimation> soundAnimationPool = new Pool<SoundAnimation>() { + @Override + protected SoundAnimation newObject() { + return new SoundAnimation(); + } + }; + + public static SoundAnimation get(Action action, Sound sound, long soundId, float volume, float duration) + { + SoundAnimation a = soundAnimationPool.obtain(); + + a.action = action; + a.sound = sound; + a.soundId = soundId; + a.volume = volume; + a.duration = duration; + + return a; + } + + @Override + public void dispose() + { + soundAnimationPool.free(this); + } + + @Override + protected void begin() + { + } + + @Override + protected void end() + { + dispose(); + } + + @Override + protected void update(float percent) + { + float v; + switch(action) { + case FADE_IN: + v = ( volume * percent); + sound.setVolume(soundId, v); + break; + case FADE_OUT: + v = (volume - ( volume * percent)); + sound.setVolume(soundId, v); + break; + } + } + + @Override + public void draw(Batch batch) + { + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java new file mode 100644 index 0000000..4d10210 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/SpriteAnimation.java @@ -0,0 +1,76 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import java.util.Random; + +import com.badlogic.gdx.utils.Disposable; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Drawable; +import ch.asynk.rustanddust.engine.gfx.Animation; + +public class SpriteAnimation implements Disposable, Animation +{ + private static Random random = new Random(); + private Sprites sprites; + private float duration; + private float frameDuration; + private float elapsed; + private float x0; + private float y0; + private float x1; + private float y1; + private int randFreq; + + public SpriteAnimation(Texture texture, int cols, int rows, int randFreq) + { + this.sprites = new Sprites(texture, cols, rows); + this.randFreq = randFreq; + } + + @Override + public void dispose() + { + sprites.dispose(); + } + + public void init(float duration, float x, float y) + { + this.duration = duration; + this.frameDuration = (duration / (float) sprites.frames.length); + this.x0 = x - (sprites.width / 2f); + this.y0 = y - (sprites.height / 2f); + this.elapsed = 0f; + randPos(); + } + + private void randPos() + { + this.x1 = this.x0 + (random.nextInt(sprites.width) - (sprites.width / 2)); + this.y1 = this.y0 + (random.nextInt(sprites.height) - (sprites.height / 2)); + } + + @Override + public boolean animate(float delta) + { + elapsed += delta; + return (elapsed >= duration); + } + + @Override + public void draw(Batch batch) + { + int n = (((int)(elapsed / frameDuration)) % sprites.frames.length); + if ((n > 0) && (n % randFreq) == 0) + randPos(); + batch.draw(sprites.frames[n], x1, y1); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java new file mode 100644 index 0000000..2d2a0c1 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/Sprites.java @@ -0,0 +1,38 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.TextureRegion; + +public class Sprites implements Disposable +{ + public Texture texture; + public TextureRegion[] frames; + public final int width; + public final int height; + public final int cols; + public final int rows; + + public Sprites(Texture texture, int cols, int rows) + { + this.cols = cols; + this.rows = rows; + this.width = (texture.getWidth() / cols); + this.height = (texture.getHeight() / rows); + this.texture = texture; + TextureRegion[][] tmp = TextureRegion.split(texture, width, height); + frames = new TextureRegion[cols * rows]; + int idx = 0; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + frames[idx++] = tmp[i][j]; + } + } + } + + @Override + public void dispose() + { + texture.dispose(); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java new file mode 100644 index 0000000..82a87fd --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TankFireAnimation.java @@ -0,0 +1,196 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import java.util.Random; + +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.Pool; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Drawable; +import ch.asynk.rustanddust.engine.gfx.Animation; + +public class TankFireAnimation implements Disposable, Animation, Pool.Poolable +{ + private static final float SHOT_SCATTERING = 60f; + private static final float TIME_SCATTERING = 0.6f; + private static final float START_DELAY = 0.8f; + private static final float SHOT_SPEED = 900f; + private static final float EXPLOSION_FRAME_DURATION = 0.07f; + + private TextureRegion fireRegion; + private float fire_a; + private float fire_x; + private float fire_y; + private float fire_w; + private float fire_dx; + private float fire_dy; + private float fire_dw; + + private float smoke_df; + private int smoke_frame; + + private float explosion_x; + private float explosion_y; + private float explosion_df; + private int explosion_frame; + + private boolean fired; + private boolean hit; + private float elapsed; + private float fire_time; + private float hit_time; + private float end_time; + + private float volume; + + private static final Pool<TankFireAnimation> fireAnimationPool = new Pool<TankFireAnimation>() { + @Override + protected TankFireAnimation newObject() { + return new TankFireAnimation(); + } + }; + + public static TankFireAnimation get(float volume, float x0, float y0, float x1, float y1, float halfWidth) + { + TankFireAnimation a = fireAnimationPool.obtain(); + a.set(volume, x0, y0, x1, y1, halfWidth); + return a; + } + + public TankFireAnimation() + { + this.fireRegion = new TextureRegion(FireAnimation.tankFire.frames[0]); + } + + private void set(float volume, float x0, float y0, float x1, float y1, float halfWidth) + { + this.fired = false; + this.hit = false; + this.volume = volume; + + // fire geometry + y0 -= (FireAnimation.tankFire.height / 2.0f); + x1 += ((SHOT_SCATTERING * FireAnimation.random.nextFloat()) - (SHOT_SCATTERING / 2f)); + y1 += ((SHOT_SCATTERING * FireAnimation.random.nextFloat()) - (SHOT_SCATTERING / 2f)); + + double r = Math.atan2((y0 - y1), (x0 - x1)); + float xadj = (float) (Math.cos(r) * halfWidth); + float yadj = (float) (Math.sin(r) * halfWidth); + x0 -= xadj; + y0 -= yadj; + + float a = (float) Math.toDegrees(r); + float dx = (x1 - x0); + float dy = (y1 - y0); + float w = (float) Math.sqrt((dx * dx) + (dy * dy)); + + // timing + float delay = START_DELAY + (FireAnimation.random.nextFloat() * TIME_SCATTERING); + float fire_duration = ((FireAnimation.random.nextFloat() * TIME_SCATTERING) + (w / SHOT_SPEED)); + float explosion_duration = (FireAnimation.explosion.cols * EXPLOSION_FRAME_DURATION); + + this.elapsed = 0f; + this.fire_time = delay; + this.hit_time = (fire_time + fire_duration); + this.end_time = (hit_time + explosion_duration); + + // fire vars + this.fire_a = a; + this.fire_x = x0; + this.fire_y = y0; + this.fire_w = 0; + this.fire_dx = (dx / fire_duration); + this.fire_dy = (dy / fire_duration); + this.fire_dw = (w / fire_duration); + + // smoke var + this.smoke_df = (FireAnimation.tankFire.rows / explosion_duration); + this.smoke_frame = 0; + + // explosion vars + this.explosion_x = (x1 - (FireAnimation.explosion.width / 2.0f)); + this.explosion_y = (y1 - (FireAnimation.explosion.height / 2.0f)); + this.explosion_df = (FireAnimation.explosion.cols / explosion_duration); + this.explosion_frame = (FireAnimation.random.nextInt(FireAnimation.explosion.rows) * FireAnimation.explosion.cols); + } + + @Override + public void reset() + { + } + + @Override + public void dispose() + { + fireAnimationPool.free(this); + } + + @Override + public boolean animate(float delta) + { + elapsed += delta; + + if (!fired && (elapsed < fire_time)) + return false; + + if (!fired) { + fired = true; + FireAnimation.tankFireSndPlay(volume); + } + + if (!hit && (elapsed < hit_time)) { + fire_w += (fire_dw * delta); + fire_x += (fire_dx * delta); + fire_y += (fire_dy * delta); + fireRegion.setRegionWidth((int) fire_w); + return false; + } + + if (!hit) { + hit = true; + FireAnimation.explosionSndPlay(volume); + } + + if (elapsed < end_time) { + int frame = (int) ((elapsed - hit_time) * smoke_df); + if (frame != smoke_frame) { + smoke_frame = frame; + fireRegion.setRegion(FireAnimation.tankFire.frames[smoke_frame]); + fireRegion.setRegionWidth((int) fire_w); + } + return false; + } + + return true; + } + + @Override + public void draw(Batch batch) + { + if (fired) + batch.draw(fireRegion, fire_x, fire_y, 0, 0, fireRegion.getRegionWidth(), fireRegion.getRegionHeight(), 1f, 1f, fire_a); + + if (hit) { + int frame = (explosion_frame + (int) ((elapsed - hit_time) * explosion_df)); + batch.draw(FireAnimation.explosion.frames[frame], explosion_x, explosion_y); + } + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + debugShapes.end(); + debugShapes.begin(ShapeRenderer.ShapeType.Line); + debugShapes.identity(); + debugShapes.translate(fire_x, fire_y, 0); + debugShapes.rotate(0, 0, 1, fire_a); + debugShapes.translate(-fire_x, -fire_y, 0); + debugShapes.rect(fire_x, fire_y, fire_w, FireAnimation.tankFire.height); + debugShapes.end(); + debugShapes.begin(ShapeRenderer.ShapeType.Line); + debugShapes.identity(); + } +} diff --git a/core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java new file mode 100644 index 0000000..0c0d14d --- /dev/null +++ b/core/src/ch/asynk/rustanddust/engine/gfx/animations/TimedAnimation.java @@ -0,0 +1,48 @@ +package ch.asynk.rustanddust.engine.gfx.animations; + +import com.badlogic.gdx.utils.Pool; + +import ch.asynk.rustanddust.engine.gfx.Animation; + +public abstract class TimedAnimation implements Animation, Pool.Poolable +{ + private float time; + private boolean began; + private boolean completed; + protected float duration; + + abstract protected void begin(); + abstract protected void end(); + abstract protected void update(float percent); + + @Override + public void reset() + { + time = 0f; + began = false; + completed = false; + } + + @Override + public boolean animate(float delta) + { + if (completed) return true; + + if (!began) { + begin(); + began = true; + } + + time += delta; + completed = (time >= duration); + + if (!completed) { + update(time / duration); + return false; + } + + update(1); + end(); + return true; + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Army.java b/core/src/ch/asynk/rustanddust/game/Army.java new file mode 100644 index 0000000..21cad9f --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Army.java @@ -0,0 +1,30 @@ +package ch.asynk.rustanddust.game; + +import ch.asynk.rustanddust.engine.Faction; + +public enum Army implements Faction +{ + NONE("None"), + GE("German"), + US("US"), + USSR("Soviet"), + EN("English"); + + private String s; + + Army(String s) { + this.s = s; + } + + @Override + public String toString() + { + return s; + } + + @Override + public boolean isEnemy(Faction other) + { + return (this != other); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Battle.java b/core/src/ch/asynk/rustanddust/game/Battle.java new file mode 100644 index 0000000..242e147 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Battle.java @@ -0,0 +1,40 @@ +package ch.asynk.rustanddust.game; + +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.battles.Factory.MapType; +import ch.asynk.rustanddust.ui.Position; + +public interface Battle +{ + public void init(); + + public String getName(); + + public String getDescription(); + + public Player getPlayer(); + + public Player opponent(Player player); + + public MapType getMapType(); + + public Map getMap(); + + public Player checkVictory(Ctrl ctrl); + + public boolean getReinforcement(Ctrl ctrl, Map map); + + public Zone getEntryZone(Unit unit); + + public Zone getExitZone(Unit unit); + + public Position getHudPosition(Player player); + + public State.StateType getState(Player player); + + public boolean deploymentDone(Player player); + + public void setup(Ctrl ctrl, Map map); +} diff --git a/core/src/ch/asynk/rustanddust/game/Command.java b/core/src/ch/asynk/rustanddust/game/Command.java new file mode 100644 index 0000000..40d467a --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Command.java @@ -0,0 +1,225 @@ +package ch.asynk.rustanddust.game; + +import java.util.List; + +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.utils.Json; +import com.badlogic.gdx.utils.JsonValue; + +import ch.asynk.rustanddust.engine.Order; +import ch.asynk.rustanddust.engine.Move; +import ch.asynk.rustanddust.engine.Pawn; +import ch.asynk.rustanddust.engine.Tile; + +public class Command extends Order +{ + public enum CommandType implements Order.OrderType + { + NONE, + MOVE, + ENGAGE, + PROMOTE, + END_OF_TURN; + } + + private static final Pool<Command> commandPool = new Pool<Command>() + { + @Override + protected Command newObject() { + return new Command(); + } + }; + + public static void clearPool() + { + commandPool.clear(); + } + + public static Command get(Player player) + { + Command c = commandPool.obtain(); + c.player = player; + c.ap = player.getAp(); + c.turn = player.getCurrentTurn(); + return c; + } + + public CommandType type; + public Player player; + public int ap; + public int turn; + public Unit unit; + public Unit.UnitId unitId; + public Unit.UnitType unitType; + public Tile unitTile; + public Move move; + public Engagement engagement; + + private Command() + { + reset(); + } + + @Override + public void dispose() + { + commandPool.free(this); + } + + @Override + public void reset() + { + this.type = CommandType.NONE; + this.player = null; + this.unit = null; + if (this.move != null) { + this.move.dispose(); + this.move = null; + } + if (this.engagement != null) { + this.engagement.dispose(); + this.engagement = null; + } + } + + @Override + public int compareTo(Pawn pawn) + { + if (pawn == unit) + return 0; + return 1; + } + + @Override + public boolean isA(OrderType type) + { + return (type == this.type); + } + + @Override + public String toString() + { + return String.format("%s : %s", type, unit.id); + } + + public void setMove(Unit unit, Move move) + { + this.type = CommandType.MOVE; + this.move = move; + setUnit(unit); + } + + public void setPromote(Unit unit) + { + this.type = CommandType.PROMOTE; + setUnit(unit); + } + + public void setEngage(Unit unit, Unit target) + { + this.type = CommandType.ENGAGE; + this.engagement = Engagement.get(unit, target); + setUnit(unit); + } + + private void setUnit(Unit unit) + { + this.unit = unit; + this.unitId = unit.id; + this.unitType = unit.type; + this.unitTile = unit.getTile(); + } + + @Override + public void write(Json json) + { + json.writeValue("type", type); + json.writeObjectStart("player"); + json.writeValue("army", player.getName()); + json.writeValue("turn", turn); + json.writeValue("aps", ap); + json.writeObjectEnd(); + json.writeObjectStart("unit"); + json.writeValue("id", unitId); + json.writeValue("type", unitType); + json.writeValue("ace", unit.ace); + writeTile(json, "tile", unitTile); + json.writeObjectEnd(); + if (move != null) writeMove(json, "move", move); + if (engagement != null) writeEngagement(json, "engagement", engagement); + } + + private void writeMove(Json json, String key, Move m) + { + json.writeObjectStart(key); + json.writeValue("type", move.type); + writeTile(json, "from", move.from); + writeTile(json, "to", move.to); + json.writeValue("orientation", move.orientation.r()); + writeTiles(json, "path", move.tiles); + json.writeObjectEnd(); + } + + private void writeEngagement(Json json, String key, Engagement e) + { + json.writeObjectStart(key); + writeUnit(json, "attacker", e.attacker); + writeUnit(json, "defender", e.defender); + writeUnits(json, "assists", e.assists); + json.writeObjectStart("dice"); + json.writeValue("d1", e.d1); + json.writeValue("d2", e.d2); + json.writeValue("d3", e.d3); + json.writeValue("d4", e.d4); + json.writeObjectEnd(); + json.writeObjectStart("results"); + json.writeValue("success", e.success); + json.writeValue("attackSum", e.attackSum); + json.writeValue("defenseSum", e.defenseSum); + json.writeObjectEnd(); + json.writeObjectEnd(); + } + + private void writeUnit(Json json, String key, Unit u) + { + if (key != null) json.writeObjectStart(key); + else json.writeObjectStart(); + json.writeValue("id", u.id); + json.writeValue("ace", u.ace); + json.writeValue("army", u.getArmy()); + writeTile(json, "tile", u.getTile()); + json.writeObjectEnd(); + } + + private void writeUnits(Json json, String key, List<Unit> units) + { + json.writeArrayStart(key); + for (Unit u : units) + writeUnit(json, null, u); + json.writeArrayEnd(); + } + + private void writeTile(Json json, String key, Tile t) + { + if (t == null) return; + if (key != null) json.writeObjectStart(key); + else json.writeObjectStart(); + json.writeValue("col", t.getCol()); + json.writeValue("row", t.getRow()); + json.writeObjectEnd(); + } + + private void writeTiles(Json json, String key, List<Tile> tiles) + { + json.writeArrayStart(key); + for (Tile t : tiles) + writeTile(json, null, t); + json.writeArrayEnd(); + } + + @Override + public void read(Json json, JsonValue jsonMap) + { + // FIXME Command.read(Json, JsonValue); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Config.java b/core/src/ch/asynk/rustanddust/game/Config.java new file mode 100644 index 0000000..2c27c35 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Config.java @@ -0,0 +1,76 @@ +package ch.asynk.rustanddust.game; + +public class Config +{ + public enum Graphics { + MINE("mine", 0), + ORIGINAL("original", 1); + public String s; + public int i; + Graphics(String s, int i) + { + this.s = s; + this.i = i; + } + public Graphics next() + { + if (this == ORIGINAL) + return MINE; + return ORIGINAL; + } + }; + + public enum GameMode + { + SOLO("Solo", 0), + PVE("Player vs AI", 1), + PVP("Player vs Player", 2); + public String s; + public int i; + GameMode(String s, int i) + { + this.s = s; + this.i = i; + } + public GameMode next() + { + if (this == SOLO) + return PVE; + if(this == PVE) + return PVP; + return SOLO; + } + }; + + public GameMode gameMode; + public boolean showMoves; + public boolean showTargets; + public boolean showMoveAssists; + public boolean canCancel; + public boolean mustValidate; + public boolean showEnemyPossibilities; + public boolean debug; + public Battle battle; + public float fxVolume; + public Graphics graphics; + + public Config() + { + this.gameMode = GameMode.SOLO; + this.debug = false; + this.showMoves = true; + this.showTargets = true; + this.showMoveAssists = true; + this.canCancel = false; + this.mustValidate = false; + this.showEnemyPossibilities = false; + this.graphics = Graphics.MINE; + this.battle = null; + this.fxVolume = 1f; + } + + public boolean gameModeImplemented() + { + return (gameMode == GameMode.SOLO); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Ctrl.java b/core/src/ch/asynk/rustanddust/game/Ctrl.java new file mode 100644 index 0000000..3b93ad8 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Ctrl.java @@ -0,0 +1,336 @@ +package ch.asynk.rustanddust.game; + +import com.badlogic.gdx.utils.Disposable; + +import com.badlogic.gdx.math.Vector3; + +import ch.asynk.rustanddust.RustAndDust; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.game.State.StateType; +import ch.asynk.rustanddust.game.states.StateSelect; +import ch.asynk.rustanddust.game.states.StateMove; +import ch.asynk.rustanddust.game.states.StateRotate; +import ch.asynk.rustanddust.game.states.StatePromote; +import ch.asynk.rustanddust.game.states.StateEngage; +import ch.asynk.rustanddust.game.states.StateBreak; +import ch.asynk.rustanddust.game.states.StateAnimation; +import ch.asynk.rustanddust.game.states.StateReinforcement; +import ch.asynk.rustanddust.game.states.StateDeployment; +import ch.asynk.rustanddust.game.states.StateWithdraw; + +public class Ctrl implements Disposable +{ + private final RustAndDust game; + public final Battle battle; + + public Map map; + public Hud hud; + public Config cfg; + public Player player; + public Player opponent; + public boolean blockMap; + public boolean blockHud; + + public Vector3 mapTouch = new Vector3(); + public Vector3 hudTouch = new Vector3(); + + private State selectState; + private State pathState; + private State rotateState; + private State promoteState; + private State engageState; + private State breakState; + private State animationState; + private State reinforcementState; + private State deploymentState; + private State withdrawState; + + private int animationCount = 0; + + private State state; + private StateType stateType; + private StateType stateAfterAnimation; + + public Ctrl(final RustAndDust game, final Battle battle) + { + this.game = game; + this.battle = battle; + this.cfg = game.config; + game.ctrl = this; + + battle.init(); + + this.map = battle.getMap(); + battle.setup(this, map); + this.map.init(); + this.player = battle.getPlayer(); + this.opponent = battle.opponent(player); + + this.selectState = new StateSelect(this, map); + this.pathState = new StateMove(); + this.rotateState = new StateRotate(); + this.promoteState = new StatePromote(); + this.engageState = new StateEngage(); + this.breakState = new StateBreak(); + this.animationState = new StateAnimation(); + this.reinforcementState = new StateReinforcement(); + this.deploymentState = new StateDeployment(); + this.withdrawState = new StateWithdraw(); + + this.state = selectState; + this.stateType = StateType.DONE; + + this.hud = new Hud(this, game); + this.blockMap = false; + this.blockHud = false; + + hud.notify(battle.toString(), 2, Position.MIDDLE_CENTER, false); + startPlayerTurn(); + } + + @Override + public void dispose() + { + hud.dispose(); + map.dispose(); + } + + public Player getPlayer(Army army) + { + return (player.is(army) ? player : opponent); + } + + public boolean isInAction() + { + return (state != selectState); + } + + public void animationsOver() + { + if (hud.dialogActive()) + return; + if (stateType == StateType.ANIMATION) + leaveAnimationState(); + } + + private void leaveAnimationState() + { + + StateType tmp = stateAfterAnimation; + stateAfterAnimation = StateType.DONE; + setState(tmp); + } + + private void startPlayerTurn() + { + player.turnStart(); + // hud.notify(player.getName() + "'s turn", 2, Position.MIDDLE_CENTER, true); + if (battle.getReinforcement(this, map)) + hud.notify("You have reinforcement", 2, Position.MIDDLE_CENTER, true); + hud.update(); + setState(battle.getState(player)); + } + + private void endPlayerTurn() + { + player.turnEnd(); + Player winner = battle.checkVictory(this); + if (winner != null) + hud.victory(winner, ((winner == player) ? opponent : player)); + } + + private StateType actionAborted() + { + hud.notify("Action canceled"); + StateType nextState = this.state.abort(); + + if (nextState == StateType.ABORT) + nextState = battle.getState(player); + + return nextState; + } + + private void turnDone() + { + map.turnDone(); + endPlayerTurn(); + player = battle.getPlayer(); + opponent = battle.opponent(player); + startPlayerTurn(); + } + + private StateType actionDone() + { + StateType nextState = this.state.execute(); + + if (nextState == StateType.DONE) { + map.actionDone(); + if (map.activatedUnits.size() > 0) { + RustAndDust.debug("Ctrl", "burn down 1AP"); + hud.notify("1 Action Point burnt", 0.6f, Position.BOTTOM_CENTER, false); + player.burnDownOneAp(); + hud.update(); + } + if (player.apExhausted()) + hud.notifyNoMoreAP(); + } + + if (nextState == StateType.DONE) + nextState = battle.getState(player); + + return nextState; + } + + private StateType deploymentDone() + { + map.actionDone(); + return this.state.execute(); + } + + public void setState(StateType nextState) + { + if (nextState == StateType.ABORT) + nextState = actionAborted(); + else if (nextState == StateType.DONE) { + if (stateType == StateType.DEPLOYMENT) + nextState = deploymentDone(); + else + nextState = actionDone(); + } + + if (stateType == StateType.ANIMATION) { + this.blockMap = hud.dialogActive(); + } + hud.playerInfo.blockEndOfTurn(nextState != StateType.SELECT); + + this.state.leave(nextState); + + RustAndDust.debug("Ctrl", String.format(" %s -> %s : %s", stateType, nextState, player)); + + switch(nextState) { + case SELECT: + this.state = selectState; + break; + case MOVE: + this.state = pathState; + break; + case ROTATE: + this.state = rotateState; + break; + case PROMOTE: + this.state = promoteState; + break; + case ENGAGE: + this.state = engageState; + break; + case BREAK: + this.state = breakState; + break; + case WITHDRAW: + this.state = withdrawState; + break; + case ANIMATION: + this.blockMap = true; + this.state = animationState; + break; + case REINFORCEMENT: + this.state = reinforcementState; + break; + case DEPLOYMENT: + this.state = deploymentState; + break; + default: + break; + } + + StateType tmp = stateType; + stateType = nextState; + + this.state.enter(tmp); + + } + + public void touchDown() + { + if (!blockHud && hud.touchDown(hudTouch.x, hudTouch.y)) + return; + + if (!blockMap && state.downInMap(mapTouch.x, mapTouch.y)) + state.touchDown(); + } + + public void touchUp() + { + if (!blockHud && hud.touchUp(hudTouch.x, hudTouch.y)) + return; + + if (!blockMap && state.upInMap(mapTouch.x, mapTouch.y)) + state.touchUp(); + } + + public void stateTouchUp() + { + state.downInMap(-1, -1); + state.upInMap(-1, -1); + state.touchUp(); + } + + public boolean isInAnimation() + { + return (this.stateType == StateType.ANIMATION); + } + + public void setAfterAnimationState(StateType after) + { + stateAfterAnimation = after; + } + + public boolean checkDeploymentDone() + { + boolean done = battle.deploymentDone(player); + if (done) + hud.askEndDeployment(); + return done; + } + + public void reinforcementHit() + { + if (this.stateType == StateType.SELECT) + setState(StateType.REINFORCEMENT); + else if (this.stateType == StateType.REINFORCEMENT) + setState(StateType.SELECT); + } + + // Hud callbacks + public void engagementPanelClosed() + { + if (animationCount == 0) + leaveAnimationState(); + } + + public void endDeployment() + { + setState(StateType.DONE); + turnDone(); + } + + public void endGame() + { + game.switchToMenu(); + } + + public void endPlayerTurn(boolean abort) + { + if (abort) + state.abort(); + turnDone(); + } + + public void exitBoard(boolean doit) + { + if (doit) + setState(StateType.DONE); + else + setState(StateType.ABORT); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Engagement.java b/core/src/ch/asynk/rustanddust/game/Engagement.java new file mode 100644 index 0000000..b7630ed --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Engagement.java @@ -0,0 +1,114 @@ +package ch.asynk.rustanddust.game; + +import java.util.List; +import java.util.LinkedList; +import java.util.Random; + +import com.badlogic.gdx.utils.Pool; +import com.badlogic.gdx.utils.Disposable; + +public class Engagement implements Disposable, Pool.Poolable +{ + private static Random rand = new Random(); + + private static final Pool<Engagement> engagementPool = new Pool<Engagement>() { + @Override + protected Engagement newObject() { + return new Engagement(); + } + }; + + public static Engagement get(Unit attacker, Unit defender) + { + Engagement e = engagementPool.obtain(); + e.attacker = attacker; + e.defender = defender; + e.diceRoll(); + + return e; + } + + public static void clearPool() + { + engagementPool.clear(); + } + + public Unit attacker; + public Unit defender; + public List<Unit> assists; + public boolean success; + public int d1; + public int d2; + public int d3; + public int d4; + public int unitCount; + public int flankBonus; + public int unitDefense; + public int terrainDefense; + public int weatherDefense; + public int attackSum; + public int defenseSum; + + public Engagement() + { + assists = new LinkedList<Unit>(); + reset(); + } + + @Override + public void reset() + { + attacker = null; + defender = null; + assists.clear(); + } + + @Override + public void dispose() + { + assists.clear(); + engagementPool.free(this); + } + + public void addAssist(Unit unit) + { + assists.add(unit); + } + + private void diceRoll() + { + d1 = rand.nextInt(6) + 1; + d2 = rand.nextInt(6) + 1; + d3 = rand.nextInt(6) + 1; + d4 = rand.nextInt(6) + 1; + } + + public void set(int cnt, int flk, int def, int tdf, int wdf) + { + this.unitCount = cnt; + this.flankBonus = flk; + this.unitDefense = def; + this.terrainDefense = tdf; + this.weatherDefense = wdf; + if (d3 == 0) + this.attackSum = (d1 + d2 + unitCount + flankBonus); + else + this.attackSum = (d3 + d4 + unitCount + flankBonus); + this.defenseSum = (unitDefense + terrainDefense + weatherDefense); + } + + + @Override + public String toString() + { + int a, b; + if (d3 == 0) { + a = d1; + b = d2; + } else { + a = d3; + b = d4; + } + return String.format("Engagement : (%d + %d + %d + %d) vs (%d + %d + %d) -> %b", a, b, unitCount, flankBonus, unitDefense, terrainDefense, weatherDefense, success); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Hex.java b/core/src/ch/asynk/rustanddust/game/Hex.java new file mode 100644 index 0000000..b805146 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Hex.java @@ -0,0 +1,138 @@ +package ch.asynk.rustanddust.game; + +import java.util.List; +import java.util.Iterator; + +import com.badlogic.gdx.graphics.g2d.TextureAtlas; + +import ch.asynk.rustanddust.engine.Board; +import ch.asynk.rustanddust.engine.Pawn; +import ch.asynk.rustanddust.engine.Tile; +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.game.Unit.UnitType; + +public class Hex extends Tile +{ + public enum Terrain implements TileTerrain + { + OFFMAP, + BLOCKED, + CLEAR, + HILLS, + WOODS, + TOWN + } + + public static final int FOG = 0; + public static final int SELECT = 1; + public static final int AREA = 2; + public static final int MOVE = 3; + public static final int DIRECTIONS = 4; + public static final int ORIENTATION = 5; + public static final int OBJECTIVE = 6; + public static final int OBJECTIVE_HOLD = 7; + public static final int OBJECTIVE_GE = 8; + public static final int OBJECTIVE_US = 9; + public static final int EXIT = 10; + + public Terrain terrain; + public int roads; + + public String toString() + { + return String.format("(%d;%d) [%f;%f] t:%s r:%d", col, row, x, y, terrain, roads); + } + + public String toShort() + { + return String.format("(%d;%d)", col, row); + } + + public Hex(float x, float y, int col, int row, TextureAtlas atlas) + { + super(x, y, col, row, atlas); + this.terrain = Terrain.CLEAR; + this.roads = 0; + } + + public Unit getUnit() + { + return (Unit) stack.peekFirst(); + } + + @Override + public boolean isA(TileTerrain terrain) + { + return (this.terrain == terrain); + } + + @Override + public boolean isOffMap() + { + return isA(Terrain.OFFMAP); + } + + @Override + public boolean blockLineOfSightFrom(Tile tile) + { + if (isA(Terrain.CLEAR) && !hasUnits()) + return false; + + if (tile.isA(Terrain.HILLS) && isA(Terrain.CLEAR)) + return false; + + return true; + } + + @Override + public boolean atLeastOneMove(Pawn pawn) + { + if (hasUnits() || isA(Terrain.BLOCKED) || isA(Terrain.OFFMAP)) + return false; + return true; + } + + @Override + public boolean road(Orientation side) + { + return (side.s == (roads & side.s)); + } + + @Override + public int exitCost() + { + return 1; + } + + @Override + public int costFrom(Pawn pawn, Orientation side) + { + if (side == Orientation.KEEP) return 0; + if (hasUnits()) return (Integer.MAX_VALUE / 2); + if (road(side)) return 1; + + int c = 0; + switch(terrain) { + case CLEAR: + case HILLS: + c = 1; + break; + case WOODS: + case TOWN: + c = 2; + break; + case OFFMAP: + case BLOCKED: + c = (Integer.MAX_VALUE / 2); + break; + } + + return c; + } + + @Override + public int defense() + { + return (isA(Terrain.TOWN) ? 1 : 0); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/HexSet.java b/core/src/ch/asynk/rustanddust/game/HexSet.java new file mode 100644 index 0000000..4382fdb --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/HexSet.java @@ -0,0 +1,29 @@ +package ch.asynk.rustanddust.game; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import ch.asynk.rustanddust.engine.Tile; + +public class HexSet extends LinkedHashSet<Hex> +{ + private final Map map; + + public HexSet(Map map, int n) + { + super(n); + this.map = map; + } + + public void enable(int i, boolean enable) + { + for (Hex hex : this) + map.enableOverlayOn(hex, i, enable); + } + + @SuppressWarnings("unchecked") + public Collection<Tile> asTiles() + { + return (Collection) this; + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Hud.java b/core/src/ch/asynk/rustanddust/game/Hud.java new file mode 100644 index 0000000..8f9343c --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Hud.java @@ -0,0 +1,307 @@ +package ch.asynk.rustanddust.game; + +import java.util.LinkedList; + +import com.badlogic.gdx.Gdx; + +import com.badlogic.gdx.utils.Disposable; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Animation; +import ch.asynk.rustanddust.game.State.StateType; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.ui.Msg; +import ch.asynk.rustanddust.ui.OkCancel; +import ch.asynk.rustanddust.ui.Widget; +import ch.asynk.rustanddust.game.hud.PlayerInfo; +import ch.asynk.rustanddust.game.hud.ActionButtons; +import ch.asynk.rustanddust.game.hud.StatisticsPanel; +import ch.asynk.rustanddust.game.hud.EngagementPanel; + +import ch.asynk.rustanddust.RustAndDust; + +public class Hud implements Disposable, Animation +{ + public static final float OFFSET = 10f; + public static final float NOTIFY_DURATION = 2f; + + private final RustAndDust game; + private final Ctrl ctrl; + + private Object hit; + + public PlayerInfo playerInfo; + public ActionButtons actionButtons; + + private Msg msg; + private StatisticsPanel stats; + private EngagementPanel engagement; + private OkCancel okCancel; + private LinkedList<Widget> dialogs = new LinkedList<Widget>(); + + public enum OkCancelAction + { + EXIT_BOARD, + ABORT_TURN, + END_TURN, + END_DEPLOYMENT, + } + private OkCancelAction okCancelAction; + + public Hud(final Ctrl ctrl, final RustAndDust game) + { + this.game = game; + this.ctrl = ctrl; + + TextureAtlas hudAtlas = game.factory.hudAtlas; + playerInfo = new PlayerInfo(ctrl, game.fontW, game.uiAtlas, hudAtlas); + actionButtons = new ActionButtons(ctrl, game.uiAtlas, hudAtlas); + actionButtons.hide(); + msg = new Msg(game.fontB, game.uiAtlas); + okCancel = new OkCancel(game.fontB, game.uiAtlas); + stats = new StatisticsPanel(game.fontB, game.uiAtlas); + engagement = new EngagementPanel(game.fontB, game.uiAtlas, hudAtlas); + } + + @Override + public void dispose() + { + playerInfo.dispose(); + actionButtons.dispose(); + msg.dispose(); + okCancel.dispose(); + engagement.dispose(); + stats.dispose(); + } + + public void resize(int left, int bottom, int width, int height) + { + Position.update(left, bottom, width, height); + playerInfo.updatePosition(); + actionButtons.updatePosition(); + msg.updatePosition(); + stats.updatePosition(); + engagement.updatePosition(); + okCancel.updatePosition(); + } + + public void update() + { + Position position = ctrl.battle.getHudPosition(ctrl.player); + playerInfo.update(ctrl.player, position); + actionButtons.update(position.horizontalMirror()); + } + + @Override + public boolean animate(float delta) + { + msg.animate(delta); + playerInfo.animate(delta); + engagement.animate(delta); + return false; + } + + public void draw(Batch batch, boolean debug) + { + draw(batch); + if (debug) + game.fontB.draw(batch, String.format("FPS: %d", Gdx.graphics.getFramesPerSecond()), 80, 25); + } + + @Override + public void draw(Batch batch) + { + playerInfo.draw(batch); + actionButtons.draw(batch); + msg.draw(batch); + okCancel.draw(batch); + engagement.draw(batch); + stats.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + playerInfo.drawDebug(debugShapes); + actionButtons.drawDebug(debugShapes); + msg.drawDebug(debugShapes); + okCancel.drawDebug(debugShapes); + engagement.drawDebug(debugShapes); + stats.drawDebug(debugShapes); + } + + public void pushNotify(String s) + { + notify(s, NOTIFY_DURATION, Position.TOP_CENTER, true); + } + + public void notify(String s) + { + notify(s, NOTIFY_DURATION, Position.TOP_CENTER, false); + } + + public void notify(String s, float duration, Position position, boolean push) + { + if (push) msg.pushWrite(s, duration, position); + else msg.write(s, duration, position); + } + + public boolean touchDown(float x, float y) + { + hit = null; + + if (dialogs.size() > 0) { + Widget dialog = dialogs.getFirst(); + if (dialog.hit(x, y)) { + hit = dialog; + return true; + } + return false; + } + + if (ctrl.isInAnimation()) + return false; + + if (hit == null) { + if (actionButtons.touchDown(x, y)) + hit = actionButtons; + else if (playerInfo.touchDown(x, y)) + hit = playerInfo; + } + + return (hit != null); + } + + public boolean touchUp(float x, float y) + { + if (hit == null) + return false; + + if (dialogs.size() > 0) { + Widget dialog = dialogs.getFirst(); + if (hit == dialog) { + if (dialog.hit(x, y)) + closeDialog(); + hit = null; + } + } else { + if (hit == actionButtons) { + actionButtons.touchUp(x, y); + } + else if (hit == playerInfo) { + playerInfo.touchUp(x, y); + } + + hit = null; + } + + return true; + } + + private void closeDialog() + { + Widget dialog = dialogs.removeFirst(); + dialog.visible = false; + + if (dialog == okCancel) + closeOkCancel(); + else if (dialog == stats) + ctrl.endGame(); + else if (dialog == engagement) + ctrl.engagementPanelClosed(); + + if (dialogs.size() > 0) + dialogs.getFirst().visible = true; + else + ctrl.blockMap = false; + } + + private void closeOkCancel() + { + boolean ok = okCancel.ok; + + switch(okCancelAction) { + case EXIT_BOARD: + ctrl.exitBoard(ok); + break; + case END_TURN: + if (ok) + ctrl.endPlayerTurn(false); + break; + case ABORT_TURN: + if (ok) + ctrl.endPlayerTurn(true); + break; + case END_DEPLOYMENT: + if (ok) + ctrl.endDeployment(); + break; + } + } + + public boolean dialogActive() + { + return (dialogs.size() > 0); + } + + private void pushDialog(Widget dialog) + { + ctrl.blockMap = true; + if (dialogs.size() != 0) + dialog.visible = false; + dialogs.addLast(dialog); + } + + public void notifyDeploymentDone() + { + this.okCancelAction = OkCancelAction.END_TURN; + okCancel.show("Deployment Phase completed."); + okCancel.noCancel(); + pushDialog(okCancel); + } + + public void notifyNoMoreAP() + { + this.okCancelAction = OkCancelAction.END_TURN; + okCancel.show("No more Action Point left."); + okCancel.noCancel(); + pushDialog(okCancel); + } + + public void askExitBoard() + { + this.okCancelAction = OkCancelAction.EXIT_BOARD; + okCancel.show("Do you want this unit to escape the battle field ?"); + pushDialog(okCancel); + } + + public void askEndOfTurn() + { + this.okCancelAction = OkCancelAction.ABORT_TURN; + okCancel.show("You still have Action Points left.\nEnd your Turn anyway ?"); + pushDialog(okCancel); + } + + public void askEndDeployment() + { + this.okCancelAction = OkCancelAction.END_DEPLOYMENT; + okCancel.show("Deployment unit count reached.\nEnd Deployment phase ?"); + pushDialog(okCancel); + } + + public void engagementSummary(Engagement e, float volume) + { + engagement.show(e, Position.BOTTOM_CENTER, volume); + pushDialog(engagement); + } + + public void victory(Player winner, Player loser) + { + stats.show(winner, loser, Position.MIDDLE_CENTER); + pushDialog(stats); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Map.java b/core/src/ch/asynk/rustanddust/game/Map.java new file mode 100644 index 0000000..62f5723 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Map.java @@ -0,0 +1,659 @@ +package ch.asynk.rustanddust.game; + +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.assets.AssetManager; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.RustAndDust; +import ch.asynk.rustanddust.engine.Pawn; +import ch.asynk.rustanddust.engine.Board; +import ch.asynk.rustanddust.engine.Tile; +import ch.asynk.rustanddust.engine.Faction; +import ch.asynk.rustanddust.engine.Move; +import ch.asynk.rustanddust.engine.SelectedTile; +import ch.asynk.rustanddust.engine.ObjectiveSet; +import ch.asynk.rustanddust.engine.OrderList; +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.engine.Meteorology; +import ch.asynk.rustanddust.engine.PathBuilder; +import ch.asynk.rustanddust.engine.gfx.Moveable; +import ch.asynk.rustanddust.engine.gfx.Animation; +import ch.asynk.rustanddust.engine.gfx.animations.AnimationSequence; +import ch.asynk.rustanddust.engine.gfx.animations.DiceAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.FireAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.TankFireAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.InfantryFireAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.PromoteAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.DestroyAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.SoundAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.RunnableAnimation; +import ch.asynk.rustanddust.engine.gfx.animations.MoveToAnimation.MoveToAnimationCb; + +import ch.asynk.rustanddust.ui.Position; + +public abstract class Map extends Board implements MoveToAnimationCb, ObjectiveSet.ObjectiveCb +{ + private final Ctrl ctrl; + + public final HexSet possibleMoves; + public final PathBuilder pathBuilder; + + public final UnitList moveableUnits; + public final UnitList possibleTargets; + public final UnitList engagementAssists; + public final UnitList activatedUnits; + public final UnitList breakUnits; + public final ObjectiveSet objectives; + + public final Meteorology meteorology; + + private final DestroyAnimation destroy; + private final Sound tankMoveSound; + private final Sound infantryMoveSound; + private Sound sound; + private long soundId = -1; + + private OrderList commands; + + protected abstract void setup(); + + public Map(final RustAndDust game, Board.Config cfg, String textureName) + { + super(game.factory, cfg, game.manager.get(textureName, Texture.class), + new SelectedTile(game.manager.get("data/hex.png", Texture.class), new float[] {.2f, .1f, .1f, .1f, .2f, .1f} )); + this.ctrl = game.ctrl; + this.destroy = new DestroyAnimation(); + this.tankMoveSound = game.manager.get("sounds/tank_move.mp3", Sound.class); + this.infantryMoveSound = game.manager.get("sounds/infantry_move.mp3", Sound.class); + DiceAnimation.init(game.manager.get("data/dice.png", Texture.class), 16, 9, game.manager.get("sounds/dice.mp3", Sound.class)); + PromoteAnimation.init(game.manager.get("data/hud.atlas", TextureAtlas.class), + game.manager.get("sounds/promote_us.mp3", Sound.class), + game.manager.get("sounds/promote_ge.mp3", Sound.class)); + FireAnimation.init( + game.manager.get("data/infantry_fire.png", Texture.class), 1, 8, + game.manager.get("data/tank_fire.png", Texture.class), 1, 8, + game.manager.get("data/explosions.png", Texture.class), 16, 8, + game.manager.get("sounds/infantry_fire.mp3", Sound.class), + game.manager.get("sounds/tank_fire.mp3", Sound.class), + game.manager.get("sounds/tank_fire_short.mp3", Sound.class), + game.manager.get("sounds/explosion.mp3", Sound.class), + game.manager.get("sounds/explosion_short.mp3", Sound.class) + ); + + setup(); + + possibleMoves = new HexSet(this, 40); + pathBuilder = new PathBuilder(this, 10, 20, 5, 10); + moveableUnits = new UnitList(6); + + possibleTargets = new UnitList(10); + engagementAssists = new UnitList(6); + activatedUnits = new UnitList(7); + breakUnits = new UnitList(4); + + objectives = new ObjectiveSet(this, 4); + + meteorology = new Meteorology(); + commands = new OrderList(); + } + + @Override + public void dispose() + { + super.dispose(); + clearAll(); + destroy.dispose(); + pathBuilder.dispose(); + DiceAnimation.free(); + PromoteAnimation.free(); + FireAnimation.free(); + commands.dispose(); + Command.clearPool(); + Engagement.clearPool(); + } + + public void clearAll() + { + possibleMoves.clear(); + possibleTargets.clear(); + pathBuilder.clear(); + moveableUnits.clear(); + engagementAssists.clear(); + activatedUnits.clear(); + breakUnits.clear(); + } + + public Hex getHexAt(float x, float y) + { + return (Hex) getTileAt(x, y); + } + + public Hex getHex(int col, int row) + { + return (Hex) getTile(col, row); + } + + public void addObjective(int col, int row, Army army) + { + addObjective(col, row, army, true); + } + + public void addHoldObjective(int col, int row, Army army) + { + addObjective(col, row, army, false); + } + + private void addObjective(int col, int row, Army army, boolean persistent) + { + Hex hex = getHex(col, row); + objectives.add(hex, army, persistent); + showObjective(hex, army, !persistent); + } + + private void claim(Hex hex, Army army) + { + showObjective(hex, objectives.claim(hex, army)); + } + + private void unclaim(Hex hex) + { + showObjective(hex, objectives.unclaim(hex)); + } + + public int collectPossibleMoves(Unit unit) + { + if (!unit.canMove()) { + possibleMoves.clear(); + return 0; + } + return collectPossibleMoves(unit, possibleMoves.asTiles()); + } + + public int togglePathBuilderHex(Hex hex) + { + return pathBuilder.toggleCtrlTile(hex); + } + + public int collectPossibleTargets(Unit unit, UnitList foes) + { + if (!unit.canEngage()) { + possibleTargets.clear(); + return 0; + } + // return collectPossibleTargets(unit, possibleTargets); + return collectPossibleTargets(unit, foes.asPawns(), possibleTargets.asPawns()); + } + + public int collectMoveableUnits(Unit unit) + { + if (unit.canHQMove()) { + collectMoveAssists(unit, moveableUnits.asPawns()); + } else { + moveableUnits.clear(); + } + if (unit.canMove()) + moveableUnits.add(unit); + return moveableUnits.size(); + } + + public int collectAttackAssists(Unit unit, Unit target, UnitList units) + { + int s = collectAttackAssists(unit, target, units.asPawns(), engagementAssists.asPawns()); + activatedUnits.add(unit); + return s; + } + + public boolean toggleAttackAssist(Unit unit) + { + if (activatedUnits.contains(unit)) { + activatedUnits.remove(unit); + unit.hideAttack(); + unit.showAttackAssist(); + return false; + } else { + activatedUnits.add(unit); + unit.showAttack(); + unit.hideAttackAssist(); + return true; + } + } + + public void collectAndShowMovesAndAssits(Unit unit) + { + hidePossibleMoves(); + hideMoveableUnits(); + collectPossibleMoves(unit); + collectMoveableUnits(unit); + showPossibleMoves(); + showMoveableUnits(); + activatedUnits.clear(); + } + + // -> implement MoveToAnimationCb + + @Override + public void moveToAnimationEnter(Moveable moveable, float x, float y, float r) + { + claim(getHexAt(x, y), (Army) moveable.getFaction()); + } + + @Override + public void moveToAnimationLeave(Moveable moveable, float x, float y, float r) + { + unclaim(getHexAt(x, y)); + } + + @Override + public void moveToAnimationDone(Moveable moveable, float x, float y, float r) + { + } + + // <- implement MoveToAnimationCb + + private int process(Unit unit, Move move) + { + RustAndDust.debug(" Move", String.format("%s %s", move.type, move.toString())); + + int r = 1; + + switch(move.type) { + case REGULAR: + initMove(unit); + movePawn(unit, move, this); + r = moveableUnits.size(); + break; + case EXIT: + initMove(unit); + movePawn(unit, move, this); + ctrl.player.unitWithdraw(unit); + r = moveableUnits.size(); + break; + case SET: + setPawnOnto(unit, move); + ctrl.player.unitEntry(unit); + claim((Hex) move.to, unit.getArmy()); + break; + case ENTER: + enterPawn(unit, move); + ctrl.player.unitEntry(unit); + claim((Hex) move.to, unit.getArmy()); + break; + default: + System.err.println(String.format("process wrong Move type %s", move.type)); + r = -1; + break; + } + + return r; + } + + private int promoteUnit(final Unit unit, final Player player) + { + activatedUnits.add(unit); + + Hex hex = unit.getHex(); + AnimationSequence seq = AnimationSequence.get(2); + seq.addAnimation(PromoteAnimation.get((unit.getArmy() == Army.US), hex.getX(), hex.getY(), ctrl.cfg.fxVolume)); + seq.addAnimation ( RunnableAnimation.get(unit, new Runnable() { + @Override + public void run() { + player.promote(unit); + } + })); + addAnimation(seq); + return 1; + } + + private int process(Command cmd) + { + RustAndDust.debug("Command", cmd.toString()); + + int r = 1; + + switch(cmd.type) { + case MOVE: + r = process(cmd.unit, cmd.move); + break; + case PROMOTE: + r = promoteUnit(cmd.unit, cmd.player); + break; + case ENGAGE: + resolveEngagement(cmd.engagement); + r = doEngagement(cmd.engagement); + break; + default: + System.err.println(String.format("process wrong Command type %s", cmd.type)); + r = -1; + break; + } + + if (r != -1) + commands.add(cmd); + + return r; + } + + // Ctrl Methods + + public void init() + { + actionDone(); + } + + public void turnDone() + { + RustAndDust.debug("TurnDone", String.format(" Processed Commands : %d", commands.size())); + + if (objectives.modifiedCount() > 0) + throw new RuntimeException("objectives not cleared"); + + // FIXME do something with these Commands + commands.dispose(); + } + + public void actionDone() + { + objectives.forget(); + } + + // STATES ENTRY -> + + public void showOnBoard(final Unit unit, Hex to, Orientation o) + { + setPawnOnto(unit, to, o); + } + + public boolean setOnBoard(final Unit unit, Hex to, Orientation entry) + { + commands.dispose(unit); + return (process(getMoveCommand(unit, Move.getSet(unit, to, entry))) == 1); + } + + public boolean enterBoard(final Unit unit, Hex to, int allowedMoves) + { + Orientation entry = findBestEntry(unit, to, allowedMoves); + if (entry == Orientation.KEEP) + return false; + + return (process(getMoveCommand(unit, Move.getEnter(unit, to, entry))) == 1); + } + + public int exitBoard(final Unit unit) + { + return process(getMoveCommand(unit, pathBuilder.getExitMove())); + } + + public int moveUnit(final Unit unit) + { + return process(getMoveCommand(unit, pathBuilder.getMove())); + } + + public void revertMoves() + { + for (Unit unit: activatedUnits) { + RustAndDust.debug(" revertMove() " + unit); + revertLastPawnMove(unit); + commands.dispose(unit, Command.CommandType.MOVE); + } + activatedUnits.clear(); + objectives.revert(this); + } + + public void revertEnter(final Unit unit) + { + RustAndDust.debug(" revertEnter() "+ unit); + removePawn(unit); + objectives.revert(this); + ctrl.player.revertUnitEntry(unit); + commands.dispose(unit); + unit.reset(); + } + + public boolean engageUnit(final Unit unit, final Unit target) + { + attack(unit, target, true); + + Command cmd = Command.get(ctrl.player); + cmd.setEngage(unit, target); + return (process(cmd) == 1); + } + + public void promoteUnit(final Unit unit) + { + Command cmd = Command.get(ctrl.player); + cmd.setPromote(unit); + process(cmd); + } + + // STATES ENTRY <- + + private Command getMoveCommand(Unit unit, Move move) + { + Command cmd = Command.get(ctrl.player); + cmd.setMove(unit, move); + return cmd; + } + + private void initMove(Unit unit) + { + moveableUnits.remove(unit); + activatedUnits.add(unit); + playMoveSound(unit); + } + + private void playMoveSound(Unit unit) + { + if (unit.isA(Unit.UnitType.INFANTRY)) + sound = infantryMoveSound; + else + sound = tankMoveSound; + soundId = sound.play(ctrl.cfg.fxVolume); + } + + @Override + protected void animationsOver() + { + if (soundId >= 0) { + addAnimation( SoundAnimation.get(SoundAnimation.Action.FADE_OUT, sound, soundId, ctrl.cfg.fxVolume, 0.5f)); + soundId = -1; + return; + } + ctrl.animationsOver(); + } + + private void addEngagementAnimation(Unit target) + { + FireAnimation.reset(); + Hex to = target.getHex(); + for (Unit u : activatedUnits) { + Hex from = u.getHex(); + float halfWidth = (u.getWidth() / 2f); + if (u.isA(Unit.UnitType.INFANTRY)) + addAnimation(InfantryFireAnimation.get(ctrl.cfg.fxVolume, from.getX(), from.getY(), to.getX(), to.getY(), halfWidth)); + else + addAnimation(TankFireAnimation.get(ctrl.cfg.fxVolume, from.getX(), from.getY(), to.getX(), to.getY(), halfWidth)); + } + } + + private void resolveEngagement(Engagement e) + { + int dice = e.d1 + e.d2; + + int distance = 0; + boolean mayReroll = false; + boolean night = (meteorology.day == Meteorology.Day.NIGHT); + boolean flankAttack = false; + boolean terrainBonus = true; + + for (Unit unit : activatedUnits) { + if (unit != e.attacker) + e.addAssist(unit); + if (unit.isAce()) + mayReroll = true; + if (unit.isFlankAttack()) + flankAttack = true; + if (unit.isA(Unit.UnitType.INFANTRY)) + terrainBonus = false; + if (night) { + if (distance < unit.attackDistance()) + distance = unit.attackDistance(); + } + } + + int cnt = activatedUnits.size(); + int def = e.defender.getDefense(e.attacker.getTile()); + int flk = (flankAttack ? Unit.FLANK_ATTACK_BONUS : 0); + int tdf = (terrainBonus ? e.defender.getTile().defense() : 0); + int wdf = 0; + if (night) { + if (distance > 3) + wdf = 3; + else if (distance > 2) + wdf = 2; + else if (distance > 1) + wdf = 1; + } + int s1 = (dice + cnt + flk); + int s2 = (def + tdf + wdf); + + boolean success = false; + if (dice == 2) { + success = false; + } else if (dice == 12) { + success = true; + } else { + success = (s1 >= s2); + } + if (!success && mayReroll) { + dice = e.d3 + e.d4; + s1 = (dice + cnt + flk); + if (dice == 2) { + success = false; + } else if (dice == 12) { + success = true; + } else { + success = (s1 >= s2); + } + } else { + e.d3 = 0; + e.d4 = 0; + } + + e.set(cnt, flk, def, tdf, wdf); + e.success = success; + } + + private int doEngagement(Engagement e) + { + breakUnits.clear(); + activatedUnits.clear(); + + activatedUnits.add(e.attacker); + for (Unit u : e.assists) + activatedUnits.add(u); + + for (Unit u : activatedUnits) { + u.engage(); + if (u.isA(Unit.UnitType.INFANTRY)) + breakUnits.add(u); + } + + if (e.success) { + unclaim(e.defender.getHex()); + removePawn(e.defender); + destroy.set(2f, e.defender); + addAnimation(destroy); + } + + if ((activatedUnits.size() == 1) && e.attacker.isA(Unit.UnitType.AT_GUN) && e.defender.isHardTarget()) + activatedUnits.clear(); + + ctrl.hud.engagementSummary(e, ctrl.cfg.fxVolume); + addEngagementAnimation(e.defender); + + return (e.success ? 1 : 0); + } + + // SHOW / HIDE + + public void togglePathOverlay(Hex hex) + { + boolean enable= !hex.isOverlayEnabled(Hex.MOVE); + enableOverlayOn(hex, Hex.MOVE, enable); + } + + private void showUnitsOverlay(UnitList units, int overlay, boolean on) + { + for (Unit unit : units) + unit.enableOverlay(overlay, on); + } + + public void showMoveableUnits() { showUnitsOverlay(moveableUnits, Unit.MOVE, true); } + public void hideMoveableUnits() { showUnitsOverlay(moveableUnits, Unit.MOVE, false); } + public void showPossibleTargets() { showUnitsOverlay(possibleTargets, Unit.TARGET, true); } + public void hidePossibleTargets() { showUnitsOverlay(possibleTargets, Unit.TARGET, false); } + public void showAttackAssists() { showUnitsOverlay(engagementAssists, Unit.MAY_FIRE, true); } + public void hideAttackAssists() { showUnitsOverlay(engagementAssists, Unit.FIRE, false); + showUnitsOverlay(engagementAssists, Unit.MAY_FIRE, false); } + public void showBreakUnits() { showUnitsOverlay(breakUnits, Unit.MOVE, true); } + public void hideBreakUnits() { showUnitsOverlay(breakUnits, Unit.MOVE, false); } + + public void showPossibleMoves() { possibleMoves.enable(Hex.AREA, true); } + public void hidePossibleMoves() { possibleMoves.enable(Hex.AREA, false); } + public void showPathBuilder() { pathBuilder.enable(Hex.AREA, true); } + public void hidePathBuilder() { pathBuilder.enable(Hex.AREA, false); } + public void showPath(Hex dst) { pathBuilder.enable(Hex.MOVE, true); showMove(dst); } + public void hidePath(Hex dst) { pathBuilder.enable(Hex.MOVE, false); hideMove(dst); } + + public void selectHex(Hex hex) { selectedTile.set(hex); } + public void unselectHex(Hex hex) { selectedTile.hide(); } + public void showMove(Hex hex) { enableOverlayOn(hex, Hex.MOVE, true); } + public void hideMove(Hex hex) { enableOverlayOn(hex, Hex.MOVE, false); } + public void showDirections(Hex hex) { enableOverlayOn(hex, Hex.DIRECTIONS, true); } + public void hideDirections(Hex hex) { enableOverlayOn(hex, Hex.DIRECTIONS, false); } + public void showOrientation(Hex hex, Orientation o) { enableOverlayOn(hex, Hex.ORIENTATION, o, true); } + public void hideOrientation(Hex hex) { enableOverlayOn(hex, Hex.ORIENTATION, false); } + public void showExit(Hex hex) { enableOverlayOn(hex, Hex.EXIT, true); } + public void hideExit(Hex hex) { enableOverlayOn(hex, Hex.EXIT, false); } + + public void showObjective(Hex hex, Army army, boolean hold) + { + if (hold) + enableOverlayOn(hex, Hex.OBJECTIVE_HOLD, true); + else + enableOverlayOn(hex, Hex.OBJECTIVE, true); + } + + + // -> implement ObjectiveSet.ObjectiveCb + + public void showObjective(Tile tile, Faction faction) + { + showObjective((Hex) tile, (Army) faction); + } + + // <- implement MoveToAnimationCb + + public void showObjective(Hex hex, Army army) + { + if (army == null) + army = Army.NONE; + switch(army) { + case GE: + enableOverlayOn(hex, Hex.OBJECTIVE_GE, true); + enableOverlayOn(hex, Hex.OBJECTIVE_US, false); + break; + case US: + enableOverlayOn(hex, Hex.OBJECTIVE_GE, false); + enableOverlayOn(hex, Hex.OBJECTIVE_US, true); + break; + case NONE: + default: + enableOverlayOn(hex, Hex.OBJECTIVE_GE, false); + enableOverlayOn(hex, Hex.OBJECTIVE_US, false); + break; + } + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Player.java b/core/src/ch/asynk/rustanddust/game/Player.java new file mode 100644 index 0000000..e368101 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Player.java @@ -0,0 +1,213 @@ +package ch.asynk.rustanddust.game; + +import java.util.Random; +import java.util.List; + +import ch.asynk.rustanddust.RustAndDust; + +public class Player +{ + private static final float MOVE_TIME = 0.4f; + + private static Random rand = new Random(); + + private int turn; + private int apSpent; + private int actionPoints; + private boolean deploymentDone; + + public Army army; + public UnitList units; + public UnitList casualties; + public UnitList reinforcement; + public UnitList withdrawed; + + public int actionCount; + public int lostEngagementCount; + public int wonEngagementCount; + + public Player(final RustAndDust game, Army army, int n) + { + this.army = army; + this.units = new UnitList(n); + this.casualties = new UnitList(n); + this.reinforcement = new UnitList(n); + this.withdrawed = new UnitList(n); + this.turn = 0; + this.apSpent = 0; + this.actionPoints = 0; + this.deploymentDone = false; + this.actionCount = 0; + this.lostEngagementCount = 0; + this.wonEngagementCount = 0; + } + + public String getName() + { + return army.toString(); + } + + public String toString() + { + return String.format("%s Turn:%d AP:%d units:%d casualties:%d", army, turn, actionPoints, units.size(), casualties.size()); + } + + public String getStats() + { + return String.format("%s\n%4d\n%4d\n%4d\n%4d\n%4d\n%4d", getName(), actionCount, unitsLeft(), withdrawed(), casualties(), wonEngagementCount, lostEngagementCount); + } + + public boolean is(Army army) + { + return (this.army == army); + } + + public boolean isEnemy(Unit unit) + { + return unit.isEnemy(army); + } + + public boolean isEnemy(Army other) + { + return army.isEnemy(other); + } + + public int unitsLeft() + { + return (units.size() + reinforcement.size()); + } + + public int reinforcement() + { + return reinforcement.size(); + } + + public int casualties() + { + return casualties.size(); + } + + public int withdrawed() + { + return withdrawed.size(); + } + + public void addUnit(Unit unit) + { + units.add(unit); + } + + public void addReinforcement(Unit unit) + { + reinforcement.add(unit); + } + + public void unitEntry(Unit unit) + { + reinforcement.remove(unit); + units.add(unit); + } + + public void revertUnitEntry(Unit unit) + { + units.remove(unit); + reinforcement.add(unit); + } + + public void casualty(Unit unit) + { + units.remove(unit); + casualties.add(unit); + } + + public void unitWithdraw(Unit unit) + { + units.remove(unit); + withdrawed.add(unit); + } + + public int getAp() + { + return ((apSpent < actionPoints) ? (apSpent + 1) : apSpent); + } + + public int getTurnDone() + { + return turn; + } + + public int getCurrentTurn() + { + return (turn + 1); + } + + public boolean apExhausted() + { + return (apSpent == actionPoints); + } + + public boolean isDeploymentDone() + { + return (deploymentDone || (reinforcement.size() == 0)); + } + + public void burnDownOneAp() + { + apSpent += 1; + actionCount += 1; + if (apSpent > actionPoints) RustAndDust.debug("ERROR: spent too much AP, please report"); + } + + public void turnEnd() + { + if (deploymentDone) + turn += 1; + else + deploymentDone = (reinforcement.size() == 0); + for (Unit unit : units) + unit.reset(); + } + + public void turnStart() + { + if (isDeploymentDone()) + computeActionPoints(); + } + + public int d6() + { + return rand.nextInt(6) + 1; + } + + private void computeActionPoints() + { + this.actionPoints = 2; + if (d6() > 2) { + this.actionPoints += 1; + if (d6() > 3) + this.actionPoints += 1; + } + apSpent = 0; + } + + public boolean canPromote(Unit unit) + { + if (unit.isHq()) return false; + for (Unit p: casualties) + if (p.isHqOf(unit)) return true; + return false; + } + + public boolean promote(Unit unit) + { + for (Unit p: casualties) { + if (p.isHqOf(unit)) { + unit.promote(); + p.degrade(); + return true; + } + } + + return false; + } +} diff --git a/core/src/ch/asynk/rustanddust/game/State.java b/core/src/ch/asynk/rustanddust/game/State.java new file mode 100644 index 0000000..db0d6af --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/State.java @@ -0,0 +1,35 @@ +package ch.asynk.rustanddust.game; + +public interface State +{ + enum StateType { + SELECT, + MOVE, + ROTATE, + ENGAGE, + BREAK, + PROMOTE, + ANIMATION, + REINFORCEMENT, + DEPLOYMENT, + WITHDRAW, + ABORT, + DONE + }; + + public void enter(StateType prevState); + + public void leave(StateType nextState); + + public StateType abort(); + + public StateType execute(); + + public void touchDown(); + + public void touchUp(); + + public boolean downInMap(float x, float y); + + public boolean upInMap(float x, float y); +} diff --git a/core/src/ch/asynk/rustanddust/game/Unit.java b/core/src/ch/asynk/rustanddust/game/Unit.java new file mode 100644 index 0000000..472bd20 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Unit.java @@ -0,0 +1,361 @@ +package ch.asynk.rustanddust.game; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; + +import ch.asynk.rustanddust.engine.Pawn; +import ch.asynk.rustanddust.engine.Tile; +import ch.asynk.rustanddust.engine.HeadedPawn; +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.game.Hex.Terrain; + +import ch.asynk.rustanddust.RustAndDust; + +public class Unit extends HeadedPawn +{ + public static final int MOVE = 0; + public static final int TARGET = 1; + public static final int FIRE = 2; + public static final int MAY_FIRE = 3; + public static final int ACE = 4; + public static final int HQ = 5; + public static final int HAS_FIRED = 6; + public static final int HAS_MOVED = 7; + + public static final int FLANK_ATTACK_BONUS = 1; + + public enum UnitType implements Pawn.PawnType + { + HARD_TARGET, + HARD_TARGET_HQ, + INFANTRY, + AT_GUN, + ARTILLERY + } + + public enum UnitId implements Pawn.PawnId + { + GE_AT_GUN("German Anti-Tank Gun"), + GE_INFANTRY("German Infantry"), + GE_KINGTIGER("German King Tiger"), + GE_PANZER_IV("German Panzer IV"), + GE_PANZER_IV_HQ("German Panzer IV HQ"), + GE_TIGER("German Tiger"), + GE_WESPE("German Wespe"), + + US_AT_GUN("US Anti-Tank Gun"), + US_INFANTRY("US Infantry"), + US_PERSHING("US Pershing"), + US_PERSHING_HQ("US Pershing HQ"), + US_PRIEST("US Priest"), + US_SHERMAN("US Sherman"), + US_SHERMAN_HQ("US Sherman HQ"), + US_WOLVERINE("US Wolverine"); + + private String s; + UnitId(String s) { this.s = s; } + public String toString() { return s; } + } + + public int rng; + public int def; + public int cdef; + public int mp; + public int mpLeft; + public UnitType type; + public UnitId id; + public boolean ace; + private boolean hasMoved; + private boolean hasFired; + + protected Unit(Army army, String pawn, String head, TextureAtlas pawns, TextureAtlas overlays) + { + super(army, pawn, head, pawns, overlays); + ace = false; + + } + + private void commonSetup() + { + mpLeft = mp; + enableOverlay(HQ, isHq()); + this.hasMoved = false; + this.hasFired = false; + updateDescr(); + } + + private void updateDescr() + { + if (cdef == -1) + this.descr = id.toString() + (ace ? " Ace " : "") + " (" + rng + "-" + def + "-" + mp + ")"; + else + this.descr = id.toString() + (ace ? " Ace " : "") + " (" + rng + "-" + def + "/" + cdef + "-" + mp + ")"; + } + + // hard tager + public Unit(Army army, UnitId id, UnitType type, int range, int defense, int movementPoints, String unit, String head, TextureAtlas pawns, TextureAtlas overlays) + { + this(army, unit, head, pawns, overlays); + this.rng = range; + this.def = defense; + this.cdef = -1; + this.mp = movementPoints; + this.id = id; + this.type = type; + commonSetup(); + } + + // soft tager + public Unit(Army army, UnitId id, UnitType type, int range, int defense, int concealedDefense, int movementPoints, String unit, String head, TextureAtlas pawns, TextureAtlas overlays) + { + this(army, unit, head, pawns, overlays); + this.rng = range; + this.def = defense; + this.cdef = concealedDefense; + this.mp = movementPoints; + this.id = id; + this.type = type; + commonSetup(); + } + + public Army getArmy() + { + return (Army) getFaction(); + } + + public Hex getHex() + { + return (Hex) getTile(); + } + + public boolean isAce() + { + return ace; + } + + public void setAce(boolean ace) + { + this.ace = ace; + updateDescr(); + enableOverlay(ACE, ace); + } + + @Override + public int getMovementPoints() + { + return mpLeft; + } + + @Override + public int getRoadMarchBonus() + { + return 1; + } + + @Override + public int getEngagementRangeFrom(Tile tile) + { + if (!isA(UnitType.INFANTRY) && tile.isA(Terrain.HILLS)) + return rng + 1; + return rng; + } + + @Override + public int getAngleOfAttack() + { + return orientation.getFrontSides(); + } + + @Override + public int getFlankSides() + { + return orientation.getBackSides(); + } + + @Override + public int getDefense(Tile tile) + { + if (!isHardTarget() && (tile.isA(Terrain.HILLS) || tile.isA(Terrain.WOODS) || tile.isA(Terrain.TOWN))) + return cdef; + + return def; + } + + @Override + public boolean isUnit() + { + return true; + } + + @Override + public boolean isA(PawnId i) + { + return (id == i); + } + + @Override + public boolean isA(PawnType t) + { + return (type == t); + } + + @Override + public boolean isHq() + { + return isA(UnitType.HARD_TARGET_HQ); + } + + @Override + public boolean isHqOf(Pawn other) + { + if (isA(UnitId.GE_PANZER_IV_HQ) && other.isA(UnitId.GE_PANZER_IV)) return true; + if (isA(UnitId.US_PERSHING_HQ) && other.isA(UnitId.US_PERSHING)) return true; + if (isA(UnitId.US_SHERMAN_HQ) && other.isA(UnitId.US_SHERMAN)) return true; + return false; + } + + public void promote() + { + if (isA(UnitId.GE_PANZER_IV)) + id = UnitId.GE_PANZER_IV_HQ; + else if (isA(UnitId.US_PERSHING)) + id = UnitId.US_PERSHING_HQ; + else if (isA(UnitId.US_SHERMAN)) + id = UnitId.US_SHERMAN_HQ; + else + return; + + type = UnitType.HARD_TARGET_HQ; + enableOverlay(HQ, true); + updateDescr(); + } + + public void degrade() + { + if (isA(UnitId.GE_PANZER_IV_HQ)) + id = UnitId.GE_PANZER_IV; + else if (isA(UnitId.US_PERSHING_HQ)) + id = UnitId.US_PERSHING; + else if (isA(UnitId.US_SHERMAN_HQ)) + id = UnitId.US_SHERMAN; + else + return; + + type = UnitType.HARD_TARGET; + enableOverlay(HQ, false); + updateDescr(); + } + + @Override + public boolean isHardTarget() + { + return (isA(UnitType.HARD_TARGET) || isA(UnitType.HARD_TARGET_HQ) || isA(UnitType.ARTILLERY)); + } + + @Override + public boolean canRotate() + { + return canMove(); + } + + @Override + public boolean canMove() + { + if (isHardTarget()) return !hasMoved; + return (!hasMoved && !hasFired); + } + + @Override + public boolean canEngage() + { + if (isHardTarget()) return !hasFired; + return (!hasMoved && !hasFired); + } + + @Override + public boolean canAssistEngagementWithoutLos() + { + return isA(UnitType.ARTILLERY); + } + + @Override + public boolean canEngage(Pawn other) + { + return (isEnemy(other) && canEngage()); + } + + public boolean canHQMove() + { + return (isHq() && ((move == null) || (!move.isEnter()))); + } + + public void setMoved() + { + hasMoved = true; + updateOverlays(); + } + + @Override + public void move() + { + int cost = move.cost; + + if (move.roadMarch && (cost > mpLeft)) + cost -= getRoadMarchBonus(); + + if (cost > mpLeft) + RustAndDust.debug("ERROR: Movement point exceeded: " + cost + "/" + mpLeft + " please report"); + + if (move.isFinal()) + setMoved(); + + mpLeft -= cost; + } + + @Override + public void engage() + { + hasFired = true; + updateOverlays(); + } + + @Override + public void reset() + { + super.reset(); + mpLeft = mp; + hasFired = false; + hasMoved = false; + hideHasMoved(); + hideHasFired(); + } + + @Override + public void revertLastMove() + { + hasMoved = false; + mpLeft = mp; + updateOverlays(); + move = null; + } + + private void updateOverlays() + { + enableOverlay(HAS_MOVED, !canMove()); + enableOverlay(HAS_FIRED, !canEngage()); + } + + // SHOW / HIDE + public void showMoveable() { enableOverlay(MOVE, true); } + public void hideMoveable() { enableOverlay(MOVE, false); } + public void showTarget() { enableOverlay(TARGET, true); } + public void hideTarget() { enableOverlay(TARGET, false); } + public void showAttack() { enableOverlay(FIRE, true); } + public void hideAttack() { enableOverlay(FIRE, false); } + public void showAttackAssist() { enableOverlay(MAY_FIRE, true); } + public void hideAttackAssist() { enableOverlay(MAY_FIRE, false); } + public void showHasMoved() { enableOverlay(HAS_MOVED, true); } + public void hideHasMoved() { enableOverlay(HAS_MOVED, false); } + public void showHasFired() { enableOverlay(HAS_FIRED, true); } + public void hideHasFired() { enableOverlay(HAS_FIRED, false); } +} diff --git a/core/src/ch/asynk/rustanddust/game/UnitList.java b/core/src/ch/asynk/rustanddust/game/UnitList.java new file mode 100644 index 0000000..9637036 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/UnitList.java @@ -0,0 +1,20 @@ +package ch.asynk.rustanddust.game; + +import java.util.Collection; +import java.util.ArrayList; + +import ch.asynk.rustanddust.engine.Pawn; + +public class UnitList extends ArrayList<Unit> +{ + public UnitList(int n) + { + super(n); + } + + @SuppressWarnings("unchecked") + public Collection<Pawn> asPawns() + { + return (Collection) this; + } +} diff --git a/core/src/ch/asynk/rustanddust/game/Zone.java b/core/src/ch/asynk/rustanddust/game/Zone.java new file mode 100644 index 0000000..ff15299 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/Zone.java @@ -0,0 +1,14 @@ +package ch.asynk.rustanddust.game; + +import ch.asynk.rustanddust.engine.Orientation; + +public class Zone extends HexSet +{ + public int allowedMoves; + public Orientation orientation; + + public Zone(Map map, int n) + { + super(map, n); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleCommon.java b/core/src/ch/asynk/rustanddust/game/battles/BattleCommon.java new file mode 100644 index 0000000..731c616 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/BattleCommon.java @@ -0,0 +1,151 @@ +package ch.asynk.rustanddust.game.battles; + +import java.util.Random; +import java.util.HashMap; +import java.util.ArrayList; + +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Battle; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.HexSet; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Unit.UnitId; +import ch.asynk.rustanddust.game.State.StateType; + +public abstract class BattleCommon implements Battle +{ + protected final static Random random = new Random(); + + protected Factory.MapType mapType; + protected String name; + protected String description; + protected Factory factory; + protected Player usPlayer; + protected Player gePlayer; + protected ArrayList<Zone> entryZone = new ArrayList<Zone>(); + protected ArrayList<Zone> exitZone = new ArrayList<Zone>(); + protected HashMap<Unit, Zone> unitEntry = new HashMap<Unit, Zone>(); + protected HashMap<Unit, Zone> unitExit = new HashMap<Unit, Zone>(); + + public BattleCommon(Factory factory) + { + this.factory = factory; + } + + @Override + public void init() + { + this.usPlayer = factory.getPlayer(Army.US); + this.gePlayer = factory.getPlayer(Army.GE); + } + + @Override + public String toString() + { + return getName(); + } + + @Override + public String getName() + { + return name; + } + + @Override + public String getDescription() + { + return description; + } + + @Override + public Factory.MapType getMapType() + { + return mapType; + } + + @Override + public Map getMap() + { + return factory.getMap(mapType); + } + + @Override + public Player opponent(Player player) + { + if (player == usPlayer) + return gePlayer; + return usPlayer; + } + + @Override + public boolean deploymentDone(Player player) + { + return player.isDeploymentDone(); + } + + @Override + public StateType getState(Player player) + { + if (!player.isDeploymentDone()) + return StateType.DEPLOYMENT; + return StateType.SELECT; + } + + @Override + public boolean getReinforcement(Ctrl ctrl, Map map) + { + return false; + } + + @Override + public Zone getEntryZone(Unit unit) + { + return unitEntry.get(unit); + } + + @Override + public Zone getExitZone(Unit unit) + { + return unitExit.get(unit); + } + + public void addEntryZone(Zone entry) + { + entryZone.add(entry); + } + + public void addExitZone(Zone exit) + { + exitZone.add(exit); + exit.enable(Hex.EXIT, true); + } + + public void addReinforcement(Player player, Zone entryZone, UnitId unitId) + { + addReinforcement(player, entryZone, unitId, false); + } + + public void addReinforcement(Player player, Zone entryZone, Zone exitZone, UnitId unitId) + { + addReinforcement(player, entryZone, exitZone, unitId, false); + } + + public void addReinforcement(Player player, Zone entryZone, UnitId unitId, boolean ace) + { + addReinforcement(player, entryZone, null, unitId, ace); + } + + public void addReinforcement(Player player, Zone entryZone, Zone exitZone, UnitId unitId, boolean ace) + { + Unit unit = factory.getUnit(unitId); + unit.setAce(ace); + player.addReinforcement(unit); + unitEntry.put(unit, entryZone); + if (exitZone != null) + unitExit.put(unit, exitZone); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleCounterAttack.java b/core/src/ch/asynk/rustanddust/game/battles/BattleCounterAttack.java new file mode 100644 index 0000000..cea1d13 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/BattleCounterAttack.java @@ -0,0 +1,150 @@ +package ch.asynk.rustanddust.game.battles; + +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Unit.UnitId; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.engine.Orientation; + +public class BattleCounterAttack extends BattleCommon +{ + public BattleCounterAttack(Factory factory) + { + super(factory); + name = "Counterattack"; + mapType = Factory.MapType.MAP_B; + } + + @Override + public Position getHudPosition(Player player) + { + return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT); + } + + @Override + public Player getPlayer() + { + if (!gePlayer.isDeploymentDone()) + return gePlayer; + if (!usPlayer.isDeploymentDone()) + return usPlayer; + if (gePlayer.getTurnDone() == usPlayer.getTurnDone()) + return gePlayer; + return usPlayer; + } + + public Player checkVictory(Ctrl ctrl) + { + if (ctrl.opponent.unitsLeft() == 0) + return ctrl.player; + + if (gePlayer.withdrawed() >= 3) + return gePlayer; + + if ((ctrl.player.getTurnDone() < 9) || (ctrl.opponent.getTurnDone() < 9)) + return null; + + return usPlayer; + } + + @Override + public boolean getReinforcement(Ctrl ctrl, Map map) + { + if (ctrl.player.is(Army.GE)) + return false; + if (ctrl.player.getCurrentTurn() != 5) + return false; + + // hex row 1 + Zone usEntry = new Zone(map, 9); + usEntry.allowedMoves = (Orientation.SOUTH.s | Orientation.SOUTH_EAST.s | Orientation.SOUTH_WEST.s); + usEntry.add(map.getHex(9, 0)); + usEntry.add(map.getHex(9, 1)); + usEntry.add(map.getHex(10, 2)); + usEntry.add(map.getHex(10, 3)); + usEntry.add(map.getHex(11, 4)); + usEntry.add(map.getHex(11, 5)); + usEntry.add(map.getHex(12, 6)); + usEntry.add(map.getHex(12, 7)); + usEntry.add(map.getHex(13, 8)); + addEntryZone(usEntry); + + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_PRIEST); + + return true; + } + + @Override + public void setup(Ctrl ctrl, Map map) + { + // hex row 1 + Zone geExit = new Zone(map, 9); + geExit.orientation = Orientation.NORTH; + geExit.add(map.getHex(9, 0)); + geExit.add(map.getHex(9, 1)); + geExit.add(map.getHex(10, 2)); + geExit.add(map.getHex(10, 3)); + geExit.add(map.getHex(11, 4)); + geExit.add(map.getHex(11, 5)); + geExit.add(map.getHex(12, 6)); + geExit.add(map.getHex(12, 7)); + geExit.add(map.getHex(13, 8)); + addExitZone(geExit); + + // hex rows 8-9 + Zone geEntry = new Zone(map, 18); + geEntry.orientation = Orientation.NORTH; + for (int i = 0; i < 2; i++) { + geEntry.add(map.getHex((1 + i), 0)); + geEntry.add(map.getHex((1 + i), 1)); + geEntry.add(map.getHex((2 + i), 2)); + geEntry.add(map.getHex((2 + i), 3)); + geEntry.add(map.getHex((3 + i), 4)); + geEntry.add(map.getHex((3 + i), 5)); + geEntry.add(map.getHex((4 + i), 6)); + geEntry.add(map.getHex((4 + i), 7)); + geEntry.add(map.getHex((5 + i), 8)); + } + addEntryZone(geEntry); + + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_TIGER); + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_TIGER); + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, geExit, UnitId.GE_WESPE); + + // hex rows 1-4 + Zone usEntry = new Zone(map, 36); + usEntry.orientation = Orientation.SOUTH; + for (int i = 0; i < 4; i++) { + usEntry.add(map.getHex((6 + i), 0)); + usEntry.add(map.getHex((6 + i), 1)); + usEntry.add(map.getHex((7 + i), 2)); + usEntry.add(map.getHex((7 + i), 3)); + usEntry.add(map.getHex((8 + i), 4)); + usEntry.add(map.getHex((8 + i), 5)); + usEntry.add(map.getHex((9 + i), 6)); + usEntry.add(map.getHex((9 + i), 7)); + usEntry.add(map.getHex((10 + i), 8)); + } + addEntryZone(usEntry); + + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE); + addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleFrontalAssault.java b/core/src/ch/asynk/rustanddust/game/battles/BattleFrontalAssault.java new file mode 100644 index 0000000..372e045 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/BattleFrontalAssault.java @@ -0,0 +1,124 @@ +package ch.asynk.rustanddust.game.battles; + +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.HexSet; +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Unit.UnitId; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.engine.Orientation; + +public class BattleFrontalAssault extends BattleCommon +{ + public BattleFrontalAssault(Factory factory) + { + super(factory); + name = "Frontal Assault"; + mapType = Factory.MapType.MAP_A; + } + + + @Override + public Player getPlayer() + { + if (!gePlayer.isDeploymentDone()) { + int n = gePlayer.reinforcement(); + if (n > 4) + return gePlayer; + else { + if (usPlayer.isDeploymentDone()) + return gePlayer; + else + return usPlayer; + } + } + if (gePlayer.getTurnDone() == usPlayer.getTurnDone()) + return usPlayer; + return gePlayer; + } + + @Override + public Position getHudPosition(Player player) + { + return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT); + } + + @Override + public boolean deploymentDone(Player player) + { + if (player.isDeploymentDone()) + return true; + return ((player.is(Army.GE) && (gePlayer.reinforcement.size() == 4))); + } + + @Override + public Player checkVictory(Ctrl ctrl) + { + if (ctrl.opponent.unitsLeft() == 0) + return ctrl.player; + + if ((ctrl.player.getTurnDone() < 10) || (ctrl.opponent.getTurnDone() < 10)) + return null; + + if (ctrl.map.objectives.count(Army.US) >= 2) + return usPlayer; + else + return gePlayer; + } + + @Override + public void setup(Ctrl ctrl, Map map) + { + // G9, E6, H4 + map.addObjective(2, 2, Army.NONE); + map.addObjective(6, 4, Army.NONE); + map.addObjective(6, 1, Army.NONE); + + // hex rows E-H + Zone geEntry = new Zone(map, 38); + geEntry.orientation = Orientation.NORTH_WEST; + for (int i = 2; i < 12; i++) + geEntry.add(map.getHex(i, 4)); + for (int i = 2; i < 11; i++) + geEntry.add(map.getHex(i, 3)); + for (int i = 1; i < 11; i++) + geEntry.add(map.getHex(i, 2)); + for (int i = 1; i < 10; i++) + geEntry.add(map.getHex(i, 1)); + addEntryZone(geEntry); + + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER); + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + + // hex rows A-B + Zone usEntry = new Zone(map, 19); + usEntry.orientation = Orientation.SOUTH_EAST; + for (int i = 4; i < 14; i++) + usEntry.add(map.getHex(i, 8)); + for (int i = 4; i < 13; i++) + usEntry.add(map.getHex(i, 7)); + addEntryZone(usEntry); + + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE); + addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_PRIEST); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleHeadToHead.java b/core/src/ch/asynk/rustanddust/game/battles/BattleHeadToHead.java new file mode 100644 index 0000000..0d88846 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/BattleHeadToHead.java @@ -0,0 +1,118 @@ +package ch.asynk.rustanddust.game.battles; + +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.HexSet; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Unit.UnitId; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.engine.Orientation; + +public class BattleHeadToHead extends BattleCommon +{ + private Army firstArmy; + + public BattleHeadToHead(Factory factory) + { + super(factory); + name = "Head To Head"; + firstArmy = ((random.nextInt(2) == 0) ? Army.US : Army.GE); + mapType = Factory.MapType.MAP_A; + } + + @Override + public Player getPlayer() + { + if (gePlayer.getTurnDone() == usPlayer.getTurnDone()) + return ((firstArmy == Army.US) ? usPlayer : gePlayer); + else + return ((firstArmy == Army.US) ? gePlayer : usPlayer); + } + + @Override + public Position getHudPosition(Player player) + { + return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT); + } + + @Override + public Player checkVictory(Ctrl ctrl) + { + if (ctrl.opponent.unitsLeft() == 0) + return ctrl.player; + + if ((ctrl.player.getTurnDone() < 10) || (ctrl.opponent.getTurnDone() < 10)) + return null; + + if (ctrl.map.objectives.count(Army.US) >= 2) + return usPlayer; + if (ctrl.map.objectives.count(Army.GE) >= 2) + return gePlayer; + + return null; + } + + @Override + public void setup(Ctrl ctrl, Map map) + { + // end deployment + usPlayer.turnEnd(); + gePlayer.turnEnd(); + + // B6, E6, H4 + map.addObjective(7, 7, Army.NONE); + map.addObjective(6, 4, Army.NONE); + map.addObjective(6, 1, Army.NONE); + + // southern hex row + Zone geEntry = new Zone(map, 9); + geEntry.allowedMoves = (Orientation.NORTH.s | Orientation.NORTH_EAST.s | Orientation.NORTH_WEST.s); + geEntry.add(map.getHex(0, 0)); + geEntry.add(map.getHex(1, 1)); + geEntry.add(map.getHex(1, 2)); + geEntry.add(map.getHex(2, 3)); + geEntry.add(map.getHex(2, 4)); + geEntry.add(map.getHex(3, 5)); + geEntry.add(map.getHex(3, 6)); + geEntry.add(map.getHex(4, 7)); + geEntry.add(map.getHex(4, 8)); + addEntryZone(geEntry); + + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER); + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + + // northern hex row + Zone usEntry = new Zone(map, 9); + usEntry.allowedMoves = (Orientation.SOUTH.s | Orientation.SOUTH_EAST.s | Orientation.SOUTH_WEST.s); + usEntry.add(map.getHex(9, 0)); + usEntry.add(map.getHex(9, 1)); + usEntry.add(map.getHex(10, 2)); + usEntry.add(map.getHex(10, 3)); + usEntry.add(map.getHex(11, 4)); + usEntry.add(map.getHex(11, 5)); + usEntry.add(map.getHex(12, 6)); + usEntry.add(map.getHex(12, 7)); + usEntry.add(map.getHex(13, 8)); + addEntryZone(usEntry); + + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE); + addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_PRIEST); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleLastStand.java b/core/src/ch/asynk/rustanddust/game/battles/BattleLastStand.java new file mode 100644 index 0000000..f898ce9 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/BattleLastStand.java @@ -0,0 +1,136 @@ +package ch.asynk.rustanddust.game.battles; + +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.HexSet; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Unit.UnitId; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.engine.Orientation; + +public class BattleLastStand extends BattleCommon +{ + public BattleLastStand(Factory factory) + { + super(factory); + name = "Last Stand"; + mapType = Factory.MapType.MAP_B; + } + + @Override + public Position getHudPosition(Player player) + { + return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT); + } + + @Override + public Player getPlayer() + { + if (!gePlayer.isDeploymentDone()) + return gePlayer; + if (!usPlayer.isDeploymentDone()) + return usPlayer; + if (gePlayer.getTurnDone() == usPlayer.getTurnDone()) + return usPlayer; + return gePlayer; + } + + public Player checkVictory(Ctrl ctrl) + { + if (ctrl.opponent.unitsLeft() == 0) + return ctrl.player; + + if ((ctrl.player.getTurnDone() < 8) || (ctrl.opponent.getTurnDone() < 8)) + return null; + + int gePoints = usPlayer.casualties(); + int usPoints = gePlayer.casualties(); + usPoints += ctrl.map.objectives.count(Army.US); + for (Unit unit : gePlayer.casualties) { + if (unit.isAce()) + usPoints += 1; + } + + if (usPoints > gePoints) + return usPlayer; + else + return gePlayer; + } + + @Override + public void setup(Ctrl ctrl, Map map) + { + // A7, E6, F6, G10 + map.addObjective(7, 8, Army.NONE); + map.addObjective(6, 4, Army.NONE); + map.addObjective(5, 3, Army.NONE); + map.addObjective(1, 2, Army.NONE); + + // 1 hex of E7 + Zone geEntry = new Zone(map, 7); + geEntry.orientation = Orientation.NORTH; + geEntry.add(map.getHex(5, 5)); + geEntry.add(map.getHex(4, 4)); + geEntry.add(map.getHex(4, 3)); + geEntry.add(map.getHex(5, 3)); + geEntry.add(map.getHex(6, 4)); + geEntry.add(map.getHex(6, 5)); + geEntry.add(map.getHex(5, 4)); + addEntryZone(geEntry); + + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER, true); + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER); + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER); + + // hex rows 7-10 + geEntry = new Zone(map, 32); + geEntry.orientation = Orientation.NORTH; + for (int i = 0; i < 4; i++) { + geEntry.add(map.getHex(i, 0)); + geEntry.add(map.getHex((i + 1), 2)); + geEntry.add(map.getHex((i + 2), 4)); + geEntry.add(map.getHex((i + 3), 6)); + geEntry.add(map.getHex((i + 4), 8)); + } + for (int i = 0; i < 3; i++) { + geEntry.add(map.getHex((i + 1), 1)); + geEntry.add(map.getHex((i + 2), 3)); + geEntry.add(map.getHex((i + 3), 5)); + geEntry.add(map.getHex((i + 4), 7)); + } + addEntryZone(geEntry); + + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_WESPE); + + // hex rows hex row 1 + E2 + C2 + Zone usEntry = new Zone(map, 11); + usEntry.orientation = Orientation.SOUTH; + usEntry.add(map.getHex(9, 0)); + usEntry.add(map.getHex(9, 1)); + usEntry.add(map.getHex(10, 2)); + usEntry.add(map.getHex(10, 3)); + usEntry.add(map.getHex(11, 4)); + usEntry.add(map.getHex(11, 5)); + usEntry.add(map.getHex(12, 6)); + usEntry.add(map.getHex(12, 7)); + usEntry.add(map.getHex(13, 8)); + usEntry.add(map.getHex(10, 4)); + usEntry.add(map.getHex(11, 6)); + addEntryZone(usEntry); + + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE); + addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleNightAction.java b/core/src/ch/asynk/rustanddust/game/battles/BattleNightAction.java new file mode 100644 index 0000000..f9817db --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/BattleNightAction.java @@ -0,0 +1,153 @@ +package ch.asynk.rustanddust.game.battles; + +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.HexSet; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Unit.UnitId; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.engine.Meteorology; + +public class BattleNightAction extends BattleCommon +{ + public BattleNightAction(Factory factory) + { + super(factory); + name = "Night Action"; + mapType = Factory.MapType.MAP_B; + } + + @Override + public Position getHudPosition(Player player) + { + return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT); + } + + @Override + public Player getPlayer() + { + if (!gePlayer.isDeploymentDone() || gePlayer.getCurrentTurn() == 1) + return gePlayer; + if (gePlayer.getTurnDone() > usPlayer.getTurnDone()) + return usPlayer; + return gePlayer; + } + + private boolean isClear(Map map, int col, int row) + { + Hex hex = map.getHex(col, row); + Unit unit = hex.getUnit(); + if ((unit != null) && unit.is(Army.GE)) { + map.selectHex(hex); + return false; + } + map.showMove(hex); + return true; + } + + public Player checkVictory(Ctrl ctrl) + { + if (ctrl.opponent.unitsLeft() == 0) + return ctrl.player; + + if ((ctrl.player.getTurnDone() < 9) || (ctrl.opponent.getTurnDone() < 9)) + return null; + + Map map = ctrl.map; + boolean clear = true; + clear &= isClear(map, 4, 8); + clear &= isClear(map, 5, 8); + clear &= isClear(map, 6, 8); + clear &= isClear(map, 7, 8); + clear &= isClear(map, 8, 8); + clear &= isClear(map, 8, 7); + clear &= isClear(map, 8, 6); + boolean upLeft = clear; + clear = true; + clear &= isClear(map, 8, 6); + clear &= isClear(map, 9, 6); + clear &= isClear(map, 10, 6); + clear &= isClear(map, 11, 6); + clear &= isClear(map, 12, 6); + boolean upRight = clear; + clear = true; + clear &= isClear(map, 1, 2); + clear &= isClear(map, 2, 3); + clear &= isClear(map, 3, 3); + clear &= isClear(map, 4, 3); + clear &= isClear(map, 5, 3); + clear &= isClear(map, 6, 4); + clear &= isClear(map, 7, 4); + clear &= isClear(map, 8, 4); + boolean bottomLeft = clear; + clear &= isClear(map, 8, 4); + clear &= isClear(map, 9, 4); + clear &= isClear(map, 10, 4); + clear &= isClear(map, 11, 4); + clear = true; + boolean bottomRight = clear; + // clear &= isClear(map, 8, 6); + // clear &= isClear(map, 8, 5); + // clear &= isClear(map, 8, 4); + // clear = true; + // boolean link = clear; + + if ((!upLeft || !upRight) && (!bottomLeft || !bottomRight)) + return gePlayer; + return usPlayer; + } + + @Override + public void setup(Ctrl ctrl, Map map) + { + map.meteorology.day = Meteorology.Day.NIGHT; + + // hex row I + Zone geEntry = new Zone(map, 10); + geEntry.orientation = Orientation.NORTH_EAST; + for (int i = 0; i < 10; i++) + geEntry.add(map.getHex(i, 0)); + addEntryZone(geEntry); + + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER); + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_INFANTRY); + addReinforcement(gePlayer, geEntry, UnitId.GE_INFANTRY); + + // hex rows A-B + Zone usEntry = new Zone(map, 19); + usEntry.orientation = Orientation.SOUTH; + for (int i = 0; i < 10; i++) { + usEntry.add(map.getHex((4 + i), 8)); + usEntry.add(map.getHex((3 + i), 6)); + usEntry.add(map.getHex((2 + i), 4)); + usEntry.add(map.getHex((1 + i), 2)); + } + for (int i = 0; i < 9; i++) { + usEntry.add(map.getHex((4 + i), 7)); + usEntry.add(map.getHex((3 + i), 5)); + usEntry.add(map.getHex((2 + i), 3)); + } + addEntryZone(usEntry); + + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, UnitId.US_WOLVERINE); + addReinforcement(usPlayer, usEntry, UnitId.US_AT_GUN); + addReinforcement(usPlayer, usEntry, UnitId.US_INFANTRY); + addReinforcement(usPlayer, usEntry, UnitId.US_INFANTRY); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleStabToTheFlank.java b/core/src/ch/asynk/rustanddust/game/battles/BattleStabToTheFlank.java new file mode 100644 index 0000000..c45bac4 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/BattleStabToTheFlank.java @@ -0,0 +1,143 @@ +package ch.asynk.rustanddust.game.battles; + +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.HexSet; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Unit.UnitId; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.engine.Orientation; + +public class BattleStabToTheFlank extends BattleCommon +{ + public BattleStabToTheFlank(Factory factory) + { + super(factory); + name = "Stab To The Flank"; + mapType = Factory.MapType.MAP_B; + } + + @Override + public Position getHudPosition(Player player) + { + return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT); + } + + @Override + public Player getPlayer() + { + if (!usPlayer.isDeploymentDone() || usPlayer.getCurrentTurn() == 1) + return usPlayer; + if (usPlayer.getTurnDone() > gePlayer.getTurnDone()) + return gePlayer; + return usPlayer; + } + + public Player checkVictory(Ctrl ctrl) + { + if (ctrl.opponent.unitsLeft() == 0) + return ctrl.player; + + if ((ctrl.player.getTurnDone() < 9) || (ctrl.opponent.getTurnDone() < 9)) + return null; + + int gePoints = usPlayer.casualties(); + int usPoints = gePlayer.casualties(); + usPoints += ctrl.map.objectives.count(Army.US); + + int withdrawed = usPlayer.withdrawed(); + if (withdrawed == 0) + gePoints += 1; + else + usPoints += withdrawed; + + if (usPoints > gePoints) + return usPlayer; + else + return gePlayer; + } + + @Override + public boolean getReinforcement(Ctrl ctrl, Map map) + { + if (ctrl.player.is(Army.US)) + return false; + if (ctrl.player.getCurrentTurn() != 3) + return false; + + // hex rows I + Zone geEntry = new Zone(map, 9); + geEntry.allowedMoves = (Orientation.SOUTH_WEST.s | Orientation.NORTH_WEST.s); + for (int i = 0; i < 10; i++) + geEntry.add(map.getHex(i, 0)); + addEntryZone(geEntry); + + addReinforcement(gePlayer, geEntry, UnitId.GE_TIGER, true); + + return true; + } + + @Override + public void setup(Ctrl ctrl, Map map) + { + // F6, E6 + map.addHoldObjective(5, 3, Army.NONE); + map.addObjective(6, 4, Army.NONE); + + // hex rows D-I + Zone geEntry = new Zone(map, 57); + geEntry.orientation = Orientation.NORTH; + for (int i = 3; i < 12; i++) + geEntry.add(map.getHex(i, 5)); + for (int i = 2; i < 12; i++) + geEntry.add(map.getHex(i, 4)); + for (int i = 2; i < 11; i++) + geEntry.add(map.getHex(i, 3)); + for (int i = 1; i < 11; i++) + geEntry.add(map.getHex(i, 2)); + for (int i = 1; i < 10; i++) + geEntry.add(map.getHex(i, 1)); + for (int i = 0; i < 10; i++) + geEntry.add(map.getHex(i, 0)); + + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV_HQ); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_PANZER_IV); + addReinforcement(gePlayer, geEntry, UnitId.GE_AT_GUN); + addReinforcement(gePlayer, geEntry, UnitId.GE_INFANTRY); + + // hex row I + Zone usExit = new Zone(map, 10); + usExit.orientation = Orientation.NORTH_EAST; + for (int i = 0; i < 10; i++) + geEntry.add(map.getHex(i, 0)); + addExitZone(usExit); + + // hex rows A-B + Zone usEntry = new Zone(map, 19); + usEntry.orientation = Orientation.SOUTH_EAST; + for (int i = 4; i < 13; i++) { + usEntry.add(map.getHex(i, 8)); + usEntry.add(map.getHex(i, 7)); + } + usEntry.add(map.getHex(13, 8)); + addEntryZone(usEntry); + + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN_HQ); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_SHERMAN); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_INFANTRY); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_INFANTRY); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_PRIEST); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/BattleTest.java b/core/src/ch/asynk/rustanddust/game/battles/BattleTest.java new file mode 100644 index 0000000..a237bca --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/BattleTest.java @@ -0,0 +1,128 @@ +package ch.asynk.rustanddust.game.battles; + +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.HexSet; +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Unit.UnitId; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.engine.Orientation; + +public class BattleTest extends BattleCommon +{ + private Zone usExit; + + public BattleTest(Factory factory) + { + super(factory); + name = "*** Test ***"; + mapType = Factory.MapType.MAP_B; + } + + @Override + public Player getPlayer() + { + if (!gePlayer.isDeploymentDone()) + return gePlayer; + + if (gePlayer.getTurnDone() == usPlayer.getTurnDone()) + return usPlayer; + return gePlayer; + } + + @Override + public Position getHudPosition(Player player) + { + return (player.is(Army.US) ? Position.TOP_RIGHT: Position.TOP_LEFT); + } + + @Override + public Player checkVictory(Ctrl ctrl) + { + if (usPlayer.getTurnDone() > 2) + return usPlayer; + return null; + } + + @Override + public boolean getReinforcement(Ctrl ctrl, Map map) + { + if (ctrl.player.is(Army.GE)) + return false; + if (ctrl.player.getCurrentTurn() != 2) + return false; + + Zone usEntry = new Zone(map, 1); + usEntry.allowedMoves = (Orientation.SOUTH.s | Orientation.SOUTH_EAST.s | Orientation.SOUTH_WEST.s); + usEntry.add(map.getHex(12, 6)); + addEntryZone(usEntry); + addReinforcement(usPlayer, usEntry, usExit, UnitId.US_WOLVERINE); + + return true; + } + + private Unit setUnit(Map map, Player player, UnitId unitId, int col, int row, Orientation orientation, Zone exitZone) + { + return setUnit(map, player, unitId, col, row, orientation, false, exitZone); + } + + private Unit setUnit(Map map, Player player, UnitId unitId, int col, int row, Orientation orientation, boolean ace, Zone exitZone) + { + Unit u = factory.getUnit(unitId); + u.setAce(ace); + if (exitZone != null) + unitExit.put(u, exitZone); + map.setOnBoard(u, map.getHex(col, row), orientation); + return u; + } + + @Override + public void setup(Ctrl ctrl, Map map) + { + map.addObjective(6, 4, Army.NONE); + map.addHoldObjective(5, 3, Army.NONE); + map.addObjective(3, 4, Army.NONE); + map.addHoldObjective(3, 3, Army.NONE); + + ctrl.player = gePlayer; + setUnit(map, gePlayer, UnitId.GE_WESPE, 5, 8, Orientation.NORTH, null); + setUnit(map, gePlayer, UnitId.GE_TIGER, 6, 4, Orientation.NORTH, null); + setUnit(map, gePlayer, UnitId.GE_PANZER_IV, 4, 5, Orientation.NORTH_WEST, null); + setUnit(map, gePlayer, UnitId.GE_INFANTRY, 1, 2, Orientation.NORTH_WEST, null); + setUnit(map, gePlayer, UnitId.GE_KINGTIGER, 1, 1, Orientation.NORTH_WEST, null); + Zone geEntry = new Zone(map, 6); + geEntry.orientation = Orientation.NORTH; + geEntry.add(map.getHex(1, 2)); + geEntry.add(map.getHex(1, 1)); + geEntry.add(map.getHex(3, 3)); + geEntry.add(map.getHex(3, 4)); + geEntry.add(map.getHex(4, 0)); + geEntry.add(map.getHex(5, 0)); + addEntryZone(geEntry); + addReinforcement(gePlayer, geEntry, UnitId.GE_AT_GUN); + + usExit = new Zone(map, 9); + usExit.orientation = Orientation.NORTH; + usExit.add(map.getHex(11, 4)); + usExit.add(map.getHex(11, 5)); + usExit.add(map.getHex(12, 6)); + addExitZone(usExit); + + ctrl.player = usPlayer; + usPlayer.casualty(factory.getUnit(UnitId.US_SHERMAN_HQ)); + setUnit(map, usPlayer, UnitId.US_PRIEST, 10, 8, Orientation.SOUTH_EAST, usExit); + setUnit(map, usPlayer, UnitId.US_SHERMAN, 7, 3, Orientation.SOUTH, true, usExit); + setUnit(map, usPlayer, UnitId.US_SHERMAN_HQ, 8, 4, Orientation.SOUTH, usExit); + setUnit(map, usPlayer, UnitId.US_WOLVERINE, 9, 7, Orientation.SOUTH_EAST, usExit); + setUnit(map, usPlayer, UnitId.US_PERSHING, 6, 6, Orientation.NORTH_EAST, usExit); + setUnit(map, usPlayer, UnitId.US_INFANTRY, 5, 3, Orientation.NORTH_WEST, usExit); + setUnit(map, usPlayer, UnitId.US_AT_GUN, 10, 3, Orientation.SOUTH, usExit); + usPlayer.turnEnd(); + map.init(); + map.turnDone(); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/Factory.java b/core/src/ch/asynk/rustanddust/game/battles/Factory.java new file mode 100644 index 0000000..4390663 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/Factory.java @@ -0,0 +1,192 @@ +package ch.asynk.rustanddust.game.battles; + +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; + +import ch.asynk.rustanddust.RustAndDust; +import ch.asynk.rustanddust.engine.Board; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Unit.UnitId; +import ch.asynk.rustanddust.game.Unit.UnitType; +import ch.asynk.rustanddust.game.Battle; +import ch.asynk.rustanddust.game.battles.BattleHeadToHead; +import ch.asynk.rustanddust.game.battles.BattleFrontalAssault; +import ch.asynk.rustanddust.game.battles.BattleLastStand; +import ch.asynk.rustanddust.game.battles.BattleCounterAttack; +import ch.asynk.rustanddust.game.battles.BattleStabToTheFlank; +import ch.asynk.rustanddust.game.battles.BattleNightAction; +import ch.asynk.rustanddust.game.battles.BattleTest; + +public class Factory implements Board.TileBuilder, Disposable +{ + public enum MapType + { + MAP_A, + MAP_B + } + + public enum Scenarios + { + FAKE + } + + public boolean assetsLoaded; + public TextureAtlas hudAtlas; + public TextureAtlas pawnsAtlas; + public TextureAtlas pawnOverlaysAtlas; + public TextureAtlas tileOverlaysAtlas; + public Battle battles[]; + private final RustAndDust game; + + public Factory(final RustAndDust game) + { + this.game = game; + this.assetsLoaded = false; + battles = new Battle[] { + new BattleHeadToHead(this), + new BattleFrontalAssault(this), + new BattleLastStand(this), + new BattleCounterAttack(this), + new BattleStabToTheFlank(this), + new BattleNightAction(this), + new BattleTest(this), + }; + } + + public void assetsLoaded() + { + if (assetsLoaded) return; + int i = game.config.graphics.i; + this.hudAtlas = game.manager.get("data/hud.atlas", TextureAtlas.class); + this.tileOverlaysAtlas = game.manager.get("data/hex-overlays.atlas", TextureAtlas.class); + this.pawnsAtlas = game.manager.get(String.format("data/units%d.atlas", i), TextureAtlas.class); + this.pawnOverlaysAtlas = game.manager.get(String.format("data/unit-overlays%d.atlas", i), TextureAtlas.class); + this.assetsLoaded = true; + } + + @Override + public void dispose() + { + if (!assetsLoaded) return; + hudAtlas.dispose(); + pawnsAtlas.dispose(); + pawnOverlaysAtlas.dispose(); + tileOverlaysAtlas.dispose(); + this.assetsLoaded = false; + } + + private Board.Config config() + { + Board.Config cfg = new Board.Config(); + cfg.cols = 10; + cfg.rows = 9; + cfg.x0 = 86; + cfg.y0 = 182; + cfg.w = 189; + cfg.dw = 94; + cfg.s = 110; + cfg.dh = 53.6f; + cfg.h = cfg.s + cfg.dh; + cfg.slope = (cfg.dh / (float) cfg.dw); + + return cfg; + } + + public Map getMap(MapType t) + { + Board.Config cfg = config(); + + Map m = null; + switch(t) { + case MAP_A: + m = new MapA(game, config(), "data/map_a.png"); + break; + case MAP_B: + m = new MapB(game, config(), "data/map_b.png"); + break; + } + + return m; + } + + public Player getPlayer(Army army) + { + if (army == Army.US) + return new Player(game, Army.US, 10); + else + return new Player(game, Army.GE, 10); + } + + public Unit getUnit(UnitId id) + { + Unit u = null; + UnitType ut = UnitType.HARD_TARGET; + UnitType utHq = UnitType.HARD_TARGET_HQ; + switch(id) { + case GE_AT_GUN: + ut = UnitType.AT_GUN; + u = new Unit(Army.GE, id, ut, 3, 8, 9, 1, "ge-at-gun", "ge-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case GE_INFANTRY: + ut = UnitType.INFANTRY; + u = new Unit(Army.GE, id, ut, 1, 7, 10, 1, "ge-infantry", "ge-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case GE_KINGTIGER: + u = new Unit(Army.GE, id, ut, 3, 12, 1, "ge-kingtiger", "ge-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case GE_PANZER_IV: + u = new Unit(Army.GE, id, ut, 2, 9, 2, "ge-panzer-iv", "ge-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case GE_PANZER_IV_HQ: + u = new Unit(Army.GE, id, utHq, 2, 9, 2, "ge-panzer-iv", "ge-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case GE_TIGER: + u = new Unit(Army.GE, id, ut, 3, 11, 1, "ge-tiger", "ge-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case GE_WESPE: + ut = UnitType.ARTILLERY; + u = new Unit(Army.GE, id, ut, 5, 8, 1, "ge-wespe", "ge-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case US_AT_GUN: + ut = UnitType.AT_GUN; + u = new Unit(Army.US, id, ut, 1, 7, 10, 1, "us-at-gun", "us-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case US_INFANTRY: + ut = UnitType.INFANTRY; + u = new Unit(Army.US, id, ut, 1, 7, 10, 1, "us-infantry", "us-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case US_PERSHING: + u = new Unit(Army.US, id, ut, 3, 10, 2, "us-pershing", "us-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case US_PERSHING_HQ: + u = new Unit(Army.US, id, utHq, 3, 10, 2, "us-pershing", "us-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case US_PRIEST: + ut = UnitType.ARTILLERY; + u = new Unit(Army.US, id, ut, 5, 8, 1, "us-priest", "us-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case US_SHERMAN: + u = new Unit(Army.US, id, ut, 2, 9, 2, "us-sherman", "us-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case US_SHERMAN_HQ: + u = new Unit(Army.US, id, utHq, 2, 9, 2, "us-sherman", "us-head", pawnsAtlas, pawnOverlaysAtlas); + break; + case US_WOLVERINE: + u = new Unit(Army.US, id, ut, 3, 8, 3, "us-wolverine", "us-head", pawnsAtlas, pawnOverlaysAtlas); + break; + } + + return u; + } + + public Hex getNewTile(float x, float y, int col, int row, boolean offmap) + { + Hex hex = new Hex(x, y, col, row, tileOverlaysAtlas); + if (offmap) hex.terrain = Hex.Terrain.OFFMAP; + return hex; + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/MapA.java b/core/src/ch/asynk/rustanddust/game/battles/MapA.java new file mode 100644 index 0000000..491b370 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/MapA.java @@ -0,0 +1,77 @@ +package ch.asynk.rustanddust.game.battles; + +import ch.asynk.rustanddust.RustAndDust; +import ch.asynk.rustanddust.engine.Board; +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; + +public class MapA extends Map +{ + public MapA(final RustAndDust game, Board.Config cfg, String textureName) + { + super(game, cfg, textureName); + } + + @Override + protected void setup() + { + getHex(5, 1).terrain = Hex.Terrain.HILLS; + getHex(7, 3).terrain = Hex.Terrain.HILLS; + getHex(7, 8).terrain = Hex.Terrain.HILLS; + getHex(8, 8).terrain = Hex.Terrain.HILLS; + + getHex(5, 0).terrain = Hex.Terrain.WOODS; + getHex(6, 0).terrain = Hex.Terrain.WOODS; + getHex(3, 3).terrain = Hex.Terrain.WOODS; + getHex(4, 3).terrain = Hex.Terrain.WOODS; + getHex(10, 7).terrain = Hex.Terrain.WOODS; + getHex(11, 7).terrain = Hex.Terrain.WOODS; + getHex(11, 8).terrain = Hex.Terrain.WOODS; + + getHex(6, 1).terrain = Hex.Terrain.TOWN; + getHex(2, 2).terrain = Hex.Terrain.TOWN; + getHex(6, 4).terrain = Hex.Terrain.TOWN; + getHex(10, 5).terrain = Hex.Terrain.TOWN; + getHex(7, 7).terrain = Hex.Terrain.TOWN; + getHex(4, 6).terrain = Hex.Terrain.TOWN; + + getHex(10, 1).terrain = Hex.Terrain.OFFMAP; + getHex(11, 3).terrain = Hex.Terrain.OFFMAP; + getHex(12, 5).terrain = Hex.Terrain.OFFMAP; + getHex(13, 7).terrain = Hex.Terrain.OFFMAP; + + int N = Orientation.NORTH.s; + int S = Orientation.SOUTH.s; + int NE = Orientation.NORTH_EAST.s; + int NW = Orientation.NORTH_WEST.s; + int SE = Orientation.SOUTH_EAST.s; + int SW = Orientation.SOUTH_WEST.s; + + getHex(6, 1).roads = (NW | SW); + for (int i = 1; i < 11; i++) { + if (i == 6) + getHex(i, 2).roads = (NE | S | SW); + else if (i == 7) + getHex(i, 2).roads = (N | SE); + else + getHex(i, 2).roads = (N | S); + } + getHex(6, 3).roads = (NE | SW); + getHex(6, 4).roads = (N | NE | SW); + getHex(7, 4).roads = (N | S); + getHex(8, 4).roads = (NW | S); + getHex(6, 5).roads = (NE | SW); + getHex(8, 5).roads = (N | SW); + getHex(9, 5).roads = (N | S | NE); + getHex(10, 5).roads = (N | S); + getHex(11, 5).roads = (N | S); + getHex(3, 6).roads = (N | S); + getHex(4, 6).roads = (N | S); + getHex(5, 6).roads = (N | S); + getHex(6, 6).roads = (NE | NW | S); + getHex(8, 6).roads = (NE | SW); + getHex(7, 7).roads = (N | SE); + getHex(8, 7).roads = (NE | S); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/battles/MapB.java b/core/src/ch/asynk/rustanddust/game/battles/MapB.java new file mode 100644 index 0000000..8636481 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/battles/MapB.java @@ -0,0 +1,75 @@ +package ch.asynk.rustanddust.game.battles; + +import ch.asynk.rustanddust.RustAndDust; +import ch.asynk.rustanddust.engine.Board; +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; + +public class MapB extends Map +{ + public MapB(final RustAndDust game, Board.Config cfg, String textureName) + { + super(game, cfg, textureName); + } + + @Override + protected void setup() + { + getHex(4, 0).terrain = Hex.Terrain.HILLS; + getHex(5, 0).terrain = Hex.Terrain.HILLS; + getHex(1, 1).terrain = Hex.Terrain.HILLS; + getHex(9, 7).terrain = Hex.Terrain.HILLS; + getHex(10, 7).terrain = Hex.Terrain.HILLS; + + getHex(3, 0).terrain = Hex.Terrain.WOODS; + getHex(6, 0).terrain = Hex.Terrain.WOODS; + getHex(8, 1).terrain = Hex.Terrain.WOODS; + getHex(9, 2).terrain = Hex.Terrain.WOODS; + getHex(4, 5).terrain = Hex.Terrain.WOODS; + getHex(5, 6).terrain = Hex.Terrain.WOODS; + getHex(6, 6).terrain = Hex.Terrain.WOODS; + getHex(11, 8).terrain = Hex.Terrain.WOODS; + + getHex(1, 2).terrain = Hex.Terrain.TOWN; + getHex(5, 3).terrain = Hex.Terrain.TOWN; + getHex(6, 4).terrain = Hex.Terrain.TOWN; + getHex(7, 8).terrain = Hex.Terrain.TOWN; + + getHex(10, 1).terrain = Hex.Terrain.OFFMAP; + getHex(11, 3).terrain = Hex.Terrain.OFFMAP; + getHex(12, 5).terrain = Hex.Terrain.OFFMAP; + getHex(13, 7).terrain = Hex.Terrain.OFFMAP; + + int N = Orientation.NORTH.s; + int S = Orientation.SOUTH.s; + int NE = Orientation.NORTH_EAST.s; + int NW = Orientation.NORTH_WEST.s; + int SE = Orientation.SOUTH_EAST.s; + int SW = Orientation.SOUTH_WEST.s; + + getHex(1, 2).roads = (S | NW); + getHex(2, 3).roads = (SE | N); + getHex(3, 3).roads = (S | N); + getHex(4, 3).roads = (S | N); + getHex(5, 3).roads = (S | NW); + getHex(6, 4).roads = (SE | N); + getHex(7, 4).roads = (S | N); + getHex(8, 4).roads = (S | SW | N); + getHex(9, 4).roads = (S | N); + getHex(10, 4).roads = (S | N); + getHex(11, 4).roads = (S | N); + getHex(4, 8).roads = (S | N); + getHex(5, 8).roads = (S | N); + getHex(6, 8).roads = (S | N); + getHex(7, 8).roads = (S | N); + getHex(8, 8).roads = (S | NE); + getHex(8, 7).roads = (SW | NE); + getHex(8, 6).roads = (SW | NE | N); + getHex(8, 5).roads = (SW | NE); + getHex(9, 6).roads = (S | N); + getHex(10, 6).roads = (S | N); + getHex(11, 6).roads = (S | N); + getHex(12, 6).roads = (S | N); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/hud/ActionButtons.java b/core/src/ch/asynk/rustanddust/game/hud/ActionButtons.java new file mode 100644 index 0000000..323767f --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/hud/ActionButtons.java @@ -0,0 +1,185 @@ +package ch.asynk.rustanddust.game.hud; + +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.State.StateType; +import ch.asynk.rustanddust.ui.Widget; +import ch.asynk.rustanddust.ui.Bg; +import ch.asynk.rustanddust.ui.Position; + +public class ActionButtons extends Widget +{ + public static int PADDING = 5; + + private final Ctrl ctrl; + + public enum Buttons { + NONE(-1, 0), + PROMOTE(0, 1), + DONE(1, 2), + ABORT(2, 4), + LAST(3, 0); + + Buttons(int i, int b) + { + this.i = i; + this.b = b; + } + + public int i; + public int b; + } + + private Sprite bg; + private int idx; + private Bg buttons []; + private StateType states []; + + public ActionButtons(Ctrl ctrl, TextureAtlas uiAtlas, TextureAtlas hudAtlas) + { + this.bg = new Sprite(uiAtlas.findRegion("disabled")); + this.ctrl = ctrl; + this.visible = false; + this.position = Position.BOTTOM_RIGHT; + this.idx = Buttons.NONE.i; + + + this.buttons = new Bg[Buttons.LAST.i]; + this.buttons[Buttons.DONE.i] = new Bg(uiAtlas.findRegion("ok")); + this.buttons[Buttons.ABORT.i] = new Bg(uiAtlas.findRegion("cancel")); + this.buttons[Buttons.PROMOTE.i] = new Bg(hudAtlas.findRegion("promote")); + + this.states = new StateType[Buttons.LAST.i]; + this.states[Buttons.DONE.i] = StateType.DONE; + this.states[Buttons.ABORT.i] = StateType.ABORT; + this.states[Buttons.PROMOTE.i] = StateType.PROMOTE; + } + + @Override + public void dispose() + { + for (int i = 0; i < Buttons.LAST.i; i++) + buttons[i].dispose(); + } + + public void update(Position position) + { + setPosition(position); + updatePosition(); + } + + public void updatePosition() + { + if (!visible) return; + float dx = (position.getX(rect.width) - rect.x); + float dy = (position.getY(rect.height) - rect.y); + translate(dx, dy); + for (int i = 0; i < Buttons.LAST.i; i++) + buttons[i].translate(dx, dy); + } + + public void hide() + { + for (int i = 0; i < Buttons.LAST.i; i++) + buttons[i].visible = false; + this.visible = false; + } + + private float setButton(Bg btn, float x, float y) + { + btn.visible = true; + btn.setPosition(x, y); + return (y + btn.getHeight() + PADDING); + } + + public void show(int bits) + { + int b = bits; + int count = 0; + while (b > 0) { + if ((b & 0x01) == 1) + count += 1; + b /= 2; + } + + if (count == 0) { + this.visible = false; + return; + } + + rect.width = (buttons[0].getWidth() + (2 * PADDING)); + rect.height = ((buttons[0].getHeight() * count) + ((count + 1) * PADDING)); + rect.x = position.getX(rect.width); + rect.y = position.getY(rect.height); + + float x = (rect.x + PADDING); + float y = (rect.y + PADDING); + + b = 1; + for (int i = 0; i < Buttons.LAST.i; i++) { + if ((bits & b) == b) + y = setButton(buttons[i], x, y); + else + buttons[i].visible = false; + b *= 2; + } + + this.visible = true; + } + + public boolean touchDown(float x, float y) + { + idx = Buttons.NONE.i; + + if (!super.hit(x,y)) + return false; + + for (int i = 0; i < Buttons.LAST.i; i++) { + if (buttons[i].hit(x, y)) { + idx = i; + break; + } + } + + return (idx != Buttons.NONE.i); + } + + public boolean touchUp(float x, float y) + { + if (idx == Buttons.NONE.i) + return false; + + boolean ret = false; + + if (super.hit(x,y) && buttons[idx].hit(x, y)) { + ctrl.setState(states[idx]); + ret = true; + } + + idx = Buttons.NONE.i; + + return ret; + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + batch.draw(bg, rect.x, rect.y, rect.width, rect.height); + for (int i = 0; i < Buttons.LAST.i; i++) + buttons[i].draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer shapes) + { + if (!visible) return; + super.drawDebug(shapes); + for (int i = 0; i < Buttons.LAST.i; i++) + buttons[i].drawDebug(shapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/hud/EngagementPanel.java b/core/src/ch/asynk/rustanddust/game/hud/EngagementPanel.java new file mode 100644 index 0000000..790c8b5 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/hud/EngagementPanel.java @@ -0,0 +1,255 @@ +package ch.asynk.rustanddust.game.hud; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.game.Engagement; +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.engine.gfx.Animation; +import ch.asynk.rustanddust.engine.gfx.animations.DiceAnimation; +import ch.asynk.rustanddust.ui.Bg; +import ch.asynk.rustanddust.ui.Label; +import ch.asynk.rustanddust.ui.Patch; +import ch.asynk.rustanddust.ui.Position; + +public class EngagementPanel extends Patch implements Animation +{ + private enum State { ROLL1, MOVE, ROLL2, RESULT }; + + public static int FLAG_HEIGHT = 24; + public static int OK_OFFSET = 10; + public static int PADDING = 20; + public static int VSPACING = 10; + public static int HSPACING = 5; + public static float MOVE_STEP = 2f; + + private State state; + private boolean reroll; + private float rerollY; + private Sprite usFlag; + private Sprite geFlag; + private Sprite winner; + private Sprite attackImg; + private Sprite defenseImg; + private Label attack; + private Label defense; + private Label attackR; + private Label defenseR; + private Bg okBtn; + private DiceAnimation d1Animation; + private DiceAnimation d2Animation; + private DiceAnimation d3Animation; + private DiceAnimation d4Animation; + + public EngagementPanel(BitmapFont font, TextureAtlas uiAtlas, TextureAtlas hudAtlas) + { + super(uiAtlas.createPatch("typewriter")); + usFlag = new Sprite(hudAtlas.findRegion("us-flag")); + geFlag = new Sprite(hudAtlas.findRegion("ge-flag")); + attackImg = new Sprite(hudAtlas.findRegion("attack")); + defenseImg = new Sprite(hudAtlas.findRegion("defense")); + this.attack = new Label(font); + this.defense = new Label(font); + this.attackR = new Label(font); + this.defenseR = new Label(font); + this.okBtn = new Bg(uiAtlas.findRegion("ok")); + this.visible = false; + this.d1Animation = new DiceAnimation(); + this.d2Animation = new DiceAnimation(); + this.d3Animation = new DiceAnimation(); + this.d4Animation = new DiceAnimation(); + } + + public void updatePosition() + { + if (!visible) return; + float dx = (position.getX(rect.width) - rect.x); + float dy = (position.getY(rect.height) - rect.y); + translate(dx, dy); + winner.translate(dx, dy); + attackImg.translate(dx, dy); + defenseImg.translate(dx, dy); + attack.translate(dx, dy); + defense.translate(dx, dy); + attackR.translate(dx, dy); + defenseR.translate(dx, dy); + okBtn.translate(dx, dy); + d1Animation.translate(dx, dy); + d2Animation.translate(dx, dy); + d3Animation.translate(dx, dy); + d4Animation.translate(dx, dy); + } + + public void show(Engagement e, Position position, float volume) + { + DiceAnimation.initSound(volume); + attack.write(String.format(" + %d + %d =", e.unitCount, e.flankBonus)); + if (e.weatherDefense == 0) + defense.write(String.format("%d + %d =", e.unitDefense, e.terrainDefense)); + else + defense.write(String.format("%d + %d + %d =", e.unitDefense, e.terrainDefense, e.weatherDefense)); + attackR.write(String.format(" %2d", e.attackSum)); + defenseR.write(String.format(" %2d", e.defenseSum)); + if (e.success) + winner = ((e.attacker.getArmy() == Army.US) ? usFlag : geFlag); + else + winner = ((e.attacker.getArmy() == Army.US) ? geFlag : usFlag); + + this.position = position; + placeElements(); + + state = State.ROLL1; + reroll = (e.d3 != 0); + + d1Animation.set(e.d1); + d2Animation.set(e.d2); + if (reroll) { + d3Animation.set(e.d3); + d4Animation.set(e.d4); + } + + visible = true; + } + + private void placeElements() + { + float w = attackR.getWidth(); + float w2 = defenseR.getWidth(); + if (w2 > w) + w = w2; + float height = (okBtn.getHeight() + attackImg.getHeight() + defenseImg.getHeight() + (2 * VSPACING) + (2 * PADDING)); + float width = (attackImg.getWidth() + (2 * d1Animation.getWidth()) + attack.getWidth() + w + (4 * HSPACING) + (2 * PADDING)); + float x = position.getX(width); + float y = position.getY(height); + setPosition(x, y, width, height); + + okBtn.setPosition((x + width - okBtn.getWidth() + OK_OFFSET), (y - OK_OFFSET)); + + x = getX() + PADDING; + y = getY() + PADDING; + winner.setPosition((getX() + (width / 2f) - (winner.getWidth() / 2f)), y); + y += (winner.getHeight() + VSPACING); + + defenseImg.setPosition(x, y); + y = (y + (defenseImg.getHeight() / 2f) - (defense.getHeight() / 2f)); + defenseR.setPosition((getX() + width - w - PADDING), y); + // x += (defenseImg.getWidth() + HSPACING); + defense.setPosition((defenseR.getX() - defense.getWidth() - HSPACING), y); + + x = getX() + PADDING; + y += defenseImg.getHeight() + VSPACING; + attackImg.setPosition(x, y); + x += (attackImg.getWidth() + HSPACING); + d1Animation.setPosition(x, y); + d3Animation.setPosition(x, y); + x += (d1Animation.getWidth() + HSPACING); + d2Animation.setPosition(x, (y)); + d4Animation.setPosition(x, y); + x += (d1Animation.getWidth() + HSPACING); + y = (y + (attackImg.getHeight() / 2f) - (attack.getHeight() / 2f)); + attack.setPosition(x, y); + attackR.setPosition(defenseR.getX(), y); + + rerollY = (d1Animation.getY() + d1Animation.getHeight() + VSPACING); + } + + @Override + public boolean hit(float x, float y) + { + return rect.contains(x, y); + } + + @Override + public boolean animate(float delta) + { + if (!visible) return true; + if (state == State.ROLL1) { + d1Animation.animate(delta); + d2Animation.animate(delta); + if (d1Animation.isDone() && d2Animation.isDone()) { + if (reroll) + state = State.MOVE; + else + state = State.RESULT; + } + } + + if (state == State.MOVE) { + float y = (d1Animation.getY() + MOVE_STEP); + if (y >= rerollY) { + y = rerollY; + state = State.ROLL2; + } + setPosition(getX(), getY(), getWidth(), (y + d1Animation.getHeight() + VSPACING - getY())); + d1Animation.setPosition(d1Animation.getX(), y); + d2Animation.setPosition(d2Animation.getX(), y); + } + + if (state == State.ROLL2) { + if (d1Animation.getY() < rerollY) { + d1Animation.setPosition(d1Animation.getX(), (d1Animation.getY() + d1Animation.getHeight() + VSPACING)); + d2Animation.setPosition(d2Animation.getX(), (d2Animation.getY() + d2Animation.getHeight() + VSPACING)); + } else { + d3Animation.animate(delta); + d4Animation.animate(delta); + if (d3Animation.isDone() && d4Animation.isDone()) + state = State.RESULT; + } + } + + return false; + } + + @Override + public void dispose() + { + super.dispose(); + attack.dispose(); + defense.dispose(); + attackR.dispose(); + defenseR.dispose(); + d1Animation.dispose(); + d2Animation.dispose(); + d3Animation.dispose(); + d4Animation.dispose(); + okBtn.dispose(); + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + super.draw(batch); + attackImg.draw(batch); + d1Animation.draw(batch); + d2Animation.draw(batch); + if ((state == State.ROLL2) || (reroll && (state == State.RESULT))) { + d3Animation.draw(batch); + d4Animation.draw(batch); + } + attack.draw(batch); + defenseImg.draw(batch); + defense.draw(batch); + defenseR.draw(batch); + okBtn.draw(batch); + if (state == State.RESULT) { + attackR.draw(batch); + winner.draw(batch); + } + } + + @Override + public void drawDebug(ShapeRenderer shapes) + { + if (!visible) return; + super.drawDebug(shapes); + attack.drawDebug(shapes); + defense.drawDebug(shapes); + attackR.drawDebug(shapes); + defenseR.drawDebug(shapes); + okBtn.drawDebug(shapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/hud/PlayerInfo.java b/core/src/ch/asynk/rustanddust/game/hud/PlayerInfo.java new file mode 100644 index 0000000..dd77c8e --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/hud/PlayerInfo.java @@ -0,0 +1,202 @@ +package ch.asynk.rustanddust.game.hud; + +import com.badlogic.gdx.utils.Disposable; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Animation; +import ch.asynk.rustanddust.engine.gfx.Drawable; + +import ch.asynk.rustanddust.game.State.StateType; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Hud; +import ch.asynk.rustanddust.game.Army; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.ui.LabelImage; +import ch.asynk.rustanddust.ui.Position; + +public class PlayerInfo implements Disposable, Drawable, Animation +{ + public static int PADDING = 5; + + private final Ctrl ctrl; + + private Object hit; + + private Sprite flag; + private Sprite usFlag; + private Sprite geFlag; + private LabelImage turns; + private LabelImage aps; + private LabelImage reinforcement; + public UnitDock unitDock; + private Position position; + + public PlayerInfo(Ctrl ctrl, BitmapFont font, TextureAtlas uiAtlas, TextureAtlas hudAtlas) + { + this.ctrl = ctrl; + this.position = Position.MIDDLE_CENTER; + usFlag = new Sprite(hudAtlas.findRegion("us-flag")); + geFlag = new Sprite(hudAtlas.findRegion("ge-flag")); + turns = new LabelImage(hudAtlas.findRegion("turns"), font, 5f); + aps = new LabelImage(hudAtlas.findRegion("aps"), font, 5f); + reinforcement = new LabelImage(hudAtlas.findRegion("reinforcement"), font, 5f); + unitDock = new UnitDock(ctrl, uiAtlas.findRegion("disabled"), hudAtlas.findRegion("reinforcement-selected"), 10f); + } + + @Override + public void dispose() + { + turns.dispose(); + aps.dispose(); + reinforcement.dispose(); + unitDock.dispose(); + } + + public void updatePosition() + { + float dx = (position.getX(usFlag.getWidth()) - usFlag.getX()); + float dy = (position.getY(usFlag.getHeight()) - usFlag.getY()); + usFlag.translate(dx, dy); + geFlag.translate(dx, dy); + turns.translate(dx, dy); + aps.translate(dx, dy); + reinforcement.translate(dx, dy); + unitDock.translate(dx, dy); + } + + public void setPosition(Position position) + { + if (this.position == position) + return; + this.position = position; + + float width = (usFlag.getWidth() + turns.getWidth() + aps.getWidth() + (2 * PADDING)); + float height = (usFlag.getHeight() + reinforcement.getHeight() + (1 * PADDING)); + float x = position.getX(width); + float y = position.getY(height); + + if (position.isLeft()) { + reinforcement.setPosition(x, y); + y += (reinforcement.getHeight() + PADDING); + usFlag.setPosition(x, y); + geFlag.setPosition(x, y); + x += (usFlag.getWidth() + PADDING); + turns.setPosition(x, y); + x += (turns.getWidth() + PADDING); + aps.setPosition(x, y); + } else { + x = (x + width); + reinforcement.setPosition((x - reinforcement.getWidth()), y); + y += (reinforcement.getHeight() + PADDING); + x -= usFlag.getWidth(); + usFlag.setPosition(x, y); + geFlag.setPosition(x, y); + x -= (turns.getWidth() + PADDING); + turns.setPosition(x, y); + x -= (aps.getWidth() + PADDING); + aps.setPosition(x, y); + } + aps.setLabelPosition(Position.TOP_RIGHT); + turns.setLabelPosition(Position.MIDDLE_CENTER); + reinforcement.setLabelPosition(Position.TOP_LEFT); + unitDock.setPosition(position, reinforcement.getY() - PADDING); + } + + public void update(Player player, Position position) + { + unitDock.hide(); + turns.write(String.format("%d", player.getCurrentTurn())); + aps.write(String.format("%d", player.getAp())); + int r = player.reinforcement(); + if (r == 0) { + reinforcement.visible = false; + } else { + reinforcement.visible = true; + reinforcement.write(String.format("%d", r)); + } + + if (player.is(Army.GE)) + flag = geFlag; + else + flag = usFlag; + + setPosition(position); + } + + public void blockEndOfTurn(boolean blocked) + { + turns.blocked = blocked; + } + + public boolean touchDown(float x, float y) + { + hit = null; + + if (reinforcement.hit(x, y)) + hit = reinforcement; + else if (unitDock.hit(x, y)) + hit = unitDock; + else if (turns.hit(x,y)) + hit = turns; + + return (hit != null); + } + + public boolean touchUp(float x, float y) + { + if (hit == null) + return false; + + if (hit == turns) { + if (turns.hit(x, y)) + ctrl.hud.askEndOfTurn(); + } + else if (hit == reinforcement) { + if (reinforcement.hit(x, y)) + ctrl.reinforcementHit(); + } + else if (hit == unitDock) { + if (unitDock.hit(x, y)) { + ctrl.hud.notify(unitDock.select(x, y).toString()); + ctrl.stateTouchUp(); + } + } + + hit = null; + + return true; + } + + @Override + public boolean animate(float delta) + { + unitDock.animate(delta); + return false; + } + + @Override + public void draw(Batch batch) + { + flag.draw(batch); + turns.draw(batch); + aps.draw(batch); + reinforcement.draw(batch); + unitDock.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer debugShapes) + { + turns.drawDebug(debugShapes); + aps.drawDebug(debugShapes); + reinforcement.drawDebug(debugShapes); + unitDock.drawDebug(debugShapes); + debugShapes.rect(flag.getX(), flag.getY(), flag.getWidth(), flag.getHeight()); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/hud/StatisticsPanel.java b/core/src/ch/asynk/rustanddust/game/hud/StatisticsPanel.java new file mode 100644 index 0000000..2e6546b --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/hud/StatisticsPanel.java @@ -0,0 +1,120 @@ +package ch.asynk.rustanddust.game.hud; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.game.Player; +import ch.asynk.rustanddust.ui.Bg; +import ch.asynk.rustanddust.ui.Label; +import ch.asynk.rustanddust.ui.Patch; +import ch.asynk.rustanddust.ui.Position; + +public class StatisticsPanel extends Patch +{ + public static int OK_OFFSET = 10; + public static int PADDING = 20; + public static int VSPACING = 10; + public static int HSPACING = 10; + + private Label title; + private Label header; + private Label stats1; + private Label stats2; + private Bg okBtn; + + public StatisticsPanel(BitmapFont font, TextureAtlas atlas) + { + super(atlas.createPatch("typewriter")); + this.title = new Label(font); + this.header = new Label(font); + this.stats1 = new Label(font); + this.stats2 = new Label(font); + this.okBtn = new Bg(atlas.findRegion("ok")); + this.visible = false; + this.header.write("\nActions\nUnits Left\nUnits Withrawed\nCasualties\nWon Attacks\nLost Attacks"); + } + + public void updatePosition() + { + if (!visible) return; + float dx = (position.getX(rect.width) - rect.x); + float dy = (position.getY(rect.height) - rect.y); + translate(dx, dy); + title.translate(dx, dy); + header.translate(dx, dy); + stats1.translate(dx, dy); + stats2.translate(dx, dy); + okBtn.translate(dx, dy); + } + + public void show(Player winner, Player loser, Position position) + { + title.write(winner.getName() + " player won the battle in " + winner.getTurnDone() + " turns."); + stats1.write(winner.getStats()); + stats2.write(loser.getStats()); + + float height = (title.getHeight() + header.getHeight() + (2 * PADDING) + (1 * VSPACING)); + float width = (header.getWidth() + stats1.getWidth() + stats2.getWidth() + (2 * PADDING) + (4 * HSPACING)); + float w2 = (title.getWidth() + (2 * PADDING)); + if (w2 > width) width = w2; + float x = position.getX(width); + float y = position.getY(height); + setPosition(x, y, width, height); + + okBtn.setPosition((x + width - okBtn.getWidth() + OK_OFFSET), (y - OK_OFFSET)); + + y += PADDING; + x += PADDING; + header.setPosition(x, y); + stats1.setPosition((x + header.getWidth() + (2 * HSPACING)), y); + stats2.setPosition((stats1.getX() + stats1.getWidth() + (2 * HSPACING)), y); + y += (header.getHeight() + VSPACING); + title.setPosition(x, y); + visible = true; + } + + @Override + public boolean hit(float x, float y) + { + if (okBtn.hit(x, y)) + return true; + return false; + } + + @Override + public void dispose() + { + super.dispose(); + title.dispose(); + header.dispose(); + stats1.dispose(); + stats2.dispose(); + okBtn.dispose(); + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + super.draw(batch); + title.draw(batch); + header.draw(batch); + stats1.draw(batch); + stats2.draw(batch); + okBtn.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer shapes) + { + if (!visible) return; + super.drawDebug(shapes); + title.drawDebug(shapes); + header.drawDebug(shapes); + stats1.drawDebug(shapes); + stats2.drawDebug(shapes); + okBtn.drawDebug(shapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/hud/UnitDock.java b/core/src/ch/asynk/rustanddust/game/hud/UnitDock.java new file mode 100644 index 0000000..11895ba --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/hud/UnitDock.java @@ -0,0 +1,226 @@ +package ch.asynk.rustanddust.game.hud; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Rectangle; + +import ch.asynk.rustanddust.engine.gfx.Animation; +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.UnitList; +import ch.asynk.rustanddust.ui.Bg; +import ch.asynk.rustanddust.ui.Position; + +public class UnitDock extends Bg implements Animation +{ + private static final float SCALE = 0.4f; + private static final float STEP = 5f; + private final Ctrl ctrl; + + private int n; + private float y; + private float to; + private float dx; + private float step; + private boolean show; + private boolean mvtDone; + public Unit selectedUnit; + private Sprite selected; + private UnitList units; + private Vector3 point; + private Matrix4 saved; + private Matrix4 transform; + private Rectangle scaledRect; + + public UnitDock(Ctrl ctrl, TextureRegion region, TextureRegion selected, float padding) + { + super(region); + this.ctrl = ctrl; + this.padding = padding; + this.mvtDone = true; + this.point = new Vector3(); + this.saved = new Matrix4(); + this.transform = new Matrix4(); + this.scaledRect = new Rectangle(); + this.selected = new Sprite(selected); + this.visible = false; + this.dx = 0f; + } + + @Override + public void translate(float _dx, float _dy) + { + this.y += _dy; + if (!visible) return; + super.translate(_dx, _dy); + for (Unit unit : units) + unit.translate(_dx, _dy); + to = position.getX(rect.width * SCALE); + transform.idt(); + transform.translate((rect.x + dx), (rect.y + rect.height), 0).scale(SCALE, SCALE, 0).translate(-rect.x, - (rect.y + rect.height), 0); + point.set(rect.x, rect.y, 0).mul(transform); + scaledRect.x = point.x; + scaledRect.y = point.y; + point.set((rect.x + rect.width), (rect.y + rect.height), 0).mul(transform); + scaledRect.width = point.x - scaledRect.x; + scaledRect.height = point.y - scaledRect.y; + } + + public void setPosition(Position position, float y) + { + if (this.position == position) + return; + this.position = position; + this.y = y; + this.step = (position.isLeft() ? STEP : -STEP); + this.mvtDone = true; + this.visible = false; + this.dx = 0f; + } + + @Override + public void dispose() + { + super.dispose(); + } + + @Override + public boolean hit(float x, float y) + { + return (visible && scaledRect.contains(x, y)); + } + + public Unit select(float x, float y) + { + int i = (int) ((scaledRect.y + scaledRect.height - y) / (scaledRect.height / units.size())); + selectedUnit = units.get(i); + return selectedUnit; + } + + public void hide() + { + if (!visible) return; + resize(); + to = rect.x; + + show = false; + mvtDone = false; + selectedUnit = null; + } + + public void show() + { + if (!resize()) + return; + to = position.getX(rect.width * SCALE); + + show = true; + mvtDone = false; + selectedUnit = null; + visible = true; + } + + private boolean resize() + { + int count = ctrl.player.reinforcement(); + if (count == 0) { + n = 0; + return false; + } + if (count == n) return true; + n = count; + + units = ctrl.player.reinforcement; + rect.width = units.get(0).getWidth() + (2 * padding); + rect.height = ((units.get(0).getHeight() * n) + ((n + 1) * padding)); + float scaledWidth = (rect.width * SCALE); + to = position.getX(scaledWidth); + rect.x = to + (position.isLeft() ? -scaledWidth : scaledWidth); + rect.y = y - rect.height; + + float px = rect.x; + float py = rect.y + rect.height; + float ph = units.get(0).getHeight(); + for (Unit unit : units) { + py -= (ph + padding); + // unit.setPosition(px, py, Orientation.SOUTH.r()); + unit.centerOn((px + (rect.width / 2)), py + (ph / 2)); + unit.setRotation(position.isLeft() ? Orientation.NORTH.r() : Orientation.SOUTH.r()); + } + + return true; + } + + @Override + public boolean animate(float delta) + { + if (!visible) return true; + if (mvtDone) return true; + + float x = (rect.x + dx); + if (show) { + if ((position.isLeft() && (x < to)) || (!position.isLeft() && x > to)) + dx += step; + else { + dx = (to - rect.x); + mvtDone = true; + } + } else { + if ((position.isLeft() && (x > to)) || (!position.isLeft() && x < to)) + dx -= step; + else { + dx = (to - rect.x); + mvtDone = true; + visible = false; + } + } + + transform.idt(); + transform.translate((rect.x + dx), (rect.y + rect.height), 0).scale(SCALE, SCALE, 0).translate(-rect.x, - (rect.y + rect.height), 0); + point.set(rect.x, rect.y, 0).mul(transform); + scaledRect.x = point.x; + scaledRect.y = point.y; + point.set((rect.x + rect.width), (rect.y + rect.height), 0).mul(transform); + scaledRect.width = point.x - scaledRect.x; + scaledRect.height = point.y - scaledRect.y; + return false; + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + + saved.set(batch.getTransformMatrix()); + batch.setTransformMatrix(transform); + + super.draw(batch); + for (Unit unit : units) { + unit.draw(batch); + if (unit == selectedUnit) { + selected.setCenter((unit.getX() + (unit.getWidth() / 2)), (unit.getY() + (unit.getHeight() / 2))); + selected.draw(batch); + } + } + + batch.setTransformMatrix(saved); + } + + @Override + public void drawDebug(ShapeRenderer shapes) + { + if (!visible) return; + + saved.set(shapes.getTransformMatrix()); + shapes.setTransformMatrix(transform); + + shapes.rect(rect.x, rect.y, rect.width, rect.height); + + shapes.setTransformMatrix(saved); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateAnimation.java b/core/src/ch/asynk/rustanddust/game/states/StateAnimation.java new file mode 100644 index 0000000..41831e0 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateAnimation.java @@ -0,0 +1,37 @@ +package ch.asynk.rustanddust.game.states; + +public class StateAnimation extends StateCommon +{ + @Override + public void enter(StateType prevState) + { + ctrl.hud.actionButtons.hide(); + } + + @Override + public void leave(StateType nextState) + { + } + + @Override + public StateType abort() + { + return StateType.ABORT; + } + + @Override + public StateType execute() + { + return StateType.DONE; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateBreak.java b/core/src/ch/asynk/rustanddust/game/states/StateBreak.java new file mode 100644 index 0000000..f1e40f6 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateBreak.java @@ -0,0 +1,90 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons; + +import ch.asynk.rustanddust.RustAndDust; + +public class StateBreak extends StateCommon +{ + private Orientation o = Orientation.KEEP; + + @Override + public void enter(StateType prevState) + { + activeUnit = null; + ctrl.hud.actionButtons.show(Buttons.DONE.b); + ctrl.hud.pushNotify("Break move possible"); + map.showBreakUnits(); + } + + @Override + public void leave(StateType nextState) + { + map.hideBreakUnits(); + map.hideMove(to); + map.hideDirections(to); + map.hideOrientation(to); + if (activeUnit != null) map.hideMove(activeUnit.getHex()); + } + + @Override + public StateType abort() + { + return StateType.ABORT; + } + + @Override + public StateType execute() + { + return StateType.DONE; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + // TODO : cancel preview move before showing rotation + if (activeUnit == null) { + Unit unit = upHex.getUnit(); + if (map.breakUnits.contains(unit)) { + activeUnit = unit; + map.showMove(upHex); + map.showMove(to); + map.showDirections(to); + map.hideBreakUnits(); + } + } else { + o = Orientation.fromAdj(to, upHex); + + if (o == Orientation.KEEP) return; + + if (ctrl.cfg.mustValidate) { + map.hideDirections(to); + map.showOrientation(to, o); + ctrl.hud.actionButtons.show(Buttons.DONE.b); + } else { + doRotation(o); + ctrl.setState(StateType.ANIMATION); + } + } + } + + private void doRotation(Orientation o) + { + if (activeUnit == null) return; + + map.pathBuilder.init(activeUnit); + if (map.pathBuilder.build(to) == 1) { + map.pathBuilder.orientation = o; + map.moveUnit(activeUnit); + ctrl.setAfterAnimationState(StateType.DONE); + } else + RustAndDust.debug("That's very wrong there should be only one path"); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateCommon.java b/core/src/ch/asynk/rustanddust/game/states/StateCommon.java new file mode 100644 index 0000000..443182d --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateCommon.java @@ -0,0 +1,68 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.State; + +import ch.asynk.rustanddust.RustAndDust; + +public abstract class StateCommon implements State +{ + protected static Ctrl ctrl; + protected static Map map; + + protected static Hex selectedHex = null; + protected static Hex downHex = null; + protected static Hex upHex = null; + protected static Hex to = null; + + protected boolean isEnemy; + protected static Unit activeUnit; + protected static Unit selectedUnit; + + protected StateCommon() + { + } + + public StateCommon(Ctrl ctrl, Map map) + { + this.ctrl = ctrl; + this.map = map; + } + + @Override + public boolean downInMap(float x, float y) + { + downHex = map.getHexAt(x, y); + return (downHex != null); + } + + @Override + public boolean upInMap(float x, float y) + { + upHex = map.getHexAt(x, y); + return (upHex != null); + } + + protected boolean hasUnit() + { + return (selectedUnit != null); + } + + protected void showPossibilities(Unit unit) + { + if (ctrl.cfg.showMoves && unit.canMove()) map.showPossibleMoves(); + if (ctrl.cfg.showTargets && unit.canEngage()) map.showPossibleTargets(); + if (ctrl.cfg.showMoveAssists && unit.canMove()) map.showMoveableUnits(); + unit.enableOverlay(Unit.MOVE, false); + } + + protected void hidePossibilities() + { + map.hidePossibleMoves(); + map.hidePossibleTargets(); + map.hideMoveableUnits(); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateDeployment.java b/core/src/ch/asynk/rustanddust/game/states/StateDeployment.java new file mode 100644 index 0000000..9528d2a --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateDeployment.java @@ -0,0 +1,138 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.UnitList; +import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons; + +import ch.asynk.rustanddust.RustAndDust; + +public class StateDeployment extends StateCommon +{ + private boolean completed; + private Zone entryZone; + private UnitList deployedUnits = new UnitList(10); + + @Override + public void enter(StateType prevState) + { + if (selectedHex != null) + map.unselectHex(selectedHex); + completed = false; + entryZone = null; + selectedHex = null; + selectedUnit = null; + ctrl.hud.actionButtons.hide(); + ctrl.hud.playerInfo.unitDock.show(); + } + + @Override + public void leave(StateType nextState) + { + selectedUnit = null; + if (selectedHex != null) + map.unselectHex(selectedHex); + if (entryZone != null) + entryZone.enable(Hex.AREA, false); + ctrl.hud.playerInfo.unitDock.hide(); + } + + @Override + public StateType abort() + { + if (activeUnit != null) + undo(); + return StateType.DEPLOYMENT; + } + + @Override + public StateType execute() + { + deployedUnits.clear(); + return StateType.DONE; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + Unit unit = ctrl.hud.playerInfo.unitDock.selectedUnit; + if (!completed && (unit != null) && (unit != activeUnit)) { + showEntryZone(unit); + } else if (selectedUnit != null) { + doRotation(Orientation.fromAdj(selectedHex, upHex)); + } else if (!completed && (entryZone != null) && (upHex != null)) { + if (upHex.isEmpty() && entryZone.contains(upHex)) + unitEnter(activeUnit); + } else { + unit = downHex.getUnit(); + if (deployedUnits.contains(unit)) { + showRotation(unit, downHex); + activeUnit = unit; + } + } + } + + private void showEntryZone(Unit unit) + { + activeUnit = unit; + if (entryZone != null) entryZone.enable(Hex.AREA, false); + entryZone = ctrl.battle.getEntryZone(activeUnit); + entryZone.enable(Hex.AREA, true); + } + + private void undo() + { + map.unselectHex(selectedHex); + map.hideDirections(selectedHex); + map.revertEnter(activeUnit); + activeUnit = null; + selectedUnit = null; + ctrl.hud.update(); + } + + private void unitEnter(Unit unit) + { + selectedUnit = unit; + selectedHex = upHex; + ctrl.player.reinforcement.remove(unit); + map.showOnBoard(unit, upHex, entryZone.orientation); + deployedUnits.add(unit); + entryZone.enable(Hex.AREA, false); + showRotation(unit, upHex); + ctrl.hud.update(); + } + + private void showRotation(Unit unit, Hex hex) + { + selectedUnit = unit; + selectedHex = hex; + map.selectHex(selectedHex); + map.showDirections(selectedHex); + ctrl.hud.playerInfo.unitDock.hide(); + ctrl.hud.actionButtons.show(Buttons.ABORT.b); + } + + private void doRotation(Orientation o) + { + map.unselectHex(selectedHex); + map.hideDirections(selectedHex); + + if (o != Orientation.KEEP) + map.setOnBoard(selectedUnit, selectedHex, o); + + ctrl.hud.actionButtons.hide(); + ctrl.hud.playerInfo.unitDock.show(); + entryZone = null; + activeUnit = null; + selectedUnit = null; + if (ctrl.checkDeploymentDone()) + completed = true; + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateEngage.java b/core/src/ch/asynk/rustanddust/game/states/StateEngage.java new file mode 100644 index 0000000..4588cb2 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateEngage.java @@ -0,0 +1,105 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons; + +import ch.asynk.rustanddust.RustAndDust; + +public class StateEngage extends StateCommon +{ + @Override + public void enter(StateType prevState) + { + map.possibleTargets.clear(); + ctrl.hud.actionButtons.show(ctrl.cfg.canCancel ? Buttons.ABORT.b : 0); + + // activeUnit is the target + if (prevState == StateType.SELECT) { + activeUnit = null; + // use selectedHex and selectedUnit + map.hidePossibleTargets(); + map.collectPossibleTargets(selectedUnit, ctrl.opponent.units); + map.showPossibleTargets(); + if (to != null) { + // quick fire -> replay touchUp + upHex = to; + touchUp(); + } + selectedUnit.showAttack(); + map.selectHex(selectedHex); + } else + RustAndDust.debug("should not happen"); + } + + @Override + public void leave(StateType nextState) + { + selectedUnit.hideAttack(); + map.hideAttackAssists(); + map.hidePossibleTargets(); + map.unselectHex(selectedHex); + if (to != null) + map.unselectHex(to); + } + + @Override + public StateType abort() + { + map.activatedUnits.clear(); + return StateType.ABORT; + } + + @Override + public StateType execute() + { + StateType nextState = StateType.DONE; + if (map.engageUnit(selectedUnit, activeUnit)) { + ctrl.player.wonEngagementCount += 1; + ctrl.opponent.casualty(activeUnit); + if (map.breakUnits.size() > 0) { + nextState = StateType.BREAK; + } + } else { + ctrl.player.lostEngagementCount += 1; + } + + activeUnit.showTarget(); + ctrl.setAfterAnimationState(nextState); + return StateType.ANIMATION; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + Unit unit = upHex.getUnit(); + + // activeUnit is the target, selectedTarget is the engagement leader + if (unit == selectedUnit) { + ctrl.setState(StateType.ABORT); + } else if ((activeUnit == null) && map.possibleTargets.contains(unit)) { + // ctrl.hud.notify("Engage " + unit); + map.hidePossibleTargets(); + to = upHex; + activeUnit = unit; + activeUnit.showTarget(); + map.collectAttackAssists(selectedUnit, activeUnit, ctrl.player.units); + map.showAttackAssists(); + ctrl.hud.actionButtons.show((ctrl.cfg.mustValidate ? Buttons.DONE.b : 0) | (ctrl.cfg.canCancel ? Buttons.ABORT.b : 0)); + } + else if (unit == activeUnit) { + ctrl.setState(StateType.DONE); + } + else if ((activeUnit != null) && map.engagementAssists.contains(unit)) { + map.toggleAttackAssist(unit); + // if(map.toggleAttackAssist(unit)) + // ctrl.hud.notify(unit + " will fire"); + // else + // ctrl.hud.notify(unit + " wont fire"); + } + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateMove.java b/core/src/ch/asynk/rustanddust/game/states/StateMove.java new file mode 100644 index 0000000..b59b133 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateMove.java @@ -0,0 +1,193 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons; + +public class StateMove extends StateCommon +{ + @Override + public void enter(StateType prevState) + { + ctrl.hud.actionButtons.show( + ((map.activatedUnits.size() > 0) ? Buttons.DONE.b : 0) + | (ctrl.cfg.canCancel ? Buttons.ABORT.b : 0)); + + if (prevState == StateType.WITHDRAW) { + if (map.pathBuilder.size() == 1) + ctrl.setState(StateType.ROTATE); + return; + } + + map.pathBuilder.clear(); + + if (prevState == StateType.SELECT) { + // use selectedHex and selectedUnit + activeUnit = selectedUnit; + activeUnit.showMoveable(); + map.pathBuilder.init(activeUnit); + map.collectAndShowMovesAndAssits(activeUnit); + if (to != null) { + // quick move -> replay touchUp + upHex = to; + touchUp(); + } else + checkExit(activeUnit, activeUnit.getHex()); + } else { + // back from rotation -> chose next Pawn + if (selectedUnit.canMove()) { + changeUnit(selectedUnit); + } else { + changeUnit(map.moveableUnits.get(0)); + } + } + + activeUnit.enableOverlay(Unit.MOVE, false); + } + + @Override + public void leave(StateType nextState) + { + if (nextState == StateType.WITHDRAW) + return; + + // hide all but assists : want them when in rotation + activeUnit.hideMoveable(); + map.hidePossibleMoves(); + map.unselectHex(activeUnit.getHex()); + if (to != null) + map.hidePath(to); + + if (nextState != StateType.SELECT) { + if (to == null) + to = activeUnit.getHex(); + } + } + + @Override + public StateType abort() + { + hideAssists(); + if (activeUnit.justEntered()) { + map.revertEnter(activeUnit); + return StateType.ABORT; + } + int n = map.activatedUnits.size(); + if (n == 0) + return StateType.ABORT; + map.revertMoves(); + return StateType.ANIMATION; + } + + @Override + public StateType execute() + { + hideAssists(); + // be sure that the hq is activated + if (selectedUnit.canMove() && (map.activatedUnits.size() > 0)) + selectedUnit.setMoved(); + + return StateType.DONE; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + if (upHex == activeUnit.getHex()) { + if (to != null) + map.hidePath(to); + to = null; + map.pathBuilder.clear(); + ctrl.setState(StateType.ROTATE); + return; + } + + int s = map.pathBuilder.size(); + + Unit unit = upHex.getUnit(); + + if (map.moveableUnits.contains(unit)) { + if(unit != activeUnit) + changeUnit(unit); + } else if ((s == 0) && map.possibleMoves.contains(upHex)) { + s = collectPaths(upHex); + } else if (map.pathBuilder.contains(upHex)) { + s = togglePoint(downHex, s); + } + + if (s == 1) { + if (!checkExit(activeUnit, upHex)) + ctrl.setState(StateType.ROTATE); + } + } + + private void hideAssists() + { + map.hideMoveableUnits(); + } + + private void changeUnit(Unit unit) + { + if (activeUnit != null ) { + map.unselectHex(activeUnit.getHex()); + if (activeUnit.canMove()) + activeUnit.enableOverlay(Unit.MOVE, true); + } + activeUnit = unit; + Hex hex = activeUnit.getHex(); + map.pathBuilder.init(activeUnit, hex); + activeUnit.showMoveable(); + map.hidePossibleMoves(); + map.collectPossibleMoves(activeUnit); + map.showPossibleMoves(); + map.selectHex(hex); + activeUnit.enableOverlay(Unit.MOVE, false); + ctrl.hud.notify(activeUnit.toString()); + checkExit(activeUnit, hex); + } + + private int collectPaths(Hex hex) + { + to = hex; + int s = map.pathBuilder.build(to); + map.showMove(to); + map.hidePossibleMoves(); + map.showPathBuilder(); + return s; + } + + private int togglePoint(Hex hex, int s) + { + if (hex == activeUnit.getHex()) { + // + } else if (hex == to) { + // + } else { + map.hidePathBuilder(); + map.togglePathOverlay(hex); + s = map.togglePathBuilderHex(hex); + map.showPathBuilder(); + } + + return s; + } + + private boolean checkExit(Unit unit, Hex hex) + { + if ((hex == unit.getHex()) && (unit.justEntered())) + return false; + Zone exitZone = ctrl.battle.getExitZone(unit); + if ((exitZone == null) || !exitZone.contains(hex)) + return false; + if ((unit.getHex() != hex) && !map.pathBuilder.canExit(exitZone.orientation)) + return false; + ctrl.setState(StateType.WITHDRAW); + return true; + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StatePromote.java b/core/src/ch/asynk/rustanddust/game/states/StatePromote.java new file mode 100644 index 0000000..8543c89 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StatePromote.java @@ -0,0 +1,42 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.game.Unit; + +public class StatePromote extends StateCommon +{ + @Override + public void enter(StateType prevState) + { + ctrl.setAfterAnimationState(StateType.DONE); + ctrl.setState(StateType.ANIMATION); + map.promoteUnit(selectedUnit); + } + + @Override + public void leave(StateType nextState) + { + map.unselectHex(selectedHex); + } + + @Override + public StateType abort() + { + return StateType.ABORT; + } + + @Override + public StateType execute() + { + return StateType.DONE; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateReinforcement.java b/core/src/ch/asynk/rustanddust/game/states/StateReinforcement.java new file mode 100644 index 0000000..77ff826 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateReinforcement.java @@ -0,0 +1,87 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons; + +public class StateReinforcement extends StateCommon +{ + private Zone entryZone; + + @Override + public void enter(StateType prevState) + { + map.clearAll(); + if (selectedHex != null) + map.unselectHex(selectedHex); + entryZone = null; + selectedHex = null; + ctrl.hud.playerInfo.unitDock.show(); + } + + @Override + public void leave(StateType nextState) + { + if (selectedHex != null) + map.unselectHex(selectedHex); + if (entryZone != null) + entryZone.enable(Hex.AREA, false); + ctrl.hud.playerInfo.unitDock.hide(); + } + + @Override + public StateType abort() + { + return StateType.ABORT; + } + + @Override + public StateType execute() + { + return StateType.DONE; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + Unit unit = ctrl.hud.playerInfo.unitDock.selectedUnit; + if ((unit != null) && (unit != activeUnit)) + changeUnit(unit); + else if ((entryZone != null) && upHex.isEmpty() && entryZone.contains(upHex)) + unitEnter(activeUnit); + else + ctrl.setState(StateType.SELECT); + } + + private void changeUnit(Unit unit) + { + activeUnit = unit; + if (entryZone != null) + entryZone.enable(Hex.AREA, false); + entryZone = ctrl.battle.getEntryZone(activeUnit); + entryZone.enable(Hex.AREA, true); + ctrl.hud.actionButtons.show(((ctrl.cfg.canCancel) ? Buttons.ABORT.b : 0)); + } + + private void unitEnter(Unit unit) + { + selectedUnit = unit; + selectedHex = upHex; + map.selectHex(selectedHex); + entryZone.enable(Hex.AREA, false); + if (map.enterBoard(unit, upHex, entryZone.allowedMoves)) { + if (unit.getMovementPoints() > 0) + ctrl.setState(StateType.MOVE); + else + ctrl.setState(StateType.ROTATE); + } else { + ctrl.hud.notify("Can not enter the map at that position"); + } + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateRotate.java b/core/src/ch/asynk/rustanddust/game/states/StateRotate.java new file mode 100644 index 0000000..4d91740 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateRotate.java @@ -0,0 +1,111 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.engine.Orientation; +import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons; + +import ch.asynk.rustanddust.RustAndDust; + +public class StateRotate extends StateCommon +{ + private boolean rotateOnly; + private boolean rotationSet; + + @Override + public void enter(StateType prevState) + { + ctrl.hud.actionButtons.show((ctrl.cfg.canCancel && (map.moveableUnits.size() > 1))? Buttons.ABORT.b : 0); + + if (activeUnit == null) + activeUnit = selectedUnit; + if (to == null) + to = activeUnit.getHex(); + + if (!map.pathBuilder.isSet()) { + map.pathBuilder.init(activeUnit); + map.pathBuilder.build(to); + } + + if (map.pathBuilder.size() != 1) + RustAndDust.debug("ERROR: pathBuilder.size() == " + map.pathBuilder.size()); + + rotateOnly = (to == activeUnit.getHex()); + + if (!rotateOnly) + map.showPath(to); + map.selectHex(activeUnit.getHex()); + map.showDirections(to); + + rotationSet = false; + } + + @Override + public void leave(StateType nextState) + { + map.unselectHex(activeUnit.getHex()); + map.hidePath(to); + map.hideDirections(to); + map.hideOrientation(to); + map.pathBuilder.clear(); + to = null; + } + + @Override + public StateType abort() + { + StateType nextState = StateType.ABORT; + ctrl.hud.actionButtons.hide(); + if (activeUnit.justEntered()) { + map.revertEnter(activeUnit); + nextState = StateType.ABORT; + } else if (map.activatedUnits.size() == 0) { + map.hideMoveableUnits(); + } else { + nextState = StateType.MOVE; + } + return nextState; + } + + @Override + public StateType execute() + { + StateType whenDone = StateType.DONE; + + if (map.moveUnit(activeUnit) > 0) + whenDone = StateType.MOVE; + + ctrl.setAfterAnimationState(whenDone); + return StateType.ANIMATION; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + if (rotationSet) return; + + Orientation o = Orientation.fromAdj(to, upHex); + if (o == Orientation.KEEP) { + ctrl.setState(StateType.ABORT); + return; + } + + if (!activeUnit.justEntered() && rotateOnly && (o == activeUnit.getOrientation())) + return; + + map.pathBuilder.orientation = o; + rotationSet = true; + + if (ctrl.cfg.mustValidate) { + map.hideDirections(to); + map.showOrientation(to, o); + ctrl.hud.actionButtons.show(Buttons.DONE.b | ((ctrl.cfg.canCancel) ? Buttons.ABORT.b : 0)); + } else { + execute(); + ctrl.setState(StateType.ANIMATION); + } + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateSelect.java b/core/src/ch/asynk/rustanddust/game/states/StateSelect.java new file mode 100644 index 0000000..9161a6b --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateSelect.java @@ -0,0 +1,132 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.game.Map; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.Unit; +import ch.asynk.rustanddust.game.Ctrl; +import ch.asynk.rustanddust.game.hud.ActionButtons.Buttons; + +import ch.asynk.rustanddust.RustAndDust; + +public class StateSelect extends StateCommon +{ + public StateSelect(Ctrl ctrl, Map map) + { + super(ctrl, map); + } + + @Override + public void enter(StateType prevState) + { + to = null; + selectedHex = null; + selectedUnit = null; + activeUnit = null; + map.clearAll(); + ctrl.hud.actionButtons.hide(); + } + + @Override + public void leave(StateType nextState) + { + hidePossibilities(); + } + + @Override + public StateType abort() + { + if (selectedHex != null) + map.unselectHex(selectedHex); + hidePossibilities(); + map.clearAll(); + return StateType.ABORT; + } + + @Override + public StateType execute() + { + return StateType.DONE; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + if (!isEnemy) { + if (map.possibleMoves.contains(upHex)) { + // quick move + to = upHex; + ctrl.setState(StateType.MOVE); + return; + } + if (map.possibleTargets.contains(upHex.getUnit())) { + // quick fire + to = upHex; + ctrl.setState(StateType.ENGAGE); + return; + } + } + + if (selectedHex != null) + map.unselectHex(selectedHex); + + hidePossibilities(); + if (upHex.isOffMap()) { + selectedUnit = null; + return; + } + + Unit unit = upHex.getUnit(); + + if (unit == null) { + isEnemy = false; + ctrl.hud.actionButtons.hide(); + map.clearAll(); + selectedUnit = null; + return; + } + + isEnemy = ctrl.player.isEnemy(unit); + if (!isEnemy && (unit == selectedUnit) && unit.canMove()) { + if (unit.isHq()) { + ctrl.hud.notify("HQ activation"); + select(upHex, unit, isEnemy); + ctrl.setState(StateType.MOVE); + } else { + // quick rotate + to = upHex; + ctrl.setState(StateType.ROTATE); + } + } else { + select(upHex, unit, isEnemy); + ctrl.hud.notify(selectedUnit.toString()); + } + } + + private void select(Hex hex, Unit unit, boolean isEnemy) + { + selectedHex = hex; + selectedUnit = unit; + + if (isEnemy && !ctrl.cfg.showEnemyPossibilities) + return; + + int moves = map.collectPossibleMoves(selectedUnit); + int targets = map.collectPossibleTargets(selectedUnit, (isEnemy ? ctrl.player.units : ctrl.opponent.units)); + + if (moves > 0) + map.collectMoveableUnits(selectedUnit); + + if ((moves > 0) || (targets > 0)) { + map.selectHex(selectedHex); + showPossibilities(selectedUnit); + } + + ctrl.hud.actionButtons.show((ctrl.player.canPromote(selectedUnit)) ? Buttons.PROMOTE.b : 0 ); + RustAndDust.debug("Select", selectedHex.toString() + " " + selectedUnit + (isEnemy ? " enemy " : " friend ")); + } +} diff --git a/core/src/ch/asynk/rustanddust/game/states/StateWithdraw.java b/core/src/ch/asynk/rustanddust/game/states/StateWithdraw.java new file mode 100644 index 0000000..f4f11a6 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/game/states/StateWithdraw.java @@ -0,0 +1,71 @@ +package ch.asynk.rustanddust.game.states; + +import ch.asynk.rustanddust.game.Zone; +import ch.asynk.rustanddust.game.Hex; +import ch.asynk.rustanddust.game.Unit; + +public class StateWithdraw extends StateCommon +{ + @Override + public void enter(StateType prevState) + { + ctrl.hud.askExitBoard(); + } + + @Override + public void leave(StateType nextState) + { + } + + @Override + public StateType abort() + { + return StateType.MOVE; + } + + @Override + public StateType execute() + { + if (activeUnit == null) + activeUnit = selectedUnit; + + ctrl.setAfterAnimationState(withdraw(activeUnit)); + return StateType.ANIMATION; + } + + @Override + public void touchDown() + { + } + + @Override + public void touchUp() + { + } + + private StateType withdraw(Unit unit) + { + Zone exitZone = ctrl.battle.getExitZone(unit); + Hex hex = unit.getHex(); + + // rotation + if (map.pathBuilder.to == null) + map.pathBuilder.build(hex); + + Hex exitHex = (Hex) map.pathBuilder.to; + if (!exitZone.contains(exitHex)) + throw new RuntimeException(String.format("%s not in exitZone", exitHex)); + + map.pathBuilder.setExit(exitZone.orientation); + + unit.hideMoveable(); + if (to != null) + map.hidePath(to); + map.hidePossibleMoves(); + map.unselectHex(hex); + + if (map.exitBoard(unit) > 0) + return StateType.MOVE; + return StateType.DONE; + } +} diff --git a/core/src/ch/asynk/rustanddust/loading/LoadingBar.java b/core/src/ch/asynk/rustanddust/loading/LoadingBar.java new file mode 100644 index 0000000..5a6181a --- /dev/null +++ b/core/src/ch/asynk/rustanddust/loading/LoadingBar.java @@ -0,0 +1,32 @@ +package ch.asynk.rustanddust.loading; + +import com.badlogic.gdx.graphics.g2d.Animation; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.scenes.scene2d.Actor; + +public class LoadingBar extends Actor +{ + Animation animation; + TextureRegion reg; + float stateTime; + + public LoadingBar(Animation animation) + { + this.animation = animation; + reg = animation.getKeyFrame(0); + } + + @Override + public void act(float delta) + { + stateTime += delta; + reg = animation.getKeyFrame(stateTime); + } + + @Override + public void draw(Batch batch, float parentAlpha) + { + batch.draw(reg, getX(), getY()); + } +} diff --git a/core/src/ch/asynk/rustanddust/menu/MainMenu.java b/core/src/ch/asynk/rustanddust/menu/MainMenu.java new file mode 100644 index 0000000..769adb3 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/menu/MainMenu.java @@ -0,0 +1,67 @@ +package ch.asynk.rustanddust.menu; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; + +import ch.asynk.rustanddust.ui.Menu; + +public class MainMenu extends Menu +{ + public enum Items implements Menu.MenuItem + { + EXIT(0), + OPTIONS(1), + TUTORIALS(2), + SCENARIOS(3), + NONE(4); + public int i; + Items(int i) + { + this.i = i; + } + public int i() { return i; } + public int last() { return NONE.i; } + }; + + public MainMenu(BitmapFont font, TextureAtlas atlas) + { + super(Items.NONE, font, atlas.createPatch("typewriter")); + + label(Items.OPTIONS).write("Options"); + label(Items.TUTORIALS).write("Tutorials"); + label(Items.SCENARIOS).write("Scenarios"); + label(Items.EXIT).write("Exit"); + + this.visible = true; + } + + public Items getMenu() + { + return (Items) menuItem; + } + + @Override + public boolean hit(float x, float y) + { + boolean ret = false; + menuItem = Items.NONE; + + if (!visible) return ret; + + if (label(Items.SCENARIOS).hit(x, y)) { + menuItem = Items.SCENARIOS; + ret = true; + } else if (label(Items.TUTORIALS).hit(x, y)) { + menuItem = Items.TUTORIALS; + ret = true; + } else if (label(Items.OPTIONS).hit(x, y)) { + menuItem = Items.OPTIONS; + ret = true; + } else if (label(Items.EXIT).hit(x, y)) { + Gdx.app.exit(); + } + + return ret; + } +} diff --git a/core/src/ch/asynk/rustanddust/menu/OptionsMenu.java b/core/src/ch/asynk/rustanddust/menu/OptionsMenu.java new file mode 100644 index 0000000..9a7c675 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/menu/OptionsMenu.java @@ -0,0 +1,257 @@ +package ch.asynk.rustanddust.menu; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; + +import ch.asynk.rustanddust.ui.Label; +import ch.asynk.rustanddust.ui.Bg; +import ch.asynk.rustanddust.ui.Patch; +import ch.asynk.rustanddust.ui.OkCancel; + +import ch.asynk.rustanddust.RustAndDust; + +public class OptionsMenu extends Patch +{ + public static int PADDING = 40; + public static int OK_PADDING = 10; + public static int TITLE_PADDING = 30; + public static int VSPACING = 5; + public static int HSPACING = 30; + public static String CHECK = "#"; + + private final RustAndDust game; + private final BitmapFont font; + + private String [] checkStrings = { + "Debug", + "Must Validate", + "Can Cancel", + "Show Enemy Possibilities", + "Show Moves Assists", + "Show Targets", + "Show Moves", + }; + private String [] fxStrings = { "OFF", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "ON" }; + + private float checkDy; + private Label title; + private Label fxVolume; + private Label fxVolumeValue; + private Label graphics; + private Label graphicsValue; + private Label gameMode; + private Label gameModeValue; + private Label [] checkLabels; + private boolean [] checkValues; + private OkCancel okCancel; + protected Bg okBtn; + + public OptionsMenu(RustAndDust game, BitmapFont font, TextureAtlas atlas) + { + super(atlas.createPatch("typewriter")); + this.game = game; + this.font = font; + this.okCancel = new OkCancel(font, atlas); + this.okBtn = new Bg(atlas.findRegion("ok")); + this.title = new Label(font); + this.title.write("- Options"); + this.fxVolume = new Label(font); + this.fxVolume.write("Fx Volume"); + this.fxVolumeValue = new Label(font); + this.fxVolumeValue.write(fxStrings[(int) (game.config.fxVolume * 10)]); + this.graphics = new Label(font); + this.graphics.write("Graphics"); + this.graphicsValue = new Label(font); + this.graphicsValue.write(game.config.graphics.s); + this.gameMode = new Label(font); + this.gameMode.write("Game mode"); + this.gameModeValue = new Label(font); + this.gameModeValue.write(game.config.gameMode.s); + this.checkValues = new boolean[checkStrings.length]; + this.checkLabels = new Label[checkStrings.length]; + for (int i = 0; i < checkLabels.length; i++) { + Label l = new Label(font, 5f); + l.write(checkStrings[i]); + this.checkLabels[i] = l; + } + getValues(); + GlyphLayout layout = new GlyphLayout(); + layout.setText(font, CHECK); + checkDy = layout.height + 5; + + this.visible = false; + } + + private void getValues() + { + checkValues[6] = game.config.showMoves; + checkValues[5] = game.config.showTargets; + checkValues[4] = game.config.showMoveAssists; + checkValues[3] = game.config.showEnemyPossibilities; + checkValues[2] = game.config.canCancel; + checkValues[1] = game.config.mustValidate; + checkValues[0] = game.config.debug; + } + + private boolean apply() + { + game.config.showMoves = checkValues[6]; + game.config.showTargets = checkValues[5]; + game.config.showMoveAssists = checkValues[4]; + game.config.showEnemyPossibilities = checkValues[3]; + game.config.canCancel = checkValues[2]; + game.config.mustValidate = checkValues[1]; + game.config.debug = checkValues[0]; + if (!game.config.gameModeImplemented()) { + this.visible = false; + okCancel.show(String.format("'%s' Game Mode not implemented yet.", game.config.gameMode.s)); + okCancel.noCancel(); + return false; + } + return true; + } + + private void cycleFxVolume() + { + int i = (int) (game.config.fxVolume * 10) + 1; + if (i > 10) i = 0; + float fx = fxVolumeValue.getX(); + float fy = fxVolumeValue.getY(); + fxVolumeValue.write(fxStrings[i]); + fxVolumeValue.setPosition(fx, fy); + game.config.fxVolume = (i / 10f); + } + + private void cycleGraphics() + { + game.config.graphics = game.config.graphics.next(); + float fx = graphicsValue.getX(); + float fy = graphicsValue.getY(); + graphicsValue.write(game.config.graphics.s); + graphicsValue.setPosition(fx, fy); + } + + private void cycleGameMode() + { + game.config.gameMode = game.config.gameMode.next(); + float fx = gameModeValue.getX(); + float fy = gameModeValue.getY(); + gameModeValue.write(game.config.gameMode.s); + gameModeValue.setPosition(fx, fy); + } + + public void setPosition() + { + float h = (title.getHeight() + TITLE_PADDING + ((checkLabels.length - 1) * VSPACING) + (2 * PADDING)); + for (int i = 0; i < checkLabels.length; i++) + h += checkLabels[i].getHeight(); + h += (graphics.getHeight() + VSPACING); + h += (gameMode.getHeight() + VSPACING); + h += (fxVolume.getHeight() + VSPACING); + + float w = title.getWidth(); + for (int i = 0; i < checkLabels.length; i++) { + float t = checkLabels[i].getWidth(); + if (t > w) + w = t; + } + w += (2 * PADDING) + HSPACING; + + float x = position.getX(w); + float y = position.getY(h); + setPosition(x, y, w, h); + + okBtn.setPosition((x + w - okBtn.getWidth() + OK_PADDING), (y - OK_PADDING)); + + y += PADDING; + x += PADDING + HSPACING; + float dy = (VSPACING + checkLabels[0].getHeight()); + + graphics.setPosition(x, y); + graphicsValue.setPosition((x + graphics.getWidth() + 10), y); + y += dy; + gameMode.setPosition(x, y); + gameModeValue.setPosition((x + gameMode.getWidth() + 10), y); + y += dy; + fxVolume.setPosition(x, y); + fxVolumeValue.setPosition((x + fxVolume.getWidth() + 10), y); + y += dy; + for (int i = 0; i < checkLabels.length; i++) { + checkLabels[i].setPosition(x, y); + y += dy; + } + y += (TITLE_PADDING - VSPACING); + title.setPosition(x, y); + } + + @Override + public boolean hit(float x, float y) + { + if (okCancel.hit(x, y)) { + this.visible = true; + okCancel.visible = false; + return false; + } + + if (!visible) return false; + + if (okBtn.hit(x, y)) { + return apply(); + } else if (fxVolume.hit(x, y) || fxVolumeValue.hit(x, y)) { + cycleFxVolume(); + } else if (graphics.hit(x, y) || graphicsValue.hit(x, y)) { + cycleGraphics(); + } else if (gameMode.hit(x, y) || gameModeValue.hit(x, y)) { + cycleGameMode(); + } else { + for (int i = 0; i < checkLabels.length; i++) { + if (checkLabels[i].hit(x, y)) + checkValues[i] =! checkValues[i]; + } + } + + return false; + } + + @Override + public void dispose() + { + super.dispose(); + title.dispose(); + okBtn.dispose(); + okCancel.dispose(); + fxVolume.dispose(); + fxVolumeValue.dispose(); + graphics.dispose(); + graphicsValue.dispose(); + gameMode.dispose(); + gameModeValue.dispose(); + for (int i = 0; i < checkLabels.length; i++) + checkLabels[i].dispose(); + } + + @Override + public void draw(Batch batch) + { + okCancel.draw(batch); + + if (!visible) return; + super.draw(batch); + title.draw(batch); + okBtn.draw(batch); + fxVolume.draw(batch); + fxVolumeValue.draw(batch); + graphics.draw(batch); + graphicsValue.draw(batch); + gameMode.draw(batch); + gameModeValue.draw(batch); + for (int i = 0; i < checkLabels.length; i++) { + Label l = checkLabels[i]; + l.draw(batch); + if (checkValues[i]) + font.draw(batch, CHECK, (l.getX() - HSPACING) , l.getY() + checkDy); + } + } +} diff --git a/core/src/ch/asynk/rustanddust/menu/ScenariosMenu.java b/core/src/ch/asynk/rustanddust/menu/ScenariosMenu.java new file mode 100644 index 0000000..d5b99ed --- /dev/null +++ b/core/src/ch/asynk/rustanddust/menu/ScenariosMenu.java @@ -0,0 +1,141 @@ +package ch.asynk.rustanddust.menu; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; + +import ch.asynk.rustanddust.ui.Label; +import ch.asynk.rustanddust.ui.Bg; +import ch.asynk.rustanddust.ui.Patch; +import ch.asynk.rustanddust.RustAndDust; +import ch.asynk.rustanddust.game.Battle; + +public class ScenariosMenu extends Patch +{ + public static int PADDING = 40; + public static int BTN_PADDING = 10; + public static int TITLE_PADDING = 30; + public static int VSPACING = 5; + public static int HSPACING = 30; + public static String CHECK = "#"; + + private final RustAndDust game; + private final BitmapFont font; + + private float checkDy; + private Label title; + protected Bg okBtn; + protected Bg cancelBtn; + private Label [] battleLabels; + + public boolean launch; + + public ScenariosMenu(RustAndDust game, BitmapFont font, TextureAtlas atlas) + { + super(atlas.createPatch("typewriter")); + this.game = game; + this.font = font; + this.okBtn = new Bg(atlas.findRegion("ok")); + this.cancelBtn = new Bg(atlas.findRegion("cancel")); + this.title = new Label(font); + this.title.write("- Scenarios"); + this.battleLabels = new Label[game.factory.battles.length]; + for (int i = 0; i < battleLabels.length; i++) { + Label l = new Label(font, 8f); + l.write(game.factory.battles[i].getName()); + battleLabels[i] = l; + } + GlyphLayout layout = new GlyphLayout(); + layout.setText(font, CHECK); + checkDy = layout.height + 9; + + this.visible = false; + this.launch = false; + } + + public void setPosition() + { + float h = (title.getHeight() + TITLE_PADDING + ((battleLabels.length - 1) * VSPACING) + (2 * PADDING)); + for (int i = 0; i < battleLabels.length; i++) + h += battleLabels[i].getHeight(); + + float w = title.getWidth(); + for (int i = 0; i < battleLabels.length; i++) { + float t = battleLabels[i].getWidth(); + if (t > w) + w = t; + } + w += (2 * PADDING) + HSPACING; + + float x = position.getX(w); + float y = position.getY(h); + setPosition(x, y, w, h); + + okBtn.setPosition((x + w - okBtn.getWidth() + BTN_PADDING), (y - BTN_PADDING)); + cancelBtn.setPosition((x - BTN_PADDING), okBtn.getY()); + + y += PADDING; + x += PADDING + HSPACING; + float dy = (VSPACING + battleLabels[0].getHeight()); + + for (int i = (battleLabels.length - 1); i > -1; i--) { + battleLabels[i].setPosition(x, y); + y += dy; + } + y += (TITLE_PADDING - VSPACING); + title.setPosition(x, y); + } + + @Override + public boolean hit(float x, float y) + { + if (!visible) return false; + + if (okBtn.hit(x, y)) { + this.launch = (game.config.battle != null); + return true; + } else if (cancelBtn.hit(x, y)) { + this.launch = false; + return true; + } else { + for (int i = 0; i <battleLabels.length; i++) { + if (battleLabels[i].hit(x, y)) { + if (game.config.battle == game.factory.battles[i]) + game.config.battle = null; + else + game.config.battle = game.factory.battles[i]; + } + } + } + + return false; + } + + @Override + public void dispose() + { + super.dispose(); + title.dispose(); + okBtn.dispose(); + cancelBtn.dispose(); + for (int i = 0; i < battleLabels.length; i++) + battleLabels[i].dispose(); + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + super.draw(batch); + title.draw(batch); + okBtn.draw(batch); + cancelBtn.draw(batch); + for (int i = 0; i < battleLabels.length; i++) { + Label l = battleLabels[i]; + l.draw(batch); + if (game.config.battle == game.factory.battles[i]) + font.draw(batch, CHECK, (l.getX() - HSPACING) , l.getY() + checkDy); + } + } +} diff --git a/core/src/ch/asynk/rustanddust/menu/TutorialsMenu.java b/core/src/ch/asynk/rustanddust/menu/TutorialsMenu.java new file mode 100644 index 0000000..7f54aad --- /dev/null +++ b/core/src/ch/asynk/rustanddust/menu/TutorialsMenu.java @@ -0,0 +1,94 @@ +package ch.asynk.rustanddust.menu; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; + +import ch.asynk.rustanddust.ui.Label; +import ch.asynk.rustanddust.ui.Bg; +import ch.asynk.rustanddust.ui.Patch; +import ch.asynk.rustanddust.RustAndDust; + +public class TutorialsMenu extends Patch +{ + public static int PADDING = 40; + public static int OK_PADDING = 10; + public static int TITLE_PADDING = 30; + public static int VSPACING = 20; + + private final RustAndDust game; + private final BitmapFont font; + + private Label title; + private Label msg; + protected Bg okBtn; + + public TutorialsMenu(RustAndDust game, BitmapFont font, TextureAtlas atlas) + { + super(atlas.createPatch("typewriter")); + this.game = game; + this.font = font; + this.okBtn = new Bg(atlas.findRegion("ok")); + this.title = new Label(font); + this.title.write("- Tutorials"); + this.msg = new Label(font); + this.msg.write("Not implemented yet."); + + this.visible = false; + } + + public void setPosition() + { + float h = (title.getHeight() + TITLE_PADDING + (2 * PADDING)); + h += msg.getHeight(); + + float w = title.getWidth(); + if (msg.getWidth() > w) + w = msg.getWidth(); + w += (2 * PADDING); + + float x = position.getX(w); + float y = position.getY(h); + setPosition(x, y, w, h); + + okBtn.setPosition((x + w - okBtn.getWidth() + OK_PADDING), (y - OK_PADDING)); + + y += PADDING; + x += PADDING; + + msg.setPosition(x, y); + + y += (msg.getHeight() + TITLE_PADDING); + title.setPosition(x, y); + } + + @Override + public boolean hit(float x, float y) + { + if (!visible) return false; + + if (okBtn.hit(x, y)) + return true; + + return false; + } + + @Override + public void dispose() + { + super.dispose(); + title.dispose(); + msg.dispose(); + okBtn.dispose(); + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + super.draw(batch); + title.draw(batch); + msg.draw(batch); + okBtn.draw(batch); + } +} diff --git a/core/src/ch/asynk/rustanddust/screens/GameCamera.java b/core/src/ch/asynk/rustanddust/screens/GameCamera.java new file mode 100644 index 0000000..0dbcb50 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/screens/GameCamera.java @@ -0,0 +1,212 @@ +package ch.asynk.rustanddust.screens; + +import com.badlogic.gdx.Gdx; + +import com.badlogic.gdx.graphics.OrthographicCamera; + +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.MathUtils; + +public class GameCamera extends OrthographicCamera +{ + private static final float ZEROF = 0.01f; + + private int screenWidth; + private int screenHeight; + private float zoomOut; + private float zoomIn; + private float viewportAspect; + private float widthFactor; + private float heightFactor; + private Rectangle virtual; + private Rectangle window; + private Rectangle hud; + private Matrix4 hudMatrix; + private Matrix4 hudInvProjMatrix; + private int hudCorrection; + private int hudLeft; + private int hudBottom; + private boolean fixedHud; + + public GameCamera(float virtualWidth, float virtualHeight, float zoomOut, float zoomIn, int hudCorrection, boolean fixedHud) + { + super(virtualWidth, virtualHeight); + this.zoomOut = zoomOut; + this.zoomIn = zoomIn; + this.viewportAspect = (viewportWidth / viewportHeight); + this.virtual = new Rectangle(); + this.virtual.set(0, 0, virtualWidth, virtualHeight); + this.window = new Rectangle(); + this.hud = new Rectangle(); + this.hudMatrix = new Matrix4(); + this.hudInvProjMatrix = new Matrix4(); + this.hudCorrection = hudCorrection; + this.fixedHud = fixedHud; + } + + public void updateViewport(int screenWidth, int screenHeight) + { + this.screenWidth = screenWidth; + this.screenHeight = screenHeight; + + float aspect = (screenWidth / (float) screenHeight); + float diff = (viewportAspect - aspect); + + if (diff < -ZEROF) { + // wider than tall + window.width = java.lang.Math.min((screenHeight * viewportAspect / zoom), screenWidth); + window.height = screenHeight; + window.x = ((screenWidth - window.width) / 2f); + window.y = 0f; + viewportWidth = (viewportHeight * (window.width / window.height)); + hud.y = hudCorrection; + hud.x = (hud.y * viewportWidth / viewportHeight); + } else if (diff > ZEROF) { + // taller than wide + // FIXME fix hud vertical position + window.width = screenWidth; + window.height = java.lang.Math.min((screenWidth * viewportAspect / zoom), screenHeight); + window.x = 0f; + window.y = ((screenHeight - window.height) / 2f); + viewportHeight = (viewportWidth * (window.height / window.width)); + hud.x = hudCorrection; + hud.y = (hud.x * viewportHeight / viewportWidth); + } + + if (fixedHud) { + hud.x = 0; + hud.y = 0; + hud.width = screenWidth; + hud.height = screenHeight; + } else { + hud.width = (window.width - (2 * hud.x)); + hud.height = (window.height - (2 * hud.y)); + } + + widthFactor = (viewportWidth / screenWidth); + heightFactor = (viewportHeight / screenHeight); + + clampPosition(); + update(true); + hudMatrix.setToOrtho2D(hud.x, hud.y, hud.width, hud.height); + hudInvProjMatrix.set(hudMatrix); + Matrix4.inv(hudInvProjMatrix.val); + } + + public void applyMapViewport() + { + Gdx.gl.glViewport((int)window.x, (int)window.y, (int)window.width, (int)window.height); + } + + public void applyHudViewport() + { + if (fixedHud) + Gdx.gl.glViewport(0, 0, screenWidth, screenHeight); + } + + public Matrix4 getHudMatrix() + { + return hudMatrix; + } + + public int getScreenWidth() + { + return screenWidth; + } + + public int getScreenHeight() + { + return screenHeight; + } + + public int getHudLeft() + { + return (int) hud.x; + } + + public int getHudBottom() + { + return (int) hud.y; + } + + public int getHudWidth() + { + return (int) hud.width; + } + + public int getHudHeight() + { + return (int) hud.height; + } + + public void centerOnWorld() + { + position.set((viewportWidth / 2f), (viewportHeight / 2f), 0f); + } + + public void zoom(float dz) + { + zoom += dz; + clampZoom(); + updateViewport(screenWidth, screenHeight); + } + + public void translate(float dx, float dy) + { + float deltaX = (dx * zoom * widthFactor); + float deltaY = (dy * zoom * heightFactor); + translate(deltaX, -deltaY, 0); + clampPosition(); + update(true); + } + + public void clampZoom() + { + zoom = MathUtils.clamp(zoom, zoomIn, zoomOut); + } + + public void clampPosition() + { + float cx = (viewportWidth * zoom); + float cy = (viewportHeight * zoom); + + if ((virtual.width - cx) > ZEROF) { + cx /= 2f; + position.x = MathUtils.clamp(position.x, cx, (virtual.width - cx)); + } else + position.x = (virtual.width / 2f); + + if ((virtual.height - cy) > ZEROF) { + cy /= 2f; + position.y = MathUtils.clamp(position.y, cy, (virtual.height - cy)); + } else + position.y = (virtual.height / 2f); + } + + public void debug() + { + System.err.println(String.format(" VIEWPORT: %dx%d * %.2f -> %dx%d", (int)viewportWidth, (int)viewportHeight, + zoom, (int)(viewportWidth * zoom), (int)(viewportHeight * zoom))); + System.err.println(String.format(" WINDOW: %d;%d %dx%d", (int)window.x, (int)window.y, (int)window.width, (int)window.height)); + System.err.println("MATRIX:" + combined.toString()); + } + + public void unproject(int x, int y, Vector3 v) + { + unproject(v.set(x, y, 0), window.x, window.y, window.width, window.height); + } + + public void unprojectHud(float x, float y, Vector3 v) + { + Rectangle r = (fixedHud ? hud : window); + x = x - r.x; + y = Gdx.graphics.getHeight() - y - 1; + y = y - r.y; + v.x = (2 * x) / r.width - 1; + v.y = (2 * y) / r.height - 1; + v.z = 2 * v.z - 1; + v.prj(hudInvProjMatrix); + } +} diff --git a/core/src/ch/asynk/rustanddust/screens/GameScreen.java b/core/src/ch/asynk/rustanddust/screens/GameScreen.java new file mode 100644 index 0000000..c369989 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/screens/GameScreen.java @@ -0,0 +1,221 @@ +package ch.asynk.rustanddust.screens; + +import com.badlogic.gdx.Gdx; + +import com.badlogic.gdx.Screen; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.InputAdapter; +import com.badlogic.gdx.InputMultiplexer; +import com.badlogic.gdx.input.GestureDetector; +import com.badlogic.gdx.input.GestureDetector.GestureAdapter; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import com.badlogic.gdx.math.Vector2; + +import ch.asynk.rustanddust.RustAndDust; + +import ch.asynk.rustanddust.game.Ctrl; + +public class GameScreen implements Screen +{ + private static boolean DEBUG = false; + + private static final boolean FIXED_HUD = true; + private static final float INPUT_DELAY = 0.1f; + private static final float ZOOM_IN_MAX = 0.3f; + private static final float ZOOM_OUT_MAX = 1f; + private static final float ZOOM_GESTURE_FACTOR = .01f; + private static final float ZOOM_SCROLL_FACTOR = .1f; + private static final int DRAGGED_Z_INDEX = 10; + private static final int DRAG_THRESHOLD = 6; + + private final GameCamera cam; + + private final SpriteBatch batch; + private ShapeRenderer debugShapes = null; + + private final RustAndDust game; + private Ctrl ctrl; + + private int dragged; + private boolean blocked; + private float inputDelay = 0f; + private Vector2 dragPos = new Vector2(); + + public GameScreen(final RustAndDust game) + { + DEBUG = game.config.debug; + + this.game = game; + this.dragged = 0; + this.blocked = false; + + this.batch = new SpriteBatch(); + this.ctrl = new Ctrl(game, game.config.battle); + this.cam = new GameCamera(ctrl.map.getWidth(), ctrl.map.getHeight(), ZOOM_OUT_MAX, ZOOM_IN_MAX, game.hudCorrection, FIXED_HUD); + + if (DEBUG) this.debugShapes = new ShapeRenderer(); + + Gdx.input.setInputProcessor(getMultiplexer()); + } + + + private InputMultiplexer getMultiplexer() + { + final InputMultiplexer multiplexer = new InputMultiplexer(); + multiplexer.addProcessor(new GestureDetector(new GestureAdapter() { + @Override + public boolean zoom(float initialDistance, float distance) + { + if (initialDistance > distance) + cam.zoom(ZOOM_GESTURE_FACTOR); + else + cam.zoom(-ZOOM_GESTURE_FACTOR); + ctrl.hud.resize(cam.getHudLeft(), cam.getHudBottom(), cam.getHudWidth(), cam.getHudHeight()); + blocked = true; + inputDelay = INPUT_DELAY; + return true; + } + })); + multiplexer.addProcessor(new InputAdapter() { + @Override + public boolean touchDragged(int x, int y, int pointer) + { + dragged += 1; + cam.translate((dragPos.x - x), (dragPos.y - y)); + dragPos.set(x, y); + return true; + } + @Override + public boolean touchDown(int x, int y, int pointer, int button) + { + if (blocked) return true; + if (button == Input.Buttons.LEFT) { + dragPos.set(x, y); + cam.unproject(x, y, ctrl.mapTouch); + cam.unprojectHud(x, y, ctrl.hudTouch); + ctrl.touchDown(); + } + return true; + } + @Override + public boolean touchUp(int x, int y, int pointer, int button) + { + if (blocked) return true; + if (dragged > DRAG_THRESHOLD) { + dragged = 0; + return true; + } + dragged = 0; + if (button == Input.Buttons.LEFT) { + cam.unproject(x, y, ctrl.mapTouch); + cam.unprojectHud(x, y, ctrl.hudTouch); + ctrl.touchUp(); + } + return true; + } + @Override + public boolean scrolled(int amount) + { + cam.zoom(amount * ZOOM_SCROLL_FACTOR); + ctrl.hud.resize(cam.getHudLeft(), cam.getHudBottom(), cam.getHudWidth(), cam.getHudHeight()); + return true; + } + }); + + return multiplexer; + } + + @Override + public void render(float delta) + { + if (inputDelay > 0f) { + inputDelay -= delta; + if (inputDelay <= 0f) + blocked = false; + } + + ctrl.hud.animate(delta); + ctrl.map.animate(delta); + + Gdx.gl.glClearColor(0, 0, 0, 1); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + + // cam.update(); + cam.applyMapViewport(); + batch.setProjectionMatrix(cam.combined); + batch.begin(); + ctrl.map.draw(batch); + batch.end(); + + + if (DEBUG) { + Gdx.gl.glEnable(GL20.GL_BLEND); + debugShapes.setAutoShapeType(true); + debugShapes.setProjectionMatrix(cam.combined); + debugShapes.begin(); + ctrl.map.drawDebug(debugShapes); + debugShapes.end(); + } + + cam.applyHudViewport(); + batch.setProjectionMatrix(cam.getHudMatrix()); + batch.begin(); + ctrl.hud.draw(batch, DEBUG); + batch.end(); + + if (DEBUG) { + Gdx.gl.glEnable(GL20.GL_BLEND); + debugShapes.setAutoShapeType(true); + debugShapes.setProjectionMatrix(cam.getHudMatrix()); + debugShapes.begin(); + debugShapes.rect(cam.getHudLeft(), cam.getHudBottom(), cam.getHudWidth(), cam.getHudHeight()); + ctrl.hud.drawDebug(debugShapes); + debugShapes.end(); + } + } + + @Override + public void resize(int width, int height) + { + // RustAndDust.debug("GameScreen", "resize (" + width + "," + height + ")"); + cam.updateViewport(width, height); + ctrl.hud.resize(cam.getHudLeft(), cam.getHudBottom(), cam.getHudWidth(), cam.getHudHeight()); + } + + @Override + public void dispose() + { + // RustAndDust.debug("GameScreen", "dispose()"); + batch.dispose(); + ctrl.dispose(); + if (DEBUG) debugShapes.dispose(); + } + + @Override + public void show() + { + // RustAndDust.debug("GameScreen", "show()"); + } + + @Override + public void hide() + { + // RustAndDust.debug("GameScreen", "hide()"); + } + + @Override + public void pause() + { + // RustAndDust.debug("GameScreen", "pause()"); + } + + @Override + public void resume() + { + // RustAndDust.debug("GameScreen", "resume()"); + } +} diff --git a/core/src/ch/asynk/rustanddust/screens/MenuCamera.java b/core/src/ch/asynk/rustanddust/screens/MenuCamera.java new file mode 100644 index 0000000..be8341a --- /dev/null +++ b/core/src/ch/asynk/rustanddust/screens/MenuCamera.java @@ -0,0 +1,112 @@ +package ch.asynk.rustanddust.screens; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Rectangle; + +public class MenuCamera extends OrthographicCamera +{ + private static final float ZEROF = 0.01f; + + private float virtualAspect; + private final Rectangle virtual; + private final Rectangle window; + private int hudLeft; + private int hudBottom; + private int hudCorrection; + + private Matrix4 uiMatrix; + private Matrix4 uiInvProjMatrix; + + public MenuCamera(int cx, int cy, int width, int height, int hudCorrection) + { + super(width, height); + this.virtual = new Rectangle(); + this.virtual.set(cx, cy, width, height); + this.virtualAspect = (virtual.width / virtual.height); + this.window = new Rectangle(); + this.window.set(0, 0, 0, 0); + this.position.set(virtual.x, virtual.y, 0f); + this.hudLeft = 0; + this.hudBottom = 0; + this.hudCorrection = hudCorrection; + + this.uiMatrix = new Matrix4(); + this.uiInvProjMatrix = new Matrix4(); + } + + public void updateViewport(int screenWidth, int screenHeight) + { + float aspect = (screenWidth / (float) screenHeight); + float diff = (virtualAspect - aspect); + + if (diff < -ZEROF) { + viewportWidth = (virtual.height * aspect); + viewportHeight = virtual.height; + } else if (diff > ZEROF) { + viewportWidth = virtual.width; + viewportHeight = (virtual.width / aspect); + } + + window.width = screenWidth; + window.height = screenHeight; + hudLeft = hudCorrection; + hudBottom = (int) (hudLeft / aspect); + + Gdx.gl.glViewport((int)window.x, (int)window.y, (int)window.width, (int)window.height); + + update(true); + + uiMatrix.setToOrtho2D(getHudLeft(), getHudBottom(), getHudWidth(), getHudHeight()); + uiInvProjMatrix.set(uiMatrix); + Matrix4.inv(uiInvProjMatrix.val); + } + + public float getScreenWidth() + { + return window.width; + } + + public float getScreenHeight() + { + return window.height; + } + + public int getHudLeft() + { + return hudLeft; + } + + public int getHudBottom() + { + return hudBottom; + } + + public int getHudWidth() + { + return (int) window.width - (2 * getHudLeft()); + } + + public int getHudHeight() + { + return (int) window.height - (2 * getHudBottom()); + } + + public void uiUnproject(float x, float y, Vector3 v) + { + x = x - window.x; + y = Gdx.graphics.getHeight() - y - 1; + y = y - window.y; + v.x = (2 * x) / window.width - 1; + v.y = (2 * y) / window.height - 1; + v.z = 2 * v.z - 1; + v.prj(uiInvProjMatrix); + } + + public Matrix4 uiCombined() + { + return uiMatrix; + } +} diff --git a/core/src/ch/asynk/rustanddust/screens/MenuScreen.java b/core/src/ch/asynk/rustanddust/screens/MenuScreen.java new file mode 100644 index 0000000..0ca8a63 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/screens/MenuScreen.java @@ -0,0 +1,258 @@ +package ch.asynk.rustanddust.screens; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Screen; +import com.badlogic.gdx.InputAdapter; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g2d.Sprite; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.Interpolation; + +import ch.asynk.rustanddust.RustAndDust; +import ch.asynk.rustanddust.ui.Position; +import ch.asynk.rustanddust.menu.MainMenu; +import ch.asynk.rustanddust.menu.OptionsMenu; +import ch.asynk.rustanddust.menu.ScenariosMenu; +import ch.asynk.rustanddust.menu.TutorialsMenu; + +public class MenuScreen implements Screen +{ + private final RustAndDust game; + + private final int OFFSET = 20; + private final int V_WIDTH = 1600; + private final int V_HEIGHT = 1125; + private final int V_CENTER_X = 1000; + private final int V_CENTER_Y = 890; + + private float percent; + private float delay = 0.0f; + private float dx; + private float dy; + private int[] xPath = { 369, 558, 747, 936, 1125, 1030, 936, 1125, 1314, 1408, 1597}; + private int[] yPath = { 565, 565, 565, 565, 565, 729, 892, 892, 892, 1056, 1056}; + private int n = xPath.length; + + private boolean ready; + private boolean gameAssetsLoading; + private Texture bg; + + private Sprite unit; + private Sprite move; + private Sprite from; + private Sprite to; + private Sprite geFlag; + private Sprite usFlag; + + private MainMenu mainMenu; + private OptionsMenu optionsMenu; + private ScenariosMenu scenariosMenu; + private TutorialsMenu tutorialsMenu; + + private final MenuCamera camera; + private final SpriteBatch batch; + private Vector3 touch = new Vector3(); + + public MenuScreen(final RustAndDust game) + { + this.game = game; + this.batch = new SpriteBatch(); + + float width = Gdx.graphics.getWidth(); + float height = Gdx.graphics.getHeight(); + + this.camera = new MenuCamera(V_CENTER_X, V_CENTER_Y, V_WIDTH, V_HEIGHT, game.hudCorrection); + + this.gameAssetsLoading = false; + + this.bg = game.manager.get("data/map_a.png", Texture.class); + + this.unit = new Sprite(game.menuAtlas.findRegion("unit")); + this.move = new Sprite(game.menuAtlas.findRegion("move")); + this.from = new Sprite(game.menuAtlas.findRegion("from")); + this.to = new Sprite(game.menuAtlas.findRegion("to")); + this.usFlag = new Sprite(game.menuAtlas.findRegion("us-flag")); + this.geFlag = new Sprite(game.menuAtlas.findRegion("ge-flag")); + + this.mainMenu = new MainMenu(game.fontB, game.uiAtlas); + this.optionsMenu = new OptionsMenu(game, game.fontB, game.uiAtlas); + this.scenariosMenu = new ScenariosMenu(game, game.fontB, game.uiAtlas); + this.tutorialsMenu = new TutorialsMenu(game, game.fontB, game.uiAtlas); + + this.game.config.battle = null; + + Gdx.input.setInputProcessor(new InputAdapter() { + @Override + public boolean touchDown(int x, int y, int pointer, int button) + { + camera.uiUnproject(x, y, touch); + return hit(touch.x, touch.y); + } + }); + } + + private boolean hit(float x, float y) + { + if (mainMenu.hit(x, y)) { + mainMenu.visible = false; + showNextMenu(); + return true; + } else if (optionsMenu.hit(x, y)) { + mainMenu.visible = true; + optionsMenu.visible = false; + return true; + } else if (scenariosMenu.hit(x, y)) { + mainMenu.visible = true; + scenariosMenu.visible = false; + if (scenariosMenu.launch) + startLoading(); + return true; + } else if (tutorialsMenu.hit(x, y)) { + mainMenu.visible = true; + tutorialsMenu.visible = false; + return true; + } + + return false; + } + + private void showNextMenu() + { + MainMenu.Items item = mainMenu.getMenu(); + + if (item == MainMenu.Items.OPTIONS) + optionsMenu.visible = true; + else if (item == MainMenu.Items.SCENARIOS) + scenariosMenu.visible = true; + else if (item == MainMenu.Items.TUTORIALS) + tutorialsMenu.visible = true; + } + + private void startLoading() + { + mainMenu.visible = false; + game.loadGameAssets(); + gameAssetsLoading = true; + } + + private void gameAssetsLoadingCompleted() + { + RustAndDust.debug("LoadScreen", "assets ready : " + (Gdx.app.getJavaHeap()/1024.0f) + "KB"); + game.switchToGame(); + dispose(); + } + + @Override + public void render(float delta) + { + float x = xPath[0]; + float y = yPath[0]; + if (gameAssetsLoading) { + if (game.manager.update()) { + delay += delta; + if (delay >= 0.6f) + gameAssetsLoadingCompleted(); + } + + percent = Interpolation.linear.apply(percent, game.manager.getProgress(), 0.1f); + int idx = (int) (percent * 10); + float fraction = ((percent * 100 ) % 10 / 10); + x = (xPath[idx] + ((xPath[idx + 1] - xPath[idx]) * fraction)); + y = (yPath[idx] + ((yPath[idx + 1] - yPath[idx]) * fraction)); + } + + Gdx.gl.glClearColor(0, 0, 0, 1); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + + batch.setProjectionMatrix(camera.combined); + batch.begin(); + batch.draw(bg, 0, 0); + from.draw(batch); + to.draw(batch); + usFlag.draw(batch); + geFlag.draw(batch); + for (int i = 1; i < (n - 1); i++) + drawCentered(batch, move, xPath[i], yPath[i]); + drawCentered(batch, unit, (int) (x + dx), (int) (y + dy)); + batch.end(); + + batch.setProjectionMatrix(camera.uiCombined()); + batch.begin(); + mainMenu.draw(batch); + optionsMenu.draw(batch); + scenariosMenu.draw(batch); + tutorialsMenu.draw(batch); + batch.end(); + } + + private void drawCentered(SpriteBatch batch, TextureRegion region, int x, int y) + { + batch.draw(region, (x - (region.getRegionWidth() / 2f)), (y - (region.getRegionHeight() / 2f))); + } + + private void setCenteredPosition(Sprite sprite, int x, int y) + { + sprite.setPosition((x - (sprite.getWidth() / 2f)), (y - (sprite.getHeight() / 2f))); + } + + private void update(int width, int height) + { + camera.updateViewport(width, height); + Position.update(camera.getHudLeft(), camera.getHudBottom(), camera.getHudWidth(), camera.getHudHeight()); + + setCenteredPosition(from, xPath[0], yPath[0]); + setCenteredPosition(to, xPath[n - 1], yPath[n - 1]); + setCenteredPosition(usFlag, xPath[0], yPath[0]); + setCenteredPosition(geFlag, xPath[n - 1], yPath[n - 1]); + + mainMenu.setPosition(); + optionsMenu.setPosition(); + scenariosMenu.setPosition(); + tutorialsMenu.setPosition(); + } + + @Override + public void resize(int width, int height) + { + update(width, height); + } + + @Override + public void dispose() + { + mainMenu.dispose(); + optionsMenu.dispose(); + scenariosMenu.dispose(); + tutorialsMenu.dispose(); + } + + @Override + public void show() + { + int width = (int) Gdx.graphics.getWidth(); + int height = (int) Gdx.graphics.getHeight(); + update(width, height); + } + + @Override + public void hide() + { + // RustAndDust.debug("MenuScreen", "hide()"); + } + + @Override + public void pause() + { + // RustAndDust.debug("MenuScreen", "pause()"); + } + + @Override + public void resume() + { + // RustAndDust.debug("MenuScreen", "resume()"); + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/Bg.java b/core/src/ch/asynk/rustanddust/ui/Bg.java new file mode 100644 index 0000000..cac3ddc --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/Bg.java @@ -0,0 +1,28 @@ +package ch.asynk.rustanddust.ui; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; + +public class Bg extends Widget +{ + private TextureRegion region; + + public Bg(TextureRegion region) + { + super(); + this.region = region; + setPosition(0, 0, region.getRegionWidth(), region.getRegionHeight()); + } + + @Override + public void dispose() + { + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + batch.draw(region, rect.x, rect.y, rect.width, rect.height); + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/Label.java b/core/src/ch/asynk/rustanddust/ui/Label.java new file mode 100644 index 0000000..6a6d809 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/Label.java @@ -0,0 +1,72 @@ +package ch.asynk.rustanddust.ui; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; + +public class Label extends Widget +{ + private BitmapFont font; + private GlyphLayout layout; + private float dx; + private float dy; + protected String text; + + public Label(BitmapFont font) + { + this(font, 0f); + } + + public Label(BitmapFont font, float padding) + { + this(font, padding, Position.MIDDLE_CENTER); + } + + public Label(BitmapFont font, float padding, Position position) + { + super(); + this.font = font; + this.padding = padding; + this.position = position; + this.layout = new GlyphLayout(); + } + + @Override + public void dispose() + { + } + + @Override + public void translate(float dx, float dy) + { + setPosition((rect.x + dx), (rect.y + dy)); + } + + @Override + public void setPosition(float x, float y) + { + this.layout.setText(font, (text == null) ? "" : text); + setPosition(x, y, (layout.width + (2 * padding)), (layout.height + (2 * padding))); + this.dx = (x + padding); + this.dy = (y + padding + layout.height); + } + + public void write(String text) + { + this.text = text; + setPosition(position); + } + + public void write(String text, float x, float y) + { + this.text = text; + setPosition(x, y); + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + font.draw(batch, layout, dx, dy); + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/LabelImage.java b/core/src/ch/asynk/rustanddust/ui/LabelImage.java new file mode 100644 index 0000000..0fb6ecb --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/LabelImage.java @@ -0,0 +1,72 @@ +package ch.asynk.rustanddust.ui; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +public class LabelImage extends Bg +{ + private Label label; + + public LabelImage(TextureRegion region, BitmapFont font) + { + this(region, font, 0f); + } + + public LabelImage(TextureRegion region, BitmapFont font, float padding) + { + this(region, font, padding, Position.MIDDLE_CENTER); + } + + public LabelImage(TextureRegion region, BitmapFont font, float padding, Position position) + { + super(region); + this.label = new Label(font, padding, position); + } + + @Override + public void dispose() + { + label.dispose(); + } + + @Override + public void translate(float dx, float dy) + { + super.translate(dx, dy); + label.translate(dx, dy); + } + + public void setPosition(float x, float y) + { + super.setPosition(x, y); + label.setPosition(x, y); + } + + public void setLabelPosition(Position position) + { + label.setPosition(position, this); + } + + public void write(String text) + { + this.label.write(text); + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + super.draw(batch); + label.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer shapes) + { + if (!visible) return; + super.drawDebug(shapes); + label.drawDebug(shapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/LabelStack.java b/core/src/ch/asynk/rustanddust/ui/LabelStack.java new file mode 100644 index 0000000..92933e3 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/LabelStack.java @@ -0,0 +1,72 @@ +package ch.asynk.rustanddust.ui; + +import java.util.ArrayDeque; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; + +import ch.asynk.rustanddust.engine.gfx.Animation; + +public class LabelStack extends Label implements Animation +{ + class MsgInfo + { + String text; + float duration; + Position position; + MsgInfo(String text, float duration, Position position) + { + this.text = text; + this.duration = duration; + this.position = position; + } + } + + private float duration; + private float elapsed; + private ArrayDeque<MsgInfo> stack; + + public LabelStack(BitmapFont font, float padding) + { + super(font, padding); + this.visible = false; + this.stack = new ArrayDeque<MsgInfo>(); + } + + public void pushWrite(String text, float duration, Position position) + { + if (visible) + stack.push(new MsgInfo(text, duration, position)); + else + write(text, duration, position); + } + + public void write(String text, float duration, Position position) + { + this.position = position; + write(text, duration); + } + + public void write(String text, float duration) + { + this.duration = duration; + this.visible = true; + this.elapsed = 0f; + write(text); + } + + @Override + public boolean animate(float delta) + { + if (!visible) return true; + elapsed += delta; + if (elapsed >= duration) { + visible = false; + if (stack.size() > 0) { + MsgInfo info = stack.pop(); + write(info.text, info.duration, info.position); + } + } + return false; + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/Menu.java b/core/src/ch/asynk/rustanddust/ui/Menu.java new file mode 100644 index 0000000..2fe93a7 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/Menu.java @@ -0,0 +1,93 @@ +package ch.asynk.rustanddust.ui; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.NinePatch; + +public class Menu extends Patch +{ + public static int PADDING = 40; + public static int VSPACING = 8; + + protected Label []labels; + + public interface MenuItem + { + public int last(); + public int i(); + }; + + protected MenuItem menuItem; + + public Menu(MenuItem menuItem, BitmapFont font, NinePatch ninePatch) + { + super(ninePatch); + this.menuItem = menuItem; + this.labels = new Label[menuItem.last()]; + for (int i = 0; i< menuItem.last(); i ++) + labels[i] = new Label(font, 10); + } + + protected Label label(MenuItem m) + { + return labels[m.i()]; + } + + protected float widestLabel() + { + float w = 0f; + for (int i = 0; i< menuItem.last(); i ++) { + float t = labels[i].getWidth(); + if (t> w) + w = t; + } + return w; + } + + protected float highestLabel() + { + float h = 0f; + for (int i = 0; i< menuItem.last(); i ++) { + float t = labels[i].getHeight(); + if (t> h) + h = t; + } + return h; + } + + public void setPosition() + { + float lh = highestLabel(); + float h = ((menuItem.last() * lh) + (2 * PADDING) + ((menuItem.last() - 1) * VSPACING)); + float w = (widestLabel() + (2 * PADDING)); + float x = position.getX(w); + float y = position.getY(h); + setPosition(x, y, w, h); + + y += PADDING; + x += PADDING; + float dy = (VSPACING + lh); + + for (int i = 0; i< menuItem.last(); i ++) { + labels[i].setPosition(x, y); + y += dy; + } + } + + @Override + public void dispose() + { + super.dispose(); + for (int i = 0; i < menuItem.last(); i ++) + labels[i].dispose(); + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + super.draw(batch); + for (int i = 0; i < menuItem.last(); i ++) + labels[i].draw(batch); + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/Msg.java b/core/src/ch/asynk/rustanddust/ui/Msg.java new file mode 100644 index 0000000..e1e7c13 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/Msg.java @@ -0,0 +1,79 @@ +package ch.asynk.rustanddust.ui; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +public class Msg extends Patch +{ + private LabelStack label; + + public Msg(BitmapFont font, TextureAtlas atlas) + { + super(atlas.createPatch("typewriter")); + label = new LabelStack(font, 20f); + } + + @Override + public void dispose() + { + super.dispose(); + label.dispose(); + } + + public void updatePosition() + { + if (!visible) return; + float dx = (position.getX(rect.width) - rect.x); + float dy = (position.getY(rect.height) - rect.y); + translate(dx, dy); + label.translate(dx, dy); + } + + public void write(String text, float duration) + { + label.write(text, duration); + resize(); + } + + public void write(String text, float duration, Position position) + { + this.position = position; + label.write(text, duration, position); + resize(); + } + + public void pushWrite(String text, float duration, Position position) + { + this.position = position; + label.pushWrite(text, duration, position); + resize(); + } + + private void resize() + { + setPosition(label.getX(), label.getY(), label.getWidth(), label.getHeight()); + } + + public boolean animate(float delta) + { + return label.animate(delta); + } + + @Override + public void draw(Batch batch) + { + if (!label.visible) return; + super.draw(batch); + label.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer shapes) + { + if (!label.visible) return; + super.drawDebug(shapes); + label.drawDebug(shapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/OkCancel.java b/core/src/ch/asynk/rustanddust/ui/OkCancel.java new file mode 100644 index 0000000..f30a65b --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/OkCancel.java @@ -0,0 +1,115 @@ +package ch.asynk.rustanddust.ui; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +public class OkCancel extends Patch +{ + public static int PADDING = 20; + public static int VSPACING = 10; + public static int HSPACING = 10; + + public boolean ok; + protected Label label; + protected Bg okBtn; + protected Bg cancelBtn; + + public OkCancel(BitmapFont font, TextureAtlas atlas) + { + super(atlas.createPatch("typewriter")); + this.label = new Label(font); + this.okBtn = new Bg(atlas.findRegion("ok")); + this.cancelBtn = new Bg(atlas.findRegion("cancel")); + this.visible = false; + } + + public void updatePosition() + { + if (!visible) return; + float dx = (position.getX(rect.width) - rect.x); + float dy = (position.getY(rect.height) - rect.y); + translate(dx, dy); + label.translate(dx, dy); + okBtn.translate(dx, dy); + cancelBtn.translate(dx, dy); + } + + public void show(String msg) + { + show(msg, Position.MIDDLE_CENTER); + } + + public void show(String msg, Position position) + { + label.write(msg); + + float height = (label.getHeight() + okBtn.getHeight() + (2 * PADDING) + (2 * VSPACING)); + float width = (label.getWidth() + (2 * PADDING)); + float w2 = (okBtn.getWidth() + cancelBtn.getWidth() + (2 * PADDING) + (1 * HSPACING)); + if (w2 > width) + width = w2; + float x = position.getX(width); + float y = position.getY(height); + setPosition(x, y, width, height); + + okBtn.setPosition((x + width - okBtn.getWidth() - PADDING), (y + PADDING)); + cancelBtn.setPosition((x + PADDING), okBtn.getY()); + label.setPosition((x + PADDING), (y + PADDING + okBtn.getHeight() + VSPACING)); + cancelBtn.visible = true; + visible = true; + ok = false; + } + + public void noCancel() + { + cancelBtn.visible = false; + } + + @Override + public boolean hit(float x, float y) + { + if (!cancelBtn.visible && super.hit(x, y)) { + ok = true; + return true; + } + if (okBtn.hit(x, y)) { + ok = true; + return true; + } else if (cancelBtn.hit(x, y)) { + ok = false; + return true; + } + return false; + } + + @Override + public void dispose() + { + super.dispose(); + label.dispose(); + okBtn.dispose(); + cancelBtn.dispose(); + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + super.draw(batch); + label.draw(batch); + okBtn.draw(batch); + cancelBtn.draw(batch); + } + + @Override + public void drawDebug(ShapeRenderer shapes) + { + if (!visible) return; + super.drawDebug(shapes); + label.drawDebug(shapes); + okBtn.drawDebug(shapes); + cancelBtn.drawDebug(shapes); + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/Patch.java b/core/src/ch/asynk/rustanddust/ui/Patch.java new file mode 100644 index 0000000..acff791 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/Patch.java @@ -0,0 +1,28 @@ +package ch.asynk.rustanddust.ui; + +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.NinePatch; + +public class Patch extends Widget +{ + private NinePatch patch; + + public Patch(NinePatch patch) + { + super(); + this.patch = patch; + setPosition(0, 0, patch.getTotalWidth(), patch.getTotalHeight()); + } + + @Override + public void dispose() + { + } + + @Override + public void draw(Batch batch) + { + if (!visible) return; + patch.draw(batch, rect.x, rect.y, rect.width, rect.height); + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/Position.java b/core/src/ch/asynk/rustanddust/ui/Position.java new file mode 100644 index 0000000..d8c6096 --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/Position.java @@ -0,0 +1,239 @@ +package ch.asynk.rustanddust.ui; + +import com.badlogic.gdx.Gdx; +import ch.asynk.rustanddust.game.Hud; + +public enum Position +{ + TOP_LEFT, + TOP_RIGHT, + TOP_CENTER, + MIDDLE_LEFT, + MIDDLE_RIGHT, + MIDDLE_CENTER, + BOTTOM_LEFT, + BOTTOM_RIGHT, + BOTTOM_CENTER; + + public Position verticalMirror() + { + Position p = this; + switch(this) { + case TOP_LEFT: + p = TOP_RIGHT; + break; + case MIDDLE_LEFT: + p = MIDDLE_RIGHT; + break; + case BOTTOM_LEFT: + p = BOTTOM_RIGHT; + break; + case TOP_RIGHT: + p = TOP_LEFT; + break; + case MIDDLE_RIGHT: + p = MIDDLE_LEFT; + break; + case BOTTOM_RIGHT: + p = BOTTOM_LEFT; + break; + } + return p; + } + + public Position horizontalMirror() + { + Position p = this; + switch(this) { + case TOP_LEFT: + p = BOTTOM_LEFT; + break; + case TOP_CENTER: + p = BOTTOM_CENTER; + break; + case TOP_RIGHT: + p = BOTTOM_RIGHT; + break; + case BOTTOM_LEFT: + p = TOP_LEFT; + break; + case BOTTOM_CENTER: + p = TOP_CENTER; + break; + case BOTTOM_RIGHT: + p = TOP_RIGHT; + break; + } + return p; + } + + public boolean isLeft() + { + boolean r = false; + switch(this) { + case TOP_LEFT: + case MIDDLE_LEFT: + case BOTTOM_LEFT: + r = true; + break; + default: + r = false; + break; + } + return r; + } + + public boolean isRight() + { + boolean r = false; + switch(this) { + case TOP_RIGHT: + case MIDDLE_RIGHT: + case BOTTOM_RIGHT: + r = true; + break; + default: + r = false; + break; + } + return r; + } + + public boolean isCenter() + { + boolean r = false; + switch(this) { + case TOP_CENTER: + case MIDDLE_CENTER: + case BOTTOM_CENTER: + r = true; + break; + default: + r = false; + break; + } + return r; + } + + private static int hudLeft = 0; + private static int hudBottom = 0; + private static int hudWidth = Gdx.graphics.getWidth(); + private static int hudHeight = Gdx.graphics.getHeight(); + + public static void update(int width, int height) + { + update(0, 0, width, height); + } + + public static void update(int left, int bottom, int width, int height) + { + hudLeft = left; + hudBottom = bottom; + hudWidth = width; + hudHeight = height; + } + + public float getX(float width) + { + float x = hudLeft; + switch(this) { + case TOP_LEFT: + case MIDDLE_LEFT: + case BOTTOM_LEFT: + x += Hud.OFFSET; + break; + case TOP_CENTER: + case MIDDLE_CENTER: + case BOTTOM_CENTER: + x += ((hudWidth - width) / 2); + break; + case TOP_RIGHT: + case MIDDLE_RIGHT: + case BOTTOM_RIGHT: + x += (hudWidth - width - Hud.OFFSET); + break; + default: + x += ((hudWidth - width) / 2); + break; + } + return x; + } + + public float getY(float height) + { + float y = hudBottom; + switch(this) { + case TOP_LEFT: + case TOP_CENTER: + case TOP_RIGHT: + y += (hudHeight - height - Hud.OFFSET); + break; + case MIDDLE_LEFT: + case MIDDLE_CENTER: + case MIDDLE_RIGHT: + y += ((hudHeight - height) / 2); + break; + case BOTTOM_LEFT: + case BOTTOM_CENTER: + case BOTTOM_RIGHT: + y += Hud.OFFSET; + break; + default: + y += ((hudHeight - height) / 2); + break; + } + return y; + } + + public float getX(Widget widget, float width) + { + float x = 0; + switch(this) { + case TOP_LEFT: + case MIDDLE_LEFT: + case BOTTOM_LEFT: + x = widget.getX(); + break; + case TOP_CENTER: + case MIDDLE_CENTER: + case BOTTOM_CENTER: + x = (widget.getX() + ((widget.getWidth() - width) / 2)); + break; + case TOP_RIGHT: + case MIDDLE_RIGHT: + case BOTTOM_RIGHT: + x = (widget.getX() + widget.getWidth() - width); + break; + default: + x = (widget.getX() + ((widget.getWidth() - width) / 2)); + break; + } + return x; + } + + public float getY(Widget widget, float height) + { + float y = 0; + switch(this) { + case TOP_LEFT: + case TOP_CENTER: + case TOP_RIGHT: + y = (widget.getY() + widget.getHeight() - height); + break; + case MIDDLE_LEFT: + case MIDDLE_CENTER: + case MIDDLE_RIGHT: + y = (widget.getY() + ((widget.getHeight() - height) / 2)); + break; + case BOTTOM_LEFT: + case BOTTOM_CENTER: + case BOTTOM_RIGHT: + y = widget.getY(); + break; + default: + y = (widget.getY() + ((widget.getHeight() - height) / 2)); + break; + } + return y; + } +} diff --git a/core/src/ch/asynk/rustanddust/ui/Widget.java b/core/src/ch/asynk/rustanddust/ui/Widget.java new file mode 100644 index 0000000..4ae8afd --- /dev/null +++ b/core/src/ch/asynk/rustanddust/ui/Widget.java @@ -0,0 +1,93 @@ +package ch.asynk.rustanddust.ui; + +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import ch.asynk.rustanddust.engine.gfx.Drawable; + +public abstract class Widget implements Disposable, Drawable +{ + public boolean blocked; + public boolean visible; + protected float padding; + protected Rectangle rect; + protected Position position; + protected Widget parent; + + protected Widget() + { + this.parent = null; + this.blocked = false; + this.visible = true; + this.padding = 0f; + this.rect = new Rectangle(0, 0, 0, 0); + this.position = Position.MIDDLE_CENTER; + } + + public float getX() { return rect.x; } + public float getY() { return rect.y; } + public float getWidth() { return rect.width; } + public float getHeight() { return rect.height; } + + public void translate(float dx, float dy) + { + rect.x += dx; + rect.y += dy; + } + + public void setPosition(Rectangle r) + { + rect.set(r); + } + + public void setPosition(float x, float y) + { + rect.x = x; + rect.y = y; + } + + public void setPosition(float x, float y, float w, float h) + { + rect.set(x, y, w, h); + } + + public void setPosition(Position position) + { + this.position = position; + setParent(this.parent); + } + + public void setPosition(Position position, Widget parent) + { + this.position = position; + setParent(parent); + } + + public void setParent(Widget parent) + { + this.parent = parent; + if (parent == null) { + rect.x = position.getX(rect.width); + rect.y = position.getY(rect.height); + } else { + rect.x = position.getX(parent, rect.width); + rect.y = position.getY(parent, rect.height); + } + // might trigger something if overriden + setPosition(rect.x, rect.y); + } + + public boolean hit(float x, float y) + { + if (blocked || !visible) return false; + return rect.contains(x, y); + } + + @Override + public void drawDebug(ShapeRenderer shapes) + { + if (!visible) return; + shapes.rect(rect.x, rect.y, rect.width, rect.height); + } +} |