Friday, April 26, 2013

Artificial Intelligence (Level 3)

It seems a little silly trying to tackle AI at level 3, but I felt like it was the next part that needed to be built.

As I started on this part, I realized that AI was going to require at least 2 major parts:
  1. The ability for the computer to decide what a good move is
  2. The ability for the computer to execute that move, in an attractive way. 
That 2nd point makes me think again of Final Fantasy Tactics - consider, for instance, this video:
When it's the enemy's turn, it doesn't just instantly flash to the end, it shows it progress gradually.  It's like the enemy decides where to move first, then moves, then spends a second deciding where to attack.  And you see the full range of displays: the movement range is highlighted, even  though the computer doesn't need to "see" it.  The cell they select is highlighted.  There are pauses in between segments.  It all flows quite nicely.

In this article I will mostly tackle the 2nd point, though the computer will decide what to do, there decision will be pretty lame.  But it serves as a framework to expand later.

Part 1 - Deciding on a plan
Entities which are computer controlled will get a new Component called AI.  AI will have a field of type Plan.  Plan will contain the information about what the entity will do:
  • Where will it move?
  • Where will it attack?
  • What attack will it be using?
  • Will it move first, or will it attack first and then move?
  • How "good" is this plan?
Plan will also implement Comparable so we can generate an Array of Plans, then sort them based on their score to find the best one.

To determine the best Plan, the computer will loop over all possible combinations of what it can do.  For now let's juts assume that they entity will move first, and act second.  Basically, we must loop over:
  • All possible cells you can move to
  • All possible actions you can do
  • All cells you can target with that action
    • This may be a single cell, or it may hit a whole field of cells centered on a particular one
Each of these describes one possible Plan - find some way to score them (this is what we are totally going to skimp on).  Then sort the list of Plans and grab the best one.  For some semi-pseudo-code, we want it to look like this:

Array<Plan> plans = new Array<plan>();

moveFirst = true;

