kyle.game.besiege.army.Army.java Source code

Java tutorial

Introduction

Here is the source code for kyle.game.besiege.army.Army.java

Source

/*******************************************************************************
 * Besiege
 * by Kyle Dhillon
 * Source Code available under a read-only license. Do not copy, modify, or distribute.
 ******************************************************************************/
package kyle.game.besiege.army;

import java.util.Stack;

import kyle.game.besiege.Assets;
import kyle.game.besiege.Destination;
import kyle.game.besiege.Faction;
import kyle.game.besiege.Kingdom;
import kyle.game.besiege.Map;
import kyle.game.besiege.Path;
import kyle.game.besiege.Point;
import kyle.game.besiege.Siege;
import kyle.game.besiege.battle.Battle;
import kyle.game.besiege.location.City;
import kyle.game.besiege.location.Location;
import kyle.game.besiege.location.Village;
import kyle.game.besiege.location.Location.LocationType;
import kyle.game.besiege.panels.BottomPanel;
import kyle.game.besiege.panels.Panel;
import kyle.game.besiege.party.Party;
import kyle.game.besiege.party.PartyType;
import kyle.game.besiege.voronoi.Center;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.utils.Array;

public class Army extends Actor implements Destination {
    private static final int UPDATE_POLYGON_FREQ = 100; // update polygon every x frames
    protected final static int SPEED_DISPLAY_FACTOR = 10; // what you multiply by to display speed
    private static final float SCALE_FACTOR = 600f; // smaller is bigger
    private static final float BASE_SPEED = 1;
    private static final float WAIT = 3; // 3 second wait after garrisoning
    private static final float scale = .6f;
    private static final float cityCollisionDistance = 25;
    private static final float battleCollisionDistance = 15;
    private static final float COLLISION_FACTOR = 10; // higher means must be closer
    public static final float ORIGINAL_SPEED_FACTOR = .10f;
    public static final int A_STAR_FREQ = 20; // army may only set new target every x frames
    private static final float SIZE_FACTOR = .025f; // amount that party size detracts from total speed
    private static final float BASE_LOS = 40;
    private static final int MAX_STACK_SIZE = 10;
    private static final float LOS_FACTOR = 4; // times troops in party
    private static final float momentumDecay = 6; // every N hours, momentum -= 1
    private static final int offset = 30;
    private static final String DEFAULT_TEXTURE = "Player";
    private static final double REPAIR_FACTOR = .5; // if a party gets below this many troops it will go to repair itself.

    private boolean mouseOver;
    protected boolean passive; // passive if true (won't attack) aggressive if false;

    private Kingdom kingdom; // parent actor, kingdom
    private String name;
    private Faction faction;

    private TextureRegion region;

    private float speed;
    public float speedFactor;
    private float lineOfSight;

    private PartyType partyType;
    private Party party;

    private int morale;
    private int momentum; //changes with recent events

    private int updatePolygon;

    protected int lastPathCalc;
    private boolean stopped;
    private boolean normalWaiting;
    private double waitUntil;// seconds goal
    public boolean forceWait;
    private boolean shouldRepair;

    private boolean startedRunning;

    private Location garrisonedIn;

    public boolean shouldEject; // useful for farmers, who don't need to F during nighttime.
    //   protected boolean isNoble;

    public enum ArmyType {
        PATROL, NOBLE, MERCHANT, BANDIT, FARMER, MILITIA
    }; // 3 for patrol, 

    public ArmyType type;

    private Battle battle;
    public float retreatCounter; // needed in battles
    private Siege siege;
    private Destination target;
    private Destination defaultTarget;
    //   private Location defaultTarget;
    protected Stack<Destination> targetStack;
    public Path path;
    private Army runFrom;
    public Destination runTo; // use for running
    public Array<Army> targetOf; // armies that have this army as a target
    public Center containing;
    private Array<Army> closeArmies;
    private Array<Center> closeCenters;

    Vector2 toTarget;

    private int currentHour; // used for decreasing momentum every hour
    public boolean playerTouched; // kinda parallel to location.playerIn

    public Army(Kingdom kingdom, String name, Faction faction, float posX, float posY, PartyType pt) {
        this.kingdom = kingdom;
        this.name = name;
        this.faction = faction;
        this.partyType = pt;

        if (pt != null)
            this.party = pt.generate();
        else
            this.party = new Party();

        this.speedFactor = ORIGINAL_SPEED_FACTOR;
        this.speed = calcSpeed();
        this.lineOfSight = calcLOS();

        this.morale = calcMorale();
        this.currentHour = Kingdom.getTotalHour();

        this.stopped = true;
        this.normalWaiting = false;
        this.waitUntil = 0;

        this.battle = null;
        this.siege = null;
        this.runFrom = null;
        this.garrisonedIn = null;
        this.shouldEject = true;

        this.lastPathCalc = 0;
        this.targetStack = new Stack<Destination>();
        this.path = new Path(this);
        this.targetOf = new Array<Army>();

        this.closeArmies = new Array<Army>();
        this.closeCenters = new Array<Center>();

        this.setPosition(posX, posY);
        this.setRotation(0);

        this.toTarget = new Vector2();

        setTextureRegion(DEFAULT_TEXTURE); // default texture

        playerTouched = false;

    }

