/* * Copyright (c) 2012. HappyDroids LLC, All rights reserved. */ package com.happydroids.droidtowers.controllers; import aurelienribon.tweenengine.BaseTween; import aurelienribon.tweenengine.Timeline; import aurelienribon.tweenengine.Tween; import aurelienribon.tweenengine.TweenCallback; import aurelienribon.tweenengine.equations.Linear; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import com.google.common.collect.Lists; import com.happydroids.droidtowers.TowerConsts; import com.happydroids.droidtowers.entities.Avatar; import com.happydroids.droidtowers.entities.Stair; import com.happydroids.droidtowers.graphics.TransitLine; import com.happydroids.droidtowers.grid.GameGrid; import com.happydroids.droidtowers.grid.GridPosition; import com.happydroids.droidtowers.math.Direction; import com.happydroids.droidtowers.tween.TweenSystem; import com.happydroids.droidtowers.utils.Random; import java.util.List; import static aurelienribon.tweenengine.TweenCallback.COMPLETE; import static com.happydroids.droidtowers.controllers.AvatarState.MOVING; import static com.happydroids.droidtowers.math.Direction.*; import static com.happydroids.droidtowers.tween.GameObjectAccessor.POSITION; public class AvatarSteeringManager { private static final String TAG = AvatarSteeringManager.class.getSimpleName(); public static final float MOVEMENT_SPEED = 30; private final Avatar avatar; private final GameGrid gameGrid; private Array<GridPosition> path; private boolean running; private GridPosition currentPos; private Vector2 currentWorldPos; private Vector2 nextWorldPos; private TransitLine transitLine = new TransitLine(); private boolean movingHorizontally; private Direction horizontalDirection; private int currentState; private Runnable completeCallback; private int pointsTraveled; private float randomSpeedModifier; public AvatarSteeringManager(Avatar avatar, GameGrid gameGrid) { this.avatar = avatar; this.gameGrid = gameGrid; currentWorldPos = new Vector2(); nextWorldPos = new Vector2(); currentState = 0; transitLine = new TransitLine(); transitLine.setVisible(avatar.isVisible()); transitLine.setColor(avatar.getColor()); gameGrid.getRenderer().addTransitLine(transitLine); } public void start() { running = true; pointsTraveled = 0; currentState = 0; randomSpeedModifier = MathUtils.random(0.5f, 1.75f); transitLine.clear(); for (int i = 0, pathSize = path.size; i < pathSize; i++) { GridPosition position = path.get(i); transitLine.addPoint(position.worldPoint()); } advancePosition(); } public void finished() { if (!running) { return; } if (currentPos != null && currentPos.elevator != null) { currentPos.elevator.removePassenger(this); } running = false; pointsTraveled = 0; TweenSystem.manager().killTarget(avatar); avatar.afterReachingTarget(); } private void advancePosition() { if (!running) { return; } pointsTraveled += 1; if (path.size == 0 || pointsTraveled == path.size) { finished(); return; } transitLine.highlightPoint(pointsTraveled); currentPos = path.get(pointsTraveled); currentWorldPos.set(currentPos.worldPoint()); if (pointsTraveled + 1 < path.size) { GridPosition next = path.get(pointsTraveled + 1); if (next != null && next.y != currentPos.y) { if (currentPos.stair != null) { pointsTraveled++; traverseStair(next); return; } else if (currentPos.elevator != null) { GridPosition endOfElevator = null; do { if (next != null && next.elevator != null && next.elevator .equals(currentPos.elevator) && currentPos.y != next.y) { transitLine.highlightPoint(pointsTraveled++); endOfElevator = next; } else { break; } next = pointsTraveled + 1 < path.size ? path.get(pointsTraveled + 1) : null; } while (next != null); if (endOfElevator != null) { traverseElevator(endOfElevator); return; } } } } moveAvatarTo(currentPos, new TweenCallback() { public void onEvent(int type, BaseTween source) { advancePosition(); } }); } private void traverseElevator(final GridPosition destination) { int offsetX = TowerConsts.GRID_UNIT_SIZE + Random.randomInt(0, TowerConsts.GRID_UNIT_SIZE); nextWorldPos.set(currentPos.worldPoint()); GridPosition leftOfElevator = gameGrid.positionCache().getPosition(currentPos.x - 1, currentPos.y); if (leftOfElevator != null && !leftOfElevator.isEmpty()) { nextWorldPos.x -= -offsetX; } else { GridPosition rightOfElevator = gameGrid.positionCache().getPosition(currentPos.x + 1, currentPos.y); if (rightOfElevator != null && !rightOfElevator.isEmpty()) { nextWorldPos.x += offsetX; } } moveAvatarTo(nextWorldPos, new TweenCallback() { @Override public void onEvent(int type, BaseTween source) { if (currentPos.elevator == null) { finished(); return; } boolean addedPassenger = currentPos.elevator .addPassenger(AvatarSteeringManager.this, currentPos.y, destination.y, uponArrivalAtElevatorDestination(destination)); if (!addedPassenger) { Gdx.app.error(TAG, "ZOMG CANNOT REACH FLOOR!!!"); finished(); } } }); } private Runnable uponArrivalAtElevatorDestination(final GridPosition destination) { return new Runnable() { @Override public void run() { currentPos = destination; moveAvatarTo(currentPos, new TweenCallback() { @Override public void onEvent(int type, BaseTween source) { currentState = AvatarState.MOVING; advancePosition(); } }); } }; } private void traverseStair(final GridPosition nextPosition) { if (currentPos.stair == null) { finished(); return; } currentState = AvatarState.USING_STAIRS; Direction verticalDir = nextPosition.y < currentPos.y ? DOWN : UP; Stair stair = verticalDir.equals(UP) ? currentPos.stair : nextPosition.stair; if (stair == null) { finished(); return; } Rectangle stairBounds = stair.getWorldBounds(); Vector2 stairBottomRight = new Vector2(stairBounds.x + stairBounds.width - avatar.getWidth(), stairBounds.y); final Vector2 stairTopLeft = new Vector2(stairBounds.x, stairBounds.y + stairBounds.height); List<Vector2> points = Lists.newArrayList(); points.add(currentPos.worldPoint()); if (verticalDir.equals(UP)) { points.add(stairBottomRight); points.add(stairTopLeft); } else { points.add(stairTopLeft); points.add(stairBottomRight); } points.add(nextPosition.worldPoint()); final TransitLine stairLine = new TransitLine(); stairLine.addPoints(points); stairLine.setColor(avatar.getColor()); gameGrid.getRenderer().addTransitLine(stairLine); Timeline sequence = Timeline.createSequence(); Vector2 lastPos = currentPos.worldPoint(); for (Vector2 point : points) { sequence.push(Tween.to(avatar, POSITION, lastPos.dst(point) * MOVEMENT_SPEED * randomSpeedModifier) .target(point.x, point.y) .ease(Linear.INOUT)); lastPos = point; } sequence.setCallback(new TweenCallback() { public void onEvent(int type, BaseTween source) { gameGrid.getRenderer().removeTransitLine(stairLine); currentState = AvatarState.MOVING; advancePosition(); } }); sequence.start(TweenSystem.manager()); } public void moveAvatarTo(GridPosition gridPosition, TweenCallback endCallback) { moveAvatarTo(nextWorldPos.set(gridPosition.worldPoint()), endCallback); } public void moveAvatarTo(Vector2 endPoint, final TweenCallback endCallback) { currentState |= MOVING; TweenSystem.manager().killTarget(avatar); horizontalDirection = (int) endPoint.x < (int) avatar.getX() ? LEFT : RIGHT; float distanceBetweenPoints = endPoint.dst(avatar.getX(), avatar.getY()); Tween.to(avatar, POSITION, (int) (distanceBetweenPoints * MOVEMENT_SPEED * randomSpeedModifier)) .ease(Linear.INOUT) .target(endPoint.x - avatar.getWidth(), endPoint.y) .setCallback(new TweenCallback() { @Override public void onEvent(int type, BaseTween source) { currentState &= ~MOVING; endCallback.onEvent(type, source); } }) .setCallbackTriggers(COMPLETE) .start(TweenSystem.manager()); } public boolean isRunning() { return running; } public Avatar getAvatar() { return avatar; } public Direction horizontalDirection() { return horizontalDirection; } public int getCurrentState() { return currentState; } public void setCompleteCallback(Runnable runnable) { completeCallback = runnable; } public void boardElevator(final Runnable runnable) { currentState = AvatarState.USING_ELEVATOR; nextWorldPos.set(currentPos.worldPoint()); nextWorldPos.x += Random.randomInt(8, 40); moveAvatarTo(nextWorldPos, new TweenCallback() { @Override public void onEvent(int type, BaseTween source) { currentState = AvatarState.MOVING; runnable.run(); } }); } public void setPath(Array<GridPosition> path) { this.path = path; } public GridPosition getCurrentPos() { return currentPos; } }