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 NPCComponent extends GameComponent {
23    private float mPauseTime;
24    private float mTargetXVelocity;
25    private int mLastHitTileX;
26    private int mLastHitTileY;
27
28    private int mDialogEvent;
29    private int mDialogIndex;
30
31    private HitReactionComponent mHitReactComponent;
32
33    private int[] mQueuedCommands;
34    private int mQueueTop;
35    private int mQueueBottom;
36    private boolean mExecutingQueue;
37
38    private Vector2 mPreviousPosition;
39
40    private float mUpImpulse;
41    private float mDownImpulse;
42    private float mHorizontalImpulse;
43    private float mSlowHorizontalImpulse;
44    private float mAcceleration;
45
46    private int mGameEvent;
47    private int mGameEventIndex;
48    private boolean mSpawnGameEventOnDeath;
49
50    private boolean mReactToHits;
51    private boolean mFlying;
52    private boolean mPauseOnAttack;
53
54    private float mDeathTime;
55	private float mDeathFadeDelay;
56
57    private static final float UP_IMPULSE = 400.0f;
58    private static final float DOWN_IMPULSE = -10.0f;
59    private static final float HORIZONTAL_IMPULSE = 200.0f;
60    private static final float SLOW_HORIZONTAL_IMPULSE = 50.0f;
61    private static final float ACCELERATION = 300.0f;
62    private static final float HIT_IMPULSE = 300.0f;
63    private static final float HIT_ACCELERATION = 700.0f;
64
65    private static final float DEATH_FADE_DELAY = 4.0f;
66
67    private static final float PAUSE_TIME_SHORT = 1.0f;
68    private static final float PAUSE_TIME_MEDIUM = 4.0f;
69    private static final float PAUSE_TIME_LONG = 8.0f;
70    private static final float PAUSE_TIME_ATTACK = 1.0f;
71    private static final float PAUSE_TIME_HIT_REACT = 1.0f;
72
73    private static final int COMMAND_QUEUE_SIZE = 16;
74
75    public NPCComponent() {
76        super();
77        setPhase(ComponentPhases.THINK.ordinal());
78        mQueuedCommands = new int[COMMAND_QUEUE_SIZE];
79        mPreviousPosition = new Vector2();
80        reset();
81    }
82
83    @Override
84    public void reset() {
85        mPauseTime = 0.0f;
86        mTargetXVelocity = 0.0f;
87        mLastHitTileX = 0;
88        mLastHitTileY = 0;
89        mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1;
90        mDialogIndex = 0;
91        mHitReactComponent = null;
92        mQueueTop = 0;
93        mQueueBottom = 0;
94        mPreviousPosition.zero();
95        mExecutingQueue = false;
96        mUpImpulse = UP_IMPULSE;
97        mDownImpulse = DOWN_IMPULSE;
98        mHorizontalImpulse = HORIZONTAL_IMPULSE;
99        mSlowHorizontalImpulse = SLOW_HORIZONTAL_IMPULSE;
100        mAcceleration = ACCELERATION;
101        mGameEvent = -1;
102        mGameEventIndex = -1;
103        mSpawnGameEventOnDeath = false;
104        mReactToHits = false;
105        mFlying = false;
106        mDeathTime = 0.0f;
107        mDeathFadeDelay = DEATH_FADE_DELAY;
108        mPauseOnAttack = true;
109    }
110
111    @Override
112    public void update(float timeDelta, BaseObject parent) {
113
114        GameObject parentObject = (GameObject)parent;
115
116        if (mReactToHits &&
117        		mPauseTime <= 0.0f &&
118        		parentObject.getCurrentAction() == ActionType.HIT_REACT) {
119        	mPauseTime = PAUSE_TIME_HIT_REACT;
120            pauseMovement(parentObject);
121        	parentObject.getVelocity().x = -parentObject.facingDirection.x * HIT_IMPULSE;
122        	parentObject.getAcceleration().x = HIT_ACCELERATION;
123
124        } else if (parentObject.getCurrentAction() == ActionType.DEATH) {
125        	if (mSpawnGameEventOnDeath && mGameEvent != -1) {
126        		if (Utils.close(parentObject.getVelocity().x, 0.0f)
127        				&& parentObject.touchingGround()) {
128
129        			if (mDeathTime < mDeathFadeDelay && mDeathTime + timeDelta >= mDeathFadeDelay) {
130        				HudSystem hud = sSystemRegistry.hudSystem;
131
132        	        	if (hud != null) {
133        	        		hud.startFade(false, 1.5f);
134        	        		hud.sendGameEventOnFadeComplete(mGameEvent, mGameEventIndex);
135        	        		mGameEvent = -1;
136        	        	}
137        			}
138        			mDeathTime += timeDelta;
139
140        		}
141        	}
142        	// nothing else to do.
143        	return;
144        } else if (parentObject.life <= 0) {
145        	parentObject.setCurrentAction(ActionType.DEATH);
146        	parentObject.getTargetVelocity().x = 0;
147        	return;
148        } else if (parentObject.getCurrentAction() == ActionType.INVALID ||
149        		(!mReactToHits && parentObject.getCurrentAction() == ActionType.HIT_REACT)) {
150        	parentObject.setCurrentAction(ActionType.MOVE);
151        }
152
153        if (mPauseTime <= 0.0f) {
154
155            HotSpotSystem hotSpotSystem = sSystemRegistry.hotSpotSystem;
156
157            if (hotSpotSystem != null) {
158            	final float centerX = parentObject.getCenteredPositionX();
159                final int hitTileX = hotSpotSystem.getHitTileX(centerX);
160                final int hitTileY = hotSpotSystem.getHitTileY(parentObject.getPosition().y + 10.0f);
161                boolean accepted = true;
162
163                if (hitTileX != mLastHitTileX || hitTileY != mLastHitTileY) {
164
165            	    final int hotSpot = hotSpotSystem.getHotSpotByTile(hitTileX, hitTileY);
166
167                    if (hotSpot >= HotSpotSystem.HotSpotType.NPC_GO_RIGHT && hotSpot <= HotSpotSystem.HotSpotType.NPC_SLOW) {
168                    	// movement-related commands are immediate
169                        parentObject.setCurrentAction(ActionType.MOVE);
170                    	accepted = executeCommand(hotSpot, parentObject, timeDelta);
171                    } else if (hotSpot == HotSpotSystem.HotSpotType.ATTACK && !mPauseOnAttack) {
172                    	// when mPauseOnAttack is false, attacks are also immediate.
173                    	accepted = executeCommand(hotSpot, parentObject, timeDelta);
174                    } else if (hotSpot == HotSpotSystem.HotSpotType.NPC_RUN_QUEUED_COMMANDS) {
175                    	if (!mExecutingQueue && mQueueTop != mQueueBottom) {
176                    		mExecutingQueue = true;
177                        }
178                    } else if (hotSpot > HotSpotSystem.HotSpotType.NONE) {
179                    	queueCommand(hotSpot);
180                    }
181                }
182
183                if (mExecutingQueue) {
184                	if (mQueueTop != mQueueBottom) {
185                		accepted = executeCommand(nextCommand(), parentObject, timeDelta);
186                		if (accepted) {
187                			advanceQueue();
188                		}
189                	} else {
190                		mExecutingQueue = false;
191                	}
192                }
193
194                if (accepted) {
195                	mLastHitTileX = hitTileX;
196                	mLastHitTileY = hitTileY;
197                }
198
199            }
200        } else {
201            mPauseTime -= timeDelta;
202            if (mPauseTime < 0.0f) {
203                resumeMovement(parentObject);
204                mPauseTime = 0.0f;
205                parentObject.setCurrentAction(ActionType.MOVE);
206            }
207        }
208
209        mPreviousPosition.set(parentObject.getPosition());
210    }
211
212    private boolean executeCommand(int hotSpot, GameObject parentObject, float timeDelta) {
213    	boolean hitAccepted = true;
214    	final CameraSystem camera = sSystemRegistry.cameraSystem;
215
216    	switch(hotSpot) {
217        case HotSpotSystem.HotSpotType.WAIT_SHORT:
218            if (mPauseTime == 0.0f) {
219                mPauseTime = PAUSE_TIME_SHORT;
220                pauseMovement(parentObject);
221            }
222            break;
223        case HotSpotSystem.HotSpotType.WAIT_MEDIUM:
224            if (mPauseTime == 0.0f) {
225                mPauseTime = PAUSE_TIME_MEDIUM;
226                pauseMovement(parentObject);
227            }
228            break;
229        case HotSpotSystem.HotSpotType.WAIT_LONG:
230            if (mPauseTime == 0.0f) {
231                mPauseTime = PAUSE_TIME_LONG;
232                pauseMovement(parentObject);
233            }
234            break;
235        case HotSpotSystem.HotSpotType.ATTACK:
236        	if (mPauseOnAttack) {
237	            if (mPauseTime == 0.0f) {
238	                mPauseTime = PAUSE_TIME_ATTACK;
239	                pauseMovement(parentObject);
240
241	            }
242        	}
243            parentObject.setCurrentAction(ActionType.ATTACK);
244
245            break;
246
247        case HotSpotSystem.HotSpotType.TALK:
248        	if (mHitReactComponent != null) {
249            	if (parentObject.lastReceivedHitType != HitType.COLLECT) {
250            		mHitReactComponent.setSpawnGameEventOnHit(
251            				HitType.COLLECT, mDialogEvent, mDialogIndex);
252            		if (parentObject.getVelocity().x != 0.0f) {
253            			pauseMovement(parentObject);
254            		}
255            		hitAccepted = false;
256            	} else {
257                    parentObject.setCurrentAction(ActionType.MOVE);
258
259            		resumeMovement(parentObject);
260            		mHitReactComponent.setSpawnGameEventOnHit(HitType.INVALID, 0, 0);
261            		parentObject.lastReceivedHitType = HitType.INVALID;
262            	}
263        	}
264        	break;
265
266        case HotSpotSystem.HotSpotType.WALK_AND_TALK:
267        	if (mDialogEvent != GameFlowEvent.EVENT_INVALID) {
268        		LevelSystem level = sSystemRegistry.levelSystem;
269        		level.sendGameEvent(mDialogEvent, mDialogIndex, true);
270        		mDialogEvent = GameFlowEvent.EVENT_INVALID;
271        	}
272        	break;
273
274        case HotSpotSystem.HotSpotType.TAKE_CAMERA_FOCUS:
275        	if (camera != null) {
276        		camera.setTarget(parentObject);
277        	}
278        	break;
279
280        case HotSpotSystem.HotSpotType.RELEASE_CAMERA_FOCUS:
281
282        	if (camera != null) {
283        		GameObjectManager gameObjectManager = sSystemRegistry.gameObjectManager;
284        		camera.setTarget(gameObjectManager.getPlayer());
285        	}
286        	break;
287
288        case HotSpotSystem.HotSpotType.END_LEVEL:
289        	HudSystem hud = sSystemRegistry.hudSystem;
290
291        	if (hud != null) {
292        		hud.startFade(false, 1.5f);
293        		hud.sendGameEventOnFadeComplete(GameFlowEvent.EVENT_GO_TO_NEXT_LEVEL, 0);
294        	}
295        	break;
296        case HotSpotSystem.HotSpotType.GAME_EVENT:
297        	if (mGameEvent != -1) {
298    			LevelSystem level = sSystemRegistry.levelSystem;
299    			if (level != null) {
300    				level.sendGameEvent(mGameEvent, mGameEventIndex, true);
301    				mGameEvent = -1;
302    			}
303    		}
304        	break;
305
306        case HotSpotSystem.HotSpotType.NPC_GO_UP_FROM_GROUND:
307            if (!parentObject.touchingGround()) {
308                hitAccepted = false;
309                break;
310            }
311            // fall through
312        case HotSpotSystem.HotSpotType.NPC_GO_UP:
313        	parentObject.getVelocity().y = mUpImpulse;
314        	parentObject.getTargetVelocity().y = 0.0f;
315            mTargetXVelocity = 0.0f;
316
317            break;
318        case HotSpotSystem.HotSpotType.NPC_GO_DOWN_FROM_CEILING:
319            if (!parentObject.touchingCeiling()) {
320                hitAccepted = false;
321                break;
322            }
323            // fall through
324        case HotSpotSystem.HotSpotType.NPC_GO_DOWN:
325        	parentObject.getVelocity().y = mDownImpulse;
326        	parentObject.getTargetVelocity().y = 0.0f;
327        	if (mFlying) {
328        		mTargetXVelocity = 0.0f;
329        	}
330            break;
331        case HotSpotSystem.HotSpotType.NPC_GO_LEFT:
332        	parentObject.getTargetVelocity().x = -mHorizontalImpulse;
333        	parentObject.getAcceleration().x = mAcceleration;
334        	if (mFlying) {
335        		parentObject.getVelocity().y = 0.0f;
336        		parentObject.getTargetVelocity().y = 0.0f;
337        	}
338            break;
339        case HotSpotSystem.HotSpotType.NPC_GO_RIGHT:
340        	parentObject.getTargetVelocity().x = mHorizontalImpulse;
341        	parentObject.getAcceleration().x = mAcceleration;
342        	if (mFlying) {
343        		parentObject.getVelocity().y = 0.0f;
344        		parentObject.getTargetVelocity().y = 0.0f;
345        	}
346
347            break;
348        case HotSpotSystem.HotSpotType.NPC_GO_UP_RIGHT:
349        	parentObject.getVelocity().y = mUpImpulse;
350        	parentObject.getTargetVelocity().x = mHorizontalImpulse;
351        	parentObject.getAcceleration().x = mAcceleration;
352
353
354            break;
355        case HotSpotSystem.HotSpotType.NPC_GO_UP_LEFT:
356        	parentObject.getVelocity().y = mUpImpulse;
357        	parentObject.getTargetVelocity().x = -mHorizontalImpulse;
358        	parentObject.getAcceleration().x = mAcceleration;
359
360
361            break;
362        case HotSpotSystem.HotSpotType.NPC_GO_DOWN_RIGHT:
363        	parentObject.getVelocity().y = mDownImpulse;
364        	parentObject.getTargetVelocity().x = mHorizontalImpulse;
365        	parentObject.getAcceleration().x = mAcceleration;
366
367
368            break;
369        case HotSpotSystem.HotSpotType.NPC_GO_DOWN_LEFT:
370        	parentObject.getVelocity().y = mDownImpulse;
371        	parentObject.getTargetVelocity().x = -mHorizontalImpulse;
372        	parentObject.getAcceleration().x = mAcceleration;
373
374
375            break;
376        case HotSpotSystem.HotSpotType.NPC_GO_TOWARDS_PLAYER:
377            int direction = 1;
378            GameObjectManager manager = sSystemRegistry.gameObjectManager;
379            if (manager != null) {
380                GameObject player = manager.getPlayer();
381                if (player != null) {
382                    direction = Utils.sign(
383                            player.getCenteredPositionX() -
384                            parentObject.getCenteredPositionX());
385                }
386            }
387            parentObject.getTargetVelocity().x = mHorizontalImpulse * direction;
388            if (mFlying) {
389            	parentObject.getVelocity().y = 0.0f;
390        		parentObject.getTargetVelocity().y = 0.0f;
391        	}
392            break;
393        case HotSpotSystem.HotSpotType.NPC_GO_RANDOM:
394        	parentObject.getTargetVelocity().x = mHorizontalImpulse * (Math.random() > 0.5f ? -1.0f : 1.0f);
395        	if (mFlying) {
396        		parentObject.getVelocity().y = 0.0f;
397        		parentObject.getTargetVelocity().y = 0.0f;
398        	}
399            break;
400
401        case HotSpotSystem.HotSpotType.NPC_STOP:
402        	parentObject.getTargetVelocity().x = 0.0f;
403        	parentObject.getVelocity().x = 0.0f;
404            break;
405
406        case HotSpotSystem.HotSpotType.NPC_SLOW:
407        	parentObject.getTargetVelocity().x = mSlowHorizontalImpulse * Utils.sign(parentObject.getTargetVelocity().x);
408            break;
409
410        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1:
411        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_2:
412        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_3:
413        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_4:
414        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_5:
415        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1:
416        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_2:
417        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_3:
418        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_4:
419        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_5:
420        	selectDialog(hotSpot);
421        	break;
422        case HotSpotSystem.HotSpotType.NONE:
423            if (parentObject.touchingGround() && parentObject.getVelocity().y <= 0.0f) {
424                //resumeMovement(parentObject);
425            }
426            break;
427    	}
428
429    	return hitAccepted;
430    }
431
432    private void pauseMovement(GameObject parentObject) {
433    	mTargetXVelocity = parentObject.getTargetVelocity().x;
434    	parentObject.getTargetVelocity().x = 0.0f;
435    	parentObject.getVelocity().x = 0.0f;
436    }
437
438    private void resumeMovement(GameObject parentObject) {
439    	parentObject.getTargetVelocity().x = mTargetXVelocity;
440    	parentObject.getAcceleration().x = mAcceleration;
441    }
442
443    private void selectDialog(int hitSpot) {
444		mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1;
445    	mDialogIndex = hitSpot - HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1;
446
447    	if (hitSpot >= HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1) {
448    		mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER2;
449    		mDialogIndex = hitSpot - HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1;
450    	}
451    }
452
453    private int nextCommand() {
454    	int result = HotSpotSystem.HotSpotType.NONE;
455    	if (mQueueTop != mQueueBottom) {
456    		result = mQueuedCommands[mQueueTop];
457    	}
458    	return result;
459    }
460
461    private int advanceQueue() {
462    	int result = HotSpotSystem.HotSpotType.NONE;
463    	if (mQueueTop != mQueueBottom) {
464    		result = mQueuedCommands[mQueueTop];
465    		mQueueTop = (mQueueTop + 1) % COMMAND_QUEUE_SIZE;
466    	}
467    	return result;
468    }
469
470    private void queueCommand(int hotspot) {
471    	int nextSlot = (mQueueBottom + 1) % COMMAND_QUEUE_SIZE;
472    	if (nextSlot != mQueueTop) { // only comply if there is space left in the buffer
473    		mQueuedCommands[mQueueBottom] = hotspot;
474    		mQueueBottom = nextSlot;
475    	}
476    }
477
478    public void setHitReactionComponent(HitReactionComponent hitReact) {
479    	mHitReactComponent = hitReact;
480    }
481
482    public void setSpeeds(float horizontalImpulse, float slowHorizontalImpulse, float upImpulse, float downImpulse, float acceleration) {
483    	mHorizontalImpulse = horizontalImpulse;
484    	mSlowHorizontalImpulse = slowHorizontalImpulse;
485    	mUpImpulse = upImpulse;
486    	mDownImpulse = downImpulse;
487    	mAcceleration = acceleration;
488    }
489
490    public void setGameEvent(int event, int index, boolean spawnOnDeath) {
491    	mGameEvent = event;
492    	mGameEventIndex = index;
493    	mSpawnGameEventOnDeath = spawnOnDeath;
494    }
495
496    public void setReactToHits(boolean react) {
497    	mReactToHits = react;
498    }
499
500    public void setFlying(boolean flying) {
501    	mFlying = flying;
502    }
503
504    public void setPauseOnAttack(boolean pauseOnAttack) {
505    	mPauseOnAttack = pauseOnAttack;
506    }
507}
508