/* * Copyright (c) 2003-onwards Shaven Puppy Ltd * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'Shaven Puppy' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package worm.entities; import java.util.ArrayList; import org.lwjgl.util.Rectangle; import worm.Entity; import worm.MapRenderer; import worm.WormGameState; import worm.path.AStar; import com.shavenpuppy.jglib.interpolators.LinearInterpolator; import com.shavenpuppy.jglib.util.IntList; import com.shavenpuppy.jglib.util.Util; /** * Handles movement for a unit */ class UnitMovement implements Movement { private static final long serialVersionUID = 1L; private static final ArrayList<Entity> COLLISIONS = new ArrayList<Entity>(); private static final Rectangle BOUNDS = new Rectangle(); private static final int MAX_TOTAL_THINK_TIME = 128; private static final int MAX_THINK_TIME = 32; private static int totalThinkTime = 0; /** The unit */ final Unit unit; /** Pathfinding */ final AStar astar; /** Topology */ final UnitGameMapTopology topology; /** Path */ final IntList path = new IntList(true, WormGameState.ABS_MAX_SIZE); /** Movement start and end */ float sourceMapX, sourceMapY, targetMapX, targetMapY; /** Movement paused */ boolean paused; /** Thinking */ boolean thinking; /** Movement tick */ int tick, currentSpeed; /** Occupied position */ int occupiedX, occupiedY; /** * C'tor */ UnitMovement(Unit unit) { this.unit = unit; topology = new UnitGameMapTopology(this); astar = new AStar(topology); } /** * @return true if the move succeeded */ boolean updateLocation() { float ratio = (float) tick / currentSpeed; float oldX = unit.getMapX(); float oldY = unit.getMapY(); float newX; unit.setLocation ( newX = LinearInterpolator.instance.interpolate(targetMapX, sourceMapX, ratio), LinearInterpolator.instance.interpolate(targetMapY, sourceMapY, ratio) ); // If we bump into another unit, rollback Entity.getCollisions(unit.getBounds(BOUNDS), COLLISIONS); for (int i = COLLISIONS.size(); -- i >= 0; ) { Entity e = COLLISIONS.get(i); if (e == unit) { continue; } if (e instanceof Unit && System.identityHashCode(e) > System.identityHashCode(unit)) { // Rollback unit.setLocation(oldX, oldY); return false; } } if (oldX < newX) { unit.setMirrored(false); } else if (oldX > newX) { unit.setMirrored(true); } return true; } @Override public void adjust(float newX, float newY) { } @Override public void remove() { astar.cancel(); } @Override public boolean isMoving() { return !paused && !thinking; } @Override public void tick() { if (paused) { tick --; if (tick <= 0) { paused = false; // Choose next square to move to and start moving chooseDestination(); } } else { if (thinking) { think(); if (thinking) { // Still thinking return; } } if (tick > 0) { tick --; if (!updateLocation()) { tick ++; } } if (tick == 0) { chooseDestination(); } } } /** * @return the unit */ public Unit getUnit() { return unit; } @Override public void reset() { path.clear(); } /** * Choose the next square to move to */ void chooseDestination() { sourceMapX = unit.getMapX(); sourceMapY = unit.getMapY(); // Now, where are we going? // Firstly if we've got a path that leads to the destination, let's check to see if the next step in that path is clear. If it is clear, // let's carry on using it instead of invoking A*. if (next()) { return; } path.clear(); astar.findPath(UnitGameMapTopology.pack(unit.getTileX(), unit.getTileY()), UnitGameMapTopology.pack(unit.getTarget().getTileX(), unit.getTarget().getTileY()), path); thinking = true; think(); } /** * Take the next step along our chosen path. * @return false if we need to calculate a new path */ boolean next() { if (path.size() == 0) { return false; } int nextTarget = path.remove(0); int targetTileX = GidrahGameMapTopology.getX(nextTarget); int targetTileY = GidrahGameMapTopology.getY(nextTarget); if (topology.canMove(unit.getTileX(), unit.getTileY(), targetTileX, targetTileY)) { // If this is a diagonal move, we take a bit longer over it, and ensure at least 1 route if (Math.abs(unit.getTileX() - targetTileX) + Math.abs(unit.getTileY() - targetTileY) > 1) { tick = (int) (unit.getFeature().getSpeed() * 1.42f); } else { tick = unit.getFeature().getSpeed(); } currentSpeed = tick; targetMapX = targetTileX * MapRenderer.TILE_SIZE + Util.random(0, MapRenderer.TILE_SIZE - 1); targetMapY = targetTileY * MapRenderer.TILE_SIZE + Util.random(0, MapRenderer.TILE_SIZE - 1); thinking = false; paused = false; return true; } else { return false; } } /** * Search A* pathfinder for a few steps. If we find the goal, start moving. If we fail, choose destination again. If * we still haven't found anything, just return. */ void think() { for (int i = 0; i < MAX_THINK_TIME && totalThinkTime ++ < MAX_TOTAL_THINK_TIME; i ++) { switch (astar.nextStep()) { case AStar.SEARCH_STATE_SUCCEEDED: // Found the goal! Move one step closer. next(); return; case AStar.SEARCH_STATE_FAILED: // Total failure. Wait a bit then think again. thinking = false; paused = true; tick = 8; path.clear(); return; case AStar.SEARCH_STATE_SEARCHING: // Carry on searching; break; default: assert false; break; } } // Just return return; } @Override public void maybeRethink(Rectangle bounds) { // Ignore } @Override public void attack() { // Ignore } @Override public void dontAttack() { // Ignore } public static void resetTotalThinkTime() { totalThinkTime = 0; } }