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;
21import com.replica.replicaisland.GameObject.Team;
22import com.replica.replicaisland.GameObjectFactory.GameObjectType;
23
24/**
25 * A general-purpose component that responds to dynamic collision notifications.  This component
26 * may be configured to produce common responses to hit (taking damage, being knocked back, etc), or
27 * it can be derived for entirely different responses.  This component must exist on an object for
28 * that object to respond to dynamic collisions.
29 */
30public class HitReactionComponent extends GameComponent {
31    private static final float ATTACK_PAUSE_DELAY = (1.0f / 60) * 4;
32    private final static float DEFAULT_BOUNCE_MAGNITUDE = 200.0f;
33    private final static float EVENT_SEND_DELAY = 5.0f;
34
35    private boolean mPauseOnAttack;
36    private float mPauseOnAttackTime;
37    private boolean mBounceOnHit;
38    private float mBounceMagnitude;
39    private float mInvincibleAfterHitTime;
40    private float mLastHitTime;
41    private boolean mInvincible;
42    private boolean mDieOnCollect;
43    private boolean mDieOnAttack;
44    private ChangeComponentsComponent mPossessionComponent;
45    private InventoryComponent.UpdateRecord mInventoryUpdate;
46    private LauncherComponent mLauncherComponent;
47    private int mLauncherHitType;
48    private float mInvincibleTime;
49    private int mGameEventHitType;
50    private int mGameEventOnHit;
51    private int mGameEventIndexData;
52    private float mLastGameEventTime;
53    private boolean mForceInvincibility;
54    private SoundSystem.Sound mTakeHitSound;
55    private SoundSystem.Sound mDealHitSound;
56    private int mDealHitSoundHitType;
57    private int mTakeHitSoundHitType;
58
59    private GameObjectFactory.GameObjectType mSpawnOnDealHitObjectType;
60    private int mSpawnOnDealHitHitType;
61    private boolean mAlignDealHitObjectToVictimX;
62    private boolean mAlignDealHitObjectToVictimY;
63
64
65    public HitReactionComponent() {
66        super();
67        reset();
68        setPhase(ComponentPhases.PRE_DRAW.ordinal());
69    }
70
71    @Override
72    public void reset() {
73        mPauseOnAttack = false;
74        mPauseOnAttackTime = ATTACK_PAUSE_DELAY;
75        mBounceOnHit = false;
76        mBounceMagnitude = DEFAULT_BOUNCE_MAGNITUDE;
77        mInvincibleAfterHitTime = 0.0f;
78        mInvincible = false;
79        mDieOnCollect = false;
80        mDieOnAttack = false;
81        mPossessionComponent = null;
82        mInventoryUpdate = null;
83        mLauncherComponent = null;
84        mLauncherHitType = HitType.LAUNCH;
85        mInvincibleTime = 0.0f;
86        mGameEventOnHit = -1;
87        mGameEventIndexData = 0;
88        mLastGameEventTime = -1.0f;
89        mGameEventHitType = CollisionParameters.HitType.INVALID;
90        mForceInvincibility = false;
91        mTakeHitSound = null;
92        mDealHitSound = null;
93        mSpawnOnDealHitObjectType = GameObjectType.INVALID;
94        mSpawnOnDealHitHitType = CollisionParameters.HitType.INVALID;
95        mDealHitSoundHitType = CollisionParameters.HitType.INVALID;
96        mAlignDealHitObjectToVictimX = false;
97        mAlignDealHitObjectToVictimY = false;
98    }
99
100    /** Called when this object attacks another object. */
101    public void hitVictim(GameObject parent, GameObject victim, int hitType,
102            boolean hitAccepted) {
103        if (hitAccepted) {
104            if (mPauseOnAttack && hitType == CollisionParameters.HitType.HIT) {
105                TimeSystem time = sSystemRegistry.timeSystem;
106                time.freeze(mPauseOnAttackTime);
107            }
108
109            if (mDieOnAttack) {
110                parent.life = 0;
111            }
112
113            if (hitType == mLauncherHitType && mLauncherComponent != null) {
114                mLauncherComponent.prepareToLaunch(victim, parent);
115            }
116
117            if (mDealHitSound != null &&
118            		(hitType == mDealHitSoundHitType ||
119            				mDealHitSoundHitType == CollisionParameters.HitType.INVALID)) {
120                SoundSystem sound = sSystemRegistry.soundSystem;
121                if (sound != null) {
122                    sound.play(mDealHitSound, false, SoundSystem.PRIORITY_NORMAL);
123                }
124            }
125
126            if (mSpawnOnDealHitObjectType != GameObjectType.INVALID &&
127                    hitType == mSpawnOnDealHitHitType) {
128                final float x = mAlignDealHitObjectToVictimX ?
129                        victim.getPosition().x : parent.getPosition().x;
130                final float y = mAlignDealHitObjectToVictimY ?
131                        victim.getPosition().y : parent.getPosition().y;
132
133                GameObjectFactory factory = sSystemRegistry.gameObjectFactory;
134                GameObjectManager manager = sSystemRegistry.gameObjectManager;
135
136                if (factory != null) {
137                    GameObject object = factory.spawn(mSpawnOnDealHitObjectType, x,
138                            y, parent.facingDirection.x < 0.0f);
139
140                    if (object != null && manager != null) {
141                        manager.add(object);
142                    }
143                }
144            }
145        }
146    }
147
148    /** Called when this object is hit by another object. */
149    public boolean receivedHit(GameObject parent, GameObject attacker, int hitType) {
150        final TimeSystem time = sSystemRegistry.timeSystem;
151        final float gameTime = time.getGameTime();
152
153        if (mGameEventHitType == hitType &&
154                mGameEventHitType != CollisionParameters.HitType.INVALID ) {
155        	if (mLastGameEventTime < 0.0f || gameTime > mLastGameEventTime + EVENT_SEND_DELAY) {
156	            LevelSystem level = sSystemRegistry.levelSystem;
157	            level.sendGameEvent(mGameEventOnHit, mGameEventIndexData, true);
158	        } else {
159	        	// special case.  If we're waiting for a hit type to spawn an event and
160	        	// another event has just happened, eat this hit so we don't miss
161	        	// the chance to send the event.
162	        	hitType = CollisionParameters.HitType.INVALID;
163	        }
164        	mLastGameEventTime = gameTime;
165        }
166
167        switch(hitType) {
168            case CollisionParameters.HitType.INVALID:
169                break;
170
171            case CollisionParameters.HitType.HIT:
172                // don't hit our friends, if we have friends.
173                final boolean sameTeam = (parent.team == attacker.team && parent.team != Team.NONE);
174                if (!mForceInvincibility && !mInvincible && parent.life > 0 && !sameTeam) {
175                    parent.life -= 1;
176
177                    if (mBounceOnHit && parent.life > 0) {
178                        VectorPool pool = sSystemRegistry.vectorPool;
179                        Vector2 newVelocity = pool.allocate(parent.getPosition());
180                        newVelocity.subtract(attacker.getPosition());
181                        newVelocity.set(0.5f * Utils.sign(newVelocity.x),
182                                0.5f * Utils.sign(newVelocity.y));
183                        newVelocity.multiply(mBounceMagnitude);
184                        parent.setVelocity(newVelocity);
185                        parent.getTargetVelocity().zero();
186                        pool.release(newVelocity);
187                    }
188
189                    if (mInvincibleAfterHitTime > 0.0f) {
190                        mInvincible = true;
191                        mInvincibleTime = mInvincibleAfterHitTime;
192                    }
193
194                } else {
195                    // Ignore this hit.
196                    hitType = CollisionParameters.HitType.INVALID;
197                }
198                break;
199            case CollisionParameters.HitType.DEATH:
200                // respect teams?
201                parent.life = 0;
202                break;
203            case CollisionParameters.HitType.COLLECT:
204                if (mInventoryUpdate != null && parent.life > 0) {
205                    InventoryComponent attackerInventory = attacker.findByClass(InventoryComponent.class);
206                    if (attackerInventory != null) {
207                        attackerInventory.applyUpdate(mInventoryUpdate);
208                    }
209                }
210                if (mDieOnCollect && parent.life > 0) {
211                    parent.life = 0;
212                }
213                break;
214            case CollisionParameters.HitType.POSSESS:
215                if (mPossessionComponent != null && parent.life > 0 && attacker.life > 0) {
216                    mPossessionComponent.activate(parent);
217                } else {
218                    hitType = CollisionParameters.HitType.INVALID;
219                }
220                break;
221            case CollisionParameters.HitType.LAUNCH:
222                break;
223
224            default:
225                break;
226        }
227
228
229        if (hitType != CollisionParameters.HitType.INVALID) {
230            if (mTakeHitSound != null && hitType == mTakeHitSoundHitType) {
231                SoundSystem sound = sSystemRegistry.soundSystem;
232                if (sound != null) {
233                    sound.play(mTakeHitSound, false, SoundSystem.PRIORITY_NORMAL);
234                }
235            }
236            mLastHitTime = gameTime;
237            parent.setCurrentAction(ActionType.HIT_REACT);
238            parent.lastReceivedHitType = hitType;
239
240        }
241
242        return hitType != CollisionParameters.HitType.INVALID;
243    }
244
245    @Override
246    public void update(float timeDelta, BaseObject parent) {
247        GameObject parentObject = (GameObject)parent;
248        TimeSystem time = sSystemRegistry.timeSystem;
249
250        final float gameTime = time.getGameTime();
251
252        if (mInvincible && mInvincibleTime > 0) {
253            if (time.getGameTime() > mLastHitTime + mInvincibleTime) {
254                mInvincible = false;
255            }
256        }
257
258        // This means that the lastReceivedHitType will persist for two frames, giving all systems
259        // a chance to react.
260        if (gameTime - mLastHitTime > timeDelta) {
261            parentObject.lastReceivedHitType = CollisionParameters.HitType.INVALID;
262        }
263    }
264
265    public void setPauseOnAttack(boolean pause) {
266        mPauseOnAttack = pause;
267    }
268
269    public void setPauseOnAttackTime(float seconds) {
270        mPauseOnAttackTime = seconds;
271    }
272
273    public void setBounceOnHit(boolean bounce) {
274        mBounceOnHit = bounce;
275    }
276
277    public void setBounceMagnitude(float magnitude) {
278        mBounceMagnitude = magnitude;
279    }
280
281    public void setInvincibleTime(float time) {
282        mInvincibleAfterHitTime = time;
283    }
284
285    public void setDieWhenCollected(boolean die) {
286        mDieOnCollect = true;
287    }
288
289    public void setDieOnAttack(boolean die) {
290        mDieOnAttack = die;
291    }
292
293    public void setInvincible(boolean invincible) {
294        mInvincible = invincible;
295    }
296
297    public void setPossessionComponent(ChangeComponentsComponent component) {
298        mPossessionComponent = component;
299    }
300
301    public void setInventoryUpdate(InventoryComponent.UpdateRecord update) {
302        mInventoryUpdate = update;
303    }
304
305    public void setLauncherComponent(LauncherComponent component, int launchHitType) {
306        mLauncherComponent = component;
307        mLauncherHitType = launchHitType;
308    }
309
310    public void setSpawnGameEventOnHit(int hitType, int gameFlowEventType, int indexData) {
311        mGameEventHitType = hitType;
312        mGameEventOnHit = gameFlowEventType;
313        mGameEventIndexData = indexData;
314        if (hitType == HitType.INVALID) {
315        	// The game event has been cleared, so reset the timer blocking a
316        	// subsequent event.
317        	mLastGameEventTime = -1.0f;
318        }
319    }
320
321    public final void setForceInvincible(boolean force) {
322        mForceInvincibility = force;
323    }
324
325    public final void setTakeHitSound(int hitType, SoundSystem.Sound sound) {
326    	mTakeHitSoundHitType = hitType;
327        mTakeHitSound = sound;
328    }
329
330    public final void setDealHitSound(int hitType, SoundSystem.Sound sound) {
331        mDealHitSound = sound;
332        mDealHitSoundHitType = hitType;
333    }
334
335    public final void setSpawnOnDealHit(int hitType, GameObjectType objectType, boolean alignToVictimX,
336            boolean alignToVicitmY) {
337        mSpawnOnDealHitObjectType = objectType;
338        mSpawnOnDealHitHitType = hitType;
339        mAlignDealHitObjectToVictimX = alignToVictimX;
340        mAlignDealHitObjectToVictimY = alignToVicitmY;
341    }
342
343}
344