Saturday, March 23, 2013

Player Movement and User Input (Level 3)

The goal of this post is to expand to add the following features:
  • Players select an entity by clicking on it
  • Once an entity is selected, the tiles it can reach are highlighted
  • If a player selects one of the highlighted cells, the entity smoothly follows a path from the source tile to the target.  Afterwords* the player is deselected
  • If a player clicks anywhere not highlighted, the entity is deselected and the highlighted cells go away
*The idea is that it happens afterwords.  There is currently a (probably not very bad) bug where before the entity has reached its goal, the player can select that target cell causing the entity to be reselected before reaching it.  This has problems if the player then clicks another cell to move to before the entity has finished its first path, and while the entity will move there, its position will still be the original target cell (I know why it happens, and will discuss it in the following code.)

I'll start by talking about how I handled the smooth movement.  We have two components related to motion:
  • Movable
  • Movement
Movement is a temporary component that actually holds the  path the entity is following. 
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;
import com.blogspot.javagamexyz.gamexyz.pathfinding.Path;

public class Movement extends Component {
 
 public Path path;
 public float elapsedTime;
 
 public Movement(Path path) {
  this.path = path;
  elapsedTime = 0;
 }

}

It also has a field called elapsedTime which will be used to pace the smooth movement animation.

Movable is the more general component which entities will have if they can be moved.  It stores information such as what kinds of tiles the unit can move across, how far it can move, and how quickly it completes the movement animation.
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;

public class Movable extends Component {
 
 public float energy;  // Roughly how far can you go?
 public float slowness;  // How many seconds it takes to slide from 1 tile to an adjacent
 
 public boolean[] terrainBlocked;
 public float[] terrainCost;
 
 public Movable(float energy, float slowness) {
  this.energy = energy;
  this.slowness = slowness;
  
  terrainBlocked = new boolean[9];
  terrainCost = new float[9];
  
  for (int i = 0; i < terrainBlocked.length; i++) {
   terrainBlocked[i] = false;
   terrainCost[i] = 1.5f*Math.abs(i-4)+1;
  }
 }

}


Line 7 holds the total accumulated cost this entity can move across (sum of all tile costs for a given path).  Line 8 holds how many seconds it takes for the animation to move the entity by one tile.  For each terrain type, lines 10-11 tell the entity if it can move to that terrain, and how much energy it costs to do so.  For now in the constructor I say that units can move on all cells, and have a cost given by the function on line 22 (high cost for the extremes like deep water or mountain peaks, low cost for midlands like grass).

I created an "animation" system called MovementSystem to handle the smooth animation.
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.blogspot.javagamexyz.gamexyz.components.MapPosition;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.components.Movement;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.pathfinding.Path;

public class MovementSystem extends EntityProcessingSystem {
 @Mapper ComponentMapper<Movement> mm;
 @Mapper ComponentMapper<MapPosition> pm;
 @Mapper ComponentMapper<Movable> movem;
 
 GameMap gameMap;
 
 @SuppressWarnings("unchecked")
 public MovementSystem(GameMap gameMap) {
  super(Aspect.getAspectForAll(Movement.class, MapPosition.class, Movable.class));
  this.gameMap = gameMap;
 }

 @Override
 protected void inserted(Entity e) {
  Path path = mm.get(e).path;
  
  // If the path was null (somehow) remove the movable component and get out of here!
  if (path == null) {
   e.removeComponent(mm.get(e));
   e.changedInWorld();
  }
  
  // As far as the gameMap is concerned, move the entity there right away
  // (The animation is just for show)
  else gameMap.moveEntity(e.getId(), path.getX(0), path.getY(0));
  
  // Here we can also change the NPC's animation to a walking one 
 }
 
 @Override
 protected void process(Entity e) {
  Movement movement = mm.get(e);
  MapPosition pos = pm.get(e);
  
  // Get the speed with which we move
  float slowness = movem.get(e).slowness;
  
  // Read the path and get it's length
  Path path = movement.path;
  int size = path.getLength();
  
  // Calculate what step we are on (e.g. cell_0 to cell_1, cell_1 to cell_2, etc...)
  int step = (int)(movement.elapsedTime/slowness);
  
  // Check to see if they've reached the end / gone beyond)
  if (size - 2 - step < 0) {
   // At the end of the day, no matter what, make sure the entity ended up where
   // it belonged.
   pos.x = path.getX(0);
   pos.y = path.getY(0);
   
   // Remove the movement component and let them be on their way
   e.removeComponent(movement);
   e.changedInWorld();
   return;
  }
  
  // Otherwise we must still be on the way
  // Get the coordinates of cell_i and cell_(i+1)
  int x0 = path.getX(size - 1 - step);
  int y0 = path.getY(size - 1 - step);
  
  int x1 = path.getX(size - 2 - step);
  int y1 = path.getY(size - 2 - step);
  
  // Determine how close we are to reaching the next step
  float t = movement.elapsedTime/slowness - step;
  
  // Set position to be a linear interpolation between these too coordinates
  pos.x = x0 + t * (x1-x0);
  pos.y = y0 + t * (y1-y0);
  
  // Increase the time animation has been running
  movement.elapsedTime += Gdx.graphics.getDeltaTime(); 
 }
 
 @Override
 protected void removed(Entity e) {
  // Here we can reset the entity's animation to the default one
 }
 
 @SuppressWarnings("unused")
 private void changeStep(int x0, int y0, int x1, int y1) {
  // Here we can maybe change the animation based on which direction the npc
  // is moving.  Call MapTools.getDirectionVector(x0,y0,x1,y1) to see
  // which direction entity is moving.
 }
 
}

Notice it processes entities that have the Movement (which is a temporary component), Movable, and MapPosition components.  It also has a reference to the GameMap so that it can update the entity's position in the map (this part is related to the bug mentioned at the top).

Upon insertion, the GameMap is immediately updated to believe that the entity has reached its destination on line 40.  Entities are "inserted" to the system as soon as they have a combination of all 3 required components.  Usually the entity has completes its path and has the Movement component removed naturally at the end.  If they got another Movement component before they finish the first path, the 2nd one won't trigger the "inserted" method, and so the GameMap won't be updated for the 2nd path.  Everything else will work - the entity will follow a path and look like it has moved.  It just won't have according to the map, which is probably more important.

