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); +    } +}  | 
