/* * 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 PlayerComponent extends GameComponent { private static final float GROUND_IMPULSE_SPEED = 5000.0f; private static final float AIR_HORIZONTAL_IMPULSE_SPEED = 4000.0f; private static final float AIR_VERTICAL_IMPULSE_SPEED = 1200.0f; private static final float AIR_VERTICAL_IMPULSE_SPEED_FROM_GROUND = 250.0f; private static final float AIR_DRAG_SPEED = 4000.0f; private static final float MAX_GROUND_HORIZONTAL_SPEED = 500.0f; private static final float MAX_AIR_HORIZONTAL_SPEED = 150.0f; private static final float MAX_UPWARD_SPEED = 250.0f; private static final float VERTICAL_IMPULSE_TOLERANCE = 50.0f; private static final float FUEL_AMOUNT = 1.0f; private static final float JUMP_TO_JETS_DELAY = 0.5f; private static final float STOMP_VELOCITY = -1000.0f; private static final float STOMP_DELAY_TIME = 0.15f; private static final float STOMP_AIR_HANG_TIME = 0.0f; //0.25f; private static final float STOMP_SHAKE_MAGNITUDE = 15.0f; private static final float STOMP_VIBRATE_TIME = 0.05f; private static final float HIT_REACT_TIME = 0.5f; private static final float GHOST_REACTIVATION_DELAY = 0.3f; private static final float GHOST_CHARGE_TIME = 0.75f; private static final int MAX_GEMS_PER_LEVEL = 3; private static final float NO_GEMS_GHOST_TIME = 3.0f; private static final float ONE_GEM_GHOST_TIME = 8.0f; private static final float TWO_GEMS_GHOST_TIME = 0.0f; // no limit. public enum State { MOVE, STOMP, HIT_REACT, DEAD, WIN, FROZEN, POST_GHOST_DELAY } private boolean mTouchingGround; private State mState; private float mTimer; private float mTimer2; private float mFuel; private float mJumpTime; private boolean mGhostActive; private float mGhostDeactivatedTime; private float mGhostChargeTime; private InventoryComponent mInventory; private Vector2 mHotSpotTestPoint; private ChangeComponentsComponent mInvincibleSwap; private float mInvincibleEndTime; private HitReactionComponent mHitReaction; private float mFuelAirRefillSpeed; private DifficultyConstants mDifficultyConstants; private final static DifficultyConstants sDifficultyArray[] = { new BabyDifficultyConstants(), new KidsDifficultyConstants(), new AdultsDifficultyConstants() }; private FadeDrawableComponent mInvincibleFader; // HACK! // Variables recorded for animation decisions. private boolean mRocketsOn; public PlayerComponent() { super(); mHotSpotTestPoint = new Vector2(); reset(); setPhase(ComponentPhases.THINK.ordinal()); } @Override public void reset() { mTouchingGround = false; mState = State.MOVE; mTimer = 0.0f; mTimer2 = 0.0f; mFuel = 0.0f; mJumpTime = 0.0f; mGhostActive = false; mGhostDeactivatedTime = 0.0f; mInventory = null; mGhostChargeTime = 0.0f; mHotSpotTestPoint.zero(); mInvincibleSwap = null; mInvincibleEndTime = 0.0f; mHitReaction = null; mDifficultyConstants = getDifficultyConstants(); mFuelAirRefillSpeed = mDifficultyConstants.getFuelAirRefillSpeed(); mInvincibleFader = null; } protected void move(float time, float timeDelta, GameObject parentObject) { VectorPool pool = sSystemRegistry.vectorPool; InputGameInterface input = sSystemRegistry.inputGameInterface; if (pool != null && input != null) { if (mFuel < FUEL_AMOUNT) { if (mTouchingGround) { mFuel += mDifficultyConstants.getFuelGroundRefillSpeed() * timeDelta; } else { mFuel += mFuelAirRefillSpeed * timeDelta; } if (mFuel > FUEL_AMOUNT) { mFuel = FUEL_AMOUNT; } } final InputXY dpad = input.getDirectionalPad(); final InputButton jumpButton = input.getJumpButton(); if (dpad.getPressed() || jumpButton.getPressed()) { Vector2 impulse = pool.allocate(); if (dpad.getPressed()) { impulse.set(dpad.getX(), 0.0f); } if (jumpButton.getPressed()) { if (jumpButton.getTriggered(time) && mTouchingGround) { // In this case, velocity is instant so we don't need to scale // it by time. impulse.y = AIR_VERTICAL_IMPULSE_SPEED_FROM_GROUND; mJumpTime = time; } else if (time > mJumpTime + JUMP_TO_JETS_DELAY) { if (mFuel > 0.0f) { mFuel -= timeDelta; impulse.y = AIR_VERTICAL_IMPULSE_SPEED * timeDelta; mRocketsOn = true; } } } float horziontalSpeed = GROUND_IMPULSE_SPEED; float maxHorizontalSpeed = MAX_GROUND_HORIZONTAL_SPEED; final boolean inTheAir = !mTouchingGround || impulse.y > VERTICAL_IMPULSE_TOLERANCE; if (inTheAir) { horziontalSpeed = AIR_HORIZONTAL_IMPULSE_SPEED; maxHorizontalSpeed = MAX_AIR_HORIZONTAL_SPEED; } impulse.x = (impulse.x * horziontalSpeed * timeDelta); // Don't let our jets move us past specific speed thresholds. float currentSpeed = parentObject.getVelocity().x; final float newSpeed = Math.abs(currentSpeed + impulse.x); if (newSpeed > maxHorizontalSpeed) { if (Math.abs(currentSpeed) < maxHorizontalSpeed) { currentSpeed = maxHorizontalSpeed * Utils.sign(impulse.x); parentObject.getVelocity().x = (currentSpeed); } impulse.x = (0.0f); } if (parentObject.getVelocity().y + impulse.y > MAX_UPWARD_SPEED && Utils.sign(impulse.y) > 0) { impulse.y = (0.0f); if (parentObject.getVelocity().y < MAX_UPWARD_SPEED) { parentObject.getVelocity().y = (MAX_UPWARD_SPEED); } } if (inTheAir) { // Apply drag while in the air. if (Math.abs(currentSpeed) > maxHorizontalSpeed) { float postDragSpeed = currentSpeed - (AIR_DRAG_SPEED * timeDelta * Utils.sign(currentSpeed)); if (Utils.sign(currentSpeed) != Utils.sign(postDragSpeed)) { postDragSpeed = 0.0f; } else if (Math.abs(postDragSpeed) < maxHorizontalSpeed) { postDragSpeed = maxHorizontalSpeed * Utils.sign(postDragSpeed); } parentObject.getVelocity().x = (postDragSpeed); } } parentObject.getImpulse().add(impulse); pool.release(impulse); } } } public void update(float timeDelta, BaseObject parent) { TimeSystem time = sSystemRegistry.timeSystem; GameObject parentObject = (GameObject)parent; final float gameTime = time.getGameTime(); mTouchingGround = parentObject.touchingGround(); mRocketsOn = false; if (parentObject.getCurrentAction() == ActionType.INVALID) { gotoMove(parentObject); } if (mInventory != null && mState != State.WIN) { InventoryComponent.UpdateRecord inventory = mInventory.getRecord(); if (inventory.coinCount >= mDifficultyConstants.getCoinsPerPowerup()) { inventory.coinCount = 0; mInventory.setChanged(); parentObject.life = mDifficultyConstants.getMaxPlayerLife(); if (mInvincibleEndTime < gameTime) { mInvincibleSwap.activate(parentObject); mInvincibleEndTime = gameTime + mDifficultyConstants.getGlowDuration(); if (mHitReaction != null) { mHitReaction.setForceInvincible(true); } } else { // invincibility is already active, extend it. mInvincibleEndTime = gameTime + mDifficultyConstants.getGlowDuration(); // HACK HACK HACK. This really doesn't go here. // To extend the invincible time we need to increment the value above (easy) // and also tell the component managing the glow sprite to reset its // timer (not easy). Next time, make a shared value system for this // kind of case!! if (mInvincibleFader != null) { mInvincibleFader.resetPhase(); } } } if (inventory.rubyCount >= MAX_GEMS_PER_LEVEL) { gotoWin(gameTime); } } if (mInvincibleEndTime > 0.0f && (mInvincibleEndTime < gameTime || mState == State.DEAD)) { mInvincibleSwap.activate(parentObject); mInvincibleEndTime = 0.0f; if (mHitReaction != null) { mHitReaction.setForceInvincible(false); } } // Watch for hit reactions or death interrupting the state machine. if (mState != State.DEAD && mState != State.WIN ) { if (parentObject.life <= 0) { gotoDead(gameTime); } else if (parentObject.getPosition().y < -parentObject.height) { // we fell off the bottom of the screen, die. parentObject.life = 0; gotoDead(gameTime); } else if (mState != State.HIT_REACT && parentObject.lastReceivedHitType != HitType.INVALID && parentObject.getCurrentAction() == ActionType.HIT_REACT) { gotoHitReact(parentObject, gameTime); } else { HotSpotSystem hotSpot = sSystemRegistry.hotSpotSystem; if (hotSpot != null) { // TODO: HACK! Unify all this code. if (hotSpot.getHotSpot(parentObject.getCenteredPositionX(), parentObject.getPosition().y + 10.0f) == HotSpotSystem.HotSpotType.DIE) { parentObject.life = 0; gotoDead(gameTime); } } } } switch(mState) { case MOVE: stateMove(gameTime, timeDelta, parentObject); break; case STOMP: stateStomp(gameTime, timeDelta, parentObject); break; case HIT_REACT: stateHitReact(gameTime, timeDelta, parentObject); break; case DEAD: stateDead(gameTime, timeDelta, parentObject); break; case WIN: stateWin(gameTime, timeDelta, parentObject); break; case FROZEN: stateFrozen(gameTime, timeDelta, parentObject); break; case POST_GHOST_DELAY: statePostGhostDelay(gameTime, timeDelta, parentObject); break; default: break; } final HudSystem hud = sSystemRegistry.hudSystem; final InputGameInterface input = sSystemRegistry.inputGameInterface; if (hud != null) { hud.setFuelPercent(mFuel / FUEL_AMOUNT); } } protected void gotoMove(GameObject parentObject) { parentObject.setCurrentAction(GameObject.ActionType.MOVE); mState = State.MOVE; } protected void stateMove(float time, float timeDelta, GameObject parentObject) { if (!mGhostActive) { move(time, timeDelta, parentObject); final InputGameInterface input = sSystemRegistry.inputGameInterface; final InputButton attackButton = input.getAttackButton(); if (attackButton.getTriggered(time) && !mTouchingGround) { gotoStomp(parentObject); } else if (attackButton.getPressed() && mTouchingGround && mGhostDeactivatedTime + GHOST_REACTIVATION_DELAY < time) { mGhostChargeTime += timeDelta; if (mGhostChargeTime > GHOST_CHARGE_TIME) { GameObjectFactory factory = sSystemRegistry.gameObjectFactory; GameObjectManager manager = sSystemRegistry.gameObjectManager; if (factory != null && manager != null) { final float x = parentObject.getPosition().x; final float y = parentObject.getPosition().y; float ghostTime = NO_GEMS_GHOST_TIME; if (mInventory != null) { InventoryComponent.UpdateRecord inventory = mInventory.getRecord(); if (inventory.rubyCount == 1) { ghostTime = ONE_GEM_GHOST_TIME; } else if (inventory.rubyCount == 2) { ghostTime = TWO_GEMS_GHOST_TIME; } } GameObject ghost = factory.spawnPlayerGhost(x, y, parentObject, ghostTime); manager.add(ghost); mGhostActive = true; CameraSystem camera = sSystemRegistry.cameraSystem; if (camera != null) { camera.setTarget(ghost); } } } } else if (!attackButton.getPressed()) { mGhostChargeTime = 0.0f; } } } protected void gotoStomp(GameObject parentObject) { parentObject.setCurrentAction(GameObject.ActionType.ATTACK); mState = State.STOMP; mTimer = -1.0f; mTimer2 = -1.0f; parentObject.getImpulse().zero(); parentObject.getVelocity().set(0.0f, 0.0f); parentObject.positionLocked = true; } protected void stateStomp(float time, float timeDelta, GameObject parentObject) { if (mTimer < 0.0f) { // first frame mTimer = time; } else if (time - mTimer > STOMP_AIR_HANG_TIME) { // hang time complete parentObject.getVelocity().set(0.0f, STOMP_VELOCITY); parentObject.positionLocked = false; } if (mTouchingGround && mTimer2 < 0.0f) { mTimer2 = time; CameraSystem camera = sSystemRegistry.cameraSystem; if (camera != null) { camera.shake(STOMP_DELAY_TIME, STOMP_SHAKE_MAGNITUDE); } VibrationSystem vibrator = sSystemRegistry.vibrationSystem; if (vibrator != null) { vibrator.vibrate(STOMP_VIBRATE_TIME); } GameObjectFactory factory = sSystemRegistry.gameObjectFactory; GameObjectManager manager = sSystemRegistry.gameObjectManager; if (factory != null && manager != null) { final float x = parentObject.getPosition().x; final float y = parentObject.getPosition().y; GameObject smoke1 = factory.spawnDust(x, y - 16, true); GameObject smoke2 = factory.spawnDust(x + 32, y - 16, false); manager.add(smoke1); manager.add(smoke2); } } if (mTimer2 > 0.0f && time - mTimer2 > STOMP_DELAY_TIME) { parentObject.positionLocked = false; gotoMove(parentObject); } } protected void gotoHitReact(GameObject parentObject, float time) { if (parentObject.lastReceivedHitType == CollisionParameters.HitType.LAUNCH) { if (mState != State.FROZEN) { gotoFrozen(parentObject); } } else { mState = State.HIT_REACT; mTimer = time; } } protected void stateHitReact(float time, float timeDelta, GameObject parentObject) { // This state just waits until the timer is expired. if (time - mTimer > HIT_REACT_TIME) { gotoMove(parentObject); } } protected void gotoDead(float time) { mState = State.DEAD; mTimer = time; } protected void stateDead(float time, float timeDelta, GameObject parentObject) { if (mTouchingGround && parentObject.getCurrentAction() != ActionType.DEATH) { parentObject.setCurrentAction(ActionType.DEATH); parentObject.getVelocity().zero(); parentObject.getTargetVelocity().zero(); } if (parentObject.getPosition().y < -parentObject.height) { // fell off the bottom of the screen. parentObject.setCurrentAction(ActionType.DEATH); parentObject.getVelocity().zero(); parentObject.getTargetVelocity().zero(); } if (parentObject.getCurrentAction() == ActionType.DEATH && mTimer > 0.0f) { final float elapsed = time - mTimer; HudSystem hud = sSystemRegistry.hudSystem; if (hud != null && !hud.isFading()) { if (elapsed > 2.0f) { hud.startFade(false, 1.5f); hud.sendGameEventOnFadeComplete(GameFlowEvent.EVENT_RESTART_LEVEL, 0); EventRecorder recorder = sSystemRegistry.eventRecorder; if (recorder != null) { recorder.setLastDeathPosition(parentObject.getPosition()); } } } } } protected void gotoWin(float time) { mState = State.WIN; TimeSystem timeSystem = sSystemRegistry.timeSystem; mTimer = timeSystem.getRealTime(); timeSystem.appyScale(0.1f, 8.0f, true); } protected void stateWin(float time, float timeDelta, GameObject parentObject) { if (mTimer > 0.0f) { TimeSystem timeSystem = sSystemRegistry.timeSystem; final float elapsed = timeSystem.getRealTime() - mTimer; HudSystem hud = sSystemRegistry.hudSystem; if (hud != null && !hud.isFading()) { if (elapsed > 2.0f) { hud.startFade(false, 1.5f); hud.sendGameEventOnFadeComplete(GameFlowEvent.EVENT_GO_TO_NEXT_LEVEL, 0); } } } } protected void gotoFrozen(GameObject parentObject) { mState = State.FROZEN; parentObject.setCurrentAction(ActionType.FROZEN); } protected void stateFrozen(float time, float timeDelta, GameObject parentObject) { if (parentObject.getCurrentAction() == ActionType.MOVE) { gotoMove(parentObject); } } protected void gotoPostGhostDelay() { mState = State.POST_GHOST_DELAY; } protected void statePostGhostDelay(float time, float timeDelta, GameObject parentObject) { if (time > mGhostDeactivatedTime) { if (!mGhostActive) { // The ghost might have activated again during this delay. CameraSystem camera = sSystemRegistry.cameraSystem; if (camera != null) { camera.setTarget(parentObject); } } gotoMove(parentObject); } } public final boolean getRocketsOn() { return mRocketsOn; } public final boolean getGhostActive() { return mGhostActive; } public final void deactivateGhost(float delay) { mGhostActive = false; mGhostDeactivatedTime = sSystemRegistry.timeSystem.getGameTime() + delay; gotoPostGhostDelay(); } public final void setInventory(InventoryComponent inventory) { mInventory = inventory; } public final void setInvincibleSwap(ChangeComponentsComponent invincibleSwap) { mInvincibleSwap = invincibleSwap; } public final void setHitReactionComponent(HitReactionComponent hitReact) { mHitReaction = hitReact; } public final void setInvincibleFader(FadeDrawableComponent fader) { mInvincibleFader = fader; } public final void adjustDifficulty(GameObject parent, int levelAttemps ) { // Super basic DDA. // If we've tried this levels several times secretly increase our // hit points so the level gets easier. // Also make fuel refill faster in the air after we've died too many times. if (levelAttemps >= mDifficultyConstants.getDDAStage1Attempts()) { if (levelAttemps >= mDifficultyConstants.getDDAStage2Attempts()) { parent.life += mDifficultyConstants.getDDAStage2LifeBoost(); mFuelAirRefillSpeed = mDifficultyConstants.getDDAStage2FuelAirRefillSpeed(); } else { parent.life += mDifficultyConstants.getDDAStage1LifeBoost(); mFuelAirRefillSpeed = mDifficultyConstants.getDDAStage1FuelAirRefillSpeed(); } } } public static DifficultyConstants getDifficultyConstants() { return sDifficultyArray[sSystemRegistry.contextParameters.difficulty]; } }