The idea of the process method is that we compare elapsedTime to slowness to determine how many steps we have gone through.  Say we are on the 1st step (or step = 0).  That means we want to move from path(size-1) to path(size-2) (remember, the path is stored in reverse so path(0) is the target, path(size-1) is the starting cell).  On line 58 we figure out which step we are on.

We'll skip lines 61-71 for now, but below that we get the coordinates of the tiles we are moving from and to for this particular step.  On line 82 we figure out how far along that path we are (scaled from 0 inclusive to 1 exclusive).  Remember step was just movement.elapsedTime/slowness, but rounded down to an integer.  So by subtracting here, we get just the decimal part that was truncated.  In lines 85-86 we just do a linear interpolation between the two tiles.  Note that when t=0, we just go to (x0,y0).  When t=1, it would be (x1,y1).

After that we just increase elapsedTime and call it good!  Lines 61-71 sort of form a failsafe.  I remember when I was working on Spaceship Warrior, I played a lot with the particle explosions.  If there were too many, things would get laggy, and a lot of time would elapse between steps.  Consequently the particles sometimes jumped WAY farther than they should have ever gone before fading away.  This wasn't so bad - they didn't do anything and disappeared soon anyway.  But in case that happens here, we'd be accessing path(-1), (-2) or beyond!  No WAY do we want to do that!  So I included that just to make sure we didn't overshoot out goal.  It's also a nice place to go ahead and remove the Movement component, because it's a guarantee that the animation is done.

I noted in the inserted method that they would be a good spot to switch the entity to a different animation, perhaps a walking one, and then in removed that it would be a good place to switch back the default one.  I also put a shell of a method changeStep() which might be called whenever we go from step 0 to 1, 1 to 2, or so on.  In it, I reference a bit of code I wrote in MapTools which can get a vector (actually a FloatPair) pointing in the direction from one tile to another, so you can potentially switch animations from an entity moving down/right to moving up/right, or so on.
 public static FloatPair getDirectionVector(int x1, int y1, int x2, int y2) {
  FloatPair cell1 = world2window(x1, y1);
  FloatPair cell2 = world2window(x2, y2);
  return new FloatPair(cell2.x-cell1.x, cell2.y-cell1.y);
 }

Notice in lines 85-86 we have the chance of getting a decimal value for the position.  In fact, we really want that for a smooth animation.  I had started with MapPosition holding floats for position, changed it to ints, and have now changed it back to floats.  To handle that though, I had to update the world2window method to permit honest to goodness floating point world positions.
 public static FloatPair world2window(float x, float y) {
  int x0 = (int)x;
  float dx = x - x0; // purely the decimal part
  
  float posX = 5.5f + (x + 0.5f)*col_multiple;
  float posY = row_multiple*(y + 0.5f + (x0%2)*(0.5f - dx/2f) + (x0+1)%2 * dx/2f);
  
  return new FloatPair(posX, posY);
 }

Okay, so that's all good and well, but we need to let the user control stuff now.  I did a LOT of thinking about what I wanted the user experience to be, and what I want to code to be, and how to reconcile to two.  Ultimately, I want the user to click on an entity, get a little menu, select "Move" or whatever, and get options based on that.

I still think the finite state machine method I implemented last update is a good way to think of it, but now I think its a crappy way to implement it.  The control class will have to be riddled with crap like if (state == whatever) {} else {} else {} else{} blah blah blah.

I ultimately decided to split it into multiple controller files, like OverworldDefaultControl, OverworldMovingControl, etc...  My initial problem with this was that so much code will have to be copied - like click-dragging - into dozens of control systems, which would TOTALLY suck!

I ended up using the libgdx InputMultiplexer to stack controls, and it was way easier than I had imagined!  The documentation on that class was pretty sparse I thought, and there were no good tutorials.  It just kind of confused me all around...

That is, until I actually looked at its code.  It's WAY simple in hindsight!  No wonder nobody felt the need to document it!  It's so cool, it literally just is an InputProcessor that doesn't do any input processing on its own.  Instead, it has a list of InputProcessors, and it asks each one in turn: "Can you handle this?"  "Can you handle it?"  "Can you?"  As soon as the first one does, it says "Alright, it's handled!  Done!"  It goes in the order you put them in, but if you manage that you'll be okay!

Before we get to that code though, there's more we need to discuss.  First, I don't like the PlayerSelected component.  We're only ever going to have a single entity selected at a time, and it doesn't mean they get any special "processing".  Also, the MapRenderSystem I have now isn't really rendering any entities, or doing any Artemis related stuff, so why am I adding it to the Artemis World?  I even have to process it manually, so what the heck?  So there were a few changes that have been made.
  • MapRenderSystem is now just a regular old class held within OverworldScreen.
  • To handle that, OverworldScreen now must have the SpriteBatch (in fact I just gave it to AbstractScreen).
  • In the future, rendering that doesn't render entities will be handled similarly.
  • OverworldScreen just has an int to hold the ID of whatever player is selected.  No components, no nonsense.  Just an integer to say which one is the focus.
  • OverworldScreen has an Array<Pair> to hold the coordinates for all cells which the selected entity can reach, given its energy and terrain functions.
  • OverworldScreen doesn't just have an InputProcesor, but an InputMultiplexer.
  • There are presently two input systems, OverworldDefaultController and OverworldMovingController.  When the player selects an entity to move, it adds the OverworldMovingController to the InputMultiplexer so that they can move entities.
Here's what OverworldScreen looks like now:
package com.blogspot.javagamexyz.gamexyz.screens;

import com.artemis.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.EntityFactory;
import com.blogspot.javagamexyz.gamexyz.GameXYZ;
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.OverworldDefaultController;
import com.blogspot.javagamexyz.gamexyz.systems.HudRenderSystem;
import com.blogspot.javagamexyz.gamexyz.systems.MovementSystem;
import com.blogspot.javagamexyz.gamexyz.systems.SpriteRenderSystem;

public class OverworldScreen extends AbstractScreen {
 
 public static GameMap gameMap;
 private OrthographicCamera hudCam;
 
 private SpriteRenderSystem spriteRenderSystem;
 private HudRenderSystem hudRenderSystem;
 
 private MapRenderer mapRenderer;
 private MapHighlighter mapHighlighter;
 
 public int selectedEntity;
 public Array reachableCells;
 public boolean renderMap;
 public boolean renderMovementRange;
 
