/* * 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.CollisionParameters.HitType; import com.replica.replicaisland.GameObject.ActionType; public class NPCComponent extends GameComponent { private float mPauseTime; private float mTargetXVelocity; private int mLastHitTileX; private int mLastHitTileY; private int mDialogEvent; private int mDialogIndex; private HitReactionComponent mHitReactComponent; private int[] mQueuedCommands; private int mQueueTop; private int mQueueBottom; private boolean mExecutingQueue; private Vector2 mPreviousPosition; private float mUpImpulse; private float mDownImpulse; private float mHorizontalImpulse; private float mSlowHorizontalImpulse; private float mAcceleration; private int mGameEvent; private int mGameEventIndex; private boolean mSpawnGameEventOnDeath; private boolean mReactToHits; private boolean mFlying; private boolean mPauseOnAttack; private float mDeathTime; private float mDeathFadeDelay; private static final float UP_IMPULSE = 400.0f; private static final float DOWN_IMPULSE = -10.0f; private static final float HORIZONTAL_IMPULSE = 200.0f; private static final float SLOW_HORIZONTAL_IMPULSE = 50.0f; private static final float ACCELERATION = 300.0f; private static final float HIT_IMPULSE = 300.0f; private static final float HIT_ACCELERATION = 700.0f; private static final float DEATH_FADE_DELAY = 4.0f; private static final float PAUSE_TIME_SHORT = 1.0f; private static final float PAUSE_TIME_MEDIUM = 4.0f; private static final float PAUSE_TIME_LONG = 8.0f; private static final float PAUSE_TIME_ATTACK = 1.0f; private static final float PAUSE_TIME_HIT_REACT = 1.0f; private static final int COMMAND_QUEUE_SIZE = 16; public NPCComponent() { super(); setPhase(ComponentPhases.THINK.ordinal()); mQueuedCommands = new int[COMMAND_QUEUE_SIZE]; mPreviousPosition = new Vector2(); reset(); } @Override public void reset() { mPauseTime = 0.0f; mTargetXVelocity = 0.0f; mLastHitTileX = 0; mLastHitTileY = 0; mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1; mDialogIndex = 0; mHitReactComponent = null; mQueueTop = 0; mQueueBottom = 0; mPreviousPosition.zero(); mExecutingQueue = false; mUpImpulse = UP_IMPULSE; mDownImpulse = DOWN_IMPULSE; mHorizontalImpulse = HORIZONTAL_IMPULSE; mSlowHorizontalImpulse = SLOW_HORIZONTAL_IMPULSE; mAcceleration = ACCELERATION; mGameEvent = -1; mGameEventIndex = -1; mSpawnGameEventOnDeath = false; mReactToHits = false; mFlying = false; mDeathTime = 0.0f; mDeathFadeDelay = DEATH_FADE_DELAY; mPauseOnAttack = true; } @Override public void update(float timeDelta, BaseObject parent) { GameObject parentObject = (GameObject)parent; if (mReactToHits && mPauseTime <= 0.0f && parentObject.getCurrentAction() == ActionType.HIT_REACT) { mPauseTime = PAUSE_TIME_HIT_REACT; pauseMovement(parentObject); parentObject.getVelocity().x = -parentObject.facingDirection.x * HIT_IMPULSE; parentObject.getAcceleration().x = HIT_ACCELERATION; } else if (parentObject.getCurrentAction() == ActionType.DEATH) { if (mSpawnGameEventOnDeath && mGameEvent != -1) { if (Utils.close(parentObject.getVelocity().x, 0.0f) && parentObject.touchingGround()) { if (mDeathTime < mDeathFadeDelay && mDeathTime + timeDelta >= mDeathFadeDelay) { HudSystem hud = sSystemRegistry.hudSystem; if (hud != null) { hud.startFade(false, 1.5f); hud.sendGameEventOnFadeComplete(mGameEvent, mGameEventIndex); mGameEvent = -1; } } mDeathTime += timeDelta; } } // nothing else to do. return; } else if (parentObject.life <= 0) { parentObject.setCurrentAction(ActionType.DEATH); parentObject.getTargetVelocity().x = 0; return; } else if (parentObject.getCurrentAction() == ActionType.INVALID || (!mReactToHits && parentObject.getCurrentAction() == ActionType.HIT_REACT)) { parentObject.setCurrentAction(ActionType.MOVE); } if (mPauseTime <= 0.0f) { HotSpotSystem hotSpotSystem = sSystemRegistry.hotSpotSystem; if (hotSpotSystem != null) { final float centerX = parentObject.getCenteredPositionX(); final int hitTileX = hotSpotSystem.getHitTileX(centerX); final int hitTileY = hotSpotSystem.getHitTileY(parentObject.getPosition().y + 10.0f); boolean accepted = true; if (hitTileX != mLastHitTileX || hitTileY != mLastHitTileY) { final int hotSpot = hotSpotSystem.getHotSpotByTile(hitTileX, hitTileY); if (hotSpot >= HotSpotSystem.HotSpotType.NPC_GO_RIGHT && hotSpot <= HotSpotSystem.HotSpotType.NPC_SLOW) { // movement-related commands are immediate parentObject.setCurrentAction(ActionType.MOVE); accepted = executeCommand(hotSpot, parentObject, timeDelta); } else if (hotSpot == HotSpotSystem.HotSpotType.ATTACK && !mPauseOnAttack) { // when mPauseOnAttack is false, attacks are also immediate. accepted = executeCommand(hotSpot, parentObject, timeDelta); } else if (hotSpot == HotSpotSystem.HotSpotType.NPC_RUN_QUEUED_COMMANDS) { if (!mExecutingQueue && mQueueTop != mQueueBottom) { mExecutingQueue = true; } } else if (hotSpot > HotSpotSystem.HotSpotType.NONE) { queueCommand(hotSpot); } } if (mExecutingQueue) { if (mQueueTop != mQueueBottom) { accepted = executeCommand(nextCommand(), parentObject, timeDelta); if (accepted) { advanceQueue(); } } else { mExecutingQueue = false; } } if (accepted) { mLastHitTileX = hitTileX; mLastHitTileY = hitTileY; } } } else { mPauseTime -= timeDelta; if (mPauseTime < 0.0f) { resumeMovement(parentObject); mPauseTime = 0.0f; parentObject.setCurrentAction(ActionType.MOVE); } } mPreviousPosition.set(parentObject.getPosition()); } private boolean executeCommand(int hotSpot, GameObject parentObject, float timeDelta) { boolean hitAccepted = true; final CameraSystem camera = sSystemRegistry.cameraSystem; switch(hotSpot) { case HotSpotSystem.HotSpotType.WAIT_SHORT: if (mPauseTime == 0.0f) { mPauseTime = PAUSE_TIME_SHORT; pauseMovement(parentObject); } break; case HotSpotSystem.HotSpotType.WAIT_MEDIUM: if (mPauseTime == 0.0f) { mPauseTime = PAUSE_TIME_MEDIUM; pauseMovement(parentObject); } break; case HotSpotSystem.HotSpotType.WAIT_LONG: if (mPauseTime == 0.0f) { mPauseTime = PAUSE_TIME_LONG; pauseMovement(parentObject); } break; case HotSpotSystem.HotSpotType.ATTACK: if (mPauseOnAttack) { if (mPauseTime == 0.0f) { mPauseTime = PAUSE_TIME_ATTACK; pauseMovement(parentObject); } } parentObject.setCurrentAction(ActionType.ATTACK); break; case HotSpotSystem.HotSpotType.TALK: if (mHitReactComponent != null) { if (parentObject.lastReceivedHitType != HitType.COLLECT) { mHitReactComponent.setSpawnGameEventOnHit( HitType.COLLECT, mDialogEvent, mDialogIndex); if (parentObject.getVelocity().x != 0.0f) { pauseMovement(parentObject); } hitAccepted = false; } else { parentObject.setCurrentAction(ActionType.MOVE); resumeMovement(parentObject); mHitReactComponent.setSpawnGameEventOnHit(HitType.INVALID, 0, 0); parentObject.lastReceivedHitType = HitType.INVALID; } } break; case HotSpotSystem.HotSpotType.WALK_AND_TALK: if (mDialogEvent != GameFlowEvent.EVENT_INVALID) { LevelSystem level = sSystemRegistry.levelSystem; level.sendGameEvent(mDialogEvent, mDialogIndex, true); mDialogEvent = GameFlowEvent.EVENT_INVALID; } break; case HotSpotSystem.HotSpotType.TAKE_CAMERA_FOCUS: if (camera != null) { camera.setTarget(parentObject); } break; case HotSpotSystem.HotSpotType.RELEASE_CAMERA_FOCUS: if (camera != null) { GameObjectManager gameObjectManager = sSystemRegistry.gameObjectManager; camera.setTarget(gameObjectManager.getPlayer()); } break; case HotSpotSystem.HotSpotType.END_LEVEL: HudSystem hud = sSystemRegistry.hudSystem; if (hud != null) { hud.startFade(false, 1.5f); hud.sendGameEventOnFadeComplete(GameFlowEvent.EVENT_GO_TO_NEXT_LEVEL, 0); } break; case HotSpotSystem.HotSpotType.GAME_EVENT: if (mGameEvent != -1) { LevelSystem level = sSystemRegistry.levelSystem; if (level != null) { level.sendGameEvent(mGameEvent, mGameEventIndex, true); mGameEvent = -1; } } break; case HotSpotSystem.HotSpotType.NPC_GO_UP_FROM_GROUND: if (!parentObject.touchingGround()) { hitAccepted = false; break; } // fall through case HotSpotSystem.HotSpotType.NPC_GO_UP: parentObject.getVelocity().y = mUpImpulse; parentObject.getTargetVelocity().y = 0.0f; mTargetXVelocity = 0.0f; break; case HotSpotSystem.HotSpotType.NPC_GO_DOWN_FROM_CEILING: if (!parentObject.touchingCeiling()) { hitAccepted = false; break; } // fall through case HotSpotSystem.HotSpotType.NPC_GO_DOWN: parentObject.getVelocity().y = mDownImpulse; parentObject.getTargetVelocity().y = 0.0f; if (mFlying) { mTargetXVelocity = 0.0f; } break; case HotSpotSystem.HotSpotType.NPC_GO_LEFT: parentObject.getTargetVelocity().x = -mHorizontalImpulse; parentObject.getAcceleration().x = mAcceleration; if (mFlying) { parentObject.getVelocity().y = 0.0f; parentObject.getTargetVelocity().y = 0.0f; } break; case HotSpotSystem.HotSpotType.NPC_GO_RIGHT: parentObject.getTargetVelocity().x = mHorizontalImpulse; parentObject.getAcceleration().x = mAcceleration; if (mFlying) { parentObject.getVelocity().y = 0.0f; parentObject.getTargetVelocity().y = 0.0f; } break; case HotSpotSystem.HotSpotType.NPC_GO_UP_RIGHT: parentObject.getVelocity().y = mUpImpulse; parentObject.getTargetVelocity().x = mHorizontalImpulse; parentObject.getAcceleration().x = mAcceleration; break; case HotSpotSystem.HotSpotType.NPC_GO_UP_LEFT: parentObject.getVelocity().y = mUpImpulse; parentObject.getTargetVelocity().x = -mHorizontalImpulse; parentObject.getAcceleration().x = mAcceleration; break; case HotSpotSystem.HotSpotType.NPC_GO_DOWN_RIGHT: parentObject.getVelocity().y = mDownImpulse; parentObject.getTargetVelocity().x = mHorizontalImpulse; parentObject.getAcceleration().x = mAcceleration; break; case HotSpotSystem.HotSpotType.NPC_GO_DOWN_LEFT: parentObject.getVelocity().y = mDownImpulse; parentObject.getTargetVelocity().x = -mHorizontalImpulse; parentObject.getAcceleration().x = mAcceleration; break; case HotSpotSystem.HotSpotType.NPC_GO_TOWARDS_PLAYER: int direction = 1; GameObjectManager manager = sSystemRegistry.gameObjectManager; if (manager != null) { GameObject player = manager.getPlayer(); if (player != null) { direction = Utils.sign( player.getCenteredPositionX() - parentObject.getCenteredPositionX()); } } parentObject.getTargetVelocity().x = mHorizontalImpulse * direction; if (mFlying) { parentObject.getVelocity().y = 0.0f; parentObject.getTargetVelocity().y = 0.0f; } break; case HotSpotSystem.HotSpotType.NPC_GO_RANDOM: parentObject.getTargetVelocity().x = mHorizontalImpulse * (Math.random() > 0.5f ? -1.0f : 1.0f); if (mFlying) { parentObject.getVelocity().y = 0.0f; parentObject.getTargetVelocity().y = 0.0f; } break; case HotSpotSystem.HotSpotType.NPC_STOP: parentObject.getTargetVelocity().x = 0.0f; parentObject.getVelocity().x = 0.0f; break; case HotSpotSystem.HotSpotType.NPC_SLOW: parentObject.getTargetVelocity().x = mSlowHorizontalImpulse * Utils.sign(parentObject.getTargetVelocity().x); break; case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1: case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_2: case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_3: case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_4: case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_5: case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1: case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_2: case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_3: case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_4: case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_5: selectDialog(hotSpot); break; case HotSpotSystem.HotSpotType.NONE: if (parentObject.touchingGround() && parentObject.getVelocity().y <= 0.0f) { //resumeMovement(parentObject); } break; } return hitAccepted; } private void pauseMovement(GameObject parentObject) { mTargetXVelocity = parentObject.getTargetVelocity().x; parentObject.getTargetVelocity().x = 0.0f; parentObject.getVelocity().x = 0.0f; } private void resumeMovement(GameObject parentObject) { parentObject.getTargetVelocity().x = mTargetXVelocity; parentObject.getAcceleration().x = mAcceleration; } private void selectDialog(int hitSpot) { mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1; mDialogIndex = hitSpot - HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1; if (hitSpot >= HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1) { mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER2; mDialogIndex = hitSpot - HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1; } } private int nextCommand() { int result = HotSpotSystem.HotSpotType.NONE; if (mQueueTop != mQueueBottom) { result = mQueuedCommands[mQueueTop]; } return result; } private int advanceQueue() { int result = HotSpotSystem.HotSpotType.NONE; if (mQueueTop != mQueueBottom) { result = mQueuedCommands[mQueueTop]; mQueueTop = (mQueueTop + 1) % COMMAND_QUEUE_SIZE; } return result; } private void queueCommand(int hotspot) { int nextSlot = (mQueueBottom + 1) % COMMAND_QUEUE_SIZE; if (nextSlot != mQueueTop) { // only comply if there is space left in the buffer mQueuedCommands[mQueueBottom] = hotspot; mQueueBottom = nextSlot; } } public void setHitReactionComponent(HitReactionComponent hitReact) { mHitReactComponent = hitReact; } public void setSpeeds(float horizontalImpulse, float slowHorizontalImpulse, float upImpulse, float downImpulse, float acceleration) { mHorizontalImpulse = horizontalImpulse; mSlowHorizontalImpulse = slowHorizontalImpulse; mUpImpulse = upImpulse; mDownImpulse = downImpulse; mAcceleration = acceleration; } public void setGameEvent(int event, int index, boolean spawnOnDeath) { mGameEvent = event; mGameEventIndex = index; mSpawnGameEventOnDeath = spawnOnDeath; } public void setReactToHits(boolean react) { mReactToHits = react; } public void setFlying(boolean flying) { mFlying = flying; } public void setPauseOnAttack(boolean pauseOnAttack) { mPauseOnAttack = pauseOnAttack; } }