package propra2012.gruppe33.bomberman.graphics.rendering.scenegraph.grid.movement; import java.awt.Point; import java.util.EnumMap; import java.util.Iterator; import java.util.Map; import propra2012.gruppe33.bomberman.GameRoutines; import propra2012.gruppe33.bomberman.graphics.rendering.scenegraph.grid.input.Input; import propra2012.gruppe33.bomberman.graphics.rendering.scenegraph.grid.items.ItemSpawner; import com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity; import com.indyforge.twod.engine.graphics.rendering.scenegraph.GraphicsEntity; import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.Grid; import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.MathExt; import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.Vector2f; import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.Vector2f.Direction; import com.indyforge.twod.engine.graphics.rendering.scenegraph.network.input.InputChange; import com.indyforge.twod.engine.util.iteration.FilteredIterator; /** * This class manages the grid movement. If you attach this entity to an entity * the entity will react (process movement) using the given input map. * * @author Christopher Probst * @author Malte Schmidt * @author Matthias Hesse */ public final class GridMovement extends Entity { /** * */ private static final long serialVersionUID = 1L; /* * Important state variables. Used to decide when to change the key * priority. */ private float lv = 0, lh = 0; // A local movement var private final Vector2f movement = Vector2f.zero(); // Stores the moving state of this entity private boolean moving = false; // Stores the last direction (useful for sprite animation) private Direction direction = Direction.North; // The input map private final Map<Direction, Boolean> inputMap = new EnumMap<Direction, Boolean>( Direction.class); private void processMovement(boolean negative, boolean vertical, GraphicsEntity gridEntity, GraphicsEntity node, GraphicsEntity graphicsEntity, float maxSpeed, Vector2f dest, float tpf) { // Get center vector Vector2f center = node.position().round(); // Get point Point nearest = center.point(); // Calc abs pos Vector2f pos = center.add(graphicsEntity.position()); // Calc dominant site boolean negativeDominant = (vertical ? center.x - pos.x : center.y - pos.y) > 0f; // Is the entity already centered ? boolean isCentered = vertical ? MathExt.equals(pos.x, center.x) : MathExt.equals(pos.y, center.y); // Get the grid Grid grid = gridEntity.typeProp(Grid.class); // Calc direction int dir = negative ? -1 : 1; // Calc the max velocity using tpf, maxSpeed float maxMovement = tpf * maxSpeed, // Calc the movement based on the grid movement = dir * GameRoutines.lineOfSight(gridEntity, node, graphicsEntity .position(), GameRoutines.VELOCITY_NODE_FILTER, maxMovement, vertical ? (negative ? Direction.North : Direction.South) : (negative ? Direction.West : Direction.East)); // If movement != 0 -> We can move! boolean canMove = !MathExt.equals(movement, 0); /* * If centered and able to move! */ if (isCentered && canMove) { // Simply apply the movement if (vertical) { dest.y += movement; } else { dest.x += movement; } } else { // The offset int offset = 0; /* * Very important. If we can not move (into the default direction) * we have to check the nearest corner. This applies to centered / * non-centered positions. */ if (!canMove) { // The valid flag! boolean valid = false; /* * This is a very tricky for-loop: We want to loop while i < max * and valid == false. The max value depends on the isCentered * flag. */ for (int i = 0, max = isCentered ? 2 : 1; i < max && !valid; i++) { // Calc the dominant direction (offest) offset = negativeDominant ? -1 : 1; // Calc dom a Point a = new Point(nearest.x + (vertical ? offset : 0), nearest.y + (vertical ? 0 : offset)); // Calc dom b Point b = new Point(nearest.x + (vertical ? offset : dir), nearest.y + (vertical ? dir : offset)); // Is the dom path valid ? valid = GameRoutines.VELOCITY_NODE_FILTER.accept(gridEntity .childAt(grid.index(a))) && GameRoutines.VELOCITY_NODE_FILTER .accept(gridEntity.childAt(grid.index(b))); // Swap negativeDominant = !negativeDominant; } /* * If the movement is not valid, exit here! */ if (!valid) { return; } } /* * We have calculated the field of interest successfully. */ Vector2f fieldOfInterest = new Vector2f(nearest.x + (vertical ? offset : 0), nearest.y + (vertical ? 0 : offset)); // Recalc the distance float centerDistance = vertical ? fieldOfInterest.x - pos.x : fieldOfInterest.y - pos.y; // Calc the orthogonal movement float orthogonalMovement = (centerDistance >= 0 ? 1f : -1f) * maxMovement; // Limit the speed... if (Math.abs(centerDistance) < Math.abs(orthogonalMovement)) { orthogonalMovement = centerDistance; } // Move orthogonal if (vertical) { dest.x += orthogonalMovement; } else { dest.y += orthogonalMovement; } } } /* * (non-Javadoc) * * @see * com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity#onUpdate * (float) */ @Override protected void onUpdate(float tpf) { /* * LOOKUP THE CONTROLLED PARENT, THE NODE AND THE GRID ENTITY! */ // Get and filter parents... Iterator<Entity> parents = new FilteredIterator<Entity>( GraphicsEntity.TYPE_FILTER, parentIterator(false)); if (!parents.hasNext()) { return; } // Convert parent GraphicsEntity controlledParent = (GraphicsEntity) parents.next(); if (!parents.hasNext()) { return; } // Convert node GraphicsEntity node = (GraphicsEntity) parents.next(); if (!parents.hasNext()) { return; } // Convert grid entity GraphicsEntity gridEntity = (GraphicsEntity) parents.next(); /* * PROCESS THE MOVEMENT! */ // Look up the speed on the given field Float maxSpeedObj = node.typeProp(Float.class); // Check for speed property if (maxSpeedObj == null) { throw new IllegalStateException("The node " + node + " does not have a speed property."); } // Calc the max speed float maxSpeed = maxSpeedObj.floatValue() * controlledParent.typeProp(ItemSpawner.class) .speedPercentage(); // Init the input flags boolean north = hasInputFor(Direction.North), south = hasInputFor(Direction.South), west = hasInputFor(Direction.West), east = hasInputFor(Direction.East); // Set zero movement.setZero(); // Check both axes boolean vertical = north != south, horizontal = west != east; /* * Never process two axes at the same time. */ if (vertical != horizontal) { if (vertical) { // Calc vertical movement processMovement(north, true, gridEntity, node, controlledParent, maxSpeed, movement, tpf); } else { // Calc horizontal movement processMovement(west, false, gridEntity, node, controlledParent, maxSpeed, movement, tpf); } } else if (vertical) { /* * Well, both axes at the same time is complicated. To provide a * nice gameplay we should work with priorities. We do this with * checking the last direction of our entity. This direction has * higher priority as long as the entity has not walked one raster * size. After this the other axes gets higher priority. It is not * perfect but a beginning. */ switch (direction) { case North: case South: if (lv >= 1.25f) { // Calc horizontal movement processMovement(west, false, gridEntity, node, controlledParent, maxSpeed, movement, tpf); } else { // Calc vertical movement processMovement(north, true, gridEntity, node, controlledParent, maxSpeed, movement, tpf); } break; case West: case East: if (lh >= 1.25f) { // Calc vertical movement processMovement(north, true, gridEntity, node, controlledParent, maxSpeed, movement, tpf); } else { // Calc horizontal movement processMovement(west, false, gridEntity, node, controlledParent, maxSpeed, movement, tpf); } break; } } // Limit the x velocity movement.x = MathExt.absClamp(movement.x, 0f, maxSpeed * tpf); // Limit the y veloctiy movement.y = MathExt.absClamp(movement.y, 0f, maxSpeed * tpf); // Calc thresholds boolean xT = MathExt.equals(movement.x, 0), yT = MathExt.equals( movement.y, 0); // Look if at least one component is 0 if (xT || yT) { // Set to true moving = true; if (!xT) { if (movement.x > 0) { direction = Direction.East; } else { direction = Direction.West; } // Add raster distance lv = 0; lh += Math.abs(movement.x); } else if (!yT) { if (movement.y > 0) { direction = Direction.South; } else { direction = Direction.North; } // Add raster distance lh = 0; lv += Math.abs(movement.y); } else { // This entity is not moving anymore moving = false; } // Finally add the new movement component controlledParent.position().addLocal(movement); /* * Very important! This method rearranges the node because the * change of the controller will not be visible yet. Other * controllers could cause collisions. */ GameRoutines.rearrangeGridNode(node); } } /* * (non-Javadoc) * * @see * com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity#onEvent * (com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity, * java.lang.Object, java.lang.Object[]) */ @SuppressWarnings("unchecked") @Override protected void onEvent(Entity source, Object event, Object... params) { super.onEvent(source, event, params); if (event == InputChange.class) { Map<Input, Boolean> input = (Map<Input, Boolean>) params[0]; inputMap.put(Direction.North, input.get(Input.Up)); inputMap.put(Direction.South, input.get(Input.Down)); inputMap.put(Direction.West, input.get(Input.Left)); inputMap.put(Direction.East, input.get(Input.Right)); } } /** * @return the input map of this controller. */ public Map<Direction, Boolean> inputMap() { return inputMap; } /** * @param dir * The direction. * @return true if the input map is set for the given direction, otherwise * false. */ public boolean hasInputFor(Direction dir) { Boolean value = inputMap.get(dir); return value != null ? value : false; } /** * @return the last direction of the entity. */ public Direction direction() { return direction; } /** * @return the moving flag. */ public boolean isMoving() { return moving; } }