 public InputMultiplexer inputSystem;
 
 
 public OverworldScreen(GameXYZ game, SpriteBatch batch, World world) {
  super(game,world,batch);

     gameMap  = new GameMap();
     hudCam = new OrthographicCamera();
     
     mapRenderer = new MapRenderer(camera,batch,gameMap.map);
     mapHighlighter = new MapHighlighter(camera, batch);
     
     world.setSystem(new MovementSystem(gameMap));
     spriteRenderSystem = world.setSystem(new SpriteRenderSystem(camera,batch), true);
     hudRenderSystem = world.setSystem(new HudRenderSystem(hudCam, batch),true);
     world.initialize();
     
     this.inputSystem = new InputMultiplexer(new OverworldDefaultController(camera,world,gameMap,this));
     Gdx.input.setInputProcessor(inputSystem);
     
     int x, y;
     for (int i=0; i<100; 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();
     }
     
     selectedEntity = -1;
     renderMap = true;
     renderMovementRange = false;
     
 }
 
 @Override
 public void render(float delta) {
  super.render(delta);
  
  if (renderMap) {
   mapRenderer.render();
   spriteRenderSystem.process();
  }
  
  if (renderMovementRange) {
   mapHighlighter.render(reachableCells,0.2f,0.2f,0.8f,0.3f);
  }
  hudRenderSystem.process();
 }

 @Override
 public void show() {
  // TODO Auto-generated method stub
  
 }
 
 @Override
 public void resize(int width, int height) {
  super.resize(width, height);
  hudCam.setToOrtho(false, width, height);
 }

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


On lines 42-69 I just initialize all the systems, variables, etc.  Notice on line 50 I've added MovementSystem to the world here instead of up in Game.  It strikes me that I don't want the MovementSystem processing while the map screen isn't showing.  On line 55 I create the InputMultiplexer.  Initially I pass an argument for the actual InputProcessors I want it focusing on, and at first, I only want the default processor.

In render() I used boolean flags to mark whether certain renderers should run.  mapRenderer.render() draws the tiles, and mapHighlighter.render() is used to highlight the reachable cells (which are set in OverworldDefaultProcessor when the player clicks on an entity).  I also pass the color, so this is a highly transparent blue shade.  Before we go to the controllers, we'll go over these two renderers:
package com.blogspot.javagamexyz.gamexyz.renderers;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.custom.FloatPair;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;

public class MapRenderer extends AbstractRenderer {

 private TextureAtlas atlas;
 private Array<AtlasRegion> textures;
 private int[][] map;

 public MapRenderer(OrthographicCamera camera, SpriteBatch batch, int[][] map) {
  super(camera, batch);
  this.map = map;
  
  atlas = new TextureAtlas(Gdx.files.internal("textures/maptiles.atlas"),Gdx.files.internal("textures"));
  textures = atlas.findRegions(MapTools.name);
 }
 
 public void render() {
  begin();
  
  TextureRegion reg;
  
  // Get bottom left and top right coordinates of camera viewport and convert
  // into grid coordinates for the map
  int x0 = MathUtils.floor(camera.frustum.planePoints[0].x / (float)MapTools.col_multiple) - 1;
  int y0 = MathUtils.floor(camera.frustum.planePoints[0].y / (float)MapTools.row_multiple) - 1;
  int x1 = MathUtils.floor(camera.frustum.planePoints[2].x / (float)MapTools.col_multiple) + 1;
  int y1 = MathUtils.floor(camera.frustum.planePoints[2].y / (float)MapTools.row_multiple) + 1;
  
  // Restrict the grid coordinates to realistic values
  if (x0 % 2 == 1) x0 -= 1;
  if (x0 < 0) x0 = 0;
  if (x1 > map.length) x1 = map.length;
  if (y0 < 0) y0 = 0;
  if (y1 > map[0].length) y1 = map[0].length; 
  
  // Loop over everything in the window to draw
  for (int row = y0; row < y1; row++) {
   for (int col = x0; col < x1; col++) {
    reg = textures.get(map[col][row]);
    FloatPair position = MapTools.world2window(col,row);
    batch.draw(reg, position.x-reg.getRegionWidth()/2, position.y-reg.getRegionHeight()/2);
   }
  }
   
  // This line can draw a small image of the whole map
  //batch.draw(gameMap.texture,0,0);
  
  end();
 }
 
}
package com.blogspot.javagamexyz.gamexyz.renderers;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.custom.FloatPair;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;

public class MapRenderer extends AbstractRenderer {

 private TextureAtlas atlas;
 private Array&lt;AtlasRegion&gt; textures;
 private int[][] map;

 public MapRenderer(OrthographicCamera camera, SpriteBatch batch, int[][] map) {
  super(camera, batch);
  this.map = map;
  
  atlas = new TextureAtlas(Gdx.files.internal(&quot;textures/maptiles.atlas&quot;),Gdx.files.internal(&quot;textures&quot;));
  textures = atlas.findRegions(MapTools.name);
 }
 
 public void render() {
  begin();
  
  TextureRegion reg;
  
  // Get bottom left and top right coordinates of camera viewport and convert
  // into grid coordinates for the map
  int x0 = MathUtils.floor(camera.frustum.planePoints[0].x / (float)MapTools.col_multiple) - 1;
  int y0 = MathUtils.floor(camera.frustum.planePoints[0].y / (float)MapTools.row_multiple) - 1;
  int x1 = MathUtils.floor(camera.frustum.planePoints[2].x / (float)MapTools.col_multiple) + 1;
  int y1 = MathUtils.floor(camera.frustum.planePoints[2].y / (float)MapTools.row_multiple) + 1;
  
  // Restrict the grid coordinates to realistic values
  if (x0 % 2 == 1) x0 -= 1;
  if (x0 &lt; 0) x0 = 0;
  if (x1 &gt; map.length) x1 = map.length;
  if (y0 &lt; 0) y0 = 0;
  if (y1 &gt; map[0].length) y1 = map[0].length; 
  
  // Loop over everything in the window to draw
  for (int row = y0; row &lt; y1; row++) {
   for (int col = x0; col &lt; x1; col++) {
    reg = textures.get(map[col][row]);
    FloatPair position = MapTools.world2window(col,row);
    batch.draw(reg, position.x-reg.getRegionWidth()/2, position.y-reg.getRegionHeight()/2);
   }
  }
   
  // This line can draw a small image of the whole map
  //batch.draw(gameMap.texture,0,0);
  
  end();
 }
 
}

Also, to make things a little easier, I created an AbstractRenderer class to start and end the batch, plus deal with color and the projection matrix.
package com.blogspot.javagamexyz.gamexyz.renderers;

import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public abstract class AbstractRenderer {
 
 protected OrthographicCamera camera;
 protected SpriteBatch batch;
 
 public AbstractRenderer(OrthographicCamera camera, SpriteBatch batch) {
  this.camera = camera;
  this.batch = batch;
 }
 
 protected void begin() {
  batch.setProjectionMatrix(camera.combined);
  batch.begin();
 }
 
 protected void end() {
  batch.end();
  batch.setColor(1f,1f,1f,1f);
 }
 
}

In HighlightRenderer.java I load in hex_blank.png, which is just a totally white hex cell I made:
Since white has all colors, it's perfect to filter using setColor() to make it look like whatever color you want, in our current case, blue.

OverworldDefaultController looks like this:
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.badlogic.gdx.math.Vector2;
import com.blogspot.javagamexyz.gamexyz.EntityFactory;
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.screens.OverworldScreen;

public class OverworldDefaultController implements InputProcessor {
 