    private void initializeBox() {
        this.setScale(calcScale());
        this.setWidth(region.getRegionWidth() * getScaleX());
        this.setHeight(region.getRegionHeight() * getScaleY());
        this.setOrigin(region.getRegionWidth() * getScaleX() / 2, region.getRegionWidth() * getScaleY() / 2);
    }

    @Override
    public void act(float delta) {

        if (this.lastPathCalc > 0)
            this.lastPathCalc--;
        //      setLineOfSight();
        // Player's Line of Sight:
        if (kingdom.getMapScreen().losOn) {
            if (Kingdom.distBetween(this, kingdom.getPlayer()) > kingdom.getPlayer().getLineOfSight())
                this.setVisible(false);
            else if (!this.isGarrisoned() && !this.isInBattle())
                this.setVisible(true);
        } else if (!this.isGarrisoned() && !this.isInBattle())
            this.setVisible(true);

        if (!kingdom.isPaused()) {
            playerTouched = false; // only can be selected when game is paused;
        }

        lineOfSight = calcLOS();
        setMorale(calcMorale()); // update morale
        setScale(calcScale()); // set scale;

        // simple army control flow
        if (isForcedWaiting()) {
            wait(delta);
        } else {
            if (!isInBattle()) {
                if (isGarrisoned())
                    garrisonAct(delta);
                else {
                    setSpeed(calcSpeed());
                    detectNearby();
                    // int result = detectNearby();
                    //               if (result != 0)
                    //                  System.out.println(getName() + " detectNearby() = " + result); // 0 none, 1 run, 2 attack
                    if (isRunning()) {
                        run();
                    } else if (isWaiting())
                        wait(delta);
                    else if (isInSiege())
                        siegeAct(delta);
                    else {
                        uniqueAct();
                        if (!path.isEmpty()) {
                            path.travel();
                            if (targetLost())
                                nextTarget(); // forgot to do this before...
                        } else if (this.hasTarget()) {
                            //                     if (this.type == ArmyType.FARMER) System.out.println(getName() + " here"); 
                            detectCollision();
                        }
                    }
                }
            }
        }

        //      if (forceWait) { // forces player to wait
        //         wait(delta);
        //         if (!isWaiting())  
        //            forceWait = false;
        //      }
        //      else {
        //         if (!isGarrisoned()) {
        //            if (!isInSiege()) {
        //               if (shouldRepair() && !shouldRepair) shouldRepair = true;
        //               else if ((!shouldRepair() || defaultTarget == null) && shouldRepair) shouldRepair = false;
        //               if (shouldRepair && this.getTarget() != defaultTarget) this.setTarget(defaultTarget);
        //               if (!isInBattle()) {
        //                  setStopped(false);
        //                  setSpeed(calcSpeed());   // update speed
        //                  detectNearby();
        //                  if (isRunning()) {
        //                     run();
        //                     // wait(delta);
        //                  }
        //                  else {
        //                     if (isWaiting()) {
        //                        wait(delta);
        //                     }
        //                     else {
        //                        uniqueAct();
        //                        if (getTarget() != null) {
        //                           path.travel();
        //                           if (targetLost()) {
        //                              nextTarget();
        //                           }
        //                        }
        //                        else nextTarget();
        //                     }
        //                  }
        //               }
        //            }
        //            else if (isInSiege()) {
        //               //decide if should leave siege
        //               detectNearbyRunOnly();
        //            }
        //         }
        //         else if (isGarrisoned()) {
        //
        //            party.checkUpgrades();
        //            // if garrisoned and waiting, wait
        //            if (isWaiting()) {
        //               //               System.out.println(this.getName() + " waiting " + this.waitUntil);
        //               wait(delta);
        //            }
        //            // if garrisoned and patrolling, check if coast is clear
        //            else if (hasTarget() || type == ArmyType.NOBLE) {
        //               Army army = closestHostileArmy();
        //               if (army == null || !shouldRunFrom(army)) {
        //                  if (shouldEject) {
        //                     eject();
        //                     setTarget(null);
        //                  }
        //                  uniqueAct();
        //               }
        //            }
        //         }
        //      }
        party.act(delta);
        momentumDecay();
        //party.distributeExp(60);
    }

