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 PlayerComponent extends GameComponent { 23 24 private static final float GROUND_IMPULSE_SPEED = 5000.0f; 25 private static final float AIR_HORIZONTAL_IMPULSE_SPEED = 4000.0f; 26 private static final float AIR_VERTICAL_IMPULSE_SPEED = 1200.0f; 27 private static final float AIR_VERTICAL_IMPULSE_SPEED_FROM_GROUND = 250.0f; 28 private static final float AIR_DRAG_SPEED = 4000.0f; 29 private static final float MAX_GROUND_HORIZONTAL_SPEED = 500.0f; 30 private static final float MAX_AIR_HORIZONTAL_SPEED = 150.0f; 31 private static final float MAX_UPWARD_SPEED = 250.0f; 32 private static final float VERTICAL_IMPULSE_TOLERANCE = 50.0f; 33 private static final float FUEL_AMOUNT = 1.0f; 34 35 private static final float JUMP_TO_JETS_DELAY = 0.5f; 36 37 private static final float STOMP_VELOCITY = -1000.0f; 38 private static final float STOMP_DELAY_TIME = 0.15f; 39 private static final float STOMP_AIR_HANG_TIME = 0.0f; //0.25f; 40 private static final float STOMP_SHAKE_MAGNITUDE = 15.0f; 41 private static final float STOMP_VIBRATE_TIME = 0.05f; 42 private static final float HIT_REACT_TIME = 0.5f; 43 44 private static final float GHOST_REACTIVATION_DELAY = 0.3f; 45 private static final float GHOST_CHARGE_TIME = 0.75f; 46 47 private static final int MAX_GEMS_PER_LEVEL = 3; 48 49 private static final float NO_GEMS_GHOST_TIME = 3.0f; 50 private static final float ONE_GEM_GHOST_TIME = 8.0f; 51 private static final float TWO_GEMS_GHOST_TIME = 0.0f; // no limit. 52 53 54 public enum State { 55 MOVE, 56 STOMP, 57 HIT_REACT, 58 DEAD, 59 WIN, 60 FROZEN, 61 POST_GHOST_DELAY 62 } 63 64 private boolean mTouchingGround; 65 private State mState; 66 private float mTimer; 67 private float mTimer2; 68 private float mFuel; 69 private float mJumpTime; 70 private boolean mGhostActive; 71 private float mGhostDeactivatedTime; 72 private float mGhostChargeTime; 73 private InventoryComponent mInventory; 74 private Vector2 mHotSpotTestPoint; 75 private ChangeComponentsComponent mInvincibleSwap; 76 private float mInvincibleEndTime; 77 private HitReactionComponent mHitReaction; 78 private float mFuelAirRefillSpeed; 79 private DifficultyConstants mDifficultyConstants; 80 private final static DifficultyConstants sDifficultyArray[] = { 81 new BabyDifficultyConstants(), 82 new KidsDifficultyConstants(), 83 new AdultsDifficultyConstants() 84 }; 85 private FadeDrawableComponent mInvincibleFader; // HACK! 86 87 // Variables recorded for animation decisions. 88 private boolean mRocketsOn; 89 90 public PlayerComponent() { 91 super(); 92 mHotSpotTestPoint = new Vector2(); 93 reset(); 94 setPhase(ComponentPhases.THINK.ordinal()); 95 } 96 97 @Override 98 public void reset() { 99 mTouchingGround = false; 100 mState = State.MOVE; 101 mTimer = 0.0f; 102 mTimer2 = 0.0f; 103 mFuel = 0.0f; 104 mJumpTime = 0.0f; 105 mGhostActive = false; 106 mGhostDeactivatedTime = 0.0f; 107 mInventory = null; 108 mGhostChargeTime = 0.0f; 109 mHotSpotTestPoint.zero(); 110 mInvincibleSwap = null; 111 mInvincibleEndTime = 0.0f; 112 mHitReaction = null; 113 mDifficultyConstants = getDifficultyConstants(); 114 mFuelAirRefillSpeed = mDifficultyConstants.getFuelAirRefillSpeed(); 115 mInvincibleFader = null; 116 } 117 118 protected void move(float time, float timeDelta, GameObject parentObject) { 119 VectorPool pool = sSystemRegistry.vectorPool; 120 InputGameInterface input = sSystemRegistry.inputGameInterface; 121 122 if (pool != null && input != null) { 123 124 if (mFuel < FUEL_AMOUNT) { 125 if (mTouchingGround) { 126 mFuel += mDifficultyConstants.getFuelGroundRefillSpeed() * timeDelta; 127 } else { 128 mFuel += mFuelAirRefillSpeed * timeDelta; 129 } 130 131 if (mFuel > FUEL_AMOUNT) { 132 mFuel = FUEL_AMOUNT; 133 } 134 } 135 136 final InputXY dpad = input.getDirectionalPad(); 137 final InputButton jumpButton = input.getJumpButton(); 138 139 if (dpad.getPressed() || jumpButton.getPressed()) { 140 Vector2 impulse = pool.allocate(); 141 142 if (dpad.getPressed()) { 143 impulse.set(dpad.getX(), 0.0f); 144 } 145 146 if (jumpButton.getPressed()) { 147 if (jumpButton.getTriggered(time) && mTouchingGround) { 148 // In this case, velocity is instant so we don't need to scale 149 // it by time. 150 impulse.y = AIR_VERTICAL_IMPULSE_SPEED_FROM_GROUND; 151 mJumpTime = time; 152 } else if (time > mJumpTime + JUMP_TO_JETS_DELAY) { 153 if (mFuel > 0.0f) { 154 mFuel -= timeDelta; 155 impulse.y = AIR_VERTICAL_IMPULSE_SPEED * timeDelta; 156 mRocketsOn = true; 157 } 158 159 } 160 } 161 162 float horziontalSpeed = GROUND_IMPULSE_SPEED; 163 float maxHorizontalSpeed = MAX_GROUND_HORIZONTAL_SPEED; 164 final boolean inTheAir = !mTouchingGround 165 || impulse.y > VERTICAL_IMPULSE_TOLERANCE; 166 if (inTheAir) { 167 horziontalSpeed = AIR_HORIZONTAL_IMPULSE_SPEED; 168 maxHorizontalSpeed = MAX_AIR_HORIZONTAL_SPEED; 169 } 170 171 impulse.x = (impulse.x * horziontalSpeed * timeDelta); 172 173 // Don't let our jets move us past specific speed thresholds. 174 float currentSpeed = parentObject.getVelocity().x; 175 final float newSpeed = Math.abs(currentSpeed + impulse.x); 176 if (newSpeed > maxHorizontalSpeed) { 177 if (Math.abs(currentSpeed) < maxHorizontalSpeed) { 178 currentSpeed = maxHorizontalSpeed * Utils.sign(impulse.x); 179 parentObject.getVelocity().x = (currentSpeed); 180 } 181 impulse.x = (0.0f); 182 } 183 184 if (parentObject.getVelocity().y + impulse.y > MAX_UPWARD_SPEED 185 && Utils.sign(impulse.y) > 0) { 186 impulse.y = (0.0f); 187 if (parentObject.getVelocity().y < MAX_UPWARD_SPEED) { 188 parentObject.getVelocity().y = (MAX_UPWARD_SPEED); 189 } 190 } 191 192 if (inTheAir) { 193 // Apply drag while in the air. 194 if (Math.abs(currentSpeed) > maxHorizontalSpeed) { 195 float postDragSpeed = currentSpeed - 196 (AIR_DRAG_SPEED * timeDelta * Utils.sign(currentSpeed)); 197 if (Utils.sign(currentSpeed) != Utils.sign(postDragSpeed)) { 198 postDragSpeed = 0.0f; 199 } else if (Math.abs(postDragSpeed) < maxHorizontalSpeed) { 200 postDragSpeed = maxHorizontalSpeed * Utils.sign(postDragSpeed); 201 } 202 parentObject.getVelocity().x = (postDragSpeed); 203 } 204 } 205 206 parentObject.getImpulse().add(impulse); 207 pool.release(impulse); 208 } 209 210 } 211 } 212 213 public void update(float timeDelta, BaseObject parent) { 214 215 TimeSystem time = sSystemRegistry.timeSystem; 216 GameObject parentObject = (GameObject)parent; 217 218 final float gameTime = time.getGameTime(); 219 mTouchingGround = parentObject.touchingGround(); 220 221 mRocketsOn = false; 222 223 224 if (parentObject.getCurrentAction() == ActionType.INVALID) { 225 gotoMove(parentObject); 226 } 227 228 if (mInventory != null && mState != State.WIN) { 229 InventoryComponent.UpdateRecord inventory = mInventory.getRecord(); 230 if (inventory.coinCount >= mDifficultyConstants.getCoinsPerPowerup()) { 231 inventory.coinCount = 0; 232 mInventory.setChanged(); 233 parentObject.life = mDifficultyConstants.getMaxPlayerLife(); 234 if (mInvincibleEndTime < gameTime) { 235 mInvincibleSwap.activate(parentObject); 236 mInvincibleEndTime = gameTime + mDifficultyConstants.getGlowDuration(); 237 if (mHitReaction != null) { 238 mHitReaction.setForceInvincible(true); 239 } 240 } else { 241 // invincibility is already active, extend it. 242 mInvincibleEndTime = gameTime + mDifficultyConstants.getGlowDuration(); 243 // HACK HACK HACK. This really doesn't go here. 244 // To extend the invincible time we need to increment the value above (easy) 245 // and also tell the component managing the glow sprite to reset its 246 // timer (not easy). Next time, make a shared value system for this 247 // kind of case!! 248 if (mInvincibleFader != null) { 249 mInvincibleFader.resetPhase(); 250 } 251 } 252 } 253 if (inventory.rubyCount >= MAX_GEMS_PER_LEVEL) { 254 gotoWin(gameTime); 255 } 256 } 257 258 if (mInvincibleEndTime > 0.0f && (mInvincibleEndTime < gameTime || mState == State.DEAD)) { 259 mInvincibleSwap.activate(parentObject); 260 mInvincibleEndTime = 0.0f; 261 if (mHitReaction != null) { 262 mHitReaction.setForceInvincible(false); 263 } 264 } 265 266 267 // Watch for hit reactions or death interrupting the state machine. 268 if (mState != State.DEAD && mState != State.WIN ) { 269 if (parentObject.life <= 0) { 270 gotoDead(gameTime); 271 } else if (parentObject.getPosition().y < -parentObject.height) { 272 // we fell off the bottom of the screen, die. 273 parentObject.life = 0; 274 gotoDead(gameTime); 275 } else if (mState != State.HIT_REACT 276 && parentObject.lastReceivedHitType != HitType.INVALID 277 && parentObject.getCurrentAction() == ActionType.HIT_REACT) { 278 gotoHitReact(parentObject, gameTime); 279 } else { 280 HotSpotSystem hotSpot = sSystemRegistry.hotSpotSystem; 281 if (hotSpot != null) { 282 // TODO: HACK! Unify all this code. 283 if (hotSpot.getHotSpot(parentObject.getCenteredPositionX(), 284 parentObject.getPosition().y + 10.0f) == HotSpotSystem.HotSpotType.DIE) { 285 parentObject.life = 0; 286 gotoDead(gameTime); 287 } 288 } 289 } 290 } 291 292 switch(mState) { 293 case MOVE: 294 stateMove(gameTime, timeDelta, parentObject); 295 break; 296 case STOMP: 297 stateStomp(gameTime, timeDelta, parentObject); 298 break; 299 case HIT_REACT: 300 stateHitReact(gameTime, timeDelta, parentObject); 301 break; 302 case DEAD: 303 stateDead(gameTime, timeDelta, parentObject); 304 break; 305 case WIN: 306 stateWin(gameTime, timeDelta, parentObject); 307 break; 308 case FROZEN: 309 stateFrozen(gameTime, timeDelta, parentObject); 310 break; 311 case POST_GHOST_DELAY: 312 statePostGhostDelay(gameTime, timeDelta, parentObject); 313 break; 314 default: 315 break; 316 } 317 318 final HudSystem hud = sSystemRegistry.hudSystem; 319 final InputGameInterface input = sSystemRegistry.inputGameInterface; 320 if (hud != null) { 321 hud.setFuelPercent(mFuel / FUEL_AMOUNT); 322 } 323 324 } 325 326 protected void gotoMove(GameObject parentObject) { 327 parentObject.setCurrentAction(GameObject.ActionType.MOVE); 328 mState = State.MOVE; 329 } 330 331 protected void stateMove(float time, float timeDelta, GameObject parentObject) { 332 if (!mGhostActive) { 333 move(time, timeDelta, parentObject); 334 335 final InputGameInterface input = sSystemRegistry.inputGameInterface; 336 final InputButton attackButton = input.getAttackButton(); 337 338 if (attackButton.getTriggered(time) && !mTouchingGround) { 339 gotoStomp(parentObject); 340 } else if (attackButton.getPressed() && mTouchingGround 341 && mGhostDeactivatedTime + GHOST_REACTIVATION_DELAY < time) { 342 mGhostChargeTime += timeDelta; 343 if (mGhostChargeTime > GHOST_CHARGE_TIME) { 344 GameObjectFactory factory = sSystemRegistry.gameObjectFactory; 345 GameObjectManager manager = sSystemRegistry.gameObjectManager; 346 if (factory != null && manager != null) { 347 final float x = parentObject.getPosition().x; 348 final float y = parentObject.getPosition().y; 349 float ghostTime = NO_GEMS_GHOST_TIME; 350 if (mInventory != null) { 351 InventoryComponent.UpdateRecord inventory = mInventory.getRecord(); 352 if (inventory.rubyCount == 1) { 353 ghostTime = ONE_GEM_GHOST_TIME; 354 } else if (inventory.rubyCount == 2) { 355 ghostTime = TWO_GEMS_GHOST_TIME; 356 } 357 } 358 GameObject ghost = factory.spawnPlayerGhost(x, y, parentObject, ghostTime); 359 360 manager.add(ghost); 361 mGhostActive = true; 362 CameraSystem camera = sSystemRegistry.cameraSystem; 363 if (camera != null) { 364 camera.setTarget(ghost); 365 } 366 } 367 } 368 } else if (!attackButton.getPressed()) { 369 mGhostChargeTime = 0.0f; 370 } 371 } 372 373 } 374 375 protected void gotoStomp(GameObject parentObject) { 376 parentObject.setCurrentAction(GameObject.ActionType.ATTACK); 377 mState = State.STOMP; 378 mTimer = -1.0f; 379 mTimer2 = -1.0f; 380 parentObject.getImpulse().zero(); 381 parentObject.getVelocity().set(0.0f, 0.0f); 382 parentObject.positionLocked = true; 383 } 384 385 protected void stateStomp(float time, float timeDelta, GameObject parentObject) { 386 if (mTimer < 0.0f) { 387 // first frame 388 mTimer = time; 389 } else if (time - mTimer > STOMP_AIR_HANG_TIME) { 390 // hang time complete 391 parentObject.getVelocity().set(0.0f, STOMP_VELOCITY); 392 parentObject.positionLocked = false; 393 } 394 395 if (mTouchingGround && mTimer2 < 0.0f) { 396 mTimer2 = time; 397 CameraSystem camera = sSystemRegistry.cameraSystem; 398 if (camera != null) { 399 camera.shake(STOMP_DELAY_TIME, STOMP_SHAKE_MAGNITUDE); 400 } 401 VibrationSystem vibrator = sSystemRegistry.vibrationSystem; 402 403 if (vibrator != null) { 404 vibrator.vibrate(STOMP_VIBRATE_TIME); 405 } 406 407 GameObjectFactory factory = sSystemRegistry.gameObjectFactory; 408 GameObjectManager manager = sSystemRegistry.gameObjectManager; 409 if (factory != null && manager != null) { 410 final float x = parentObject.getPosition().x; 411 final float y = parentObject.getPosition().y; 412 GameObject smoke1 = factory.spawnDust(x, y - 16, true); 413 GameObject smoke2 = factory.spawnDust(x + 32, y - 16, false); 414 manager.add(smoke1); 415 manager.add(smoke2); 416 } 417 } 418 419 if (mTimer2 > 0.0f && time - mTimer2 > STOMP_DELAY_TIME) { 420 parentObject.positionLocked = false; 421 gotoMove(parentObject); 422 } 423 } 424 425 protected void gotoHitReact(GameObject parentObject, float time) { 426 if (parentObject.lastReceivedHitType == CollisionParameters.HitType.LAUNCH) { 427 if (mState != State.FROZEN) { 428 gotoFrozen(parentObject); 429 } 430 } else { 431 mState = State.HIT_REACT; 432 mTimer = time; 433 434 } 435 } 436 437 protected void stateHitReact(float time, float timeDelta, GameObject parentObject) { 438 // This state just waits until the timer is expired. 439 if (time - mTimer > HIT_REACT_TIME) { 440 gotoMove(parentObject); 441 } 442 } 443 444 protected void gotoDead(float time) { 445 mState = State.DEAD; 446 mTimer = time; 447 } 448 449 protected void stateDead(float time, float timeDelta, GameObject parentObject) { 450 if (mTouchingGround && parentObject.getCurrentAction() != ActionType.DEATH) { 451 parentObject.setCurrentAction(ActionType.DEATH); 452 parentObject.getVelocity().zero(); 453 parentObject.getTargetVelocity().zero(); 454 } 455 456 if (parentObject.getPosition().y < -parentObject.height) { 457 // fell off the bottom of the screen. 458 parentObject.setCurrentAction(ActionType.DEATH); 459 parentObject.getVelocity().zero(); 460 parentObject.getTargetVelocity().zero(); 461 } 462 463 if (parentObject.getCurrentAction() == ActionType.DEATH && mTimer > 0.0f) { 464 final float elapsed = time - mTimer; 465 HudSystem hud = sSystemRegistry.hudSystem; 466 if (hud != null && !hud.isFading()) { 467 if (elapsed > 2.0f) { 468 hud.startFade(false, 1.5f); 469 hud.sendGameEventOnFadeComplete(GameFlowEvent.EVENT_RESTART_LEVEL, 0); 470 EventRecorder recorder = sSystemRegistry.eventRecorder; 471 if (recorder != null) { 472 recorder.setLastDeathPosition(parentObject.getPosition()); 473 } 474 } 475 } 476 477 } 478 } 479 480 protected void gotoWin(float time) { 481 mState = State.WIN; 482 TimeSystem timeSystem = sSystemRegistry.timeSystem; 483 mTimer = timeSystem.getRealTime(); 484 timeSystem.appyScale(0.1f, 8.0f, true); 485 } 486 487 protected void stateWin(float time, float timeDelta, GameObject parentObject) { 488 if (mTimer > 0.0f) { 489 TimeSystem timeSystem = sSystemRegistry.timeSystem; 490 final float elapsed = timeSystem.getRealTime() - mTimer; 491 HudSystem hud = sSystemRegistry.hudSystem; 492 if (hud != null && !hud.isFading()) { 493 if (elapsed > 2.0f) { 494 hud.startFade(false, 1.5f); 495 hud.sendGameEventOnFadeComplete(GameFlowEvent.EVENT_GO_TO_NEXT_LEVEL, 0); 496 497 } 498 } 499 500 } 501 } 502 503 protected void gotoFrozen(GameObject parentObject) { 504 mState = State.FROZEN; 505 parentObject.setCurrentAction(ActionType.FROZEN); 506 } 507 508 protected void stateFrozen(float time, float timeDelta, GameObject parentObject) { 509 if (parentObject.getCurrentAction() == ActionType.MOVE) { 510 gotoMove(parentObject); 511 } 512 } 513 514 protected void gotoPostGhostDelay() { 515 mState = State.POST_GHOST_DELAY; 516 } 517 518 protected void statePostGhostDelay(float time, float timeDelta, GameObject parentObject) { 519 if (time > mGhostDeactivatedTime) { 520 if (!mGhostActive) { // The ghost might have activated again during this delay. 521 CameraSystem camera = sSystemRegistry.cameraSystem; 522 if (camera != null) { 523 camera.setTarget(parentObject); 524 } 525 } 526 gotoMove(parentObject); 527 } 528 } 529 530 public final boolean getRocketsOn() { 531 return mRocketsOn; 532 } 533 534 public final boolean getGhostActive() { 535 return mGhostActive; 536 } 537 538 public final void deactivateGhost(float delay) { 539 mGhostActive = false; 540 mGhostDeactivatedTime = sSystemRegistry.timeSystem.getGameTime() + delay; 541 gotoPostGhostDelay(); 542 } 543 544 public final void setInventory(InventoryComponent inventory) { 545 mInventory = inventory; 546 } 547 548 public final void setInvincibleSwap(ChangeComponentsComponent invincibleSwap) { 549 mInvincibleSwap = invincibleSwap; 550 } 551 552 public final void setHitReactionComponent(HitReactionComponent hitReact) { 553 mHitReaction = hitReact; 554 } 555 556 public final void setInvincibleFader(FadeDrawableComponent fader) { 557 mInvincibleFader = fader; 558 } 559 560 public final void adjustDifficulty(GameObject parent, int levelAttemps ) { 561 // Super basic DDA. 562 // If we've tried this levels several times secretly increase our 563 // hit points so the level gets easier. 564 // Also make fuel refill faster in the air after we've died too many times. 565 566 if (levelAttemps >= mDifficultyConstants.getDDAStage1Attempts()) { 567 if (levelAttemps >= mDifficultyConstants.getDDAStage2Attempts()) { 568 parent.life += mDifficultyConstants.getDDAStage2LifeBoost(); 569 mFuelAirRefillSpeed = mDifficultyConstants.getDDAStage2FuelAirRefillSpeed(); 570 } else { 571 parent.life += mDifficultyConstants.getDDAStage1LifeBoost(); 572 mFuelAirRefillSpeed = mDifficultyConstants.getDDAStage1FuelAirRefillSpeed(); 573 } 574 } 575 576 577 } 578 579 public static DifficultyConstants getDifficultyConstants() { 580 return sDifficultyArray[sSystemRegistry.contextParameters.difficulty]; 581 } 582 583}