 private OrthographicCamera camera;
 private World world;
 private GameMap gameMap;
 
 // We need a copy of the screen implementing this controller (which has a copy of
 // the Game delegating to it) so we can change screens based on users making selections
 //private GameXYZ game;
 private OverworldScreen screen;
 
 private boolean dragging;
 
 public OverworldDefaultController(OrthographicCamera camera, World world, GameMap gameMap, OverworldScreen screen) {
  this.camera = camera;
  this.world = world;
  this.gameMap = gameMap;
  this.screen = screen;
  
  dragging = false;
 }

 @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) {
  if (dragging) {
   dragging = false;
   return true;
  }
  
  // Otherwise, get the coordinates they clicked on
  Pair coords = MapTools.window2world(Gdx.input.getX(), Gdx.input.getY(), 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();
   
   // For now let's just assume they are selecting the entity to move it
   // make sure they can really move!
   Entity e = world.getEntity(entityId);
   Movable movable = e.getComponent(Movable.class);
   screen.reachableCells = gameMap.pathFinder.getReachableCells(coords.x, coords.y, movable);
   screen.renderMovementRange = true;
   screen.inputSystem.addProcessor(new OverworldMovingController(camera,world,gameMap,screen));
 
   return true;
  }
  
  // If they didn't click on someone, we didn't process it
  return false;
 }

 @Override
 public boolean mouseMoved(int screenX, int screenY) {
  // TODO Auto-generated method stub
  return false;
 }
 
 @Override
 public boolean touchDragged(int screenX, int screenY, int pointer) {
  if (!dragging) dragging = true;
  Vector2 delta = new Vector2(-camera.zoom*Gdx.input.getDeltaX(), camera.zoom*Gdx.input.getDeltaY());
  camera.translate(delta);
  
  return true;
 }

 @Override
 public boolean scrolled(int amount) {
  if ((camera.zoom > 0.2f || amount == 1) && (camera.zoom < 8 || amount == -1)) camera.zoom += amount*0.1;
  return true;
 }
}

I went back to using a boolean flag to see if the player is click-dragging the screen.  touchDragged() and scrolled() look the same, and notice that they return true.  That means that it will tell the InputMultiplexer that they did handle the input.  If they returned false, the InputMultiplexer would continue asking the other InputProcessers if they handled it.

In touchUp() we first make sure they're not just releasing a drag (and if they are, we return true saying that we got it).  Otherwise we get the coordinates of the cell they clicked on.  If they are selecting a new entity, call screen.selectedEntity = entityId to tell the OverworldScreen who we're looking at.  Also, for now, we're assuming that clicking on an entity means you want to move it, though in the future we'll probably implement a menu system instead.

On line 86 it asks our AStarPathFinder to find a set of tiles that this entity can reach.  To make sure it's specific to this entity, we have to pass the entity's Movable component along.  Up till now, we would have gotten it using an @Mapper ComponentMapper<Movable> style command, but we can't use that now.  That method is only valid in classes that extend EntitySystem (or some derivative of it).  Since I don't really think this controller ought to extend EntitySystem (because I don't want it processing things every game cycle, just adding components when the user clicks) I had to use the slower e.getComponent(Movable.class).  It shouldn't be a big problem - this call should happen infrequently enough that the speed will be unnoticeable.

On line 87 it tells the OverworldScreen to start rendering the highlighted range, and then on 88 it adds the OverworldMovingController to the multiplexer.  Because it is added 2nd (after the default controller) it will be asked to process things only when OverworldDefaultController returns false on something.  Since here we've selected an entity, and I'm sure that's what we want to do for now if we click on someone, we return true.

Outside this loop, if they didn't select a new entity, we return false because this controller doesn't handle anything else.  That way OverworldMovingController has a chance to handle that input.
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.Movable;
import com.blogspot.javagamexyz.gamexyz.components.Movement;
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 OverworldMovingController implements InputProcessor {
 private OrthographicCamera camera;
 private World world;
 private GameMap gameMap;
 private OverworldScreen screen;

 public OverworldMovingController(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);
  
  // Did they click within the movable range?
  if (screen.reachableCells.contains(coords, false)) {
   Entity e = world.getEntity(screen.selectedEntity);
   Movable movable = e.getComponent(Movable.class);
   Pair p = gameMap.getCoordinatesFor(screen.selectedEntity);
   e.addComponent(new Movement(gameMap.pathFinder.findPath(p.x, p.y, coords.x, coords.y, movable)));
   e.changedInWorld();
  }

  // Wherever they clicked, they are now done with the "moving" aspect of things
  screen.renderMovementRange = false;
  screen.selectedEntity = -1;
  screen.inputSystem.removeProcessor(this);
  return true;
 }

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

 @Override
 public boolean touchDragged(int screenX, int screenY, int pointer) {
  return false;
 }

 @Override
 public boolean scrolled(int amount) {
  return false;
 }
 
}

If we reach touchUp() here, it by default means that we weren't dragging, and we weren't selecting a new entity.  Also, this system is only active if an entity has been selected to move, so we know the screen is also currently highlighting a set of cells as potential move targets.  If they click within that set (lines 57-63) we want to add a Movement component containing a path from their current location to the clicked target.  To find that path, we actually need to know the entity's current location, so I had a choice between calling e.getComponent(Position.class) or storing all entities' locations somewhere else.  The GameMap had a map to go from coordinates to entity, so I decided to also have one to go from entity to coordinates.