    public void uniqueAct() {
        //actions contained in extensions
    }

    @Override
    public void draw(SpriteBatch batch, float parentAlpha) {
        batch.draw(region, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(), 1, 1,
                getRotation());
        //if (mousedOver()) drawInfo(batch, parentAlpha);
    }

    public void drawCrest(SpriteBatch batch) {
        float size_factor = .4f;

        size_factor += .005 * this.party.getTotalSize();

        Color temp = batch.getColor();
        float zoom = getKingdom().getMapScreen().getCamera().zoom;
        zoom *= size_factor;

        Color clear_white = new Color();
        clear_white.b = 1;
        clear_white.r = 1;
        clear_white.g = 1;
        clear_white.a = .6f;
        batch.setColor(clear_white);
        batch.draw(this.getFaction().crest, getCenterX() - 14 * zoom, getCenterY() + 5 + 5 * zoom, 30 * zoom,
                45 * zoom);
        batch.setColor(temp);
    }

    public String getAction() {
        if (isInBattle())
            return "In battle";
        else if (forceWait)
            return "Regrouping (" + Panel.format(this.waitUntil - kingdom.clock() + "", 2) + ")";
        else if (isWaiting())
            return "Waiting";
        else if (isRunning())
            return "Running from " + getRunFrom().getName() + " (Speed: "
                    + Panel.format(getSpeed() * SPEED_DISPLAY_FACTOR + "", 2) + ")";
        //      else if (shouldRepair) return "SHOULD REPAIR";
        else if (isInSiege())
            return "Besieging " + siege.location.getName();
        else if (getTarget() != null && getTarget().getType() == 1)
            return "Travelling to " + getTarget().getName() + " (Speed: "
                    + Panel.format(getSpeed() * SPEED_DISPLAY_FACTOR + "", 2) + ")";
        else if (getTarget() != null && getTarget().getType() == 2)
            return "Following " + getTarget().getName() + " (Speed: "
                    + Panel.format(getSpeed() * SPEED_DISPLAY_FACTOR + "", 2) + ")";
        else
            return getUniqueAction();
    }

    public String getUniqueAction() {
        //contained in extensions;
        return "Travelling" + " (Speed: " + Panel.format(getSpeed() * SPEED_DISPLAY_FACTOR + "", 2) + ")";
    }

    public boolean detectCollision() {
        //      if (type == ArmyType.FARMER) System.out.println(getName() + " target  = " + target.getName());
        switch (target.getType()) {
        case 0: // point reached
            return detectPointCollision();
        case 1: // location reached
            return detectLocationCollision();
        case 2: // army reached
            return detectArmyCollision();
        case 4: // battle reached
            return detectBattleCollision();
        default:
            return false;
        }
    }

    public boolean detectPointCollision() {
        if (distToCenter(target) < 1) {
            target = null;
            setStopped(true);
            return true;
        }
        return false;
    }

    public boolean detectArmyCollision() {
        Army targetArmy = (Army) target;
        //      System.out.println("collision dist " + (getTroopCount() + targetArmy.getTroopCount())/COLLISION_FACTOR);
        if (distToCenter(targetArmy) < ((getTroopCount() + targetArmy.getTroopCount())) / COLLISION_FACTOR
                && !targetArmy.isGarrisoned()) {
            if (isAtWar(targetArmy))
                enemyArmyCollision(targetArmy);
            else
                friendlyArmyCollision(targetArmy);
            targetArmy.targetOf.removeValue(this, true);
            return true;
        }
        return false;
    }

    public void enemyArmyCollision(Army targetArmy) {
        if (targetArmy.getBattle() == null) {
            createBattleWith(targetArmy);
        } else {
            // join battle
            if (targetArmy.getBattle().shouldJoin(this) != 0) {
                targetArmy.getBattle().add(this);
                this.setBattle(targetArmy.getBattle());
            } else {
                this.nextTarget();
            }
        }
    }

    public void friendlyArmyCollision(Army targetArmy) {
        //follow
    }

    public void createBattleWith(Army targetArmy) {
        //      System.out.println(this.getName() + " creating battle");

        if (this == kingdom.getPlayer()) {
            BottomPanel.log("Attacking " + targetArmy.getName() + "! 1");

            //         getKingdom().getMapScreen().getSidePanel().setActiveBattle(b);
            //         getKingdom().getMapScreen().getSidePanel().setStay(true);
        } else if (targetArmy == kingdom.getPlayer()) {
            BottomPanel.log("Attacked by " + this.getName() + "!");
            //         getKingdom().getMapScreen().getSidePanel().setActiveBattle(b);
            //         getKingdom().getMapScreen().getSidePanel().setStay(true);
        } else {
            Battle b = new Battle(kingdom, this, targetArmy);
            this.setBattle(b);
            targetArmy.setBattle(b);
            kingdom.addBattle(b);
            kingdom.addActor(b);
        }
    }

