1 // Description:
2 //   Menu Manager/Controller.
3 //
4 // Copyright (C) 2001 Frank Becker
5 //
6 // This program is free software; you can redistribute it and/or modify it under
7 // the terms of the GNU General Public License as published by the Free Software
8 // Foundation;  either version 2 of the License,  or (at your option) any  later
9 // version.
10 //
11 // This program is distributed in the hope that it will be useful,  but  WITHOUT
12 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
14 //
15 #include "SDL/SDL.h" //key syms
16 
17 #include <Trace.hpp>
18 #include <MenuManager.hpp>
19 #include <XMLHelper.hpp>
20 #include <GameState.hpp>
21 #include <Game.hpp>
22 #include <Audio.hpp>
23 #include <Input.hpp>
24 #include <FontManager.hpp>
25 #include <SelectableFactory.hpp>
26 #include <ModelManager.hpp>
27 #include <Video.hpp>
28 #include <BitmapManager.hpp>
29 #include <Config.hpp>
30 
31 #include "GLee.h"
32 
33 #include <GLExtensionTextureCubeMap.hpp>
34 #include <GLTextureCubeMap.hpp>
35 #include <ResourceManager.hpp>
36 #include <zrwops.hpp>
37 
38 using namespace std;
39 
MenuManager()40 MenuManager::MenuManager():
41     _menu(0),
42     _topMenu(0),
43     _currentMenu(0),
44     _mouseX(200.0),
45     _mouseY(650.0),
46     _prevContext(Context::eUnknown),
47     _delayedExit(false),
48     _newLevelLoaded(false),
49     _angle(0.0),
50     _prevAngle(0.0),
51     _showSparks(true),
52     _burst( "SparkBurst", 1000),
53     _nextGenShippyCubeMap(0)
54 {
55     XTRACE();
56 
57     updateSettings();
58 }
59 
~MenuManager()60 MenuManager::~MenuManager()
61 {
62     XTRACE();
63 
64     clearActiveSelectables();
65 
66     SelectableFactory::cleanup();
67 
68     delete _menu;
69     _menu = 0;
70 
71     delete _nextGenShippyCubeMap;
72 }
73 
init(void)74 bool MenuManager::init( void)
75 {
76     XTRACE();
77     _menu = XMLHelper::load( "system/Menu.xml");
78     if( !_menu)
79     {
80 	_menu = 0;
81 	return false;
82     }
83 
84     _currentMenu = _menu->FirstChild("Menu");
85     _topMenu = _currentMenu;
86 
87     loadMenuLevel();
88 
89     GLBitmapCollection *icons =
90         BitmapManagerS::instance()->getBitmap( "bitmaps/menuIcons");
91     if( !icons)
92     {
93 	LOG_ERROR << "Unable to load menuIcons." << endl;
94 	return false;
95     }
96     _pointer = icons->getIndex( "Pointer");
97 
98     GLBitmapCollection *menuBoard =
99         BitmapManagerS::instance()->getBitmap( "bitmaps/menuBoard");
100     if( !menuBoard)
101     {
102 	LOG_ERROR << "Unable to load menuBoard." << endl;
103 	return false;
104     }
105     _board = menuBoard->getIndex( "MenuBoard");
106 
107     _mapleLeaf = ModelManagerS::instance()->getModel("models/MapleLeaf");
108     if( !_mapleLeaf)
109     {
110 	LOG_ERROR << "Unable to load Maple Leaf, eh?" << endl;
111 	return false;
112     }
113 
114     _nextGenShippy = ModelManagerS::instance()->getModel("models/NextGenShippy");
115     if( !_nextGenShippy)
116     {
117 	LOG_ERROR << "Unable to sphere."<< endl;
118 	return false;
119     }
120 
121     GLExtensionTextureCubeMap _nextGenShippyExt;
122     if( _nextGenShippyExt.isSupported())
123     {
124 	SDL_Surface *images[6];
125 	for( int i=0; i<6; i++)
126 	{
127 	    char buf[128];
128 	    sprintf( buf, "bitmaps/cubemap_%d.png", i);
129 	    string fileName = buf;
130 	    if( ResourceManagerS::instance()->selectResource( fileName))
131 	    {
132 		ziStream &bminfile1 = ResourceManagerS::instance()->getInputStream();
133 		SDL_RWops *src = RWops_from_ziStream( bminfile1);
134 		images[i] = IMG_LoadPNG_RW( src);
135 		SDL_RWclose( src);
136 	    }
137 	    else
138 	    {
139 		LOG_ERROR << "Could not load cubemap image\n";
140 		images[i] = 0;
141 	    }
142 	}
143 
144 	_nextGenShippyCubeMap = new GLTextureCubeMap( images);
145     }
146     else
147     {
148 	LOG_WARNING << "ARB_texture_cube_map not supported\n";
149     }
150 
151     _burst.init();
152 
153     return true;
154 }
155 
updateSettings(void)156 void MenuManager::updateSettings( void)
157 {
158     ConfigS::instance()->getBoolean( "showSparks", _showSparks);
159 }
160 
clearActiveSelectables(void)161 void MenuManager::clearActiveSelectables( void)
162 {
163     list<Selectable*>::iterator i;
164     for( i=_activeSelectables.begin(); i!=_activeSelectables.end(); i++)
165     {
166         delete (*i);
167     }
168     _activeSelectables.clear();
169 
170     Selectable::reset();
171 }
172 
loadMenuLevel(void)173 void MenuManager::loadMenuLevel( void)
174 {
175     _newLevelLoaded = true;
176     clearActiveSelectables();
177 
178     TiXmlNode *node = _currentMenu->FirstChild();
179     while( node)
180     {
181 //	LOG_INFO << "MenuItem: [" << node->Value() << "]" << endl;
182 
183 	SelectableFactory *selF = SelectableFactory::getFactory( node->Value());
184 	if( selF)
185 	{
186 	    Selectable *sel = selF->createSelectable( node);
187 	    if( sel)
188 	    {
189 		_activeSelectables.insert( _activeSelectables.end(), sel);
190 	    }
191 	}
192 	else
193 	{
194 	    LOG_WARNING << "No Factory found for:" << node->Value() << endl;
195 	}
196 	node = node->NextSibling();
197     }
198 
199     //add escape button
200     if( _currentMenu != _topMenu)
201     {
202 	BoundingBox r;
203 	r.min.x = 860;
204 	r.min.y = 530;
205 	r.max.x = r.min.x+15;
206 	r.max.y = r.min.y+15;
207 
208 	Selectable *sel = new EscapeSelectable( true, r, 2.0);
209 	_activeSelectables.insert( _activeSelectables.end(), sel);
210     }
211     _currentSelectable = _activeSelectables.end();
212     activateSelectableUnderMouse();
213 }
214 
makeMenu(TiXmlNode * _node)215 void MenuManager::makeMenu( TiXmlNode *_node)
216 {
217     _currentMenu = _node;
218     loadMenuLevel();
219 }
220 
update(void)221 bool MenuManager::update( void)
222 {
223     static float nextTime = Timer::getTime()+0.5f;
224     float thisTime = Timer::getTime();
225     if( thisTime > nextTime)
226     {
227 	updateSettings();
228 	nextTime = thisTime+0.5f;
229     }
230 #ifdef USE_ONLINE_UPDATE
231     _onlineUpdateDisplay.update();
232 #endif
233     _prevAngle = _angle;
234     _angle += 10.0f;
235 
236     if( _delayedExit)
237     {
238 	if( !Exit())
239 	{
240 	    turnMenuOff();
241 	}
242 	_delayedExit = false;
243     }
244     list<Selectable*>::iterator i;
245     for( i=_activeSelectables.begin(); i!=_activeSelectables.end(); i++)
246     {
247         (*i)->update();
248     }
249 
250     _prevMouseX = _mouseX;
251     _prevMouseY = _mouseY;
252 
253     if( _showSparks)
254     {
255 	GLBitmapCollection *icons =
256 	    BitmapManagerS::instance()->getBitmap( "bitmaps/menuIcons");
257 	float w = _mouseX + icons->getWidth( _pointer)*0.4f;
258 	float h = _mouseY - icons->getHeight( _pointer)*0.4f;
259 	for( int i=0; i<10; i++)
260 	{
261 	    _burst.newParticle( "Spark", w-i, h+i, 0);
262 	}
263 
264 	_burst.update();
265     }
266 
267     return true;
268 }
269 
draw(void)270 bool MenuManager::draw( void)
271 {
272     if( GameState::context != Context::eMenu) return true;
273 
274     glEnable( GL_LIGHTING);
275     glEnable( GL_DEPTH_TEST);
276 
277     GLfloat light_position[] = { 820.0, 620.0, 500.0, 0.0 };
278     glLightfv(GL_LIGHT0, GL_POSITION, light_position);
279 
280     glPushMatrix();
281     glTranslatef( 820.0, 620.0, 0.0);
282     float iAngle = _prevAngle+(_angle-_prevAngle)*GameState::frameFractionOther;
283     glRotatef(iAngle, 0,1.0,0);
284     _mapleLeaf->draw();
285     glPopMatrix();
286 
287     if( _nextGenShippyCubeMap)
288     {
289 	glPushMatrix();
290 	glTranslatef( 200.0, 620.0, 0.0);
291 	iAngle = _prevAngle+(_angle-_prevAngle)*GameState::frameFractionOther;
292 	glRotatef(iAngle/10.0, 0.0,1.0,0);
293 	_nextGenShippyCubeMap->bind();
294 	_nextGenShippyCubeMap->enable();
295 	_nextGenShippy->draw();
296 	_nextGenShippyCubeMap->disable();
297 	glPopMatrix();
298     }
299 
300     glDisable( GL_DEPTH_TEST);
301     glDisable( GL_LIGHTING);
302 #ifdef USE_ONLINE_UPDATE
303     _onlineUpdateDisplay.draw();
304 #endif
305     GLBitmapCollection *menuBoard =
306         BitmapManagerS::instance()->getBitmap( "bitmaps/menuBoard");
307     menuBoard->bind();
308 
309     float orthoHeight = 750.0;
310     float orthoWidth = 1000.0;
311 
312     float boardScaleX = 800.0/256.0;
313     float boardScaleY = 2.0;
314     _boardOffset.x = (int)((orthoWidth - (float)menuBoard->getWidth(_board)*boardScaleX) / 2.0);
315     _boardOffset.y = (int)((orthoHeight - (float)menuBoard->getHeight(_board)*boardScaleY) / 5.0);
316 
317     glColor4f(1.0, 1.0, 1.0, 0.7f);
318     glEnable(GL_TEXTURE_2D);
319     menuBoard->Draw( _board, _boardOffset.x, _boardOffset.y, boardScaleX, boardScaleY);
320     glDisable(GL_TEXTURE_2D);
321 
322     TiXmlElement* elem = _currentMenu->ToElement();
323     const char *val = elem->Attribute("Text");
324     if( val)
325     {
326 	GLBitmapFont &fontWhite =
327 	  *(FontManagerS::instance()->getFont( "bitmaps/menuWhite"));
328 	glColor4f(1.0, 1.0, 1.0, 1.0);
329         fontWhite.DrawString( val, _boardOffset.x + 21, _boardOffset.y + 477, 0.5, 0.5);
330     }
331 
332     list<Selectable*>::iterator i;
333     _boardOffset.x=0;
334     _boardOffset.y=0;
335     for( i=_activeSelectables.begin(); i!=_activeSelectables.end(); i++)
336     {
337         (*i)->draw( _boardOffset);
338     }
339 
340     glEnable(GL_TEXTURE_2D);
341     if( _showSparks)
342     {
343 	_burst.draw();
344     }
345 
346     GLBitmapCollection *icons =
347         BitmapManagerS::instance()->getBitmap( "bitmaps/menuIcons");
348     icons->bind();
349     glColor4f(1.0, 1.0, 1.0, 1.0);
350     float gf = GameState::frameFractionOther;
351     float interpMouseX = _prevMouseX + (_mouseX-_prevMouseX)*gf;
352     float interpMouseY = _prevMouseY + (_mouseY-_prevMouseY)*gf;
353     icons->Draw( _pointer, interpMouseX, interpMouseY, 0.5, 0.5);
354     glDisable(GL_TEXTURE_2D);
355 
356     return true;
357 }
358 
reload(void)359 void MenuManager::reload( void)
360 {
361     _nextGenShippyCubeMap->reload();
362 }
363 
turnMenuOn(void)364 void MenuManager::turnMenuOn( void)
365 {
366     AudioS::instance()->playSample( "sounds/humm.wav");
367     _prevContext = GameState::context;
368     GameState::context = Context::eMenu;
369 
370     //ask input system to forward all input to us
371     InputS::instance()->enableInterceptor( this);
372     GameState::stopwatch.pause();
373 
374     _currentSelectable = _activeSelectables.end();
375     Selectable::reset();
376 }
377 
turnMenuOff(void)378 void MenuManager::turnMenuOff( void)
379 {
380     if( _prevContext == Context::eUnknown) return;
381 
382     AudioS::instance()->playSample( "sounds/humm.wav");
383     GameState::context = _prevContext;
384 
385     //don't want anymore input
386     InputS::instance()->disableInterceptor();
387 
388     if( GameState::context == Context::eInGame)
389     {
390 	GameState::stopwatch.start();
391     }
392 }
393 
input(const Trigger & trigger,const bool & isDown)394 void MenuManager::input( const Trigger &trigger, const bool &isDown)
395 {
396     Trigger t = trigger;
397     if( isDown)
398     {
399 	switch( trigger.type)
400 	{
401 	    case eKeyTrigger:
402 		switch( trigger.data1)
403 		{
404 		    case SDLK_RETURN:
405 			Enter();
406 			break;
407 
408 		    case SDLK_ESCAPE:
409 			if( !Exit())
410 			{
411 			    turnMenuOff();
412 			}
413 			break;
414 
415 		    case SDLK_UP:
416 			Up();
417 			break;
418 		    case SDLK_DOWN:
419 			Down();
420 			break;
421 
422 		    case SDLK_F12:
423 			VideoS::instance()->takeSnapshot();
424 			break;
425 
426 		    default:
427 			break;
428 		}
429 		break;
430 
431 	    case eButtonTrigger:
432 		_prevMouseX = _mouseX;
433 	    case eMotionTrigger:
434                 {
435                     _prevMouseX = _mouseX;
436                     _prevMouseY = _mouseY;
437 
438                     _mouseX += (trigger.fData1*10.0f);
439                     _mouseY += (trigger.fData2*10.0f);
440 
441                     Clamp( _mouseX, 0.0f, 1000.0f);
442                     Clamp( _mouseY, 0.0f, 750.0f);
443 
444                     activateSelectableUnderMouse();
445                 }
446 		break;
447 
448 	    default:
449 		break;
450 	}
451     }
452 
453     //put the absolute mouse position in to trigger
454     t.fData1 = _mouseX;
455     t.fData2 = _mouseY;
456 
457     if( _currentSelectable != _activeSelectables.end())
458     {
459         (*_currentSelectable)->input( t, isDown, _boardOffset);
460     }
461 }
462 
activateSelectableUnderMouse(void)463 void MenuManager::activateSelectableUnderMouse( void)
464 {
465     list<Selectable*>::iterator i;
466     for( i=_activeSelectables.begin(); i!=_activeSelectables.end(); i++)
467     {
468         Selectable *sel = *i;
469         if( !sel)
470         {
471             LOG_ERROR << "Selectable is 0 !!!" << endl;
472             continue;
473         }
474         const BoundingBox &r = sel->getInputBox();
475         if( (_mouseX >= (r.min.x+_boardOffset.x)) &&
476            (_mouseX <= (r.max.x+_boardOffset.x)) &&
477            (_mouseY >= (r.min.y+_boardOffset.y)) &&
478            (_mouseY <= (r.max.y+_boardOffset.y)) )
479         {
480             sel->activate();
481             break;
482         }
483     }
484 }
485 
Down(void)486 void MenuManager::Down( void)
487 {
488     XTRACE();
489     if( _currentSelectable == _activeSelectables.end()) return;
490 
491     _currentSelectable++;
492     if( _currentSelectable == _activeSelectables.end())
493     {
494 	_currentSelectable = _activeSelectables.begin();
495     }
496 
497     (*_currentSelectable)->activate();
498 }
499 
Up(void)500 void MenuManager::Up( void)
501 {
502     XTRACE();
503     if( _currentSelectable == _activeSelectables.end()) return;
504 
505     if( _currentSelectable == _activeSelectables.begin())
506     {
507 	_currentSelectable = _activeSelectables.end();
508     }
509     _currentSelectable--;
510 
511     (*_currentSelectable)->activate();
512 }
513 
Goto(Selectable * s)514 void MenuManager::Goto( Selectable *s)
515 {
516     list<Selectable*>::iterator i;
517     for( i=_activeSelectables.begin(); i!=_activeSelectables.end(); i++)
518     {
519         if( (*i) == s)
520 	{
521 //	    LOG_INFO << "Goto found match" << endl;
522 	    break;
523 	}
524     }
525     _currentSelectable = i;
526 }
527 
Enter(void)528 void MenuManager::Enter( void)
529 {
530     XTRACE();
531     (*_currentSelectable)->select();
532 }
533 
Exit(bool delayed)534 bool MenuManager::Exit( bool delayed)
535 {
536     XTRACE();
537     if( delayed)
538     {
539 	//while iterating over the selectables we dont want to loadMenuLevel
540 	_delayedExit = true;
541 	return true;
542     }
543     if( _currentMenu != _topMenu)
544     {
545 	_currentMenu = _currentMenu->Parent();
546 	loadMenuLevel();
547 	AudioS::instance()->playSample( "sounds/humm.wav");
548 	return true;
549     }
550 
551     //at the top level menu
552     return false;
553 }
554