On lines 66-68 I say that no matter where they click (within the range or not) we should deselect the entity and stop rendering the movement range.  Remember, this can only be called if the default returned false, so if they click on another entity here (within the movement range or not) we will have never seen this code, so players can click on one entity, and then decide they'd rather click on another and it will just be selected automatically.  If we did get here though, we consider this deselecting to be the final meaning of the touchUp(), so we return true.

There are a few more helper functions that we need to go over.  In GameMap.java we added a new field and these methods
private ObjectMap<Integer,Pair> coordByEntity;
 public Pair getCoordinatesFor(int entityId) {
  if (coordByEntity.containsKey(entityId)) return coordByEntity.get(entityId);
  return null;
 }
 public boolean cellOccupied(int x, int y) {
  return (entityByCoord[x][y] > -1);
 }
 
 public void addEntity(int id, int x, int y) {
  entityByCoord[x][y] = id;
  coordByEntity.put(id, new Pair(x,y));
 }
 
 public void moveEntity(int id, int x, int y) {
  Pair old = coordByEntity.put(id, new Pair(x,y));
  entityByCoord[old.x][old.y] = -1;
  entityByCoord[x][y] = id;
 }

In AStarPathFinder I had to create a new method called getReachableCells.  To accomplish this, I did a breadth first search where I stop after cells exceed the mover's energy.  This required a special queue class which had the ability to remove arbitrary elements (in addition to reading off/removing the first in line).  I also updated the findPath method to use the mover's terrain costs and blocks.  Here's AStarPathFinder
package com.blogspot.javagamexyz.gamexyz.pathfinding;

import java.util.ArrayList;
import java.util.Collections;

import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.custom.MyQueue;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;


/**
 * A modified path finder implementation starting with Kevin Glass' AStar algorithm
 * at http://old.cokeandcode.com/pathfinding.  Original header:
 * 
 * A path finder implementation that uses the AStar heuristic based algorithm
 * to determine a path. 
 * 
 * @author Kevin Glass
 */
public class AStarPathFinder {
 /** The set of nodes that have been searched through */
 private Array<Node> closed = new Array<Node>();
 /** The set of nodes that we do not yet consider fully searched */
 private SortedList open = new SortedList();
 
 /** The map being searched */
 private GameMap gameMap;
 private int[][] map;
 /** The maximum depth of search we're willing to accept before giving up */
 private int maxSearchDistance;
 
 /** The complete set of nodes across the map */
 private Node[][] nodes;

 /**
  * Create a path finder 
  * 
  * @param gameMap The map to be searched
  * @param maxSearchDistance The maximum depth we'll search before giving up
  */
 public AStarPathFinder(GameMap gameMap, int maxSearchDistance) {
  this.gameMap = gameMap;
  this.map = gameMap.map;
  this.maxSearchDistance = maxSearchDistance;
  
  nodes = new Node[map.length][map[0].length];
  for (int x=0;x<map.length;x++) {
   for (int y=0;y<map[0].length;y++) {
    nodes[x][y] = new Node(x,y);
   }
  }
 }
 
 /**
  * @see PathFinder#findPath(int, int, int, int, Movable)
  */
 public Path findPath(int sx, int sy, int tx, int ty, Movable mover) {
  // easy first check, if the destination is blocked, we can't get there
  if (isCellBlocked(tx,ty,mover)) {
   return null;
  }
  
  // initial state for A*. The closed group is empty. Only the starting
  // tile is in the open list and it's cost is zero, i.e. we're already there
  nodes[sx][sy].cost = 0;
  nodes[sx][sy].depth = 0;
  closed.clear();
  open.clear();
  open.add(nodes[sx][sy]);
  
  nodes[tx][ty].parent = null;
  
  // While we still have more nodes to search and haven't exceeded our max search depth
  int maxDepth = 0;
  while ((maxDepth < maxSearchDistance) && (open.size() != 0)) {
   // pull out the first node in our open list, this is determined to 
   // be the most likely to be the next step based on our heuristic
   Node current = getFirstInOpen();
   if (current == nodes[tx][ty]) {
    break;
   }
   
   removeFromOpen(current);
   addToClosed(current);
   
   Array<Pair> neighbors = MapTools.getNeighbors(current.x, current.y);
   // search through all the neighbors of the current node evaluating
   // them as next steps
   for (Pair n : neighbors) {
    int xp = n.x;
    int yp = n.y;
    float nextStepCost = current.cost + mover.terrainCost[map[xp][yp]];//getMovementCost(current.x,current.y,xp,yp);
    Node neighbor = nodes[xp][yp];
    
    // If this step exceeds the movers energy, don't even bother with it
    if (nextStepCost > mover.energy) continue;
    
    // Check to see if we have found a new shortest route to this neighbor
    if (nextStepCost < neighbor.cost) {
     if (inOpenList(neighbor)) removeFromOpen(neighbor);
     if (inClosedList(neighbor)) removeFromClosed(neighbor);
    }
    
    // If it was a new shor
    if (!inOpenList(neighbor) && !inClosedList(neighbor)) {
     neighbor.cost = nextStepCost;
     neighbor.heuristic = (float)MapTools.distance(xp, yp, tx, ty);
     maxDepth = Math.max(maxDepth, neighbor.setParent(current));
     addToOpen(neighbor);
    }
   }
  }

  // since we've got an empty open list or we've run out of search 
  // there was no path. Just return null
  if (nodes[tx][ty].parent == null) {
   return null;
  }
  
  // At this point we've definitely found a path so we can uses the parent
  // references of the nodes to find out way from the target location back
  // to the start recording the nodes on the way.
  Path path = new Path();
  Node target = nodes[tx][ty];
  while (target != nodes[sx][sy]) {
   path.prependStep(target.x, target.y);
   target = target.parent;
  }
  path.prependStep(sx,sy);
  
  // thats it, we have our path 
  return path;
 }
 
 
 /**
  * 
  * @param x The x coordinate of the mover
  * @param y The y coordinate of the mover
  * @return An Array<Pair> containing the coordinates for all cells the mover can reach
  */
 public Array<Pair> getReachableCells(int x, int y, Movable mover) {
  Array<Pair> reachableCells = new Array<Pair>();
  MyQueue<Node> open = new MyQueue<Node>();
  closed.clear();
  Node start = nodes[x][y];
  start.depth = 0;
  start.cost = 0;
  open.push(start);
  while (open.size() > 0) {
   // poll() the open queue
   Node current = open.poll();
   
   for (Pair n : MapTools.getNeighbors(current.x,current.y)) {
    Node neighbor = nodes[n.x][n.y];
    float nextStepCost = current.cost + mover.terrainCost[map[n.x][n.y]];
    
    // If the cell is beyond our reach, or otherwise blocked, ignore it
    if (nextStepCost > mover.energy || isCellBlocked(n.x,n.y,mover)) continue;
    
    // Check to see if we have found a new shortest route to this neighbor, in
    // which case it must be totally reconsidered
    if (nextStepCost < neighbor.cost) {
     if (inClosedList(neighbor)) removeFromClosed(neighbor);
     if (open.contains(neighbor, false)) open.remove(neighbor,false);
    }

    if (!open.contains(neighbor, false) && !inClosedList(neighbor)) {
     neighbor.cost = nextStepCost;
     open.push(neighbor);
    }
   }
   addToClosed(current);
  }
  
  for (Node n : closed) {
   if (n.x != x || n.y != y) reachableCells.add(new Pair(n.x,n.y));
  }
  
  return reachableCells;

 }