    public boolean detectBattleCollision() {
        if (distToCenter(getTarget()) < battleCollisionDistance) {
            Battle targetBattle = (Battle) target;
            this.joinBattle(targetBattle);
            return true;
        }
        return false;
    }

    public void joinBattle(Battle battle) {
        if (this.party.player) {
            BottomPanel.log("player joining battle!");
        } else {
            if (battle == null) {
                System.out.println("joining null battle");
                return;
            }
            if (this.battle != null) {
                System.out.println("already in battle");
                return;
            }
            battle.add(this);
            this.setVisible(false);
            this.setBattle(battle);
        }
    }

    public boolean detectLocationCollision() {
        if (distToCenter(getTarget()) < cityCollisionDistance) {
            Location targetLocation = (Location) target;
            if (isAtWar(targetLocation)) {
                enemyLocationCollision(targetLocation);
                //            if (type == ArmyType.FARMER) System.out.println(getName() + " detectLocationCollision");
            } else
                friendlyLocationCollision(targetLocation);
            return true;
        }
        return false;
    }

    public void enemyLocationCollision(Location targetLocation) {
        if (!this.passive) {
            if (targetLocation.isVillage()) {
                raid((Village) targetLocation);
            } else {
                if (type == ArmyType.BANDIT)
                    this.nextTarget();
                else {
                    setStopped(true);
                    if (targetLocation.underSiege())
                        targetLocation.getSiege().add(this);
                    else {
                        targetLocation.beginSiege(this);
                    }
                }
            }
        }
    }

    public void friendlyLocationCollision(Location targetLocation) {
        //      System.out.println(getName() + " friendslyCollision");
        if (targetLocation != null)
            garrisonIn(targetLocation);
    }

    public void garrisonIn(Location targetCity) {
        //      System.out.println(getName() + " garrisoning");
        if (targetCity == null) {
            System.out.println("null targetcity");
            return;
        }
        // test to see if should garrison goes here

        targetCity.garrison(this);
        garrisonedIn = targetCity;

        setTarget(null);
        for (Army followedBy : this.targetOf) {
            if (followedBy.getTarget() == this)
                followedBy.nextTarget();
            followedBy.targetStack.remove(this);
        }

        // wait/pause AFTER garrisoning!
        if (party.player) {
            kingdom.setPaused(true);
            this.setWaiting(false);
        } else if (type == ArmyType.MERCHANT)
            waitFor(Merchant.MERCHANT_WAIT);
        else if (type != ArmyType.NOBLE)
            waitFor(WAIT); //arbitrary
    }

    /** do this while garrisoned
     * 
     * @param delta time elapsed since last frame
     */
    public void garrisonAct(float delta) {
        party.checkUpgrades();
        // if garrisoned and waiting, wait
        if (isWaiting()) {
            //               System.out.println(this.getName() + " waiting " + this.waitUntil);
            wait(delta);
        }
        // if garrisoned and patrolling, check if coast is clear
        else if (hasTarget() || type == ArmyType.NOBLE) {
            Army army = closestHostileArmy();
            // if Noble with faction containing only one city
            if (type == ArmyType.NOBLE) {
                if (this.getFaction().cities.size <= 1) {
                    // only eject for special reasons
                    if (army != null && shouldAttack(army)) {
                        setTarget(army);
                        eject();
                    }
                }

            } else if (army == null || !shouldRunFrom(army)) {
                if (shouldEject) {
                    eject();
                    setTarget(null);
                    //               System.out.println("ejecting " + this.getName() + " with no target");
                }
                uniqueAct();
            }
        }
    }

    /** do this while sieging
     * 
     * @param delta time elapsed since last frame
     */
    public void siegeAct(float delta) {

    }

    public void eject() {
        if (isGarrisoned())
            garrisonedIn.eject(this);
        else
            System.out.println("trying to eject from nothing");
    }

    // returns 0 if no army nearby, 1 if shouldRun (runFrom != null), and 2 if shouldAttack nearby army (target == army)
    public int detectNearby() {
        Army army = closestHostileArmy();
        //      if (this.type == ArmyType.MERCHANT) {
        //         if (army != null) System.out.println(getName() + " has cha " + army.getName());
        //         else System.out.println(getName() + " has cha null");
        //      }

        if (army != null) {
            if (shouldRunFrom(army) && runFrom != army) {
                runFrom(army);
                //            System.out.println(this.getName() + " starting to run from " + army.getName());
                return 1;
            } else if (!passive && shouldAttack(army) && (!hasTarget() || target != army)) {
                runFrom = null;
                if (this.isInSiege())
                    this.leaveSiege();
                setTarget(army);
                return 2;
            }
        }
        return 0;
    }

