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/**
20 * A very simple manager for orthographic in-game UI elements.
21 * TODO: This should probably manage a number of hud objects in keeping with the component-centric
22 * architecture of this engine.  The current code is monolithic and should be refactored.
23 */
24public class HudSystem extends BaseObject {
25    private static final int FUEL_BAR_EDGE_PADDING = 15;
26    private static final float FUEL_DECREASE_BAR_SPEED = 0.75f;
27    private static final float FUEL_INCREASE_BAR_SPEED = 2.0f;
28    private static final float FLY_BUTTON_X = -12.0f;
29    private static final float FLY_BUTTON_Y = -5.0f;
30    private static final float STOMP_BUTTON_X = 85.0f;
31    private static final float STOMP_BUTTON_Y = -10.0f;
32    private static final float STOMP_BUTTON_SCALE = 0.65f;
33    private static final int COLLECTABLE_EDGE_PADDING = 8;
34    private static final int MAX_DIGITS = 4;
35    private static final float MOVEMENT_SLIDER_BASE_X = 20.0f;
36    private static final float MOVEMENT_SLIDER_BASE_Y = 32.0f;
37    private static final float MOVEMENT_SLIDER_BUTTON_X = MOVEMENT_SLIDER_BASE_X + 32.0f;
38    private static final float MOVEMENT_SLIDER_BUTTON_Y =  MOVEMENT_SLIDER_BASE_Y - 16.0f;
39    private static final float FLY_BUTTON_WIDTH = 128;
40    private static final float STOMP_BUTTON_WIDTH = FLY_BUTTON_WIDTH * STOMP_BUTTON_SCALE;
41    private static final float MOVEMENT_SLIDER_WIDTH = 128;
42
43    private DrawableBitmap mFuelDrawable;
44    private DrawableBitmap mFuelBackgroundDrawable;
45    private float mFuelPercent;
46    private float mFuelTargetPercent;
47
48    private Texture mFadeTexture;
49    private float mFadeStartTime;
50    private float mFadeDuration;
51    private boolean mFadeIn;
52    private boolean mFading;
53    private int mFadePendingEventType;
54	private int mFadePendingEventIndex;
55
56    private DrawableBitmap mFlyButtonEnabledDrawable;
57    private DrawableBitmap mFlyButtonDisabledDrawable;
58    private DrawableBitmap mFlyButtonDepressedDrawable;
59
60    private DrawableBitmap mStompButtonEnabledDrawable;
61    private DrawableBitmap mStompButtonDepressedDrawable;
62
63    private DrawableBitmap mMovementSliderBaseDrawable;
64    private DrawableBitmap mMovementSliderButtonDrawable;
65    private DrawableBitmap mMovementSliderButtonDepressedDrawable;
66
67
68    private Vector2 mFlyButtonLocation;
69    private boolean mFlyButtonActive;
70    private boolean mFlyButtonPressed;
71
72    private Vector2 mStompButtonLocation;
73    private boolean mStompButtonPressed;
74
75    private Vector2 mMovementSliderBaseLocation;
76    private Vector2 mMovementSliderButtonLocation;
77    private boolean mMovementSliderMode;
78    private boolean mMovementSliderButtonPressed;
79
80    private DrawableBitmap mRubyDrawable;
81    private DrawableBitmap mCoinDrawable;
82
83    private int mCoinCount;
84    private int mRubyCount;
85    private Vector2 mCoinLocation;
86    private Vector2 mRubyLocation;
87    private int[] mCoinDigits;
88    private int[] mRubyDigits;
89    private boolean mCoinDigitsChanged;
90    private boolean mRubyDigitsChanged;
91
92    private int mFPS;
93    private Vector2 mFPSLocation;
94    private int[] mFPSDigits;
95    private boolean mFPSDigitsChanged;
96    private boolean mShowFPS;
97
98    private DrawableBitmap[] mDigitDrawables;
99    private DrawableBitmap mXDrawable;
100
101
102    public HudSystem() {
103        super();
104        mFlyButtonLocation = new Vector2();
105        mStompButtonLocation = new Vector2();
106        mCoinLocation = new Vector2();
107        mRubyLocation = new Vector2();
108        mFPSLocation = new Vector2();
109        mDigitDrawables = new DrawableBitmap[10];
110        mCoinDigits = new int[MAX_DIGITS];
111        mRubyDigits = new int[MAX_DIGITS];
112        mFPSDigits = new int[MAX_DIGITS];
113        mMovementSliderBaseLocation = new Vector2();
114        mMovementSliderButtonLocation = new Vector2();
115
116        reset();
117    }
118
119    @Override
120    public void reset() {
121        mFuelDrawable = null;
122        mFadeTexture = null;
123        mFuelPercent = 1.0f;
124        mFuelTargetPercent = 1.0f;
125        mFading = false;
126        mFlyButtonDisabledDrawable = null;
127        mFlyButtonEnabledDrawable = null;
128        mFlyButtonDepressedDrawable = null;
129        mFlyButtonLocation.set(FLY_BUTTON_X, FLY_BUTTON_Y);
130        mFlyButtonActive = true;
131        mFlyButtonPressed = false;
132        mStompButtonEnabledDrawable = null;
133        mStompButtonDepressedDrawable = null;
134        mStompButtonLocation.set(STOMP_BUTTON_X, STOMP_BUTTON_Y);
135        mStompButtonPressed = false;
136        mCoinCount = 0;
137        mRubyCount = 0;
138        mCoinDigits[0] = 0;
139        mCoinDigits[1] = -1;
140        mRubyDigits[0] = 0;
141        mRubyDigits[1] = -1;
142        mCoinDigitsChanged = true;
143        mRubyDigitsChanged = true;
144        mFPS = 0;
145        mFPSDigits[0] = 0;
146        mFPSDigits[1] = -1;
147        mFPSDigitsChanged = true;
148        mShowFPS = false;
149        for (int x = 0; x < mDigitDrawables.length; x++) {
150            mDigitDrawables[x] = null;
151        }
152        mXDrawable = null;
153        mFadePendingEventType = GameFlowEvent.EVENT_INVALID;
154        mFadePendingEventIndex = 0;
155
156        mMovementSliderBaseDrawable = null;
157        mMovementSliderButtonDrawable = null;
158        mMovementSliderButtonDepressedDrawable = null;
159        mMovementSliderBaseLocation.set(MOVEMENT_SLIDER_BASE_X, MOVEMENT_SLIDER_BASE_Y);
160        mMovementSliderButtonLocation.set(MOVEMENT_SLIDER_BUTTON_X, MOVEMENT_SLIDER_BUTTON_Y);
161        mMovementSliderMode = false;
162        mMovementSliderButtonPressed = false;
163    }
164
165    public void setFuelPercent(float percent) {
166        mFuelTargetPercent = percent;
167    }
168
169    public void setFuelDrawable(DrawableBitmap fuel, DrawableBitmap background) {
170        mFuelDrawable = fuel;
171        mFuelBackgroundDrawable = background;
172    }
173
174    public void setFadeTexture(Texture texture) {
175        mFadeTexture = texture;
176    }
177
178    public void setButtonDrawables(DrawableBitmap disabled, DrawableBitmap enabled, DrawableBitmap depressed,
179    		DrawableBitmap stompEnabled, DrawableBitmap stompDepressed,
180    		DrawableBitmap sliderBase, DrawableBitmap sliderButton, DrawableBitmap sliderDepressed) {
181        mFlyButtonDisabledDrawable = disabled;
182        mFlyButtonEnabledDrawable = enabled;
183        mFlyButtonDepressedDrawable = depressed;
184        mStompButtonEnabledDrawable = stompEnabled;
185        mStompButtonDepressedDrawable = stompDepressed;
186        mMovementSliderBaseDrawable = sliderBase;
187        mMovementSliderButtonDrawable = sliderButton;
188        mMovementSliderButtonDepressedDrawable = sliderDepressed;
189    }
190
191    public void setDigitDrawables(DrawableBitmap[] digits, DrawableBitmap xMark) {
192        mXDrawable = xMark;
193        for (int x = 0; x < mDigitDrawables.length && x < digits.length; x++) {
194            mDigitDrawables[x] = digits[x];
195        }
196    }
197
198    public void setCollectableDrawables(DrawableBitmap coin, DrawableBitmap ruby) {
199        mCoinDrawable = coin;
200        mRubyDrawable = ruby;
201    }
202
203    public void setButtonState(boolean pressed, boolean attackPressed, boolean sliderPressed) {
204        mFlyButtonPressed = pressed;
205        mStompButtonPressed = attackPressed;
206        mMovementSliderButtonPressed = sliderPressed;
207    }
208
209    public void startFade(boolean in, float duration) {
210        mFadeStartTime = sSystemRegistry.timeSystem.getRealTime();
211        mFadeDuration = duration;
212        mFadeIn = in;
213        mFading = true;
214    }
215
216    public void clearFade() {
217        mFading = false;
218    }
219
220    public boolean isFading() {
221        return mFading;
222    }
223
224    public void updateInventory(InventoryComponent.UpdateRecord newInventory) {
225    	mCoinDigitsChanged = (mCoinCount != newInventory.coinCount);
226    	mRubyDigitsChanged = (mRubyCount != newInventory.rubyCount);
227
228        mCoinCount = newInventory.coinCount;
229        mRubyCount = newInventory.rubyCount;
230    }
231
232    public void setFPS(int fps) {
233    	mFPSDigitsChanged = (fps != mFPS);
234    	mFPS = fps;
235    }
236
237    public void setShowFPS(boolean show) {
238    	mShowFPS = show;
239    }
240
241    public void setMovementSliderMode(boolean sliderOn) {
242    	mMovementSliderMode = sliderOn;
243    	if (sliderOn) {
244    		ContextParameters params = sSystemRegistry.contextParameters;
245    		mFlyButtonLocation.set(params.gameWidth - FLY_BUTTON_WIDTH - FLY_BUTTON_X, FLY_BUTTON_Y);
246    		mStompButtonLocation.set(params.gameWidth - STOMP_BUTTON_WIDTH - STOMP_BUTTON_X, STOMP_BUTTON_Y);
247    	} else {
248    		mFlyButtonLocation.set(FLY_BUTTON_X, FLY_BUTTON_Y);
249    		mStompButtonLocation.set(STOMP_BUTTON_X, STOMP_BUTTON_Y);
250    	}
251    }
252    public void setMovementSliderOffset(float offset) {
253        mMovementSliderButtonLocation.set(MOVEMENT_SLIDER_BUTTON_X + (offset * (MOVEMENT_SLIDER_WIDTH / 2.0f)), MOVEMENT_SLIDER_BUTTON_Y);
254    }
255
256    @Override
257    public void update(float timeDelta, BaseObject parent) {
258        final RenderSystem render = sSystemRegistry.renderSystem;
259        final VectorPool pool = sSystemRegistry.vectorPool;
260        final ContextParameters params = sSystemRegistry.contextParameters;
261        final DrawableFactory factory = sSystemRegistry.drawableFactory;
262
263        final GameObjectManager manager = sSystemRegistry.gameObjectManager;
264
265        if (manager != null && manager.getPlayer() != null) {
266        	// Only draw player-specific HUD elements when there's a player.
267	        if (mFuelDrawable != null && mFuelBackgroundDrawable != null
268	                && render != null && pool != null && factory != null && params != null) {
269	            if (mFuelPercent < mFuelTargetPercent) {
270	                mFuelPercent += (FUEL_INCREASE_BAR_SPEED * timeDelta);
271	                if (mFuelPercent > mFuelTargetPercent) {
272	                    mFuelPercent = mFuelTargetPercent;
273	                }
274	            } else if (mFuelPercent > mFuelTargetPercent) {
275	                mFuelPercent -= (FUEL_DECREASE_BAR_SPEED * timeDelta);
276	                if (mFuelPercent < mFuelTargetPercent) {
277	                    mFuelPercent = mFuelTargetPercent;
278	                }
279	            }
280
281	            if (mFuelBackgroundDrawable.getWidth() == 0) {
282	                // first time init
283	                Texture tex = mFuelDrawable.getTexture();
284	                mFuelDrawable.resize(tex.width, tex.height);
285	                Texture backgroundTex = mFuelBackgroundDrawable.getTexture();
286	                mFuelBackgroundDrawable.resize(backgroundTex.width, backgroundTex.height);
287	            }
288
289	            final int height = mFuelDrawable.getHeight();
290
291
292	            Vector2 location = pool.allocate();
293	            location.set(FUEL_BAR_EDGE_PADDING,
294	                    params.gameHeight - height - FUEL_BAR_EDGE_PADDING);
295	            render.scheduleForDraw(mFuelBackgroundDrawable, location, SortConstants.HUD, false);
296	            location.x += 2;
297	            location.y += 2;
298	            final int barWidth = (int)((100 - 4) * mFuelPercent);
299	            if (barWidth >= 1) {
300		            DrawableBitmap bitmap = factory.allocateDrawableBitmap();
301		            if (bitmap != null) {
302		                bitmap.resize(barWidth, mFuelDrawable.getHeight());
303		                bitmap.setTexture(mFuelDrawable.getTexture());
304		                render.scheduleForDraw(bitmap, location, SortConstants.HUD + 1, false);
305		            }
306	            }
307
308	            pool.release(location);
309	        }
310
311	        if (mFlyButtonDisabledDrawable != null && mFlyButtonEnabledDrawable != null
312	                && mFlyButtonDepressedDrawable != null) {
313
314	            DrawableBitmap bitmap = mFlyButtonEnabledDrawable;
315	            if (mFlyButtonActive && mFlyButtonPressed) {
316	                bitmap = mFlyButtonDepressedDrawable;
317	            } else if (!mFlyButtonActive) {
318	                bitmap = mFlyButtonDisabledDrawable;
319	            }
320
321	            if (bitmap.getWidth() == 0) {
322	                // first time init
323	                Texture tex = bitmap.getTexture();
324	                bitmap.resize(tex.width, tex.height);
325	            }
326
327	            render.scheduleForDraw(bitmap, mFlyButtonLocation, SortConstants.HUD, false);
328	        }
329
330
331
332	        if (mStompButtonEnabledDrawable != null && mStompButtonDepressedDrawable != null) {
333
334	            DrawableBitmap bitmap = mStompButtonEnabledDrawable;
335	            if (mStompButtonPressed) {
336	                bitmap = mStompButtonDepressedDrawable;
337	            }
338
339	            if (bitmap.getWidth() == 0) {
340	                // first time init
341	                Texture tex = bitmap.getTexture();
342	                bitmap.resize(tex.width, tex.height);
343	                bitmap.setWidth((int)(tex.width * STOMP_BUTTON_SCALE));
344	                bitmap.setHeight((int)(tex.height * STOMP_BUTTON_SCALE));
345	            }
346
347	            render.scheduleForDraw(bitmap, mStompButtonLocation, SortConstants.HUD, false);
348	        }
349
350	        if (mMovementSliderMode &&
351	        		mMovementSliderBaseDrawable != null && mMovementSliderButtonDrawable != null) {
352
353	            if (mMovementSliderBaseDrawable.getWidth() == 0) {
354	                // first time init
355	                Texture tex = mMovementSliderBaseDrawable.getTexture();
356	                mMovementSliderBaseDrawable.resize(tex.width, tex.height);
357	            }
358
359	            if (mMovementSliderButtonDrawable.getWidth() == 0) {
360	                // first time init
361	                Texture tex = mMovementSliderButtonDrawable.getTexture();
362	                mMovementSliderButtonDrawable.resize(tex.width, tex.height);
363	            }
364
365	            if (mMovementSliderButtonDepressedDrawable.getWidth() == 0) {
366	                // first time init
367	                Texture tex = mMovementSliderButtonDepressedDrawable.getTexture();
368	                mMovementSliderButtonDepressedDrawable.resize(tex.width, tex.height);
369	            }
370
371	            DrawableBitmap bitmap = mMovementSliderButtonDrawable;
372
373	            if (mMovementSliderButtonPressed) {
374	            	bitmap = mMovementSliderButtonDepressedDrawable;
375	            }
376
377	            render.scheduleForDraw(mMovementSliderBaseDrawable, mMovementSliderBaseLocation, SortConstants.HUD, false);
378	            render.scheduleForDraw(bitmap, mMovementSliderButtonLocation, SortConstants.HUD + 1, false);
379
380	        }
381
382
383	        if (mCoinDrawable != null) {
384	            if (mCoinDrawable.getWidth() == 0) {
385	                // first time init
386	                Texture tex = mCoinDrawable.getTexture();
387	                mCoinDrawable.resize(tex.width, tex.height);
388	                mCoinLocation.x = (params.gameWidth / 2.0f) - tex.width / 2.0f;
389	                mCoinLocation.y = params.gameHeight - tex.height - COLLECTABLE_EDGE_PADDING;
390	            }
391
392	            render.scheduleForDraw(mCoinDrawable, mCoinLocation, SortConstants.HUD, false);
393	            if (mCoinDigitsChanged) {
394	            	intToDigitArray(mCoinCount, mCoinDigits);
395	            	mCoinDigitsChanged = false;
396	            }
397	            final float offset = mCoinDrawable.getWidth() * 0.75f;
398	            mCoinLocation.x += offset;
399	            drawNumber(mCoinLocation, mCoinDigits, true);
400	            mCoinLocation.x -= offset;
401	        }
402
403	        if (mRubyDrawable != null) {
404	            if (mRubyDrawable.getWidth() == 0) {
405	                // first time init
406	                Texture tex = mRubyDrawable.getTexture();
407	                mRubyDrawable.resize(tex.width, tex.height);
408	                mRubyLocation.x = (params.gameWidth / 2.0f) + 100.0f;
409	                mRubyLocation.y = params.gameHeight - tex.height - COLLECTABLE_EDGE_PADDING;
410	            }
411	            render.scheduleForDraw(mRubyDrawable, mRubyLocation, SortConstants.HUD, false);
412	            if (mRubyDigitsChanged) {
413	            	intToDigitArray(mRubyCount, mRubyDigits);
414	            	mRubyDigitsChanged = false;
415	            }
416	            final float offset = mRubyDrawable.getWidth() * 0.75f;
417	            mRubyLocation.x += offset;
418	            drawNumber(mRubyLocation, mRubyDigits, true);
419	            mRubyLocation.x -= offset;
420	        }
421        }
422
423        if (mShowFPS) {
424        	if (mFPSDigitsChanged) {
425            	int count = intToDigitArray(mFPS, mFPSDigits);
426            	mFPSDigitsChanged = false;
427                mFPSLocation.set(params.gameWidth - 10.0f - ((count + 1) * (mDigitDrawables[0].getWidth() / 2.0f)), 10.0f);
428
429            }
430            drawNumber(mFPSLocation, mFPSDigits, false);
431        }
432
433        if (mFading && factory != null) {
434
435            final float time = sSystemRegistry.timeSystem.getRealTime();
436            final float fadeDelta = (time - mFadeStartTime);
437
438            float percentComplete = 1.0f;
439            if (fadeDelta < mFadeDuration) {
440                percentComplete = fadeDelta / mFadeDuration;
441            } else if (mFadeIn) {
442                // We've faded in.  Turn fading off.
443                mFading = false;
444            }
445
446            if (percentComplete < 1.0f || !mFadeIn) {
447                float opacityValue = percentComplete;
448                if (mFadeIn) {
449                    opacityValue = 1.0f - percentComplete;
450                }
451
452                DrawableBitmap bitmap = factory.allocateDrawableBitmap();
453                if (bitmap != null) {
454                    bitmap.setWidth(params.gameWidth);
455                    bitmap.setHeight(params.gameHeight);
456                    bitmap.setTexture(mFadeTexture);
457                    bitmap.setCrop(0, mFadeTexture.height, mFadeTexture.width, mFadeTexture.height);
458                    bitmap.setOpacity(opacityValue);
459                    render.scheduleForDraw(bitmap, Vector2.ZERO, SortConstants.FADE, false);
460                }
461            }
462
463            if (percentComplete >= 1.0f && mFadePendingEventType != GameFlowEvent.EVENT_INVALID) {
464            	LevelSystem level = sSystemRegistry.levelSystem;
465            	if (level != null) {
466            		level.sendGameEvent(mFadePendingEventType, mFadePendingEventIndex, false);
467            		mFadePendingEventType = GameFlowEvent.EVENT_INVALID;
468            		mFadePendingEventIndex = 0;
469            	}
470            }
471        }
472    }
473
474    private void drawNumber(Vector2 location, int[] digits, boolean drawX) {
475        final RenderSystem render = sSystemRegistry.renderSystem;
476
477        if (mDigitDrawables[0].getWidth() == 0) {
478            // first time init
479            for (int x = 0; x < mDigitDrawables.length; x++) {
480                Texture tex = mDigitDrawables[x].getTexture();
481                mDigitDrawables[x].resize(tex.width, tex.height);
482            }
483        }
484
485        if (mXDrawable.getWidth() == 0) {
486            // first time init
487            Texture tex = mXDrawable.getTexture();
488            mXDrawable.resize(tex.width, tex.height);
489        }
490
491        final float characterWidth = mDigitDrawables[0].getWidth() / 2.0f;
492        float offset = 0.0f;
493
494        if (mXDrawable != null && drawX) {
495            render.scheduleForDraw(mXDrawable, location, SortConstants.HUD, false);
496            location.x += characterWidth;
497            offset += characterWidth;
498         }
499
500        for (int x = 0; x < digits.length && digits[x] != -1; x++) {
501            int index = digits[x];
502            DrawableBitmap digit = mDigitDrawables[index];
503            if (digit != null) {
504                render.scheduleForDraw(digit, location, SortConstants.HUD, false);
505                location.x += characterWidth;
506                offset += characterWidth;
507            }
508        }
509
510        location.x -= offset;
511
512
513    }
514
515    public int intToDigitArray(int value, int[] digits) {
516    	int characterCount = 1;
517        if (value >= 1000) {
518            characterCount = 4;
519        } else if (value >= 100) {
520            characterCount = 3;
521        } else if (value >= 10) {
522            characterCount = 2;
523        }
524
525    	int remainingValue = value;
526        int count = 0;
527	    do {
528	        int index = remainingValue != 0 ? remainingValue % 10 : 0;
529	        remainingValue /= 10;
530	        digits[characterCount - 1 - count] = index;
531	        count++;
532	    } while (remainingValue > 0 && count < digits.length);
533
534	    if (count < digits.length) {
535	    	digits[count] = -1;
536	    }
537	    return characterCount;
538    }
539
540	public void sendGameEventOnFadeComplete(int eventType, int eventIndex) {
541		mFadePendingEventType = eventType;
542		mFadePendingEventIndex = eventIndex;
543	}
544
545
546}
547