 /**
  * Get the first element from the open list. This is the next
  * one to be searched.
  * 
  * @return The first element in the open list
  */
 protected Node getFirstInOpen() {
  return (Node) open.first();
 }
 
 /**
  * Add a node to the open list
  * 
  * @param node The node to be added to the open list
  */
 protected void addToOpen(Node node) {
  open.add(node);
 }
 
 /**
  * Check if a node is in the open list
  * 
  * @param node The node to check for
  * @return True if the node given is in the open list
  */
 protected boolean inOpenList(Node node) {
  return open.contains(node);
 }
 
 /**
  * Remove a node from the open list
  * 
  * @param node The node to remove from the open list
  */
 protected void removeFromOpen(Node node) {
  open.remove(node);
 }
 
 /**
  * Add a node to the closed list
  * 
  * @param node The node to add to the closed list
  */
 protected void addToClosed(Node node) {
  closed.add(node);
 }
 
 /**
  * Check if the node supplied is in the closed list
  * 
  * @param node The node to search for
  * @return True if the node specified is in the closed list
  */
 protected boolean inClosedList(Node node) {
  return closed.contains(node,false);
 }
 
 /**
  * Remove a node from the closed list
  * 
  * @param node The node to remove from the closed list
  */
 protected void removeFromClosed(Node node) {
  closed.removeValue(node,false);
 }
 
 /**
  * Check if a given location is valid for the supplied mover
  * 
  * @param mover The mover that would hold a given location
  * @param sx The starting x coordinate
  * @param sy The starting y coordinate
  * @param x The x coordinate of the location to check
  * @param y The y coordinate of the location to check
  * @return True if the location is valid for the given mover
  */
 protected boolean isValidLocation(int sx, int sy, int x, int y) {
  boolean invalid = (x < 0) || (y < 0) || (x >= map.length) || (y >= map[0].length);
  
  if ((!invalid) && ((sx != x) || (sy != y))) {
   //invalid = map.blocked(mover, x, y);
  }
  
  return !invalid;
 }
 
 /**
  * Get the cost to move through a given location
  * 
  * @param mover The entity that is being moved
  * @param sx The x coordinate of the tile whose cost is being determined
  * @param sy The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The cost of movement through the given tile
  */
 public float getMovementCost(int sx, int sy, int tx, int ty) {
  return (float)map[tx][ty]*3f + 1;
 }

 private boolean isCellBlocked(int x, int y, Movable mover) {
  return ((mover.terrainBlocked[map[x][y]]) || gameMap.cellOccupied(x, y));
 }
 
 /**
  * Get the heuristic cost for the given location. This determines in which 
  * order the locations are processed.
  * 
  * @param mover The entity that is being moved
  * @param x The x coordinate of the tile whose cost is being determined
  * @param y The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The heuristic cost assigned to the tile
  */
 public float getHeuristicCost(int x, int y, int tx, int ty) {
  return MapTools.distance(x, y, tx, ty);
  //return heuristic.getCost(map, mover, x, y, tx, ty);
 }
 
 /**
  * A simple sorted list
  *
  * @author kevin
  */
 private class SortedList {
  /** The list of elements */
  private ArrayList<Node> list = new ArrayList<Node>();
  
  /**
   * Retrieve the first element from the list
   *  
   * @return The first element from the list
   */
  public Object first() {
   return list.get(0);
  }
  
  /**
   * Empty the list
   */
  public void clear() {
   list.clear();
  }
  
  /**
   * Add an element to the list - causes sorting
   * 
   * @param o The element to add
   */
  public void add(Node o) {
   list.add(o);
   Collections.sort(list);
  }
  
  /**
   * Remove an element from the list
   * 
   * @param o The element to remove
   */
  public void remove(Object o) {
   list.remove(o);
  }
 
  /**
   * Get the number of elements in the list
   * 
   * @return The number of element in the list
    */
  public int size() {
   return list.size();
  }
  
  /**
   * Check if an element is in the list
   * 
   * @param o The element to search for
   * @return True if the element is in the list
   */
  public boolean contains(Object o) {
   return list.contains(o);
  }
 }
 
 /**
  * A single node in the search graph
  */
 private class Node implements Comparable<Node> {
  /** The x coordinate of the node */
  private int x;
  /** The y coordinate of the node */
  private int y;
  /** The path cost for this node */
  private float cost;
  /** The parent of this node, how we reached it in the search */
  private Node parent;
  /** The heuristic cost of this node */
  private float heuristic;
  /** The search depth of this node */
  private int depth;
  
  /**
   * Create a new node
   * 
   * @param x The x coordinate of the node
   * @param y The y coordinate of the node
   */
  public Node(int x, int y) {
   this.x = x;
   this.y = y;
  }
  
  /**
   * Set the parent of this node
   * 
   * @param parent The parent node which lead us to this node
   * @return The depth we have no reached in searching
   */
  public int setParent(Node parent) {
   depth = parent.depth + 1;
   this.parent = parent;
   
   return depth;
  }
  
  /**
   * @see Comparable#compareTo(Object)
   */
  @Override
  public int compareTo(Node o) {
   //Node o = (Node) other;
   
   float f = heuristic + cost;
   float of = o.heuristic + o.cost;
   
   if (f < of) {
    return -1;
   } else if (f > of) {
    return 1;
   } else {
    return 0;
   }
  }
  