    //   public void detectNearbyRunOnly() {
    //      //naive approach (N^2)
    //      Army army = closestHostileArmy();
    //      if (army != null) {
    //         if (shouldRunFrom(army) && (!isRunning() || runFrom != army))  {
    //            if (isInSiege())
    //               endSiege();
    //            runFrom(army);
    //         }
    //      }
    //   }

    // returns closest army should run from, else closest army should attack, or null if no armies are close.
    public Army closestHostileArmy() {
        //      if (this.type == ArmyType.PATROL) System.out.println(getName() + " in closest hostile army"); 
        // can (slightly) optimize by maintaining close Centers until this army's center changes! TODO
        // commented for testing
        double closestDistance = Float.MAX_VALUE;
        Army currentArmy = null;
        boolean shouldRun = false; // true if should run from CHA, false otherwise

        // only within 2 levels of adjacent, can expand later
        closeArmies.clear();
        closeCenters.clear();

        if (containing != null) {
            // central one
            closeCenters.add(containing);
            for (Center levelOne : containing.neighbors) {
                if (!closeCenters.contains(levelOne, true)) // level one
                    closeCenters.add(levelOne);
                for (int i = 0; i < levelOne.neighbors.size(); i++) {
                    Center levelTwo = levelOne.neighbors.get(i);
                    if (!closeCenters.contains(levelTwo, true)) // level two
                        closeCenters.add(levelTwo);
                }
            }

            for (Center containing : closeCenters)
                if (containing.armies != null)
                    closeArmies.addAll(containing.armies);

            //         System.out.println("Total Armies length: " + kingdom.getArmies().size);
            //         if (this.type == ArmyType.PATROL) System.out.println(getName() + " CloseArmies Length: " + closeArmies.size);

            for (Army army : closeArmies) {
                if (this.distToCenter(army) < lineOfSight) {
                    // hostile troop
                    if (isAtWar(army)) {
                        if (shouldRunFrom(army)) {
                            if (this.distToCenter(army) < closestDistance) {
                                shouldRun = true;
                                closestDistance = this.distToCenter(army);
                                currentArmy = army;
                            }
                        } else if (!shouldRun) {
                            if (this.distToCenter(army) < closestDistance) {
                                closestDistance = this.distToCenter(army);
                                currentArmy = army;
                            }
                        }
                    }
                }
            }
            return currentArmy;
        } else {
            //         System.out.println(this.getName() + " containing = null");
            return null;
        }
    }

    public void updatePolygon() {
        if (updatePolygon == UPDATE_POLYGON_FREQ) {
            kingdom.updateArmyPolygon(this);
            updatePolygon = 0;
        } else
            updatePolygon++;
    }

    public void waitFor(double seconds) {
        normalWaiting = true;
        this.waitUntil = seconds + kingdom.clock();
    }

    public void wait(float delta) {
        if (kingdom.clock() >= waitUntil) {
            //         if (type == ArmyType.MERCHANT) System.out.println(getName() + "stopping wait");
            normalWaiting = false;
            waitUntil = 0;
            if (forceWait) {
                forceWait = false;
                //            this.stopped = false;
            }
        }
    }

    public void forceWait(float seconds) {
        this.forceWait = true;
        this.waitFor(seconds);
    }

    // should this army attack that army?
    public boolean shouldAttack(Army that) {
        if ((this.getTroopCount() - that.getTroopCount() >= 1) && (this.getTroopCount() <= that.getTroopCount() * 4)
                && (that.getBattle() == null || that.getBattle().shouldJoin(this) != 0))
            return true;
        return false;
    }

    public boolean shouldRunFrom(Army that) {
        //      if (this.type == ArmyType.PATROL)System.out.println(getName() + " in shouldRun method"); 

        if (this.getTroopCount() < that.getTroopCount())
            return true;
        return false;
    }

    public boolean shouldRepair() {
        if (defaultTarget != null) {
            boolean repair = this.getParty().getHealthySize() <= this.partyType.getMinSize() * REPAIR_FACTOR;
            //         if (repair) System.out.println(this.getName() + " should repair (min size is " + this.partyType.getMinSize() + ") and defaultTarget is " + getDefaultTarget());
            return (repair);
        } else {
            return false;
        }
    }

    public boolean targetLost() {
        if (target != null && target.getType() == 2) {
            Army targetArmy = (Army) target;
            if (distToCenter(target) > lineOfSight || !targetArmy.hasParent() || targetArmy.isGarrisoned())
                return true;
            if (targetArmy.isInBattle())
                if (targetArmy.getBattle().shouldJoin(this) == 0) // shouldn't join
                    return true;
        }
        //      if ()
        return false;
    }

