/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.replica.replicaisland; import com.replica.replicaisland.GameObject.ActionType; import com.replica.replicaisland.HotSpotSystem.HotSpotType; /** * This component implements the "patrolling" behavior for AI characters. Patrolling characters * will walk forward on the map until they hit a direction hot spot or a wall, in which case they * may change direction. Patrollers can also be configured via this component to attack the player * if appropriate conditions are met. */ public class PatrolComponent extends GameComponent { private float mMaxSpeed; private float mAcceleration; private boolean mAttack; private float mAttackAtDistance; private boolean mAttackStopsMovement; private float mAttackDuration; private float mAttackDelay; private boolean mTurnToFacePlayer; private boolean mFlying; private float mLastAttackTime; Vector2 mWorkingVector; Vector2 mWorkingVector2; public PatrolComponent() { super(); mWorkingVector = new Vector2(); mWorkingVector2 = new Vector2(); reset(); setPhase(GameComponent.ComponentPhases.THINK.ordinal()); } @Override public void reset() { mTurnToFacePlayer = false; mMaxSpeed = 0.0f; mAcceleration = 0.0f; mAttack = false; mAttackAtDistance = 0.0f; mAttackStopsMovement = false; mAttackDuration = 0.0f; mAttackDelay = 0.0f; mWorkingVector.zero(); mWorkingVector2.zero(); mFlying = false; } @Override public void update(float timeDelta, BaseObject parent) { GameObject parentObject = (GameObject) parent; if (parentObject.getCurrentAction() == ActionType.INVALID || parentObject.getCurrentAction() == ActionType.HIT_REACT) { parentObject.setCurrentAction(GameObject.ActionType.MOVE); } if ((mFlying || parentObject.touchingGround()) && parentObject.life > 0) { GameObjectManager manager = sSystemRegistry.gameObjectManager; GameObject player = null; if (manager != null) { player = manager.getPlayer(); } if (mAttack) { updateAttack(player, parentObject); } if (parentObject.getCurrentAction() == GameObject.ActionType.MOVE && mMaxSpeed > 0.0f) { int hotSpot = HotSpotSystem.HotSpotType.NONE; HotSpotSystem hotSpotSystem = sSystemRegistry.hotSpotSystem; if (hotSpotSystem != null) { // TODO: ack, magic number hotSpot = hotSpotSystem.getHotSpot(parentObject.getCenteredPositionX(), parentObject.getPosition().y + 10.0f); } final float targetVelocityX = parentObject.getTargetVelocity().x; final float targetVelocityY = parentObject.getTargetVelocity().y; boolean goLeft = (parentObject.touchingRightWall() || hotSpot == HotSpotType.GO_LEFT) && targetVelocityX >= 0.0f; boolean goRight = (parentObject.touchingLeftWall() || hotSpot == HotSpotType.GO_RIGHT) && targetVelocityX <= 0.0f; boolean pause = (mMaxSpeed == 0.0f) || hotSpot == HotSpotType.GO_DOWN; if (mTurnToFacePlayer && player != null && player.life > 0) { final float horizontalDelta = player.getCenteredPositionX() - parentObject.getCenteredPositionX(); final int targetFacingDirection = Utils.sign(horizontalDelta); final float closestDistance = player.width / 2.0f; if (targetFacingDirection < 0.0f) { // we want to turn to the left if (goRight) { goRight = false; pause = true; } else if (targetFacingDirection != Utils.sign(parentObject.facingDirection.x)) { goLeft = true; } } else if (targetFacingDirection > 0.0f) { // we want to turn to the right if (goLeft) { goLeft = false; pause = true; } else if (targetFacingDirection != Utils.sign(parentObject.facingDirection.x)) { goRight = true; } } if (Math.abs(horizontalDelta) < closestDistance) { goRight = false; goLeft = false; pause = true; } } if (!mFlying) { if (!pause && !goLeft && !goRight && targetVelocityX == 0.0f) { if (parentObject.facingDirection.x < 0.0f) { goLeft = true; } else { goRight = true; } } if (goRight) { parentObject.getTargetVelocity().x = mMaxSpeed; parentObject.getAcceleration().x = mAcceleration; } else if (goLeft) { parentObject.getTargetVelocity().x = -mMaxSpeed; parentObject.getAcceleration().x = mAcceleration; } else if (pause) { parentObject.getTargetVelocity().x = 0; parentObject.getAcceleration().x = mAcceleration; } } else { final boolean goUp = (parentObject.touchingGround() && targetVelocityY < 0.0f) || hotSpot == HotSpotType.GO_UP; final boolean goDown = (parentObject.touchingCeiling() && targetVelocityY > 0.0f) || hotSpot == HotSpotType.GO_DOWN; if (goUp) { parentObject.getTargetVelocity().x = 0.0f; parentObject.getTargetVelocity().y = mMaxSpeed; parentObject.getAcceleration().y = mAcceleration; parentObject.getAcceleration().x = mAcceleration; } else if (goDown) { parentObject.getTargetVelocity().x = 0.0f; parentObject.getTargetVelocity().y = -mMaxSpeed; parentObject.getAcceleration().y = mAcceleration; parentObject.getAcceleration().x = mAcceleration; } else if (goRight) { parentObject.getTargetVelocity().x = mMaxSpeed; parentObject.getAcceleration().x = mAcceleration; parentObject.getAcceleration().y = mAcceleration; parentObject.getTargetVelocity().y = 0.0f; } else if (goLeft) { parentObject.getTargetVelocity().x = -mMaxSpeed; parentObject.getAcceleration().x = mAcceleration; parentObject.getAcceleration().y = mAcceleration; parentObject.getTargetVelocity().y = 0.0f; } } } } else if (!mFlying && !parentObject.touchingGround() && parentObject.life > 0) { // A non-flying unit is in the air. In this case, just watch for bounces off walls. if (Utils.sign(parentObject.getTargetVelocity().x) != Utils.sign(parentObject.getVelocity().x)) { // Todo: maybe the physics code should adjust target velocity instead in this case? parentObject.getTargetVelocity().x *= -1.0f; } } } private void updateAttack(GameObject player, GameObject parentObject) { TimeSystem time = sSystemRegistry.timeSystem; final float gameTime = time.getGameTime(); boolean visible = true; CameraSystem camera = sSystemRegistry.cameraSystem; ContextParameters context = sSystemRegistry.contextParameters; final float dx = Math.abs(parentObject.getCenteredPositionX() - camera.getFocusPositionX()); final float dy = Math.abs(parentObject.getCenteredPositionY() - camera.getFocusPositionY()); if (dx > context.gameWidth / 2.0f || dy > context.gameHeight / 2.0f) { visible = false; } if (visible && parentObject.getCurrentAction() == GameObject.ActionType.MOVE) { boolean closeEnough = false; boolean timeToAttack = (gameTime - mLastAttackTime) > mAttackDelay; if (mAttackAtDistance > 0 && player != null && player.life > 0 && timeToAttack) { // only attack if we are facing the player if (Utils.sign(player.getPosition().x - parentObject.getPosition().x) == Utils.sign(parentObject.facingDirection.x)) { mWorkingVector.set(parentObject.getPosition()); mWorkingVector.x = parentObject.getCenteredPositionX(); mWorkingVector2.set(player.getPosition()); mWorkingVector2.x = player.getCenteredPositionX(); if (mWorkingVector2.distance2(mWorkingVector) < mAttackAtDistance * mAttackAtDistance) { closeEnough = true; } } } else { closeEnough = true; // If no distance has been set, don't worry about // the player's position. } if (timeToAttack && closeEnough) { // Time to attack. parentObject.setCurrentAction(GameObject.ActionType.ATTACK); mLastAttackTime = gameTime; if (mAttackStopsMovement) { parentObject.getVelocity().zero(); parentObject.getTargetVelocity().zero(); } } } else if (parentObject.getCurrentAction() == GameObject.ActionType.ATTACK) { if (gameTime - mLastAttackTime > mAttackDuration) { parentObject.setCurrentAction(GameObject.ActionType.MOVE); if (mAttackStopsMovement) { parentObject.getTargetVelocity().x = mMaxSpeed * Utils.sign(parentObject.facingDirection.x); parentObject.getAcceleration().x = mAcceleration; } } } } public void setMovementSpeed(float speed, float acceleration) { mMaxSpeed = speed; mAcceleration = acceleration; } public void setupAttack(float distance, float duration, float delay, boolean stopMovement) { mAttack = true; mAttackAtDistance = distance; mAttackStopsMovement = stopMovement; mAttackDuration = duration; mAttackDelay = delay; } public void setTurnToFacePlayer(boolean turn) { mTurnToFacePlayer = turn; } public void setFlying(boolean flying) { mFlying = flying; } }