  /**
   * @see Object#equals(Object)
   */
  public boolean equals(Object other) {
   if (other instanceof Node) {
    Node o = (Node) other;
    
    return (o.x == x) && (o.y == y);
   }
   
   return false;
  }
  
  public String toString() {
   return "("+x+","+y+")";
  }
 }
}

And the special queue I created is
package com.blogspot.javagamexyz.gamexyz.pathfinding;

import java.util.ArrayList;
import java.util.Collections;

import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.custom.MyQueue;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;


/**
 * A modified path finder implementation starting with Kevin Glass' AStar algorithm
 * at http://old.cokeandcode.com/pathfinding.  Original header:
 * 
 * A path finder implementation that uses the AStar heuristic based algorithm
 * to determine a path. 
 * 
 * @author Kevin Glass
 */
public class AStarPathFinder {
 /** The set of nodes that have been searched through */
 private Array&lt;Node&gt; closed = new Array&lt;Node&gt;();
 /** The set of nodes that we do not yet consider fully searched */
 private SortedList open = new SortedList();
 
 /** The map being searched */
 private GameMap gameMap;
 private int[][] map;
 /** The maximum depth of search we're willing to accept before giving up */
 private int maxSearchDistance;
 
 /** The complete set of nodes across the map */
 private Node[][] nodes;

 /**
  * Create a path finder 
  * 
  * @param gameMap The map to be searched
  * @param maxSearchDistance The maximum depth we'll search before giving up
  */
 public AStarPathFinder(GameMap gameMap, int maxSearchDistance) {
  this.gameMap = gameMap;
  this.map = gameMap.map;
  this.maxSearchDistance = maxSearchDistance;
  
  nodes = new Node[map.length][map[0].length];
  for (int x=0;x&lt;map.length;x++) {
   for (int y=0;y&lt;map[0].length;y++) {
    nodes[x][y] = new Node(x,y);
   }
  }
 }
 
 /**
  * @see PathFinder#findPath(int, int, int, int, Movable)
  */
 public Path findPath(int sx, int sy, int tx, int ty, Movable mover) {
  // easy first check, if the destination is blocked, we can't get there
  if (isCellBlocked(tx,ty,mover)) {
   return null;
  }
  
  // initial state for A*. The closed group is empty. Only the starting
  // tile is in the open list and it's cost is zero, i.e. we're already there
  nodes[sx][sy].cost = 0;
  nodes[sx][sy].depth = 0;
  closed.clear();
  open.clear();
  open.add(nodes[sx][sy]);
  
  nodes[tx][ty].parent = null;
  
  // While we still have more nodes to search and haven't exceeded our max search depth
  int maxDepth = 0;
  while ((maxDepth &lt; maxSearchDistance) &amp;&amp; (open.size() != 0)) {
   // pull out the first node in our open list, this is determined to 
   // be the most likely to be the next step based on our heuristic
   Node current = getFirstInOpen();
   if (current == nodes[tx][ty]) {
    break;
   }
   
   removeFromOpen(current);
   addToClosed(current);
   
   Array&lt;Pair&gt; neighbors = MapTools.getNeighbors(current.x, current.y);
   // search through all the neighbors of the current node evaluating
   // them as next steps
   for (Pair n : neighbors) {
    int xp = n.x;
    int yp = n.y;
    float nextStepCost = current.cost + mover.terrainCost[map[xp][yp]];//getMovementCost(current.x,current.y,xp,yp);
    Node neighbor = nodes[xp][yp];
    
    // If this step exceeds the movers energy, don't even bother with it
    if (nextStepCost &gt; mover.energy) continue;
    
    // Check to see if we have found a new shortest route to this neighbor
    if (nextStepCost &lt; neighbor.cost) {
     if (inOpenList(neighbor)) removeFromOpen(neighbor);
     if (inClosedList(neighbor)) removeFromClosed(neighbor);
    }
    
    // If it was a new shor
    if (!inOpenList(neighbor) &amp;&amp; !inClosedList(neighbor)) {
     neighbor.cost = nextStepCost;
     neighbor.heuristic = (float)MapTools.distance(xp, yp, tx, ty);
     maxDepth = Math.max(maxDepth, neighbor.setParent(current));
     addToOpen(neighbor);
    }
   }
  }

  // since we've got an empty open list or we've run out of search 
  // there was no path. Just return null
  if (nodes[tx][ty].parent == null) {
   return null;
  }
  
  // At this point we've definitely found a path so we can uses the parent
  // references of the nodes to find out way from the target location back
  // to the start recording the nodes on the way.
  Path path = new Path();
  Node target = nodes[tx][ty];
  while (target != nodes[sx][sy]) {
   path.prependStep(target.x, target.y);
   target = target.parent;
  }
  path.prependStep(sx,sy);
  
  // thats it, we have our path 
  return path;
 }
 
 
 /**
  * 
  * @param x The x coordinate of the mover
  * @param y The y coordinate of the mover
  * @return An Array&lt;Pair&gt; containing the coordinates for all cells the mover can reach
  */
 public Array&lt;Pair&gt; getReachableCells(int x, int y, Movable mover) {
  Array&lt;Pair&gt; reachableCells = new Array&lt;Pair&gt;();
  MyQueue&lt;Node&gt; open = new MyQueue&lt;Node&gt;();
  closed.clear();
  Node start = nodes[x][y];
  start.depth = 0;
  start.cost = 0;
  open.push(start);
  while (open.size() &gt; 0) {
   // poll() the open queue
   Node current = open.poll();
   
   for (Pair n : MapTools.getNeighbors(current.x,current.y)) {
    Node neighbor = nodes[n.x][n.y];
    float nextStepCost = current.cost + mover.terrainCost[map[n.x][n.y]];
    
    // If the cell is beyond our reach, or otherwise blocked, ignore it
    if (nextStepCost &gt; mover.energy || isCellBlocked(n.x,n.y,mover)) continue;
    
    // Check to see if we have found a new shortest route to this neighbor, in
    // which case it must be totally reconsidered
    if (nextStepCost &lt; neighbor.cost) {
     if (inClosedList(neighbor)) removeFromClosed(neighbor);
     if (open.contains(neighbor, false)) open.remove(neighbor,false);
    }

    if (!open.contains(neighbor, false) &amp;&amp; !inClosedList(neighbor)) {
     neighbor.cost = nextStepCost;
     open.push(neighbor);
    }
   }
   addToClosed(current);
  }
  
  for (Node n : closed) {
   if (n.x != x || n.y != y) reachableCells.add(new Pair(n.x,n.y));
  }
  
  return reachableCells;

 }