    public void momentumDecay() {
        if (momentum >= 1) {
            //         System.out.println("total: " + Kingdom.getTotalHour() + " current: " + currentHour);
            if (Kingdom.getTotalHour() - currentHour >= momentumDecay) {
                momentum -= 1;
                currentHour = Kingdom.getTotalHour();
            }
        }
    }

    public void destroy() {
        if (isGarrisoned()) {
            getGarrisonedIn().eject(this);
        }
        getKingdom().removeArmy(this);
        this.remove();
    }

    //   public void verifyTarget() {
    //      if (getTarget().getType() == 2) { // army
    //         Army targetArmy = (Army) getTarget();
    //         if (!kingdom.getArmies().contains(targetArmy, true))
    //            nextTarget();
    //      }
    //   }

    public void runFrom(Army runFrom) {
        if (runFrom != null)
            setTarget(null);
        this.runFrom = runFrom;
    }

    public void stopRunning() {
        //      System.out.println(getName() + " stopping running");
        runFrom = null;
        startedRunning = false;
        nextTarget();
    }

    public Army getRunFrom() {
        return runFrom;
    }

    public void run() { // for now, find a spot far away and set path there
        if (normalWaiting)
            normalWaiting = false;
        if (startedRunning && !this.path.isEmpty())
            path.travel();
        else {
            Location goTo = detectNearbyFriendlyCity();
            if (distToCenter(runFrom) >= this.getLineOfSight()) {
                stopRunning();
                //            System.out.println(this.getName() + " stopping running because out of line of sight");
            } else if (goTo != null) {
                //            System.out.println("detected City " + goTo.getName());
                setTarget(goTo);
                setSpeed(calcSpeed()); // update speed
                path.travel();
                startedRunning = true;
            }
            // find new target an appropriate distance away, travel there.
            else { //if (!this.hasTarget()) {
                //         System.out.println(getName() + " is running");
                //         setTarget(getKingdom().getCities().get(0));
                //            System.out.println(getName() + " getting new random run target");
                float distance = getLineOfSight();

                toTarget.x = getCenterX() - runFrom.getCenterX();
                toTarget.y = getCenterY() - runFrom.getY();
                toTarget.scl(1 / toTarget.len()); // set vector length to 1
                toTarget.scl(distance);

                // TODO make memory efficient by keeping only one point
                Point p = new Point(getCenterX() + toTarget.x, getCenterY() + toTarget.y);

                float rotation = 10;
                while (getKingdom().getMap().isInWater(p) && rotation < 360) {
                    toTarget.rotate(rotation);
                    rotation += 10;
                    p.setPos(getX() + toTarget.x, getY() + toTarget.y);
                    //               System.out.println("rotating to find new target");
                }
                if (rotation > 360) {// no escape, probably in water
                    p.setPos(getCenterX(), getCenterY());
                    //            System.out.println("rotated all the way");
                }
                setTarget(p);
                //            this.runTo = p;
                startedRunning = true;
            }
        }
    }

    public Location detectNearbyFriendlyCity() {
        for (City city : getKingdom().getCities()) {
            if (!isAtWar(city) && this.distToCenter(city) < getLineOfSight()
                    && this.distToCenter(city) < runFrom.distToCenter(city)) {
                return city;
            }
        }
        return null;
    }

    public void raid(Village village) {
        //System.out.println(this.name + " is raiding " + village.getName());
        Militia militia = village.createMilitia();
        kingdom.addArmy(militia);
        createBattleWith(militia);
    }

    public void setSpeed(float speed) {
        this.speed = speed;
    }

    public float getSpeed() {
        return speed;
    }

    public float calcSpeed() {
        // make speed related to party's speed, morale, and army's unique speed factor, 
        return (BASE_SPEED + morale / 30 + party.getAvgSpd() - party.getTotalSize() * SIZE_FACTOR) * speedFactor;
    }

    public float getScale() {
        return scale;
    }

    public float calcScale() {
        return scale + scale * getTroopCount() / SCALE_FACTOR;
    }

    @Override
    public void setScale(float scale) {
        super.setScale(scale);
        this.setWidth(region.getRegionWidth() * getScaleX());
        this.setHeight(region.getRegionHeight() * getScaleY());
        this.setOrigin(region.getRegionWidth() * getScaleX() / 2, region.getRegionHeight() * getScaleY() / 2);
    }

    public void setMorale(int morale) {
        this.morale = morale;
    }

    public int calcMorale() {
        // 100 =  25                +  25      + 50;
        return (100 - getTroopCount()) / 4 + momentum;
    }

    public int getMorale() {
        return morale;
    }

