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
19
20import com.replica.replicaisland.CollisionParameters.HitType;
21import com.replica.replicaisland.GameObject.ActionType;
22import com.replica.replicaisland.SoundSystem.Sound;
23
24/**
25 * Player Animation game object component.  Responsible for selecting an animation to describe the
26 * player's current state.  Requires the object to contain a SpriteComponent to play animations.
27 */
28public class AnimationComponent extends GameComponent {
29
30    public enum PlayerAnimations {
31        IDLE,
32        MOVE,
33        MOVE_FAST,
34        BOOST_UP,
35        BOOST_MOVE,
36        BOOST_MOVE_FAST,
37        STOMP,
38        HIT_REACT,
39        DEATH,
40        FROZEN
41    }
42
43    private static final float MIN_ROCKET_TIME = 0.0f;
44    private static final float FLICKER_INTERVAL = 0.15f;
45    private static final float FLICKER_DURATION = 3.0f;
46    private static final float LAND_THUMP_DELAY = 0.5f;
47
48    private SpriteComponent mSprite;
49    private SpriteComponent mJetSprite;
50    private SpriteComponent mSparksSprite;
51
52    private PlayerComponent mPlayer;
53    private float mLastFlickerTime;
54    private boolean mFlickerOn;
55    private float mFlickerTimeRemaining;
56
57    private GameObject.ActionType mPreviousAction;
58
59    private float mLastRocketsOnTime;
60    private boolean mExplodingDeath;
61
62    private ChangeComponentsComponent mDamageSwap;
63    private Sound mLandThump;
64    private Sound mRocketSound;
65    private Sound mExplosionSound;
66    private float mLandThumpDelay;
67    private int mRocketSoundStream;
68    private boolean mRocketSoundPaused;
69
70    private int mLastRubyCount;
71    private Sound mRubySound1;
72    private Sound mRubySound2;
73    private Sound mRubySound3;
74    private InventoryComponent mInventory;
75
76
77    public AnimationComponent() {
78        super();
79        reset();
80        setPhase(ComponentPhases.ANIMATION.ordinal());
81    }
82
83    @Override
84    public void reset() {
85        mPreviousAction = ActionType.INVALID;
86        mSprite = null;
87        mJetSprite = null;
88        mSparksSprite = null;
89        mPlayer = null;
90        mLastFlickerTime = 0.0f;
91        mFlickerOn = false;
92        mFlickerTimeRemaining = 0.0f;
93        mLastRocketsOnTime = 0.0f;
94        mExplodingDeath = false;
95        mDamageSwap = null;
96        mLandThump = null;
97        mLandThumpDelay = 0.0f;
98        mRocketSound = null;
99        mRocketSoundStream = -1;
100        mLastRubyCount = 0;
101        mInventory = null;
102        mExplosionSound = null;
103    }
104
105    @Override
106    public void update(float timeDelta, BaseObject parent) {
107        if (mSprite != null) {
108
109            GameObject parentObject = (GameObject) parent;
110
111            final float velocityX = parentObject.getVelocity().x;
112            final float velocityY = parentObject.getVelocity().y;
113
114
115            GameObject.ActionType currentAction = parentObject.getCurrentAction();
116
117            if (mJetSprite != null) {
118                mJetSprite.setVisible(false);
119            }
120
121            if (mSparksSprite != null) {
122                mSparksSprite.setVisible(false);
123            }
124
125
126            final TimeSystem time = sSystemRegistry.timeSystem;
127            final float gameTime = time.getGameTime();
128
129            if (currentAction != ActionType.HIT_REACT && mPreviousAction == ActionType.HIT_REACT) {
130                mFlickerTimeRemaining = FLICKER_DURATION;
131            }
132
133
134            final boolean touchingGround = parentObject.touchingGround();
135
136            boolean boosting = mPlayer != null ? mPlayer.getRocketsOn() : false;
137
138            boolean visible = true;
139
140            SoundSystem sound = sSystemRegistry.soundSystem;
141
142            // It's usually not necessary to test to see if sound is enabled or not (when it's disabled,
143            // play() is just a nop), but in this case I have a stream that is maintained for the rocket
144            // sounds.  So it's simpler to just avoid that code if sound is off.
145            if (sound.getSoundEnabled()) {
146	            if (boosting) {
147	                mLastRocketsOnTime = gameTime;
148	            } else {
149	                if (gameTime - mLastRocketsOnTime < MIN_ROCKET_TIME
150	                        && velocityY >= 0.0f) {
151	                    boosting = true;
152	                }
153	            }
154
155	            if (mRocketSound != null) {
156		            if (boosting) {
157		            	if (mRocketSoundStream == -1) {
158		            		mRocketSoundStream = sound.play(mRocketSound, true, SoundSystem.PRIORITY_HIGH);
159		            		mRocketSoundPaused = false;
160		            	} else if (mRocketSoundPaused) {
161		            		sound.resume(mRocketSoundStream);
162		            		mRocketSoundPaused = false;
163		            	}
164		            } else {
165		            	sound.pause(mRocketSoundStream);
166		            	mRocketSoundPaused = true;
167		            }
168	            }
169            }
170
171            // Normally, for collectables like the coin, we could just tell the object to play
172            // a sound when it is collected.  The gems are a special case, though, as we
173            // want to pick a different sound depending on how many have been collected.
174            if (mInventory != null && mRubySound1 != null && mRubySound2 != null && mRubySound3 != null) {
175            	InventoryComponent.UpdateRecord inventory = mInventory.getRecord();
176            	final int rubyCount = inventory.rubyCount;
177            	if (rubyCount != mLastRubyCount) {
178            		mLastRubyCount = rubyCount;
179            		switch (rubyCount) {
180            		case 1:
181            			sound.play(mRubySound1, false, SoundSystem.PRIORITY_NORMAL);
182            			break;
183            		case 2:
184            			sound.play(mRubySound2, false, SoundSystem.PRIORITY_NORMAL);
185            			break;
186            		case 3:
187            			sound.play(mRubySound3, false, SoundSystem.PRIORITY_NORMAL);
188            			break;
189            		}
190
191            	}
192            }
193
194            // Turn on visual effects (smoke, etc) when the player's life reaches 1.
195            if (mDamageSwap != null) {
196                if (parentObject.life == 1 && !mDamageSwap.getCurrentlySwapped()) {
197                    mDamageSwap.activate(parentObject);
198                } else if (parentObject.life != 1 && mDamageSwap.getCurrentlySwapped()) {
199                    mDamageSwap.activate(parentObject);
200                }
201            }
202
203            float opacity = 1.0f;
204
205            if (currentAction == ActionType.MOVE) {
206                InputGameInterface input = sSystemRegistry.inputGameInterface;
207                final InputXY dpad = input.getDirectionalPad();
208                if (dpad.getX() < 0.0f) {
209                    parentObject.facingDirection.x = -1.0f;
210                } else if (dpad.getX() > 0.0f) {
211                    parentObject.facingDirection.x = 1.0f;
212                }
213
214                // TODO: get rid of these magic numbers!
215                if (touchingGround) {
216
217                    if (Utils.close(velocityX, 0.0f, 30.0f)) {
218                        mSprite.playAnimation(PlayerAnimations.IDLE.ordinal());
219                    } else if (Math.abs(velocityX) > 300.0f) {
220                        mSprite.playAnimation(PlayerAnimations.MOVE_FAST.ordinal());
221                    } else {
222                        mSprite.playAnimation(PlayerAnimations.MOVE.ordinal());
223                    }
224
225                    final InputButton attackButton = input.getAttackButton();
226
227                    if (attackButton.getPressed()) {
228                        // charge
229                        final float pressedTime = gameTime - attackButton.getLastPressedTime();
230                        final float wave = (float)Math.cos(pressedTime * (float)Math.PI * 2.0f);
231                        opacity = (wave * 0.25f) + 0.75f;
232                    }
233
234                } else {
235                    if (boosting) {
236                        if (mJetSprite != null) {
237                            mJetSprite.setVisible(true);
238                        }
239
240                        if (Math.abs(velocityX) < 100.0f && velocityY > 10.0f) {
241                            mSprite.playAnimation(PlayerAnimations.BOOST_UP.ordinal());
242                        } else if (Math.abs(velocityX) > 300.0f) {
243                            mSprite.playAnimation(PlayerAnimations.BOOST_MOVE_FAST.ordinal());
244                        } else {
245                            mSprite.playAnimation(PlayerAnimations.BOOST_MOVE.ordinal());
246                        }
247                    } else {
248
249                        if (Utils.close(velocityX, 0.0f, 1.0f)) {
250                            mSprite.playAnimation(PlayerAnimations.IDLE.ordinal());
251                        } else if (Math.abs(velocityX) > 300.0f) {
252                            mSprite.playAnimation(PlayerAnimations.MOVE_FAST.ordinal());
253                        } else {
254                            mSprite.playAnimation(PlayerAnimations.MOVE.ordinal());
255                        }
256                    }
257
258                }
259            } else if (currentAction == ActionType.ATTACK) {
260                mSprite.playAnimation(PlayerAnimations.STOMP.ordinal());
261                if (touchingGround && gameTime > mLandThumpDelay) {
262                    if (mLandThump != null && sound != null) {
263                        // modulate the sound slightly to avoid sounding too similar
264                        sound.play(mLandThump, false, SoundSystem.PRIORITY_HIGH, 1.0f,
265                                (float)(Math.random() * 0.5f) + 0.75f);
266                        mLandThumpDelay = gameTime + LAND_THUMP_DELAY;
267                    }
268                }
269            } else if (currentAction == ActionType.HIT_REACT) {
270                mSprite.playAnimation(PlayerAnimations.HIT_REACT.ordinal());
271
272                if (velocityX > 0.0f) {
273                    parentObject.facingDirection.x = -1.0f;
274                } else if (velocityX < 0.0f) {
275                    parentObject.facingDirection.x = 1.0f;
276                }
277
278                if (mSparksSprite != null) {
279                    mSparksSprite.setVisible(true);
280                }
281            } else if (currentAction == ActionType.DEATH) {
282                if (mPreviousAction != currentAction) {
283                	if (mExplosionSound != null) {
284                		sound.play(mExplosionSound, false, SoundSystem.PRIORITY_NORMAL);
285                	}
286                	// by default, explode when hit with the DEATH hit type.
287                    boolean explodingDeath = parentObject.lastReceivedHitType == HitType.DEATH;
288                    // or if touching a death tile.
289                    HotSpotSystem hotSpot = sSystemRegistry.hotSpotSystem;
290                    if (hotSpot != null) {
291                        // TODO: HACK!  Unify all this code.
292                        if (hotSpot.getHotSpot(parentObject.getCenteredPositionX(),
293                                parentObject.getPosition().y + 10.0f) == HotSpotSystem.HotSpotType.DIE) {
294                            explodingDeath = true;
295                        }
296                    }
297                    if (explodingDeath) {
298                        mExplodingDeath = true;
299                        GameObjectFactory factory = sSystemRegistry.gameObjectFactory;
300                        GameObjectManager manager = sSystemRegistry.gameObjectManager;
301                        if (factory != null && manager != null) {
302                            GameObject explosion = factory.spawnEffectExplosionGiant(parentObject.getPosition().x, parentObject.getPosition().y);
303                            if (explosion != null) {
304                                manager.add(explosion);
305                            }
306                        }
307                    } else {
308                        mSprite.playAnimation(PlayerAnimations.DEATH.ordinal());
309                        mExplodingDeath = false;
310                    }
311
312                    mFlickerTimeRemaining = 0.0f;
313                    if (mSparksSprite != null) {
314                        if (!mSprite.animationFinished()) {
315                            mSparksSprite.setVisible(true);
316                        }
317                    }
318                }
319                if (mExplodingDeath) {
320                    visible = false;
321                }
322            } else if (currentAction == ActionType.FROZEN) {
323                mSprite.playAnimation(PlayerAnimations.FROZEN.ordinal());
324            }
325
326            if (mFlickerTimeRemaining > 0.0f) {
327                mFlickerTimeRemaining -= timeDelta;
328                if (gameTime > mLastFlickerTime + FLICKER_INTERVAL) {
329                    mLastFlickerTime = gameTime;
330                    mFlickerOn = !mFlickerOn;
331                }
332                mSprite.setVisible(mFlickerOn);
333                if (mJetSprite != null && mJetSprite.getVisible()) {
334                    mJetSprite.setVisible(mFlickerOn);
335                }
336            } else {
337                mSprite.setVisible(visible);
338                mSprite.setOpacity(opacity);
339            }
340
341            mPreviousAction = currentAction;
342        }
343    }
344
345    public void setSprite(SpriteComponent sprite) {
346        mSprite = sprite;
347    }
348
349    public void setJetSprite(SpriteComponent sprite) {
350        mJetSprite = sprite;
351    }
352
353    public void setSparksSprite(SpriteComponent sprite) {
354        mSparksSprite = sprite;
355    }
356
357    public void setPlayer(PlayerComponent player) {
358        mPlayer = player;
359    }
360
361    public final void setDamageSwap(ChangeComponentsComponent damageSwap) {
362        mDamageSwap = damageSwap;
363    }
364
365    public void setLandThump(Sound land) {
366        mLandThump = land;
367    }
368
369	public void setRocketSound(Sound sound) {
370		mRocketSound = sound;
371	}
372
373	public void setRubySounds(Sound one, Sound two, Sound three) {
374		mRubySound1 = one;
375		mRubySound2 = two;
376		mRubySound3 = three;
377	}
378
379	public void setInventory(InventoryComponent inventory) {
380		mInventory = inventory;
381	}
382
383	public void setExplosionSound(Sound sound) {
384		mExplosionSound = sound;
385	}
386}
387