Home | History | Annotate | Download | only in replicaisland
      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 
     17 package com.replica.replicaisland;
     18 
     19 import com.replica.replicaisland.CollisionParameters.HitType;
     20 import com.replica.replicaisland.GameObject.ActionType;
     21 
     22 public 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 }