1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.replica.replicaisland; 18 19import com.replica.replicaisland.CollisionParameters.HitType; 20import com.replica.replicaisland.GameObject.ActionType; 21 22public class NPCComponent extends GameComponent { 23 private float mPauseTime; 24 private float mTargetXVelocity; 25 private int mLastHitTileX; 26 private int mLastHitTileY; 27 28 private int mDialogEvent; 29 private int mDialogIndex; 30 31 private HitReactionComponent mHitReactComponent; 32 33 private int[] mQueuedCommands; 34 private int mQueueTop; 35 private int mQueueBottom; 36 private boolean mExecutingQueue; 37 38 private Vector2 mPreviousPosition; 39 40 private float mUpImpulse; 41 private float mDownImpulse; 42 private float mHorizontalImpulse; 43 private float mSlowHorizontalImpulse; 44 private float mAcceleration; 45 46 private int mGameEvent; 47 private int mGameEventIndex; 48 private boolean mSpawnGameEventOnDeath; 49 50 private boolean mReactToHits; 51 private boolean mFlying; 52 private boolean mPauseOnAttack; 53 54 private float mDeathTime; 55 private float mDeathFadeDelay; 56 57 private static final float UP_IMPULSE = 400.0f; 58 private static final float DOWN_IMPULSE = -10.0f; 59 private static final float HORIZONTAL_IMPULSE = 200.0f; 60 private static final float SLOW_HORIZONTAL_IMPULSE = 50.0f; 61 private static final float ACCELERATION = 300.0f; 62 private static final float HIT_IMPULSE = 300.0f; 63 private static final float HIT_ACCELERATION = 700.0f; 64 65 private static final float DEATH_FADE_DELAY = 4.0f; 66 67 private static final float PAUSE_TIME_SHORT = 1.0f; 68 private static final float PAUSE_TIME_MEDIUM = 4.0f; 69 private static final float PAUSE_TIME_LONG = 8.0f; 70 private static final float PAUSE_TIME_ATTACK = 1.0f; 71 private static final float PAUSE_TIME_HIT_REACT = 1.0f; 72 73 private static final int COMMAND_QUEUE_SIZE = 16; 74 75 public NPCComponent() { 76 super(); 77 setPhase(ComponentPhases.THINK.ordinal()); 78 mQueuedCommands = new int[COMMAND_QUEUE_SIZE]; 79 mPreviousPosition = new Vector2(); 80 reset(); 81 } 82 83 @Override 84 public void reset() { 85 mPauseTime = 0.0f; 86 mTargetXVelocity = 0.0f; 87 mLastHitTileX = 0; 88 mLastHitTileY = 0; 89 mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1; 90 mDialogIndex = 0; 91 mHitReactComponent = null; 92 mQueueTop = 0; 93 mQueueBottom = 0; 94 mPreviousPosition.zero(); 95 mExecutingQueue = false; 96 mUpImpulse = UP_IMPULSE; 97 mDownImpulse = DOWN_IMPULSE; 98 mHorizontalImpulse = HORIZONTAL_IMPULSE; 99 mSlowHorizontalImpulse = SLOW_HORIZONTAL_IMPULSE; 100 mAcceleration = ACCELERATION; 101 mGameEvent = -1; 102 mGameEventIndex = -1; 103 mSpawnGameEventOnDeath = false; 104 mReactToHits = false; 105 mFlying = false; 106 mDeathTime = 0.0f; 107 mDeathFadeDelay = DEATH_FADE_DELAY; 108 mPauseOnAttack = true; 109 } 110 111 @Override 112 public void update(float timeDelta, BaseObject parent) { 113 114 GameObject parentObject = (GameObject)parent; 115 116 if (mReactToHits && 117 mPauseTime <= 0.0f && 118 parentObject.getCurrentAction() == ActionType.HIT_REACT) { 119 mPauseTime = PAUSE_TIME_HIT_REACT; 120 pauseMovement(parentObject); 121 parentObject.getVelocity().x = -parentObject.facingDirection.x * HIT_IMPULSE; 122 parentObject.getAcceleration().x = HIT_ACCELERATION; 123 124 } else if (parentObject.getCurrentAction() == ActionType.DEATH) { 125 if (mSpawnGameEventOnDeath && mGameEvent != -1) { 126 if (Utils.close(parentObject.getVelocity().x, 0.0f) 127 && parentObject.touchingGround()) { 128 129 if (mDeathTime < mDeathFadeDelay && mDeathTime + timeDelta >= mDeathFadeDelay) { 130 HudSystem hud = sSystemRegistry.hudSystem; 131 132 if (hud != null) { 133 hud.startFade(false, 1.5f); 134 hud.sendGameEventOnFadeComplete(mGameEvent, mGameEventIndex); 135 mGameEvent = -1; 136 } 137 } 138 mDeathTime += timeDelta; 139 140 } 141 } 142 // nothing else to do. 143 return; 144 } else if (parentObject.life <= 0) { 145 parentObject.setCurrentAction(ActionType.DEATH); 146 parentObject.getTargetVelocity().x = 0; 147 return; 148 } else if (parentObject.getCurrentAction() == ActionType.INVALID || 149 (!mReactToHits && parentObject.getCurrentAction() == ActionType.HIT_REACT)) { 150 parentObject.setCurrentAction(ActionType.MOVE); 151 } 152 153 if (mPauseTime <= 0.0f) { 154 155 HotSpotSystem hotSpotSystem = sSystemRegistry.hotSpotSystem; 156 157 if (hotSpotSystem != null) { 158 final float centerX = parentObject.getCenteredPositionX(); 159 final int hitTileX = hotSpotSystem.getHitTileX(centerX); 160 final int hitTileY = hotSpotSystem.getHitTileY(parentObject.getPosition().y + 10.0f); 161 boolean accepted = true; 162 163 if (hitTileX != mLastHitTileX || hitTileY != mLastHitTileY) { 164 165 final int hotSpot = hotSpotSystem.getHotSpotByTile(hitTileX, hitTileY); 166 167 if (hotSpot >= HotSpotSystem.HotSpotType.NPC_GO_RIGHT && hotSpot <= HotSpotSystem.HotSpotType.NPC_SLOW) { 168 // movement-related commands are immediate 169 parentObject.setCurrentAction(ActionType.MOVE); 170 accepted = executeCommand(hotSpot, parentObject, timeDelta); 171 } else if (hotSpot == HotSpotSystem.HotSpotType.ATTACK && !mPauseOnAttack) { 172 // when mPauseOnAttack is false, attacks are also immediate. 173 accepted = executeCommand(hotSpot, parentObject, timeDelta); 174 } else if (hotSpot == HotSpotSystem.HotSpotType.NPC_RUN_QUEUED_COMMANDS) { 175 if (!mExecutingQueue && mQueueTop != mQueueBottom) { 176 mExecutingQueue = true; 177 } 178 } else if (hotSpot > HotSpotSystem.HotSpotType.NONE) { 179 queueCommand(hotSpot); 180 } 181 } 182 183 if (mExecutingQueue) { 184 if (mQueueTop != mQueueBottom) { 185 accepted = executeCommand(nextCommand(), parentObject, timeDelta); 186 if (accepted) { 187 advanceQueue(); 188 } 189 } else { 190 mExecutingQueue = false; 191 } 192 } 193 194 if (accepted) { 195 mLastHitTileX = hitTileX; 196 mLastHitTileY = hitTileY; 197 } 198 199 } 200 } else { 201 mPauseTime -= timeDelta; 202 if (mPauseTime < 0.0f) { 203 resumeMovement(parentObject); 204 mPauseTime = 0.0f; 205 parentObject.setCurrentAction(ActionType.MOVE); 206 } 207 } 208 209 mPreviousPosition.set(parentObject.getPosition()); 210 } 211 212 private boolean executeCommand(int hotSpot, GameObject parentObject, float timeDelta) { 213 boolean hitAccepted = true; 214 final CameraSystem camera = sSystemRegistry.cameraSystem; 215 216 switch(hotSpot) { 217 case HotSpotSystem.HotSpotType.WAIT_SHORT: 218 if (mPauseTime == 0.0f) { 219 mPauseTime = PAUSE_TIME_SHORT; 220 pauseMovement(parentObject); 221 } 222 break; 223 case HotSpotSystem.HotSpotType.WAIT_MEDIUM: 224 if (mPauseTime == 0.0f) { 225 mPauseTime = PAUSE_TIME_MEDIUM; 226 pauseMovement(parentObject); 227 } 228 break; 229 case HotSpotSystem.HotSpotType.WAIT_LONG: 230 if (mPauseTime == 0.0f) { 231 mPauseTime = PAUSE_TIME_LONG; 232 pauseMovement(parentObject); 233 } 234 break; 235 case HotSpotSystem.HotSpotType.ATTACK: 236 if (mPauseOnAttack) { 237 if (mPauseTime == 0.0f) { 238 mPauseTime = PAUSE_TIME_ATTACK; 239 pauseMovement(parentObject); 240 241 } 242 } 243 parentObject.setCurrentAction(ActionType.ATTACK); 244 245 break; 246 247 case HotSpotSystem.HotSpotType.TALK: 248 if (mHitReactComponent != null) { 249 if (parentObject.lastReceivedHitType != HitType.COLLECT) { 250 mHitReactComponent.setSpawnGameEventOnHit( 251 HitType.COLLECT, mDialogEvent, mDialogIndex); 252 if (parentObject.getVelocity().x != 0.0f) { 253 pauseMovement(parentObject); 254 } 255 hitAccepted = false; 256 } else { 257 parentObject.setCurrentAction(ActionType.MOVE); 258 259 resumeMovement(parentObject); 260 mHitReactComponent.setSpawnGameEventOnHit(HitType.INVALID, 0, 0); 261 parentObject.lastReceivedHitType = HitType.INVALID; 262 } 263 } 264 break; 265 266 case HotSpotSystem.HotSpotType.WALK_AND_TALK: 267 if (mDialogEvent != GameFlowEvent.EVENT_INVALID) { 268 LevelSystem level = sSystemRegistry.levelSystem; 269 level.sendGameEvent(mDialogEvent, mDialogIndex, true); 270 mDialogEvent = GameFlowEvent.EVENT_INVALID; 271 } 272 break; 273 274 case HotSpotSystem.HotSpotType.TAKE_CAMERA_FOCUS: 275 if (camera != null) { 276 camera.setTarget(parentObject); 277 } 278 break; 279 280 case HotSpotSystem.HotSpotType.RELEASE_CAMERA_FOCUS: 281 282 if (camera != null) { 283 GameObjectManager gameObjectManager = sSystemRegistry.gameObjectManager; 284 camera.setTarget(gameObjectManager.getPlayer()); 285 } 286 break; 287 288 case HotSpotSystem.HotSpotType.END_LEVEL: 289 HudSystem hud = sSystemRegistry.hudSystem; 290 291 if (hud != null) { 292 hud.startFade(false, 1.5f); 293 hud.sendGameEventOnFadeComplete(GameFlowEvent.EVENT_GO_TO_NEXT_LEVEL, 0); 294 } 295 break; 296 case HotSpotSystem.HotSpotType.GAME_EVENT: 297 if (mGameEvent != -1) { 298 LevelSystem level = sSystemRegistry.levelSystem; 299 if (level != null) { 300 level.sendGameEvent(mGameEvent, mGameEventIndex, true); 301 mGameEvent = -1; 302 } 303 } 304 break; 305 306 case HotSpotSystem.HotSpotType.NPC_GO_UP_FROM_GROUND: 307 if (!parentObject.touchingGround()) { 308 hitAccepted = false; 309 break; 310 } 311 // fall through 312 case HotSpotSystem.HotSpotType.NPC_GO_UP: 313 parentObject.getVelocity().y = mUpImpulse; 314 parentObject.getTargetVelocity().y = 0.0f; 315 mTargetXVelocity = 0.0f; 316 317 break; 318 case HotSpotSystem.HotSpotType.NPC_GO_DOWN_FROM_CEILING: 319 if (!parentObject.touchingCeiling()) { 320 hitAccepted = false; 321 break; 322 } 323 // fall through 324 case HotSpotSystem.HotSpotType.NPC_GO_DOWN: 325 parentObject.getVelocity().y = mDownImpulse; 326 parentObject.getTargetVelocity().y = 0.0f; 327 if (mFlying) { 328 mTargetXVelocity = 0.0f; 329 } 330 break; 331 case HotSpotSystem.HotSpotType.NPC_GO_LEFT: 332 parentObject.getTargetVelocity().x = -mHorizontalImpulse; 333 parentObject.getAcceleration().x = mAcceleration; 334 if (mFlying) { 335 parentObject.getVelocity().y = 0.0f; 336 parentObject.getTargetVelocity().y = 0.0f; 337 } 338 break; 339 case HotSpotSystem.HotSpotType.NPC_GO_RIGHT: 340 parentObject.getTargetVelocity().x = mHorizontalImpulse; 341 parentObject.getAcceleration().x = mAcceleration; 342 if (mFlying) { 343 parentObject.getVelocity().y = 0.0f; 344 parentObject.getTargetVelocity().y = 0.0f; 345 } 346 347 break; 348 case HotSpotSystem.HotSpotType.NPC_GO_UP_RIGHT: 349 parentObject.getVelocity().y = mUpImpulse; 350 parentObject.getTargetVelocity().x = mHorizontalImpulse; 351 parentObject.getAcceleration().x = mAcceleration; 352 353 354 break; 355 case HotSpotSystem.HotSpotType.NPC_GO_UP_LEFT: 356 parentObject.getVelocity().y = mUpImpulse; 357 parentObject.getTargetVelocity().x = -mHorizontalImpulse; 358 parentObject.getAcceleration().x = mAcceleration; 359 360 361 break; 362 case HotSpotSystem.HotSpotType.NPC_GO_DOWN_RIGHT: 363 parentObject.getVelocity().y = mDownImpulse; 364 parentObject.getTargetVelocity().x = mHorizontalImpulse; 365 parentObject.getAcceleration().x = mAcceleration; 366 367 368 break; 369 case HotSpotSystem.HotSpotType.NPC_GO_DOWN_LEFT: 370 parentObject.getVelocity().y = mDownImpulse; 371 parentObject.getTargetVelocity().x = -mHorizontalImpulse; 372 parentObject.getAcceleration().x = mAcceleration; 373 374 375 break; 376 case HotSpotSystem.HotSpotType.NPC_GO_TOWARDS_PLAYER: 377 int direction = 1; 378 GameObjectManager manager = sSystemRegistry.gameObjectManager; 379 if (manager != null) { 380 GameObject player = manager.getPlayer(); 381 if (player != null) { 382 direction = Utils.sign( 383 player.getCenteredPositionX() - 384 parentObject.getCenteredPositionX()); 385 } 386 } 387 parentObject.getTargetVelocity().x = mHorizontalImpulse * direction; 388 if (mFlying) { 389 parentObject.getVelocity().y = 0.0f; 390 parentObject.getTargetVelocity().y = 0.0f; 391 } 392 break; 393 case HotSpotSystem.HotSpotType.NPC_GO_RANDOM: 394 parentObject.getTargetVelocity().x = mHorizontalImpulse * (Math.random() > 0.5f ? -1.0f : 1.0f); 395 if (mFlying) { 396 parentObject.getVelocity().y = 0.0f; 397 parentObject.getTargetVelocity().y = 0.0f; 398 } 399 break; 400 401 case HotSpotSystem.HotSpotType.NPC_STOP: 402 parentObject.getTargetVelocity().x = 0.0f; 403 parentObject.getVelocity().x = 0.0f; 404 break; 405 406 case HotSpotSystem.HotSpotType.NPC_SLOW: 407 parentObject.getTargetVelocity().x = mSlowHorizontalImpulse * Utils.sign(parentObject.getTargetVelocity().x); 408 break; 409 410 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1: 411 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_2: 412 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_3: 413 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_4: 414 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_5: 415 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1: 416 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_2: 417 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_3: 418 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_4: 419 case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_5: 420 selectDialog(hotSpot); 421 break; 422 case HotSpotSystem.HotSpotType.NONE: 423 if (parentObject.touchingGround() && parentObject.getVelocity().y <= 0.0f) { 424 //resumeMovement(parentObject); 425 } 426 break; 427 } 428 429 return hitAccepted; 430 } 431 432 private void pauseMovement(GameObject parentObject) { 433 mTargetXVelocity = parentObject.getTargetVelocity().x; 434 parentObject.getTargetVelocity().x = 0.0f; 435 parentObject.getVelocity().x = 0.0f; 436 } 437 438 private void resumeMovement(GameObject parentObject) { 439 parentObject.getTargetVelocity().x = mTargetXVelocity; 440 parentObject.getAcceleration().x = mAcceleration; 441 } 442 443 private void selectDialog(int hitSpot) { 444 mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1; 445 mDialogIndex = hitSpot - HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1; 446 447 if (hitSpot >= HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1) { 448 mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER2; 449 mDialogIndex = hitSpot - HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1; 450 } 451 } 452 453 private int nextCommand() { 454 int result = HotSpotSystem.HotSpotType.NONE; 455 if (mQueueTop != mQueueBottom) { 456 result = mQueuedCommands[mQueueTop]; 457 } 458 return result; 459 } 460 461 private int advanceQueue() { 462 int result = HotSpotSystem.HotSpotType.NONE; 463 if (mQueueTop != mQueueBottom) { 464 result = mQueuedCommands[mQueueTop]; 465 mQueueTop = (mQueueTop + 1) % COMMAND_QUEUE_SIZE; 466 } 467 return result; 468 } 469 470 private void queueCommand(int hotspot) { 471 int nextSlot = (mQueueBottom + 1) % COMMAND_QUEUE_SIZE; 472 if (nextSlot != mQueueTop) { // only comply if there is space left in the buffer 473 mQueuedCommands[mQueueBottom] = hotspot; 474 mQueueBottom = nextSlot; 475 } 476 } 477 478 public void setHitReactionComponent(HitReactionComponent hitReact) { 479 mHitReactComponent = hitReact; 480 } 481 482 public void setSpeeds(float horizontalImpulse, float slowHorizontalImpulse, float upImpulse, float downImpulse, float acceleration) { 483 mHorizontalImpulse = horizontalImpulse; 484 mSlowHorizontalImpulse = slowHorizontalImpulse; 485 mUpImpulse = upImpulse; 486 mDownImpulse = downImpulse; 487 mAcceleration = acceleration; 488 } 489 490 public void setGameEvent(int event, int index, boolean spawnOnDeath) { 491 mGameEvent = event; 492 mGameEventIndex = index; 493 mSpawnGameEventOnDeath = spawnOnDeath; 494 } 495 496 public void setReactToHits(boolean react) { 497 mReactToHits = react; 498 } 499 500 public void setFlying(boolean flying) { 501 mFlying = flying; 502 } 503 504 public void setPauseOnAttack(boolean pauseOnAttack) { 505 mPauseOnAttack = pauseOnAttack; 506 } 507} 508