    public void setMomentum(int momentum) {
        if (momentum >= 50) {
            this.momentum = 50;
        } else if (momentum <= 0) {
            this.momentum = 0;
        } else
            this.momentum = momentum;
    }

    public int getMomentum() {
        return momentum;
    }

    public float calcLOS() {
        //      return BASE_LOS + LOS_FACTOR * this.getTroopCount(); // * kingdom.currentDarkness
        return BASE_LOS + LOS_FACTOR * this.getTroopCount() * kingdom.currentDarkness;
    }

    public void setLOS(float lineOfSight) {
        this.lineOfSight = lineOfSight;
    }

    public Faction getFaction() {
        return faction;
    }

    public void setFaction(Faction faction) {
        this.faction = faction;
    }

    public int getType() {
        return 2; // army type
    }

    public void setBattle(Battle battle) {
        this.battle = battle;
    }

    public Battle getBattle() {
        return battle;
    }

    public void endBattle() {
        //      path.travel();
        if (siege != null)
            leaveSiege();
        battle = null;
        //      if (type == ArmyType.MERCHANT) System.out.println(getName() + " ending battle");
    }

    public void besiege(Location location) {
        if (location.getSiege() == null) {
            location.beginSiege(this);
        } else if (location.getSiege().besieging != this.faction) {
            this.nextTarget();
        } else
            location.beginSiege(this);
    }

    public void setSiege(Siege siege) {
        this.siege = siege;
    }

    public Siege getSiege() {
        return siege;
    }

    public void leaveSiege() {
        //      System.out.println(this.getName() + " is ending siege");
        nextTarget();
        if (siege != null) {
            if (siege.location == this.getTarget())
                nextTarget();
            //         while (siege.location == this.getTarget()) {
            //            System.out.println(getName() + " ending siege and going to new target");
            //            nextTarget();
            //         }
            siege.remove(this);
        }
        siege = null;
    }

    public boolean isInSiege() {
        return siege != null;
    }

    public int getTroopCount() {
        return party.getTotalSize();
    }

    public boolean setTarget(Destination newTarget) {
        if (newTarget == null) {
            path.dStack.clear();
            path.nextGoal = null;
            // figure out how to reconcile this with path?
            //         System.out.println(getName() + " has null target");
            return false;
        }
        // replace old targetof
        if (getTarget() != null && getTarget().getType() == 2) {
            ((Army) getTarget()).targetOf.removeValue(this, true);
        }

        //      if (this.type == ArmyType.FARMER)System.out.println("farmer in setTarget"); 

        // don't add same target twice in a row... this is a problem.
        //      if (newTarget.getType() == 2 && ((Army) newTarget).isGarrisoned()) System.out.println("***** TARGET GARRISONED! *****");

        boolean isInWater = kingdom.getMap().isInWater(newTarget);
        if (!isInWater && !(newTarget.getType() == 2 && ((Army) newTarget).isGarrisoned())) {
            if (this.target != newTarget && this.lastPathCalc == 0) {

                // don't add a bunch of useless point and army targets
                if (this.target != null && this.target.getType() != 2 && this.target.getType() != 0
                        && targetStack.size() < MAX_STACK_SIZE) {
                    targetStack.push(this.target);
                    //               System.out.println(getName() + " pushing " + this.target.getName() + " stack size: " + this.targetStack.size() + " new target " + newTarget.getName());
                }
                this.target = newTarget;
                //            if (newTarget != null && this.path.isEmpty()) {
                if (newTarget != null && this.path.finalGoal != newTarget) {
                    if (this.path.calcPathTo(newTarget)) {
                        this.lastPathCalc = A_STAR_FREQ;
                        path.next();
                    } else {
                        System.out.println(getName() + " failed A*");
                    }
                } else if (newTarget == this.path.finalGoal)
                    System.out.println("new goal is already in path");
            } else {
                //            System.out.println(getName() + "adding same target twice");
                return false;
            }
            if (newTarget.getType() == 2)
                ((Army) newTarget).targetOf.add(this);
            return true;
        } else if (defaultTarget != null && !kingdom.getMap().isInWater(defaultTarget)) {
            //         System.out.println(getName() + " trying to go to the water (setting target to center of land)");
            setTarget(defaultTarget);
            return true;
        } else if (!isInWater) {
            setTarget(kingdom.getMap().referencePoint);
            return true;
        } else
            return false;
    }

    public boolean hasTarget() {
        return getTarget() != null;
    }

    public Destination getTarget() {
        return target;
    }

    /* fix this */
    public void nextTarget() {
        if (!targetStack.isEmpty()) {
            if (targetStack.peek() == null || targetStack.peek() == target || (targetStack.peek().getType() == 2
                    && !kingdom.getArmies().contains((Army) getTarget(), true)))
                targetStack.pop(); // clean stack 
            if (!targetStack.isEmpty() && targetStack.peek() != null)
                setTarget(targetStack.pop());
            else
                findTarget();
        } else {
            findTarget();
        }
    }