for (Pair moveTarget : reachableCells) {
 for (Action action : availableActions) {
  for (Pair actionTarget : cellsWithinActionsRange) {
   Array<pair> field = getFieldFor(actionTarget, action);
   score = scorePlan(moveTarget, action, field);
   plans.add(new Plan(moveFirst, moveTarget, actionTarget, action, score);
  }
 }
}

plans.sort();
plan = plans.get(0);

(Note, in practice we will also want to consider that they can act first, then move later - the loop would look quite similar but there are some additional complications that might arise from this).

Let's look at these and figure out which ones we already have the foundation for, and which ones need to be developed.
  • reachableCells - DONE
  • Action and availableActions - We will need to design these
  • actionTarget and cellsWithinActionsRange - should be easy to implement if we give Action something called range
  • getFieldFor - in the simplest case, let's say that each action hits just the target cell, or the target cell + it's neighbors, or the target cell + 2 sets of neighbors, etc.  If we give Action something called "field" where field=1 means just the center, field=2 means the center+neighbors, etc, this should be easy too!
  • scorePlan - ????????
So to make this work, we need to develop a class called Action with data like range and field, and also some details of what the action does.

To accomplish this, I made a class called Action2 (Action is a class used in libgdx, so I went with Action2 to avoid any confusion) and a component called Abilities which holds the different actions an entity can do.
package com.blogspot.javagamexyz.gamexyz.abilities;

public class Action2 {
 public int range; // How far from can this ability be targeted
 public int field; // How large an area does it hit (1 = single cell, 2 = middle+neighbors, etc)
 public float baseSucessProbability; // How likely is it to hit, on its own
 public float damage; // How much damage does it do, on its own
}

package com.blogspot.javagamexyz.gamexyz.components;

public class Abilities extends Component {
 
 Action2 attack_sword;
 Action2 attack_bow;
 
 public Abilities() {
  
  attack_sword = new Action2();
  attack_sword.damage = 4;
  attack_sword.baseSucessProbability = 0.9f;
  attack_sword.range = 1;
  attack_sword.field = 1;
  
  
  attack_bow = new Action2();
  attack_bow.damage = 3;
  attack_bow.baseSucessProbability = 0.7f;
  attack_bow.range = 4;
  attack_bow.field = 1;
  
 }
 
 public Array<action2> getAbilities() {
  Array<action2> abilities = new Array<action2>();
  abilities.add(attack_sword);
  abilities.add(attack_bow);
  return abilities;
 }
}

Each entity will have 2 attacks: sword and bow.  The sword has a range and field of 1, a 90% chance of success, and does 4 damage.  The bow has a range of 4, a field of 1, a 70% chance of success, and does 3 damage.

For now it's kind of silly, but getAbilities() returns an Array containing those two actions.  With that, we can build our Plan class
package com.blogspot.javagamexyz.gamexyz.AI;

import com.blogspot.javagamexyz.gamexyz.abilities.Action2;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;


public class Plan implements Comparable<Plan> {
 
 public boolean moveFirst, moveDecided, moveDone, actionDone;
 public Action2 action;
 public Pair moveTarget, actionTarget;
 public int score;
 
 public Plan(boolean moveFirst, Pair moveTarget, Pair actionTarget, Action2 action, int score) {
  this.moveFirst = moveFirst;
  this.moveTarget = moveTarget;
  this.actionTarget = actionTarget;
  this.action = action;
  this.score = score;
  
  moveDecided = moveDone = actionDone = false;
 }
 
 @Override
 public int compareTo(Plan p) {
  if (score < p.score) return 1; // p was better
  else if (score > p.score) return -1; // p was worse
  return 0; // they were equal
 }

}

moveFirst is the boolean which, again, lets us know what we're doing first (in the loop I outlined above, everything was predicated upon moving first).  It also has an Action2, a Pair indicating where it will move, and a Pair indicating where the action will be targeted.  The other booleans will come up later.  moveDone and actionDone are to help us time things smoothly (part 2), and moveDecided will be used for when we act first, move second.

Also, notice it implements Comparable so that we can sort it to find which is the best plan.  In reality, if we are strictly going with the BEST plan, then we don't need to sort it, we just need to find the maximum.  But to shake things up later, we may want to pick one of the best strategies at random, so that we are harder to predict - in this case we may as well sort it.

Okay, now let's look at the AI component.  It should hold a Plan, and probably a timer (so we can time the execution of the plan).  Now, we don't want all of the entities with AI processing all the time, just on their own turn.  This could be accomplished at least two ways I can think of:
  1. Give each AI a boolean flag for being active or not.  Then, each turn during processTurn(), check to see if the next entity has AI - if so, set active to true.  At the end of that entity's turn, before they call processTurn() again, set their AI to inactive.
  2. Have a dummy component (CPUControllable?) to indicate that the Entity is controlled by the computer, and each turn during processTurn() check to see if the it has CPUControllable, and if so, add a new AI component.  Then, at the end of their turn, remove the AI component.
I went with option #1, but I can see pros and cons with each way.  Here is my AI component:
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;
import com.blogspot.javagamexyz.gamexyz.AI.Plan;

public class AI extends Component {

 public Plan plan;
 public float timer;
 public boolean active;
 
 public boolean planDone;
 
 public void begin() {
  timer = 0;
  active = true;
  planDone = false;
 } 
}

The first 3 things we already talked about.  We'll talk about planDone soon, but it becomes true as soon as the computer has figured out the plan.  I kind of broke the whole "component-as-data-storage-only" paradigm - but it makes it easy for when I want to start someone's AI (i.e. in processTurn()).

Now we need to look at AISystem.  It is responsible for a lot of different things, primarily deciding what Plan to do, but it's also in charge of pacing.  We don't want to do everything every time, so it has a series of flags/stops along the way.

public class AISystem extends EntityProcessingSystem {

 @Mapper ComponentMapper<AI> AIm;
 @Mapper ComponentMapper<Stats> sm;
 @Mapper ComponentMapper<Movable> mm;
 @Mapper ComponentMapper<Abilities> am;
 
 private OverworldScreen screen;
 private GameMap gameMap;
 private ActionSequencer sequencer;
 
 private boolean startMove, startAction;
 
 @SuppressWarnings("unchecked")
 public AISystem(OverworldScreen screen, GameMap gameMap) {
  super(Aspect.getAspectForAll(AI.class, Stats.class, Movable.class, Abilities.class));
  
  this.screen = screen;
  this.gameMap = gameMap;
  
  startMove = true;
  startAction = true;
 }

 @Override
 protected void process(Entity e) {
  
  AI ai = AIm.get(e);
  if (!ai.active) return;
  
  if (!ai.planDone) decidePlan(e, ai);
  
  else {
   
   // If moveFirst
   if (ai.plan.moveFirst) {
    
    // Move
    if (!ai.plan.moveDone) {
     if (startMove) {
      ai.timer = 0;
      startMove = false;
     }
     sequencer.move(gameMap, screen, ai.timer);
    }
    
    // Act
    else if (!ai.plan.actionDone) {
     if (startAction) {
      ai.timer = 0;
      startAction = false;
     }
     sequencer.act(gameMap, screen, ai.timer);
    }
   }
   
   // Else, actFirst
   else {
    
    // Act
    if (!ai.plan.actionDone) {
     if (startAction) {
      ai.timer = 0;
      startAction = false;
     }
     sequencer.act(gameMap, screen, ai.timer);
    }
    
    // Decide move location
    else if (!ai.plan.moveDecided) decideMovement(e, ai.plan);
    
    // Begin moving to target location
    else if (!ai.plan.moveDone) {
     if (startMove) {
      ai.timer = 0;
      startMove = false;
     }
     sequencer.move(gameMap, screen, ai.timer);
    }
    
   }
  }
  
  // Don't increment your counter while the camera is moving 
  // This let's us focus on the character for a second (or so)
  // before they start acting.
  if (screen.cameraMoving()) return;
  ai.timer += world.getDelta();
  
  // If everything is done, process the turn
  if (ai.plan.actionDone && ai.plan.moveDone) {
   ai.active = false;
   startMove = startAction = true;
   screen.processTurn();
  }
 }
First things first, for now it's kind of awkward, but I'm telling it to process things not just with AI, but with Stats,Movable, and Abilities (I don't even use stats yet).

ActionSequencer is a class we'll discuss next, which I use to actually pace things (Part 2).  It has two methods right now: move() and act().  They, as you might guess, control the pacing of what those ought to look like.

In process() it first checks to see that this particular AI is active.  If not, it just quits.  Next it checks to see if it has already determined the plan.  This check prevents us from determining the plan every time.  Also, and I think this is a decent strength here, it may take a while to determine the plan.  Even if it takes 1 second, this would be no big deal to the player - unless the graphics locked up for that whole second, which is exactly what would happen if we required the plan to be determined in a single go.  We don't worry about it yet, but if our needs expand, this will help us so that every time AI is processed, it can refine the plan a little bit more until it has decided, at which point it is ready to act.

If the plan is decided, we begin enacting it.  Here's a plain outline of what this section says:
  • if moveFirst
    • move
    • act
  • else
    • act
    • determine where to move
    • move
The "determine where to move" step is important.  We don't want the computer deciding where to move until it has to.  Say they expected their attack to kill the enemy, but missed?  Should they stick with the plan where they thought the enemy would be dead?  This uncertainty is what will make scoring plans where the player acts first extra difficult.  How do you rate the advantage you get by having a move left over?  I think it depends on how good your potential moves look, but I haven't even touched that stuff yet.

Remember again, this thing will be processed hundreds to thousands of times.  You don't want to act until you're done moving (or vice versa), and you don't want to determine the plan thousands of times, nor where to move.  That is the purpose of those flags, plan.moveDone and plan.actionDone.  It will do those things until they are done, then no more.  startMove and startAct are flags that tell us that we are on the very first loop where we move (or act).  This is helpful for us to reset the timer (so those will execute appropriately).  Other than that, we ask our ActionSequencer to do the appropriate action.

After all that (starting around line 87) we need to increment the timer.  However, I don't want the timer moving while the camera is still on its way to focus on the enemy - otherwise they may have already begun their turn and done things we didn't see.  So I only update the timer when the camera is not moving.

Lastly, if both actionDone and moveDone are true, then we have completed the whole turn (timing and all): deactivate the AI, reset the AI system, and processTurn().

There are still a few things I left vague.  For instance, on line 31 I call some mystery method decidePlan(), and on line 70 I call decideMovement().  We already went over the pseudo-code for decidePlan() at the beginning of this article, and for now, decideMovement() just picks a random cell from reachableCells.  Here they are:
 private void decidePlan(Entity e, AI ai) {
  Pair pos = gameMap.getCoordinatesFor(e.getId());
  Movable movable = mm.get(e);
  Abilities abilities = am.get(e);
  
  Array<Pair> reachableCells = gameMap.pathFinder.getReachableCells(pos.x, pos.y, movable);
  Array<Action2> availableActions = abilities.getAbilities(); 
  
  Array<Plan> plans = new Array<Plan>();
  
  
  
  // Loop over all possible plans where the entity moves, then acts
  // If the action doesn't hit anyone, skip that plan
  boolean moveFirst = true;
  for (Pair moveTarget : reachableCells) {
   for (Action2 action : availableActions) {
    for (Pair actionTarget : MapTools.getNeighbors(moveTarget.x, moveTarget.y, action.range)) {
     Array<Pair> field = MapTools.getNeighbors(actionTarget.x, actionTarget.y, action.field-1);
     field.add(actionTarget);
     if (!gameMap.containsEntitiesOtherThan(field,e.getId())) continue;
     int score = scorePlan(moveTarget, action, field);
     plans.add(new Plan(moveFirst, moveTarget, actionTarget, action, score));
    }
   }
  }
  
  // If there were no good plans there, add at least one plan
  // where you move at random, and do nothing
  if (plans.size == 0) plans.add(new Plan(moveFirst, reachableCells.get(MathUtils.random(reachableCells.size-1)), null, null, 0));
  
  // Now loop over all possible plans where the entity doesn't
  // move anywhere, so they still have their move stored up
  moveFirst = false;
  for (Action2 action : availableActions) {
   for (Pair actionTarget : MapTools.getNeighbors(pos.x, pos.y, action.range)) {
    Array<Pair> field = MapTools.getNeighbors(actionTarget.x, actionTarget.y, action.field-1);
    field.add(actionTarget); 
    if (!gameMap.containsEntitiesOtherThan(field,e.getId())) continue;
    int score = scorePlan(pos, action, field);
    plans.add(new Plan(moveFirst, pos, actionTarget, action, score));
    
   }
  }
  
  plans.sort();
  ai.plan = plans.get(0);
  ai.planDone = true;
  sequencer = new ActionSequencer(ai.plan, e);
 }
 private void decideMovement(Entity e, Plan plan) {
  Movable movable = mm.get(e);
  Pair pos = gameMap.getCoordinatesFor(e.getId());
  Array<Pair> reachableCells = gameMap.pathFinder.getReachableCells(pos.x, pos.y, movable);
  plan.moveTarget = reachableCells.get(MathUtils.random(reachableCells.size-1));
  plan.moveDecided = true;
 }
decidePlan has a few very cheap hacks for now.  For instance, on lines 21 and 39, I say that if that particular move/act combo has no entities in the action field, skip that plan (this way I don't have to bother scoring or sorting it).  Even worse, I had to look for targets other than the acting entity - just think, if they move one cell over and say "aha, there's someone right back where I moved from - I'll attack them!" it would be pretty silly.  containsEntitiesOtherThan() is pretty simple - just check the field to see if anyone is there, but don't count it if it's the entity whose ID is being passed.

In case they don't find any actions that work, on line 30 we give them a dummy option to move to some random location and do nothing (with a default score of 0).

The next loop is over just the actions, because they will decide where to move later.  Again, I don't know how to handle score discrepancies between these two types of Plans.  The ability to move later is worth something... probably.  But what?

After that we sort the plans, grab the best one, and tell our AISystem that were are done making the plan.  We also create a new ActionSequencer to handle this plan (we'll get to that soon).

There's still some cop-out: on lines 22 and 40 I magically call some scorePlan() method.  For now, this is as simple as can be:

Go with whichever plan has the higher potential for damage.  Don't care who you're attacking.  Don't care how safe you are.  Don't care 'bout nuthin.  In practice, this means that everyone will try to move toward and attack anyone at random who is in some range.  They prefer sword (because it's more powerful), but they'll do bow if that's all they have.  There's nothing that says if they go for bow range to try to come in close for sword, nor to keep a distance.  They just choose some random cell where they can attack from - and if it's possible, it will be right next to them for some swift sword justice.

decideMove(), like I said before, doesn't factor in anything smart.  It just picks some random place to move.

That's our AISystem.  We'll talk about the ActionSequencer in a second, but first let's look at how we want to implement our AISystem.  It's just another Artemis system, and we don't want to update it manually (it runs continuously through the enemy's turn) so just add it like a regular System.  In OverworldScreen.processTurn(), we need to check the incoming players status: is it controlled by AI?  If so, I'd like to disable player control entirely, and begin the AI processing.
 public void processTurn() {
  turnManagementSystem.process();
  activeEntity = unitOrder.get(0);
  activeEntityCell = gameMap.getCoordinatesFor(activeEntity);
  
  // As long as that entity has a location, focus the camera on them
  if (activeEntityCell != null) {
   cameraMovementSystem.move(activeEntityCell.x, activeEntityCell.y);
  }
  
  // Try to get the next entity's AI
  AI ai = world.getEntity(activeEntity).getComponent(AI.class);
  
  // If they don't have AI, give control to the human player
  if (ai == null) Gdx.input.setInputProcessor(inputSystem);
  
  // Otherwise take control away, and begin that AI's processing.
  else {
   Gdx.input.setInputProcessor(null);
   ai.begin();
  }
Whew, we're coming down to the end.  That's how the AISystems gets integrated into the rest of the game.  Now we just need to talk about the ActionSequencer (plus the few things it messed up).

The idea for ActionSequencer is to break things down into a few discrete steps.  For instance, to handle move, we want to do a few things:
  1. Highlight the movable range
  2. Specially highlight the cell they choose to move to
  3. Begin the actual movement (remember, the camera will be moving during this period too, so the timer won't be ticking until it stops)
  4. Unhighlight the target cell
  5. Finish (wrap up anything else that needs to happen at the end)
Each of these steps should last a particular amount of time, before moving on to the next one.  I broke each step into a series of if (step == 0) { ... } else if (step == 1) { ... }, etc...

Now, for each of these, the code only needs to run once (for instance, in step 1 you must figure out the reachableCells, then tell OverworldScreen to highlight them), so within each step, we do step++.  To stop it from running directly into the next step, we also monitor the AI timer to keep it from getting ahead of itself.  Here's how I did it:
package com.blogspot.javagamexyz.gamexyz.AI;

import com.artemis.Entity;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.components.Damage;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.components.Movement;
import com.blogspot.javagamexyz.gamexyz.components.Stats;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;
import com.blogspot.javagamexyz.gamexyz.screens.OverworldScreen;

public class ActionSequencer {
 
 Plan plan;
 Entity e;
 
 private int step;
 private float m_t;
 private float a_t;
 
 private float[] moveTimer;
 private float[] actTimer;
 
 public ActionSequencer(Plan plan, Entity e) {
  this.plan = plan;
  this.e = e;
  
  float t1,t2,t3,t4;
  
  step = 0;
 
  // Move timing constants
  m_t = 0.75f; // Time to wait upon focus, before showing movement range
  t1 = 1f;
  t2 = 0.5f;
  t3 = 0.05f;
  t4 = 0.25f;
  moveTimer = new float[]{t1, t2, t3, t4};
  
  // Act timing constants
  a_t = 0.75f; // Time to wait upon focus, before showing action range
  t1 = 1f;  // Highlight attackable range
  t2 = 0.6f;  // Highlight target cell
  t3 = 0.75f;  // Linger to watch damage hit
  actTimer = new float[]{t1, t2, t3};
 }
...
}
This thing has immediate access to the Plan, and the entity who is executing it.  It has an int step to keep track of which step it's on (it will only ever either be moving or acting, so we only need one step counter).  The moveTimer holds the duration for each step.  Look at lines 34-40 in the constructor.

m_t will be the "accumulated" timer, but for now, it just holds how long the camera should wait before showing the movement range (remember, the timer isn't running at all while the camera is moving, so this is 0.75 seconds after the camera has settled on the entity).  t1 says that we will then highlight reachableCells for 1 second.  t2 is how long for the next step, etc.  An array, moveTimer is created which holds those values and can conveniently be referenced by moveTimer[step] to get the appropriate time.

Below all that, we actually have the move() method:
 public void move(GameMap gameMap, OverworldScreen screen, float time) {
  
  if (time < m_t) return;
  
  // Step 1 - Highlight the reachable cells
  if (step == 0) {
   Movable movable = e.getComponent(Movable.class);
   
   Pair pos = gameMap.getCoordinatesFor(e.getId());
   
   Array<Pair> reachableCells = gameMap.pathFinder.getReachableCells(pos.x, pos.y, movable);
   screen.highlightedCells = reachableCells;
   screen.highlightMovementRange();
   
   m_t += moveTimer[step];
   step++;
   
   return;
  }
  
  // Step 2 - Highlight the cell we will move to
  else if (step == 1) {
   screen.highlightedCells.clear();
   screen.setHighlightColor(0.05f, 0.05f, 0.2f, 0.8f);
   screen.highlightedCells.add(plan.moveTarget);
   
   m_t += moveTimer[step];
   step++;
   
   return;
  }
  
  // Step 3 - Begin moving to target cell
  else if (step == 2) {
   Movable movable = e.getComponent(Movable.class);
   
   Pair pos = gameMap.getCoordinatesFor(e.getId());
   
   e.addComponent(new Movement(gameMap.pathFinder.findPath(pos.x, pos.y, plan.moveTarget.x, plan.moveTarget.y, movable, false)));
   e.changedInWorld();
   
   m_t += moveTimer[step];
   step++;
   
   return;
  }
  
  // Step 4 - We have arrived (plus a brief waiting period), unhighlight the cell
  else if (step == 3) {
   screen.renderHighlighter = false;
   
   m_t += moveTimer[step];
   step++;
   
   return;
  }
  
  // Finished: Tell the plan that the "move" is done 
  else {
   plan.moveDone = true;
   m_t = 0;
   step = 0;
  }
  
 }
The first thing (line 3) is the most important part.  If the AI timer has not exceeded the time limit we have set, then don't even bother doing any more here.  This is how we control to make sure we don't jump ahead to steps inappropriately.  In the Step 1 block, we get the reachableCells and tell the OverworldScreen to highlight them (I slightly fudged with the way highlighting works - we'll talk about it at the very end).

Step 2 clears the highlightedCells and replaces them with the moveTarget, along with a less transparent, darker blue color.

Step 3 actually adds the Movement component.

Step 4 stops the highlighter.

Notice that in each step, we increment m_t by the timer for that step.  This way, we are always getting caught by that line 3 code.  At first, it needs timer to exceed 0.75.  But after that, it must exceed 1.75 (1 second beyond what it already had - as added on in line 27).  So each step calls m_t += moveTimer[step], and each step also calls step++.  Those two lines keep the flow going.

After that, we're done, so we call plan.moveDone (which helps control how AISystem knows where to delegate its attention) .

Here's act():
 public void act(GameMap gameMap, OverworldScreen screen, float time) {
  if (plan.action == null) {
   plan.actionDone = true;
   System.err.println("action = null");
   return;
  }
  if (time < a_t) return;
  
  // Step 1 - Highlight attackable range
  if (step == 0) {
   Pair pos = gameMap.getCoordinatesFor(e.getId());
   screen.highlightedCells = MapTools.getNeighbors(pos.x, pos.y, plan.action.range);
   screen.highlightAttackRange();
   
   a_t += actTimer[step];
   step++;
  }
  
  // Step 2 - Highlight the target cell (the whole field)
  else if (step == 1) {
   screen.highlightedCells = MapTools.getNeighbors(plan.actionTarget.x, plan.actionTarget.y, plan.action.field-1);
   screen.highlightedCells.add(plan.actionTarget);
   screen.setHighlightColor(0.2f, 0.05f, 0.05f, 0.6f);
   
   a_t += actTimer[step];
   step++;
  }
  
  // Step 3 - Add damage and unhighlight target cells
  else if (step == 2) {
   Damage damage = new Damage(e.getComponent(Stats.class),plan.action);
   int entityId;
   Array<Pair> field = MapTools.getNeighbors(plan.actionTarget.x, plan.actionTarget.y, plan.action.field-1);
   field.add(plan.actionTarget);
   for (Pair target : field) {
    entityId = gameMap.getEntityAt(target.x, target.y);
    screen.addComponent(damage, entityId);
   }
   
   screen.renderHighlighter = false;
   
   a_t += actTimer[step];
   step++;
  }
  
  // Finished
  else {
   plan.actionDone = true;
   a_t = 0;
  }
 }
It basically works the same way as move(), but instead of moving, it handles acting.  To add the damage component, it needs access to the target entity, but it only has the ID.  It could get the entity from the World, but it doesn't have that either.  So I made some generally accessible method on OverworldScreen called addComponent() which will try to add a component to a particular entity.

Here's the list of silly methods in OverworldScreen I had to add at the end to make some of this come together:
 public boolean cameraMoving() {
  return cameraMovementSystem.active;
 }
 
 public void setHighlightColor(float r, float g, float b, float a) {
  mapHighlighter.setColor(r,g,b,a);
 }
 
 public void highlightMovementRange() {
  renderHighlighter = true;
  setHighlightColor(0f, 0f, 0.2f, 0.3f);
 }
 
 public void highlightAttackRange() {
  renderHighlighter = true;
  setHighlightColor(0.5f, 0f, 0f, 0.3f);
 }
 
 public void addComponent(Component component, int entityId) {
  if (entityId < 0) {
   System.err.println("No entity");
   return;
  }
  Entity e = world.getEntity(entityId);
  e.addComponent(component);
  e.changedInWorld();
 }
I replaced renderAttackRange and renderMovementRange with a general renderHighlighter (it never worked well highlighting both anyway, because they both worked off of the same timer which then went on double speed).  I also gave mapHighlighter a setColor method, and it in general remembers what color it's supposed to be working on.

I also felt like I had to tweak the Damage component a little bit, but with very minor changes.  Really this should be another update, but here it is in craptacular form now:
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;
import com.blogspot.javagamexyz.gamexyz.abilities.Action2;

public class Damage extends Component {

 public float baseDamage;
 public float baseAccuracy;
 
 public int power;
 public int accuracy;
 public Stats stats;
 
 public Damage(Stats stats) {
  this.stats = stats;
  power = stats.getStrength();
  accuracy = stats.getAgility();
  
  baseDamage = power;
  baseAccuracy = accuracy;
 }
 
 public Damage(Stats stats, Action2 action) {
  this.stats = stats;
  this.baseDamage = action.damage;
  this.baseAccuracy = action.baseSucessProbability;
  accuracy = (int)(baseAccuracy * 100);
  power = (int)(3*baseDamage);
 }
}
Holy crap that was a lot!  I hope I got it all (or at least most of it so that it will make sense).  Now you can make some computer controlled entities that will duke it out in a Battle Royale!  And you can mix it up with a few of your own characters - really the sky is the limit!

I'd encourage you all to think about how you actually want to tweak the decidePlan() and scorePlan() methods.  They are surely crap right now (I didn't really plan on making them perfect just yet though).  But whatever I come up with probably won't be that great in the end either, so this is definitely a place for your own logic to shine through!  What do you think is important for the computer to consider when deciding on a good plan?

You have gained 250 XP.  Progress to Level 4: 700/700
DING!  You have advanced to Level 4, congratulations!

Monday, April 22, 2013

Combat and Turn Management - Level 3

This started out as a nice, short, update.  But I kept adding stuff which messed with what I already had, so it kind of grew.  You can check out all the code at the repository - I probably won't put it all here verbatim: there were a lot of changes, many of which were totally cosmetic.

Last time we made a menu which users could select move, and I want to add more controls now.  This update will introduce a basic combat system, and simple turn management, plus a few jazzy effects.

Before we really get into that, there are a few organizational changes in OverworldScreen worth mentioning
  1. In the constructor, I separate code out into a few different void methods so it's a little easier to find what I'm looking for.
  2. I also keep instances of each of the Controllers in memory, so I don't have to set the input processor to a new _____Controller every time.
  3. On that note, I had to split up the drag functionality, and the character select functionality from what used to be called the OverworldDefaultController.  It's been replaced with OverworldDragController and OverworldSelectorController (I had to do this because I needed the attack controller to fit between them)
  4. I also added a "remove" method for the input multiplexor (I know it has a built in one, but for now I feel more like forcing everything to interface with it through the helper methods I made.
  5. Because it's written with libgdx, it has the potential to be ported to Android (and even iPhone now).  Consequently, not everybody will have a mouse (or touchscreen) and I started experimenting with a cursor that users can move around with arrow keys.  One problem with that is that each cell has 6 neighbors, but you only have 4 arrow keys.  The cursor, for now, just looks like an unanimated knight who starts at (0,0), and you can move it around to see how it works.  I don't like it right now, particularly when you need to go up/left or down/right.  I have plans to fix it someday though.
Here's an overview of the topics we'll cover now:
  • Combat System
    • Components: Stats, Damage
    • Systems: DamageSystem
    • Controller: OverworldAttackController
  • Damage Label
    • Components: FadingMessage
    • Systems: FadingMessageRenderSystem
  • Turn Management
    • Systems: TurnManagementSystem
  • Camera Movement
    • Systems: CameraMovementSystem

Combat System

For combat to make any sense, characters must now have things like strength and health.  I made a Stats component which looks like this:
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;
import com.badlogic.gdx.math.MathUtils;

public class Stats extends Component {
 
 public int level, xp, next_xp;
 
 private int strength, strength_modifier;
 private int intelligence, intelligence_modifier;
 private int speed, speed_modifier;
 private int agility, agility_modifier;
 private int charisma, charisma_modifier;
 private int hardiness, hardiness_modifier;
 private int resistance, resistance_modifier;
 
 public int health, maxHealth, maxHealth_modifier;
 public int magic, maxMagic, maxMagic_modifier;
 
 public String name;
 
 public int actionPoints;
 
 public Stats() {
  level = 1;
  xp = 0;
  next_xp = 100;
  
  strength = 15 + MathUtils.random(-3, 3);
  intelligence = 15 + MathUtils.random(-3, 3);
  speed = 15 + MathUtils.random(-3, 3);
  agility = 15 + MathUtils.random(-3, 3);
  charisma = 15 + MathUtils.random(-3, 3);
  hardiness = 15 + MathUtils.random(-3, 3);
  resistance = 15 + MathUtils.random(-3, 3);
  
  health = maxHealth = (int)((5*hardiness + 4*strength + 2*resistance) / 11);
  magic = maxMagic = (int)((5*intelligence + 2*resistance) / 7);
  
  strength_modifier = intelligence_modifier = speed_modifier = agility_modifier = 
    charisma_modifier = hardiness_modifier = resistance_modifier = maxHealth_modifier =
    maxMagic_modifier = 0;
  
  name = names[MathUtils.random(names.length-1)];
  
  actionPoints = 0;
  
 }
 
 private final String[] names = {"Rodann","Ranlan","Luhiri","Serl","Polm","Boray","Ostan","Inaes"};
 
 public int getAgility() {
  return agility + agility_modifier;
 }
 
 public int getHardiness() {
  return hardiness + hardiness_modifier;
 }
 
 public int getStrength() {
  return strength + strength_modifier;
 }
 
 public int getCharisma() {
  return charisma + charisma_modifier;
 }
 
 public int getIntelligence() {
  return intelligence + intelligence_modifier;
 }
 
 public int getResistance() {
  return resistance + resistance_modifier;
 }
 
 public int getSpeed() {
  return speed + speed_modifier;
 }
}


It permits storage of base stats (which are mostly totally self explanatory), plus modifiers which might come from spells, equipment, etc.  The list of names was just a short list taken from the Fantasy Name Generator over at RinkWorks.com.  HP and MP are derived stats calculated as a weighted average of a few different stats.  Right now when an entity gets created with new stats, they are all random ints from 12 to 18.

The actionPoints stat will be used to determine who gets to move, and will be discussed in more detail further down this post under Turn Management.

In OverworldScreen, I added a method "selectedAttack()" and "selectedWait()" just like we had "selectedMove()".  When they choose "Attack", selectedAttack() puts a new Controller in the mix, OverworldAttackController:

public void selectedAttack() {
  if (attacked) return;
  setInputSystems(controllerDrag,controllerAttack,controllerSelector);
  highlightedCells = MapTools.getNeighbors(activeEntityCell.x, activeEntityCell.y);
  renderAttackRange = true;
  handleStage = false;
  stage.clear();
 }

 public void selectedWait() {
  setInputSystems(controllerDrag, controllerSelector);
  processTurn();
  handleStage = false;
  stage.clear();
  selectedEntity = -1;
  
  moved = attacked = false;
 }


All this really does is highlight the cells that the player can attack.  We set the controllers we want - again, order is important!  First and foremost they can drag the screen, secondly they can attack, thirdly they can select another entity (perhaps to check its stats).

We also get the neighbors of activeEntityCell (which is a pair that is determined during the turn management part of things) to find the attackable range, set a flag renderAttackRange to true, quit handling the stage and clear it off.

In render(), we have to actually highlight these cells
  if (renderAttackRange) {
   mapHighlighter.render(highlightedCells,0.5f,0f,0f,0.3f);
  }



But most of the real magic happens over in OverworldAttackController
package com.blogspot.javagamexyz.gamexyz.screens.control.overworld;

import com.artemis.Entity;
import com.artemis.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.blogspot.javagamexyz.gamexyz.components.Damage;
import com.blogspot.javagamexyz.gamexyz.components.Stats;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;
import com.blogspot.javagamexyz.gamexyz.screens.OverworldScreen;

public class OverworldAttackController implements InputProcessor {
 
 private OrthographicCamera camera;
 private World world;
 private GameMap gameMap;
 private OverworldScreen screen;
 
 public OverworldAttackController(OrthographicCamera camera, World world, GameMap gameMap, OverworldScreen screen) {
  this.camera = camera;
  this.world = world;
  this.gameMap = gameMap;
  this.screen = screen;
 }

 @Override
 public boolean keyDown(int keycode) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean keyUp(int keycode) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean keyTyped(char character) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean touchDown(int screenX, int screenY, int pointer, int button) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean touchUp(int screenX, int screenY, int pointer, int button) {
  Pair coords = MapTools.window2world(Gdx.input.getX(), Gdx.input.getY(), camera);
  int entityId = gameMap.getEntityAt(coords.x, coords.y);
  
  // Did they click within the attackable range, and a real entity?
  if (screen.highlightedCells.contains(coords, false) && entityId > -1) {
   
   Entity source = world.getEntity(screen.selectedEntity);
   Entity target = world.getEntity(entityId);
   
   Stats stats = source.getComponent(Stats.class);
   Damage damage = new Damage(stats);
   
   target.addComponent(damage);
   target.changedInWorld();
   
   // Tell the screen that this entity has attacked this turn
   screen.attacked = true;
  }

  // Wherever they clicked, they are now done with the "attacking" aspect of things
  screen.highlightedCells = null;
  screen.renderAttackRange = false;
  screen.selectedEntity = -1;
  screen.removeInputSystems(this);
  return true;
 }

 @Override
 public boolean touchDragged(int screenX, int screenY, int pointer) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean mouseMoved(int screenX, int screenY) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean scrolled(int amount) {
  // TODO Auto-generated method stub
  return false;
 }
}


The only thing we really want to process is a touchUp(), where we get the cell they clicked, and the entity at that cell (or -1 if there is no entity).  Then if they clicked within the attackable range, and on a real entity, we define source and target entities (source is the attacker, target is the attackee).

Then we read the stats from the attacker (remember, this isn't an "EntitySystem" so we can't use the @Mapper) and pass it to a new Component called Damage.  The idea here is that entities with the Damage component will get processed by the DamageSystem, which will then doll out damage appropriately.  I wanted the actual damage calculations to happen outside of the controller, in their own system.

Then, no matter where they clicked, we're done rendering the attack stuff.

The damage component is pretty simple for now:
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;

public class Damage extends Component {

 public int power;
 public int accuracy;
 public Stats stats;
 
 public Damage(Stats stats) {
  this.stats = stats;
  power = stats.getStrength();
  accuracy = stats.getAgility();
 }
}
I know it's redundant, but since I'm sure it will be heavily tweaked later on for different types of attacks, I'm leaving it like this for now.

The DamageSystem extends EntityProcessingSystem and looks like this:
package com.blogspot.javagamexyz.gamexyz.systems;

import com.artemis.Aspect;
import com.artemis.ComponentMapper;
import com.artemis.Entity;
import com.artemis.annotations.Mapper;
import com.artemis.systems.EntityProcessingSystem;
import com.badlogic.gdx.math.MathUtils;
import com.blogspot.javagamexyz.gamexyz.EntityFactory;
import com.blogspot.javagamexyz.gamexyz.components.Damage;
import com.blogspot.javagamexyz.gamexyz.components.MapPosition;
import com.blogspot.javagamexyz.gamexyz.components.Stats;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.utils.MyMath;

public class DamageSystem extends EntityProcessingSystem {
 @Mapper ComponentMapper<Damage> dm;
 @Mapper ComponentMapper<Stats> sm;
 @Mapper ComponentMapper<MapPosition> mpm;
 
 private GameMap gameMap;
 
 @SuppressWarnings("unchecked")
 public DamageSystem(GameMap gameMap) {
  super(Aspect.getAspectForAll(Damage.class, Stats.class));
  this.gameMap = gameMap;
 }

 @Override
 protected void process(Entity e) {
  Damage damage = dm.get(e);
  Stats stats = sm.get(e);
  MapPosition position = mpm.getSafe(e); // Useful for displaying damage on screen 
  
  // Did the blow hit?
  if (damage.accuracy - stats.getAgility() + MathUtils.random(-1,4) > 0) {
   
   // Compute how much damage it did
   int dmg = MyMath.max(damage.power - stats.getHardiness() / 2 + MathUtils.random(-5,5), 1);
   
   // Update the target's health accordingly
   stats.health -= dmg;
   System.out.println(damage.stats.name + " HIT " + stats.name + "! Damage " + dmg + "\t\t Health: " + stats.health + "/" + stats.maxHealth);
   
   // If the target had a MapPosition, create a damage label showing how much damage was done
   if (position != null) {
    EntityFactory.createDamageLabel(world, ""+dmg, position.x, position.y).addToWorld();
   }
  }
 
  else { // Otherwise they missed
   System.out.println(damage.stats.name + " MISSED " + stats.name +"!");
   
   // Create a damage label of "Miss" to add insult to injury
   if (position != null) {
    EntityFactory.createDamageLabel(world, "MISS", position.x, position.y).addToWorld();
   }
  }
  
  // We've processed the damage, now it's done
  e.removeComponent(damage);
  e.changedInWorld();
 }
 
 @Override
 protected void removed(Entity e) {
  // This is called after the damage gets removed
  // We want to see if the target died in the process
  Stats stats = sm.get(e);
  if (stats.health <= 0) {
   // If so, it's toast!
   gameMap.removeEntity(e.getId());
   world.deleteEntity(e); 
  }
 }
 
 @Override
 protected boolean checkProcessing() {
  return true;
 }
}

It processes entities that have both damage and stats (I figure if they have no stats, they won't be getting damaged!).  In process(), we get the Damage and Stats, and also (if applicable) the MapPosition.  This last part is useful for actually rendering the damage dealt on the screen.

First we need to see if they actually hit them.  My check here is if source_accuracy - target_agility + rand(-1,4) > 0, then they hit.  The damage  dealt = source_power - target_hardiness + rand(-5,5).  If that damage is less than 1, they just deal 1 damage instead.  We update the target's health, and if appropriate, add a damage label.

If the attack missed, so we just add a "missed" label.

At the end, we have finished processing this damage, so we remove it (which calls the removed() method).  In this method, we check to see if the damage killed the entity.  If so, remove it from the world.

Damage Label - Fading Message

The damage label is totally cosmetic, but it's pretty cool.  Let's take a look at it in EntityFactory
 public static Entity createDamageLabel(World world, String label, float x, float y) {
  Entity e = world.createEntity();
  
  e.addComponent(new MapPosition(x,y));
  e.addComponent(new FadingMessage(label,1.2f,0f,1.3f));
  
  return e;
 }
Here we make an entity with MapPosition and a new component FadingMessage.  FadingMessage is a general component I created for giving the player a... well... a fading message.  The arguments are a String for the actual label, a float for the duration (how long it takes to fade away), and floats for horizontal and vertical velocity to it can move.  For our case, it lasts 1.2 seconds, and has a vertical velocity of 1.3 cells per second (notice that's not pixels / seconds, but literally 1.3 hex cells / second).

Here's the code for FadingMessage (which will get processed AND rendered by FadingMessageRenderSystem)
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;

public class FadingMessage extends Component {

 public String label;
 public float duration, currentTime;
 public float vx, vy;
 
 public FadingMessage(String label, float duration) {
  this(label,duration,0,0);
 }
 
 public FadingMessage(String label, float duration, float vx, float vy) {
  this.label = label;
  this.duration = duration;
  this.vx = vx;
  this.vy = vy;
  currentTime = 0f;
 } 
}
Nothing too surprising, it just holds all that info, plus currentTime which stores how long it has been alive.
package com.blogspot.javagamexyz.gamexyz.systems;

import com.artemis.Aspect;
import com.artemis.ComponentMapper;
import com.artemis.Entity;
import com.artemis.annotations.Mapper;
import com.artemis.systems.EntityProcessingSystem;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.blogspot.javagamexyz.gamexyz.components.FadingMessage;
import com.blogspot.javagamexyz.gamexyz.components.MapPosition;
import com.blogspot.javagamexyz.gamexyz.custom.FloatPair;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;

public class FadingMessageRenderSystem extends EntityProcessingSystem {
 @Mapper ComponentMapper<MapPosition> mpm;
 @Mapper ComponentMapper<FadingMessage> fmm;

 private BitmapFont font;
 private SpriteBatch batch;
 private OrthographicCamera camera;
 
 
 @SuppressWarnings("unchecked")
 public FadingMessageRenderSystem(OrthographicCamera camera, SpriteBatch batch) {
  super(Aspect.getAspectForAll(MapPosition.class, FadingMessage.class));
  this.batch = batch;
  this.camera = camera;
 }

 @Override
 protected void initialize() {
  Texture fontTexture = new Texture(Gdx.files.internal("fonts/normal_0.png"));
  fontTexture.setFilter(TextureFilter.Linear, TextureFilter.MipMapLinearLinear);
  TextureRegion fontRegion = new TextureRegion(fontTexture);
  font = new BitmapFont(Gdx.files.internal("fonts/normal.fnt"), fontRegion, false);
  font.setUseIntegerPositions(false);
 }

 @Override
 protected void begin() {
  batch.setProjectionMatrix(camera.combined);
  batch.begin();
  batch.setColor(1, 1, 1, 1);
 }

 @Override
 protected void process(Entity e) {
  MapPosition position = mpm.get(e);
  FadingMessage message = fmm.get(e);
 
  FloatPair drawPosition = MapTools.world2window(position.x, position.y);
  float posX = drawPosition.x - message.label.length() * font.getSpaceWidth();
  float posY = drawPosition.y;
  
  font.setColor(1, 1, 1, 1 - message.currentTime / message.duration);
  font.draw(batch, message.label, posX, posY);

  position.x += message.vx * world.getDelta();
  position.y += message.vy * world.getDelta();
  message.currentTime += world.getDelta();
  
  if (message.currentTime >= message.duration) e.deleteFromWorld();
 }
 
 @Override
 protected void end() {
  batch.end();
 }
}
Here we have a RenderSystem which is actually an EntityProcessingSystem again, so I modeled it after them.  It has its camera and batch, plus a font.  In process() it gets the position and does its best to center it (posX = drawPosition.x - message.label.length() * font.getSpaceLength()).  The color is set to white, and the alpha goes from 1 to 0 from start to finish.  The position is updated (remember, that's not screen position, that's world position - as in what cell it occupies) and currentTime is updated.  Then, if currentTime exceeds duration, the message gets deleted from the world.

So that's all pretty cool - you can now attack someone and have it deal damage (potentially killing them), and the damage is displayed in what I think is a pretty attractive floating/fading message.  Of course, there's nothing stopping you from attacking AGAIN AND AGAIN AND AGAIN until they're dead.  Obviously in a game you only get to attack once, and then it's the next character's turn.

To facilitate that, I created flags in OverworldScreen boolean attacked, boolean moved.  They are initialized to false, and in selectedAttack() and selectedMove(), we first check them:
public void selectedMove() {
 if (moved) return;
 ...
}

public void selectedAttack() {
 if (attacked) return;
 ...
}

In the controllers, if the player successfully moves or attacks, they are then set to true.  Then, in a new method, selectedWait(), we resent them both to false and "processTurn()".  processTurn() is a method which helps establish whose turn it is to move.
 public void processTurn() {
  turnManagementSystem.process();
  activeEntity = unitOrder.get(0);
  activeEntityCell = gameMap.getCoordinatesFor(activeEntity);
  if (activeEntityCell != null) cameraMovementSystem.move(activeEntityCell.x, activeEntityCell.y);
 }

The first bit, turnManagementSystem.process() comes up with a list representing the order units are going to be moving in.  It assumes nothing ever changes - like no units will die - so it must be run each turn to account for updates.  turnManagementSystem.process() updates an Array of ints in OverworldScreen called unitOrder (the order in which units will get their turns).  The next bit, activeEntity = unitOrder.get(0) gets the first unit from the list (the one about to go) and activeEntityCell (which we referenced earlier) gets set here.  The last bit, cameraMovementSystem, moves the camera to be centered on the new active entity (we'll talk about it next).

TurnManagementSystem

Before looking at the code, let's discuss how it works.  The idea is that each unit has something called Action Points.  Every turn, a unit's action points are incremented by their speed, and once it hits 100, that unit gets a turn.  But what if nobody has reached 100 action points?

In this case, we ask "How many turns will each player need before reaching 100 points?"  This can be calculated by taking (100 - actionPoints) / speed.  (Note: If an entity has over 100 action points, this will return a negative number of turns - in this case we just want to make it say 0).

Then, we look at the player with the fewest turns needed before reaching 100 action points.  We just "skip" those turns, and instead of incrementing everyone's action points by their speed, we increment it by speed*turnsSkipped.

We also need to remember who went, because they should have their action points reset to 0 next time.

Now let's look at how it works in an example case.  We have two units: A and B.  A has speed 5, B has speed 4.  To start with, both have 0 action points.  On the first turn, we calculate that A needs (100-0)/5 = 20 turns, and B needs (100-0)/4 = 25 turns.  Because A is closer to moving, we "skip" 20 turns, bringing them up to 100 action points, and B up to 4*20 = 80 action points.  When A moves,
their action points ought to reset to 0, then we repeat.

A now needs (100-0)/5 = 20 turns, but B only needs (100-80)/4 = 5 turns.  So we skip those 5 turns, giving B 100 action points, and A 5*5 = 25 action points.  B moves and is reduced to 0 points.

Now A needs (100-25)/5 = 15, B needs (100-0)/4 = 25 turns.  We skip 15, and A is at 100, B is at 4*15 = 60.  A moves and is reduced to 0 points.

Okay, well that's all well and good, but it may also be nice to project out the next x turns to let the player know who's moving when.  We can do that too.  In the step where we figure out who moves next, we can just repeat this process over and over again, pretending like we're updating units' action points, and simulating what it will really look like.  So without further ado, here's TurnManagementSystem:
package com.blogspot.javagamexyz.gamexyz.systems;

import com.artemis.Aspect;
import com.artemis.ComponentMapper;
import com.artemis.Entity;
import com.artemis.EntitySystem;
import com.artemis.annotations.Mapper;
import com.artemis.utils.ImmutableBag;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.components.Stats;
import com.blogspot.javagamexyz.gamexyz.utils.MyMath;

public class TurnManagementSystem extends EntitySystem {
 @Mapper ComponentMapper<Stats> sm;

 private Array<Integer> unitOrder;
 private Array<Sorter> sorter;
 
 @SuppressWarnings("unchecked")
 public TurnManagementSystem(Array<Integer> unitOrder) {
  super(Aspect.getAspectForAll(Stats.class));
  
  this.unitOrder = unitOrder;
  sorter = new Array<Sorter>();
 }

 @Override
 protected void processEntities(ImmutableBag<Entity> entities) { 
  // Get the ID of the last entity to move - because they will get reset to 0
  // To be safe, first assume that this is the first turn, so that oldEntity = -1
  int oldEntity = -1;
  
  // We'll use this to store how many turns got skipped for the next "real" turn
  float turnsSkipped = 1f;
  
  // Then, if there is a list for unitOrder, the first entity was the one that
  // moved last turn
  if (unitOrder.size > 0) oldEntity = unitOrder.get(0);
  
  // Now we just clear the unit list because it needs to be recalculated
  unitOrder.clear();
  sorter.clear();
  
  // add the entity to the sorter array
  for (int i=0; i<entities.size(); i++) {
   Entity e = entities.get(i);
   Stats stats = sm.get(e);
   
   // Earlier we stored who moved last turn as oldEntity.  Evidently they
   // just moved, so we'll reset their actionPoints to 0.
   if (e.getId() == oldEntity) stats.actionPoints = 0;
   
   sorter.add(new Sorter(e.getId(), stats.actionPoints, stats.getSpeed(), stats.getAgility()));
  }
  
  // Come up with a list of the next 30 entities to move
  for (int i = 0; i < 30; i++) {
   
   // Sort the list based on turnsSkipped
   sorter.sort();
   
   // The first unit in the sorted list is the next unit to get a turn
   unitOrder.add(sorter.get(0).id);
   
   // In case this is the 1st time we're going through the loop, that means
   // we're looking at the unit that actually gets to move this turn.  Note
   // how many turns it had to skip, because that's what we will ACTUALLY
   // use to increment unit's actionPoints.
   if (i == 0) turnsSkipped = sorter.get(0).turnsSkipped;
   
   // Update everyone's actionPoints
   for (int index = 1; index < sorter.size; index++) {
    Sorter s = sorter.get(index);
    s.actionPoints += (int)(sorter.get(0).turnsSkipped * s.speed);
    s.calculateTurnsSkipped();
   }
   
   // The first character in the array just had a turn (real or inferred),
   // so we'll set their actionPoints to 0. 
   sorter.get(0).actionPoints = 0;
   sorter.get(0).calculateTurnsSkipped();
  }
  
  // Now we've made a list of the next 30 moves, but we didn't actually update
  // any of the real entity's action points (that was all projecting into the
  // future).  Now we'll increment them all based on the turnsSkipped of the
  // unit that actually gets to move (
  for (int i=0; i<entities.size(); i++) {
   Entity e = entities.get(i);   
   Stats stats = sm.get(e);
   stats.actionPoints += stats.getSpeed() * turnsSkipped;
  }
 }
 
 @Override
 protected boolean checkProcessing() {
  return true;
 }
  
 private static class Sorter implements Comparable<Sorter> {

  public Sorter(int id, int actionPoints, int speed, int agility) {
   this.id = id;
   this.actionPoints = actionPoints;
   this.speed = speed;
   this.agility = agility;
   calculateTurnsSkipped();
  }
  
  int actionPoints;
  int speed;
  int agility;
  int id;
  float turnsSkipped;
  
  @Override
  public int compareTo(Sorter other) {
   
   // First try comparing how many "turns" each unit has to wait before its next turn
   if (turnsSkipped > other.turnsSkipped) return 1;
   if (turnsSkipped < other.turnsSkipped) return -1;
   
   // They are equal, try speed next
   if (speed < other.speed) return 1;
   if (speed > other.speed) return -1;
   
   // Speed failed, so let's check agility
   if (agility < other.agility) return 1;
   if (agility > other.agility) return -1;
   
   // Barring all that, screw it
   return 0;
  }
  
  public void calculateTurnsSkipped() {
   turnsSkipped = (float)MyMath.max(0, 100-actionPoints)/(float)speed;
  }
  
 }
}

Notice it extends EntitySystem, and processes all entities in the world with Stats.  In its constructor, it gets passed the Array<Integer> unitOrder from OverworldScreen, and since Java arguments get passed by reference, anything that happens here happens in OverworldScreen too.

I wanted to use Array.sort(), which means I needed a temporary class which implements Comparable to hold the relevant data.  I (in a most uninspired decision) called this class Sorter, and the Array which will hold this stuff to sort is Array<Sorter> sorter.  It holds information I think could be relevant to deciding who goes first.  In compareTo(), we first compare turnsSkipped.  If that's equal, we compare speed (the faster one gets to go first).  If that's equal too, as a last resort we compare agility.  If those are all equal, we admit the characters are equal and so the .sort() method just gives preference to whichever one it sees first.

No let's look at processEntities().  The first thing we do is check to see if there is already a unitList.  If there isn't, it means this is the first time we're running this.  If there is, it means we ran it once before (to line everyone up at first), but since then somebody has moved.  We need to remember who that was, so we store that in oldEntity.

Next we loop over the entities to put them in Array<Sorter> sorter.  In this loop, we grab oldEntity (the one which moved last turn) and set their actionPoints to 0.

We then simulate the next 30 "real" turns.  If we're going through the loop the first time, that means it represents the upcoming turn, so we specifically store how many turns had to be skipped to get here.  Other than that, we update all the "dummy" Sorters' actionPoints, and do it again and again.  After we get out of that, we need to process the "real" turn, so we loop over the entities again and update all their actionPoints based on the turnsSkipped for the next turn.

CameraMovementSystem

Now, characters take their turn in which they can move once, attack once, and that's it.  Of course, it can be a HUGE pain in the butt to actually figure out whose turn it is!!!  I made a system to smoothly move the camera to new locations.  My rough idea is that I wanted it to move slowly for a second, speed up to some maximum speed halfway there, then slow down again to a nice, gentle stop.  In other words, I wanted each coordinate to look something like this:
I just needed to know how to adjust the speed to make sure it gets from point A to point B correctly.  For that I had to design a crappy heuristic for how long I wanted the transition to take before I could do that - I won't talk about it too much, but it's short for nearby cells, longer for farther away cells, but as you get farther and farther, there's an economy of scale where moving 30 cells takes less than twice the time to move 15 cells.

I decided I would always try to process CameraMovementSystem, but it would just return unless it had a destination.  The biggest problem I ran into was determining which cell the camera started out focused on.  At first I used window2world(x,y,camera), but that doesn't actually work.  That get's the cell a user clicks on from "window space", but those coordinates have to be unprojected by the camera to fall into the actual coordinate system for the game (not just the coordinate system for the window).  But the camera's position isn't in window space, it's in the natural coordinate system for the game - what I will now call "libgdx space".  The code for libgdx2world(float x, float y) is the exact same as window2world(float x, float y, camera), but it doesn't use a camera to unproject the coordinates into libgdx space - it assumes the coordinates are already there.

Here's the code for CameraMovementSystem:
package com.blogspot.javagamexyz.gamexyz.systems;

import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.blogspot.javagamexyz.gamexyz.custom.FloatPair;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;

public class CameraMovementSystem {
 private float t;
 private float  x0, x1, y0, y1;
 private OrthographicCamera camera;
 private float T;
 private boolean active;
 
 public CameraMovementSystem(OrthographicCamera camera) {
  this.camera = camera;
  active = false;
 }
 
 public void move(int x1, int y1) {
  Vector3 position = camera.position;
  x0 = position.x;
  y0 = position.y;
  FloatPair p = MapTools.world2window(x1, y1);
  this.x1 = p.x;
  this.y1 = p.y;
  t=0;
  Pair start = MapTools.libgdx2world(x0, y0);
  
  // d is used to calculate how long it will take to get to the target cell.
  // If it is close, d is small - if it is far, d is large
  // Very close by, d is similar to how many cells away it is
  // For longer distances, it grows as sqrt(distance)
  float d = (float)Math.sqrt(MapTools.distance(start.x, start.y, x1, y1) + 0.25) - 0.5f;
  T = 0.4f + d/4f;
  active = true;
 }
 
 public void process(float delta) {
  if (!active) return;
  
  float vx, vy;
  float Ax = 6*(x1-x0)/(T*T);
  float Ay = 6*(y1-y0)/(T*T);
  vx = Ax*(t)*(1-t/T);
  vy = Ay*(t)*(1-t/T);
  
  camera.translate(new Vector2(vx*delta,vy*delta));
  
  t += delta;
  if (t > T) {
   active = false;
  }
 }
}

In OverworldScreen, you just need to make sure to initialize it and process it every time.  I decided to call cameraMovementSystem.move(x,y) every time I process a turn to focus on the new character, as well as every time the player moves a character, to focus on their destination.  I did the first in a method called processTurn() in OverworldScreen which basically runs turnManagementSystem, stores the ID and location of the active entity, then runs cameraMovementSystem.move().

So that's it for this time - there was a little code I didn't exactly specify but I think it should be clear when/where/how to process these new parts.  But as a reminder, be sure to check out the code from the repository to stay up to date.

You have gained 200 XP.  Progress to Level 4: 450 / 700

Friday, April 5, 2013

User Interface: Menus using libgdx scene2dui

UPDATE:  I have extended this tutorial to a "part 2" here in which I develop much more interesting menus, as well as discuss Skins.  For a comparison, here's a look at the Menu we finish developing here:


And here's a static glimpse of how it ends up in the next part:

I would still recommend starting here, because I will assume that you are at least familiar with how to use the widgets, ChangeListeners, etc...

I would never describe myself as an actual programmer, just somebody who knows how to program.  And I wouldn't even go that far for considering myself as an artist.  I don't know diddly squat about art.

This all came together to make developing a menu system extremely painful.  I found myself not wanting to work on it.  I found myself wishing I was doing almost anything else.  As a consequence, I did almost nothing on it, and what little I did I hated doing.

Finally I decided I just have to do the bare minimum and will move on.  If this game starts coming together later, I will freakin' deal with it then...

I ended up using the table-layout project combined with libgdx's scene2dui.  Another, much better, general tutorial for this stuff is over at Andrew Steigert's blog.  I'm going to talk about a few more features than he touches on, and show how I implemented it all in JaveGameXYZ.

First, scene2dui is a pretty cool set of tools, or widgets, that you can place to allow you to handle different kinds of user input.  They have
  • Button
    • Empty "Button" (just called Button)
    • TextButton
    • ImageButton
    • ButtonGroup
  • Image
  • TextLabel
  • TextField
  • CheckBox
  • SelectBox (drop down lists)
  • Slider
  • Window (like an actual MS Windows looking box with a title bar)
  • ScrollPane (like a window with a scrollbar on the side)
  • SplitPane (like HTML frames)
  • Touchpad (an onscreen joystick)
  • Tree (a collapsible list)
  • Dialog (a window with some place to add text, and some place to add buttons - like "OK" or whatever)
Each of these has built in convenience methods, like the ability to check when a button is pressed, click-drag to scroll along a scrollpane (including the fancy smartphone-esque feature where you touch-drag beyond the end of a scrollpane, and it goes a little bit but snaps back to where it should be as soon as you let go - you know what I mean), etc...

They also each have a "style" which can (when appropriate) set things like the font, the background image, etc.  These styles have different settings so that you can assign separate images for when a button is just sitting there, being hovered over, clicked, etc...

Furthermore, most widgets can have other widgets added to them - like you can add a label to a button, or you can add anything to a ScrollPane, etc.

You can also lay these widgets out manually, or you can use the table-layout package to make your life a little easier.  Whether you add them to a table or not, you must add them (or the table, or whatever) to a Stage.  In the libgdx scene2d language, each of these widgets is an "Actor", and actors must be placed on a "Stage".

My first thought was to build a menu with the following stucture:
ScrollPane {
   Table {
      Button
      Button
      ...
   }
}

I would assign a default size to the ScrollPane, and if the table became too big (hopefully only too long) the user would be able to scroll along it.  They could also click any menu item, which would be a button without really looking like a typical beveled edge button box, and then it would process whatever they had clicked on.

This would all fit inside a Stage held in OverworldScreen, and depending on the state of the game, I would display the stage and process user clicks.

I also wanted to be able to build a menu individually depending on which entity was selected.  For instance, some entities might not be able to move, or might have different actions available to them.  I wanted the menu to reflect specifically what actions they could perform.

I also wanted them to look maybe kind of cool - like have a nice texture background, similar to Final Fantasy Tactics.


Notice that both the nameplate and menu have a nice, subtle, textured background + beveled edges.  Tasteful.

Well what I got was a far cry from any of that, but the basic architecture was about right.

First things first, check out the updated OverworldScreen.java
Update: Blogger (or probably "I") somehow buggered the HTML for this set of code up, and consequently most of it got lost.  By now, OverworldScreen has changed a bit, and it would be a pain in the but to try to recall exactly what it had been.  I will post the full thing here - the parts you are interested in are selectedMove(), and append/set/prepend/removeInputSystems().  This is actually the OverworldScreen used for the next update, so there will be some bits that don't actually work for you yet, but those methods are the ones I wanted you to focus on now.

package com.blogspot.javagamexyz.gamexyz.screens;
package com.blogspot.javagamexyz.gamexyz.screens;

import com.artemis.Entity;
import com.artemis.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.EntityFactory;
import com.blogspot.javagamexyz.gamexyz.GameXYZ;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;
import com.blogspot.javagamexyz.gamexyz.renderers.MapHighlighter;
import com.blogspot.javagamexyz.gamexyz.renderers.MapRenderer;
import com.blogspot.javagamexyz.gamexyz.screens.control.overworld.OverworldAttackController;
import com.blogspot.javagamexyz.gamexyz.screens.control.overworld.OverworldDragController;
import com.blogspot.javagamexyz.gamexyz.screens.control.overworld.OverworldMovingController;
import com.blogspot.javagamexyz.gamexyz.screens.control.overworld.OverworldSelectorController;
import com.blogspot.javagamexyz.gamexyz.systems.CameraMovementSystem;
import com.blogspot.javagamexyz.gamexyz.systems.DamageSystem;
import com.blogspot.javagamexyz.gamexyz.systems.FadingMessageRenderSystem;
import com.blogspot.javagamexyz.gamexyz.systems.HudRenderSystem;
import com.blogspot.javagamexyz.gamexyz.systems.MovementSystem;
import com.blogspot.javagamexyz.gamexyz.systems.SpriteRenderSystem;
import com.blogspot.javagamexyz.gamexyz.systems.TurnManagementSystem;

public class OverworldScreen extends AbstractScreen {
 
 public static GameMap gameMap;
 private OrthographicCamera hudCam;
 
 private SpriteRenderSystem spriteRenderSystem;
 private HudRenderSystem hudRenderSystem;
 private FadingMessageRenderSystem fadingMessageRenderSystem;
 private TurnManagementSystem turnManagementSystem;
 
 private MapRenderer mapRenderer;
 private MapHighlighter mapHighlighter;
 
 public int selectedEntity;
 public int activeEntity;
 public Pair activeEntityCell;
 public Array<Pair> highlightedCells;
 public boolean renderMap;
 public boolean renderMovementRange;
 public boolean renderAttackRange;
 
 public InputMultiplexer inputSystem;

 public Stage stage;
 public boolean handleStage;
 
 public int cursor;
 
 public OverworldDragController controllerDrag;
 public OverworldSelectorController controllerSelector;
 public OverworldMovingController controllerMoving;
 public OverworldAttackController controllerAttack;
 
 public Array<Integer> unitOrder;
 public boolean moved = false;
 public boolean attacked = false;
 
 public CameraMovementSystem cameraMovementSystem; 
 private boolean firstShow = true;
 
 public OverworldScreen(GameXYZ game, SpriteBatch batch, World world) {
  super(game,world,batch);

  cameraMovementSystem = new CameraMovementSystem(camera);
  activeEntityCell = new Pair(0,0);
     gameMap  = new GameMap();
     
     unitOrder = new Array<Integer>();
     setupWorld();
     setupInputSystems();
     fillWorldWithEntities();
     
     
     selectedEntity = -1;

     renderMap = true;
     renderMovementRange = false;
     renderAttackRange = false;
     
     stage = new Stage();
     handleStage = false;
     
     
 }
 
 @Override
 public void render(float delta) {
  super.render(delta);
  
  if (firstShow) {
   cameraMovementSystem.move(activeEntityCell.x, activeEntityCell.y);
   firstShow = false;
  }
  
  if (renderMap) {
   mapRenderer.render();
   spriteRenderSystem.process();
  }
  
  if (renderMovementRange) {
   mapHighlighter.render(highlightedCells,0f,0f,0.2f,0.3f);
  }
  
  fadingMessageRenderSystem.process();
  
  if (renderAttackRange) {
   mapHighlighter.render(highlightedCells,0.5f,0f,0f,0.3f);
  }
  
  if (handleStage) {
   stage.act(delta);
   stage.draw();
  }
  
  hudRenderSystem.process();
  
  cameraMovementSystem.process(delta);
 }

 @Override
 public void show() {
  
 }
 
 @Override
 public void resize(int width, int height) {
  super.resize(width, height);
  hudCam.setToOrtho(false, width, height);
  stage.setViewport(width, height, true);
 }

 @Override
 public void hide() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void pause() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void resume() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void dispose() {
  // TODO Auto-generated method stub
  world.deleteSystem(hudRenderSystem);
  world.deleteSystem(spriteRenderSystem);
  world.deleteSystem(world.getSystem(MovementSystem.class));
 }
 
 public void selectedMove() {
  if (moved) return;
  removeInputSystems(stage);
  appendInputSystems(controllerMoving);
  Entity e = world.getEntity(selectedEntity);
  Movable movable = e.getComponent(Movable.class);
  highlightedCells = gameMap.pathFinder.getReachableCells(activeEntityCell.x, activeEntityCell.y, movable);
  renderMovementRange = true;
  handleStage = false;
  stage.clear();
 }
 
 public void selectedAttack() {
  if (attacked) return;
  setInputSystems(controllerDrag,controllerAttack,controllerSelector);
  highlightedCells = MapTools.getNeighbors(activeEntityCell.x, activeEntityCell.y);
  renderAttackRange = true;
  handleStage = false;
  stage.clear();
 }
 
 public void selectedWait() {
  setInputSystems(controllerDrag, controllerSelector);
  processTurn();
  handleStage = false;
  stage.clear();
  selectedEntity = -1;
  
  moved = attacked = false;
 }
 
 public void appendInputSystems(InputProcessor... processors) {
  for (int i = 0; i < processors.length; i++) inputSystem.addProcessor(processors[i]);
 }
 
 public void setInputSystems(InputProcessor... processors) {
  inputSystem = new InputMultiplexer(processors);
  Gdx.input.setInputProcessor(inputSystem);
 }
 
 public void prependInputSystems(InputProcessor... processors) {
  InputMultiplexer newMultiplexer = new InputMultiplexer();
  
  for (int i = 0; i < processors.length; i++) {
   newMultiplexer.addProcessor(processors[i]);
  }
  
  for (InputProcessor p : inputSystem.getProcessors()) {
   newMultiplexer.addProcessor(p);
  }
  
  inputSystem = newMultiplexer;
  Gdx.input.setInputProcessor(inputSystem);
 }
 
 public void removeInputSystems(InputProcessor... processors) {
  for (int i = 0; i < processors.length; i++) {
   inputSystem.removeProcessor(processors[i]);
  }
 }
 
 private void setupWorld() {
  hudCam = new OrthographicCamera();
     
     mapRenderer = new MapRenderer(camera,batch,gameMap.map);
     mapHighlighter = new MapHighlighter(camera, batch);
     
     world.setSystem(new MovementSystem(gameMap));
     world.setSystem(new DamageSystem(gameMap));
     spriteRenderSystem = world.setSystem(new SpriteRenderSystem(camera,batch), true);
     hudRenderSystem = world.setSystem(new HudRenderSystem(hudCam, batch),true);
     fadingMessageRenderSystem = world.setSystem(new FadingMessageRenderSystem(camera,batch),true);
     turnManagementSystem = world.setSystem(new TurnManagementSystem(unitOrder), true);
     
     
     world.initialize();
     System.out.println("The world is initialized");
     
 }
 
 private void setupInputSystems() {
  controllerSelector = new OverworldSelectorController(camera,world,gameMap,this);
     controllerMoving = new OverworldMovingController(camera,world,gameMap,this);
     controllerDrag = new OverworldDragController(camera);
     controllerAttack = new OverworldAttackController(camera,world,gameMap,this);
     
     setInputSystems(controllerDrag, controllerSelector);
 }
 
 private void fillWorldWithEntities() {
  int x, y;
     for (int i=0; i<5; i++) {
      do {
       x = MathUtils.random(MapTools.width()-1);
       y = MathUtils.random(MapTools.height()-1);
      } while (gameMap.cellOccupied(x, y));
      EntityFactory.createNPC(world,x,y,gameMap).addToWorld();
     }
     
     Entity e = EntityFactory.createCursor(world);
     cursor = e.getId();
     e.addToWorld();
     
     // You have to process the world once to get all the entities loaded up with
     // the "Stats" component - I'm not sure why, but if you don't, the bag of entities
     // that turnManagementSystem gets is empty?
     world.process();
     // Running processTurn() once here initializes the unit order, and selects the first
     // entity to go
     processTurn();
 }
 
 public void processTurn() {
  turnManagementSystem.process();
  activeEntity = unitOrder.get(0);
  activeEntityCell = gameMap.getCoordinatesFor(activeEntity);
  if (activeEntityCell != null) cameraMovementSystem.move(activeEntityCell.x, activeEntityCell.y);
 }
}
It has a Stage plus a boolean handleStage.  Down in render(), if handleStage is true, it processes the stage (stage.act(delta)) and then draws it.  Also, at the end, I added a few convenience methods to try to streamline the input processor management.  appendInputSystems() adds an array of InputProcessors to the end.  setInputSystems() replaces the current input system with all the ones given.  prependInputSystems() puts an array of InputProcessors at the front of the multiplexor.

Above that, on lines 171-181, I added a method which will be called if the user actually clicks on "Move" from the menu.  We'll get to when that happens, but for now, just notice that it removes the stage from the input multiplexer, it adds the movement controller, and finds and displays all the reachable cells, then finally clears the stage out and sets handleStage to false (we want the menu to disappear now that we've made our selection).

In OverworldDefaultController, instead of assuming the user always means to "Move", we generate a menu.  To do that, we tell the Screen to handleStage, we clear the stage (in case there's some old menu hanging around), and add a new menu.
 public boolean touchUp(int screenX, int screenY, int pointer, int button) {
  
  if (dragging) {
   dragging = false;
   return true;
  }
  
  // Get the coordinates they clicked on
  Vector3 mousePosition = new Vector3(Gdx.input.getX(), Gdx.input.getY(),0);
  Pair coords = MapTools.window2world(mousePosition.x, mousePosition.y, camera);
   
  // Check the entityID of the cell they click on
  int entityId = gameMap.getEntityAt(coords.x, coords.y);
  
  // If it's an actual entity (not empty) then "select" it (unless it's already selected)  
  if ((entityId > -1) && (entityId != screen.selectedEntity)) {
   
   // Now select the current entity
   screen.selectedEntity = entityId;
   EntityFactory.createClick(world, coords.x, coords.y, 0.1f, 4f).addToWorld();
   
   screen.handleStage = true; 
   screen.stage.clear();
   screen.stage.addActor(MenuBuilder.buildMenu(screen));
   
   screen.setInputSystems(screen.stage,this);
   screen.renderMovementRange = false;
   screen.reachableCells = null;
 
   return true;
  }
Handling the menu input is easy because a Stage is a special kind of InputProcessor, so we set our multiplexor to stage and this.  That means any input will be processed by the menu first (if it can).

However, if the user click-drags off the menu, or selects any other entity they can see the game will process those clicks.  In case a user had already selected "Move", and THEN click on another entity (which is still valid), that entity will be selected and a menu will appear for them. However, because the movement range for the first entity is still being shown, we have to shut off renderMovementRange, and for good measure, clear the reachable cells we had found (if nothing else, down the line we might get a null-pointer exception if we reference the reachableCells when we shouldn't have - setting it to null might make those problems easier to detect).

This wasn't a problem before, because every time you selected an entity, it was assumed that you wanted to move, so reachableCells were recalculated and shown.  However, now, we aren't jumping to conclusions about that, so reachableCells are no longer automatically recalculated.

The MenuBuilder.buildMenu(screen) command will hopefully someday look like MenuBuilder.buildMenu(screen, entity) because different entities should have different options on their menu.  For now, I don't care.  Let's take a look at MenuBuilder:
package com.blogspot.javagamexyz.gamexyz.ui;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Button.ButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.blogspot.javagamexyz.gamexyz.screens.OverworldScreen;

public class MenuBuilder {

 public static ScrollPane buildMenu(final OverworldScreen screen) {
  Texture fontTexture = new Texture(Gdx.files.internal("fonts/irisUPC.png"));
  fontTexture.setFilter(TextureFilter.Linear, TextureFilter.MipMapLinearLinear);
  TextureRegion fontRegion = new TextureRegion(fontTexture);
  BitmapFont font = new BitmapFont(Gdx.files.internal("fonts/irisUPC.fnt"), fontRegion, false);
  font.setUseIntegerPositions(false);  
  
  ButtonStyle style = new ButtonStyle();
  style.up= new TextureRegionDrawable(new TextureRegion(new Texture(Gdx.files.internal("textures/misc/button_down.png"))));
  style.unpressedOffsetX = 5f;
  style.pressedOffsetX = style.unpressedOffsetX + 1f;
  style.pressedOffsetY = -1f;
  
  
  LabelStyle lStyle = new LabelStyle();
  lStyle.font = font;
    
  Table mainTable = new Table();
  mainTable.defaults().width(80);
  
  ScrollPane scrollPane = new ScrollPane(mainTable);
  scrollPane.setFillParent(false);
  scrollPane.setX(-10);
  scrollPane.setY(screen.stage.getHeight()-250);
  
  Button b1 = new Button(style);
  b1.add(new Label("Move",lStyle));
  b1.left();
  b1.addListener(new ChangeListener() { 
   public void changed(ChangeEvent event, Actor actor) {
    screen.selectedMove();
   }
  });
  mainTable.add(b1);
  mainTable.row();
  
  Button b2 = new Button(style);
  b2.add(new Label("Attack",lStyle));
  b2.left();
  b2.addListener(new ChangeListener() { 
   public void changed(ChangeEvent event, Actor actor) {
    System.out.println("Attack");
   }
   });
  mainTable.add(b2);
  mainTable.row();
  
  // Make a bunch of filler buttons
  for (int i = 0; i < 10; i++) {
   Button b3 = new Button(style);
   b3.add(new Label("Wait",lStyle));
   b3.left();
   b3.addListener(new ChangeListener() { 
    public void changed(ChangeEvent event, Actor actor) {
     System.out.println("Wait");
    }
    });
   mainTable.add(b3);
   mainTable.row();
  }
  
  return scrollPane;
 } 
}

The first block of code is me making a BitmapFont out of a texture and font reference file.  I made this BitmapFont using the HIERO tool which you can get and learn about.  I just used a system font, but you can use a TrueTypeFont (.ttf) file instead.  It's got lots of cute options for effects - I think I made mine bold, and white.

The next block sets the style I'm going to use for my buttons.  I thought at first to go with TextButtons, but was annoyed that the text was centered on each button.  I wanted my menu layout to be left-aligned, and I couldn't find the right TextButton property to force that.  So instead I went with regular Buttons, and each button also has a Label actor in it, moved to the left of the button. So my button style doesn't have much.

I set a background image as style.up (which can't be a Texture or TextureRegion, but must be a Drawable - or maybe a NinePatch, which is a type of image where the corners maintain a fixed aspect ratio, but the middle parts stretch so you can fill however large an area you need, without losing an attractive/high-res bevel edge, or rounded corner).  For now I just made a png with 1 gray pixel.  The unpressedOffsetX gives the labels a little buffer on the left sides of words, and the pressed offsets make it easier to see what you actually pressed.

Below that a make the Label style, which is really just the font.

Then I declare the Table, and make it so it's children have a default width of 80, so all the buttons will be the same size.

Next I make the ScrollPane.  This is the main container that everything is being put into.  It won't take up the entire screen, and it's position is hardcoded (the contents show up under the HUD stuff now).

Next we make our first Button - I lovingly called it b1.  It gets the button style I had previously defined (offsets, background, etc...).  I also add a new Label to it with the text "Move" and the given style.  Ultimately I'd like to only add this option if the Entity selected actually has the Movable component.  b1.left() makes it so the label aligns to the left (which then gets pushed a little to the right by unpressedOffsetX).

The main thing is adding the changeListener().  This is better than clickListener() because it potentially allows users to fire it using methods other than clicks.  In the changeListener(), we call screen.selectedMove() - for convenience I like keeping the code on the screen instead of the menu generator.  Notice - screen had to be passed as a final variable for us to be allowed to use it inside the changeListener.  If you go to the constructor and remove the final modifier from final OverworldScreen screen it won't work.  Since we're not changing screen, that shouldn't be a problem.

Next I make a button for "Attack", then a boatload of buttons that all say "Wait".  I did that just to see how well it handled the scrolling, which is fun to play with.  It won't have a scrollbar, if we wanted that we would have to make a ScrollPaneStyle style variable, and set an image for the scrollbar, and tell it to display it, and decide if we wanted it fading out when the users mouse was gone for a few seconds, and whatever else.  But I don't Really want it.

The buildMenu() method returns the scrollPane filled with the table and buttons (which don't look like buttons), which OverworldDefaultController then adds to screen.stage and the input multiplexor.

I'd like to someday actually make a skin file to take care of all the styles more easily, and play with NinePatches.  One thing I noticed is that background images stretch, with no anti-aliasing.  Thus, if we use a background that's just a 2x2 pixel square, each pixel a different shade, it doesn't stretch up with a nice gradient effect or anything.  It just looks like a big stupid 4 color flag.  What I'd like to do to get those FFT-esque menus with their nice texture, is design a texture that will repeat-fill the menu, not stretch.  I don't really know if that is possible here.

It's a pretty hideous menu, and not at all specialized to different units' capabilities yet, but at least it's a start and I'm dying to move on!  

Woooooo... I guess.

You have gained 50 XP. Progress to Level 4: 250/700