 /**
  * Get the first element from the open list. This is the next
  * one to be searched.
  * 
  * @return The first element in the open list
  */
 protected Node getFirstInOpen() {
  return (Node) open.first();
 }
 
 /**
  * Add a node to the open list
  * 
  * @param node The node to be added to the open list
  */
 protected void addToOpen(Node node) {
  open.add(node);
 }
 
 /**
  * Check if a node is in the open list
  * 
  * @param node The node to check for
  * @return True if the node given is in the open list
  */
 protected boolean inOpenList(Node node) {
  return open.contains(node);
 }
 
 /**
  * Remove a node from the open list
  * 
  * @param node The node to remove from the open list
  */
 protected void removeFromOpen(Node node) {
  open.remove(node);
 }
 
 /**
  * Add a node to the closed list
  * 
  * @param node The node to add to the closed list
  */
 protected void addToClosed(Node node) {
  closed.add(node);
 }
 
 /**
  * Check if the node supplied is in the closed list
  * 
  * @param node The node to search for
  * @return True if the node specified is in the closed list
  */
 protected boolean inClosedList(Node node) {
  return closed.contains(node,false);
 }
 
 /**
  * Remove a node from the closed list
  * 
  * @param node The node to remove from the closed list
  */
 protected void removeFromClosed(Node node) {
  closed.removeValue(node,false);
 }
 
 /**
  * Check if a given location is valid for the supplied mover
  * 
  * @param mover The mover that would hold a given location
  * @param sx The starting x coordinate
  * @param sy The starting y coordinate
  * @param x The x coordinate of the location to check
  * @param y The y coordinate of the location to check
  * @return True if the location is valid for the given mover
  */
 protected boolean isValidLocation(int sx, int sy, int x, int y) {
  boolean invalid = (x &lt; 0) || (y &lt; 0) || (x &gt;= map.length) || (y &gt;= map[0].length);
  
  if ((!invalid) &amp;&amp; ((sx != x) || (sy != y))) {
   //invalid = map.blocked(mover, x, y);
  }
  
  return !invalid;
 }
 
 /**
  * Get the cost to move through a given location
  * 
  * @param mover The entity that is being moved
  * @param sx The x coordinate of the tile whose cost is being determined
  * @param sy The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The cost of movement through the given tile
  */
 public float getMovementCost(int sx, int sy, int tx, int ty) {
  return (float)map[tx][ty]*3f + 1;
 }

 private boolean isCellBlocked(int x, int y, Movable mover) {
  return ((mover.terrainBlocked[map[x][y]]) || gameMap.cellOccupied(x, y));
 }
 
 /**
  * Get the heuristic cost for the given location. This determines in which 
  * order the locations are processed.
  * 
  * @param mover The entity that is being moved
  * @param x The x coordinate of the tile whose cost is being determined
  * @param y The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The heuristic cost assigned to the tile
  */
 public float getHeuristicCost(int x, int y, int tx, int ty) {
  return MapTools.distance(x, y, tx, ty);
  //return heuristic.getCost(map, mover, x, y, tx, ty);
 }
 
 /**
  * A simple sorted list
  *
  * @author kevin
  */
 private class SortedList {
  /** The list of elements */
  private ArrayList&lt;Node&gt; list = new ArrayList&lt;Node&gt;();
  
  /**
   * Retrieve the first element from the list
   *  
   * @return The first element from the list
   */
  public Object first() {
   return list.get(0);
  }
  
  /**
   * Empty the list
   */
  public void clear() {
   list.clear();
  }
  
  /**
   * Add an element to the list - causes sorting
   * 
   * @param o The element to add
   */
  public void add(Node o) {
   list.add(o);
   Collections.sort(list);
  }
  
  /**
   * Remove an element from the list
   * 
   * @param o The element to remove
   */
  public void remove(Object o) {
   list.remove(o);
  }
 
  /**
   * Get the number of elements in the list
   * 
   * @return The number of element in the list
    */
  public int size() {
   return list.size();
  }
  
  /**
   * Check if an element is in the list
   * 
   * @param o The element to search for
   * @return True if the element is in the list
   */
  public boolean contains(Object o) {
   return list.contains(o);
  }
 }
 
 /**
  * A single node in the search graph
  */
 private class Node implements Comparable&lt;Node&gt; {
  /** The x coordinate of the node */
  private int x;
  /** The y coordinate of the node */
  private int y;
  /** The path cost for this node */
  private float cost;
  /** The parent of this node, how we reached it in the search */
  private Node parent;
  /** The heuristic cost of this node */
  private float heuristic;
  /** The search depth of this node */
  private int depth;
  
  /**
   * Create a new node
   * 
   * @param x The x coordinate of the node
   * @param y The y coordinate of the node
   */
  public Node(int x, int y) {
   this.x = x;
   this.y = y;
  }
  
  /**
   * Set the parent of this node
   * 
   * @param parent The parent node which lead us to this node
   * @return The depth we have no reached in searching
   */
  public int setParent(Node parent) {
   depth = parent.depth + 1;
   this.parent = parent;
   
   return depth;
  }
  
  /**
   * @see Comparable#compareTo(Object)
   */
  @Override
  public int compareTo(Node o) {
   //Node o = (Node) other;
   
   float f = heuristic + cost;
   float of = o.heuristic + o.cost;
   
   if (f &lt; of) {
    return -1;
   } else if (f &gt; of) {
    return 1;
   } else {
    return 0;
   }
  }
  
  /**
   * @see Object#equals(Object)
   */
  public boolean equals(Object other) {
   if (other instanceof Node) {
    Node o = (Node) other;
    
    return (o.x == x) &amp;&amp; (o.y == y);
   }
   
   return false;
  }
  
  public String toString() {
   return &quot;(&quot;+x+&quot;,&quot;+y+&quot;)&quot;;
  }
 }
}

So this was a LOT, but I think it payed off in the end.  We're now primed to create a richer user experience where they are prompted with menus and can select what action to perform, and I think that's what I'm going to focus on next.  I'm torn between using the Themable Widget Library or doing something more custom, but I like the idea of storing menu structure in an XML or JSON file.  In fact, I like the idea of storing as much as possible in stuff like that so the game is easier to change down the line, or even mod (a la Civ).  We'll see what happens!

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

No comments:

Post a Comment