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}