    public void newTarget(Destination target) {
        targetStack.removeAllElements();
        setTarget(target);
    }

    public void findTarget() {
        //      if (this.type == ArmyType.BANDIT) System.out.println("bandit finding target"); 
        setTarget(defaultTarget);
    }

    public void setDefaultTarget(Destination defaultTarget) {
        this.defaultTarget = defaultTarget;
    }

    //   public void setDefaultTarget(Location defaultTarget) {
    //      this.defaultTarget = defaultTarget;
    //   }
    public Destination getDefaultTarget() {
        return defaultTarget;
    }

    public Kingdom getKingdom() {
        return kingdom;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public float getLineOfSight() {
        return lineOfSight;
    }

    //   public void addMoney(int money) {
    //      this.money += money;
    //   }
    //   public void loseMoney(int money) {
    //      this.money -= money;
    //   }
    //   public int getMoney() { // lol
    //      return money;
    //   }
    //   public void setMoney(int money) {
    //      if (money >= 100) {
    //         this.money = 100;
    //      }
    //      if (money <= 0) {
    //         this.money = 0;
    //      }
    //      else this.money = money;
    //   }
    public boolean isAtWar(Destination destination) {
        return Faction.isAtWar(faction, destination.getFaction());
    }

    // laziness sake
    protected double distToCenter(Destination d) {
        return Kingdom.distBetween(this, d);
    }

    //   @Override
    //   public double distToCenter(Destination d) {
    //      float thisX = getX() + getOriginX();
    //      float thisY = getY() + getOriginY();
    //      float dX = d.getX() + d.getOriginX();
    //      float dY = d.getY() + d.getOriginY();
    //      return Math.sqrt((dX-thisX)*(dX-thisX) + (dY-thisY)*(dY-thisY));
    //   }
    //
    //   @Override
    //   public double distTo(Destination d) {
    ////      return Math.sqrt((d.getX()-getCenterX())*(d.getX()-getCenterX())+(d.getY()-getCenterY())*(d.getY()-getCenterY()));
    //      return Math.sqrt((d.getCenterX()-getCenterX())*(d.getCenterX()-getCenterX())+(d.getCenterY()-getCenterY())*(d.getCenterY()-getCenterY()));
    //   }

    public void setStopped(boolean stopped) {
        this.stopped = stopped;
    }

    public void setWaiting(boolean waiting) {
        this.normalWaiting = waiting;
    }

    public void setForceWait(boolean forceWait) {
        this.forceWait = forceWait;
    }

    public boolean isStopped() {
        return stopped;
    }

    public boolean isWaiting() {
        return normalWaiting || forceWait;
    }

    public boolean isForcedWaiting() {
        return forceWait;
    }

    public boolean isRunning() {
        return runFrom != null;
    }

    public boolean isInBattle() {
        return battle != null;
    }

    @Override
    public void setMouseOver(boolean mouseOver) {
        if (this.mouseOver) {
            if (!mouseOver)
                kingdom.getMapScreen().getSidePanel().returnToPrevious();
        } else if (mouseOver)
            kingdom.getMapScreen().getSidePanel().setActiveArmy(this);

        this.mouseOver = mouseOver;
    }

    public boolean mousedOver() {
        return mouseOver;
    }

    public boolean isGarrisoned() {
        return (garrisonedIn != null);
    }

    public void setGarrisonedIn(Location city) {
        this.garrisonedIn = city;
    }

    public boolean isGarrisonedIn(Location city) {
        if (this.garrisonedIn == city)
            return true;
        return false;
    }

    protected Location getGarrisonedIn() {
        return garrisonedIn;
    }

    public void setTextureRegion(String textureRegion) {
        this.region = Assets.atlas.findRegion(textureRegion);
        this.initializeBox();
    }

    public TextureRegion getTextureRegion() {
        return region;
    }

    public float getCityCollisionDistance() {
        return cityCollisionDistance;
    }

    public int getOffset() {
        return offset;
    }

    public double getWaitUntil() {
        return waitUntil;
    }

    public void setWaitUntil(double waitUntil) {
        this.waitUntil = waitUntil;
    }

    public float getCenterX() {
        // modified
        return getX() + getOriginX();
    }

    public float getCenterY() {
        // modified
        return getY() + getOriginY();
    }

    public String getFactionName() {
        return faction.name;
    }

    public void setParty(Party party) {
        this.party = party;
    }

    public Party getParty() {
        return party;
    }

    public PartyType getPartyType() {
        return partyType;
    }

    public Point toPoint() {
        return new Point(getCenterX(), getCenterY());
    }
}