1 ///////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2010 by The Allacrost Project
3 //                         All Rights Reserved
4 //
5 // This code is licensed under the GNU GPL version 2. It is free software
6 // and you may modify it and/or redistribute it under the terms of this license.
7 // See http://www.gnu.org/copyleft/gpl.html for details.
8 ///////////////////////////////////////////////////////////////////////////////
9 
10 /** ****************************************************************************
11 *** \file    map.cpp
12 *** \author  Tyler Olsen, roots@allacrost.org
13 *** \brief   Source file for map mode interface.
14 *** ***************************************************************************/
15 
16 // Allacrost engines
17 #include "audio.h"
18 #include "script.h"
19 #include "input.h"
20 #include "system.h"
21 
22 // Allacrost globals
23 #include "global.h"
24 
25 // Other mode headers
26 #include "menu.h"
27 #include "pause.h"
28 
29 // Local map mode headers
30 #include "map.h"
31 #include "map_dialogue.h"
32 #include "map_events.h"
33 #include "map_objects.h"
34 #include "map_sprites.h"
35 #include "map_tiles.h"
36 #include "map_treasure.h"
37 #include "map_zones.h"
38 
39 using namespace std;
40 using namespace hoa_utils;
41 using namespace hoa_audio;
42 using namespace hoa_input;
43 using namespace hoa_mode_manager;
44 using namespace hoa_script;
45 using namespace hoa_system;
46 using namespace hoa_video;
47 using namespace hoa_global;
48 using namespace hoa_menu;
49 using namespace hoa_pause;
50 using namespace hoa_map::private_map;
51 
52 namespace hoa_map {
53 
54 // Initialize static class variables
55 MapMode* MapMode::_current_instance = NULL;
56 
57 // ****************************************************************************
58 // ********** MapMode Public Class Methods
59 // ****************************************************************************
60 
MapMode(string filename)61 MapMode::MapMode(string filename) :
62 	GameMode(),
63 	_map_filename(filename),
64 	_map_tablespace(""),
65 	_map_event_group(NULL),
66 	_tile_supervisor(NULL),
67 	_object_supervisor(NULL),
68 	_event_supervisor(NULL),
69 	_dialogue_supervisor(NULL),
70 	_treasure_supervisor(NULL),
71 	_camera(NULL),
72 	_num_map_contexts(0),
73 	_current_context(MAP_CONTEXT_01),
74 	_ignore_input(false),
75 	_run_disabled(false),
76 	_run_forever(false),
77 	_run_stamina(10000),
78 	_show_dialogue_icons(true),
79 	_current_track(0)
80 {
81 	mode_type = MODE_MANAGER_MAP_MODE;
82 	_current_instance = this;
83 
84 	ResetState();
85 	PushState(STATE_EXPLORE);
86 
87 	// Create the event group name by modifying the filename to consists only of alphanumeric characters and underscores
88 	// This will make it a valid identifier name in Lua syntax
89 	string event_group_name = _map_filename;
90 	std::replace(event_group_name.begin(), event_group_name.end(), '/', '_');
91 	std::replace(event_group_name.begin(), event_group_name.end(), '.', '_');
92 
93 	if (GlobalManager->DoesEventGroupExist(event_group_name) == false) {
94 		GlobalManager->AddNewEventGroup(event_group_name);
95 	}
96 	_map_event_group = GlobalManager->GetEventGroup(event_group_name);
97 
98 	_tile_supervisor = new TileSupervisor();
99 	_object_supervisor = new ObjectSupervisor();
100 	_event_supervisor = new EventSupervisor();
101 	_dialogue_supervisor = new DialogueSupervisor();
102 	_treasure_supervisor = new TreasureSupervisor();
103 
104 	_intro_timer.Initialize(7000, 0);
105 	_intro_timer.EnableAutoUpdate(this);
106 
107 	// TODO: Load the map data in a seperate thread
108 	_Load();
109 
110 	// Load miscellaneous map graphics
111 	vector<uint32> timings(16, 100); // holds the timing data for the new dialogue animation; 16 frames at 100ms each
112 	_dialogue_icon.SetDimensions(2, 2);
113 	if (_dialogue_icon.LoadFromFrameSize("img/misc/dialogue_icon.png", timings, 32, 32) == false)
114 		IF_PRINT_WARNING(MAP_DEBUG) << "failed to load the new dialogue icon image" << endl;
115 
116 	if (_stamina_bar_background.Load("img/misc/stamina_bar_background.png", 227, 24) == false)
117 		IF_PRINT_WARNING(MAP_DEBUG) << "failed to load the the stamina bar background image" << endl;
118 
119 	if (_stamina_bar_infinite_overlay.Load("img/misc/stamina_bar_infinite_overlay.png", 227, 24) == false)
120 		IF_PRINT_WARNING(MAP_DEBUG) << "failed to load the the stamina bar infinite overlay image" << endl;
121 }
122 
123 
124 
~MapMode()125 MapMode::~MapMode() {
126 	for (uint32 i = 0; i < _music.size(); i++)
127 		_music[i].FreeAudio();
128 	_music.clear();
129 
130 	for (uint32 i = 0; i < _sounds.size(); i++)
131 		_sounds[i].FreeAudio();
132 	_sounds.clear();
133 
134 	for (uint32 i = 0; i < _enemies.size(); i++)
135 		delete(_enemies[i]);
136 	_enemies.clear();
137 
138 	delete(_tile_supervisor);
139 	delete(_object_supervisor);
140 	delete(_event_supervisor);
141 	delete(_dialogue_supervisor);
142 	delete(_treasure_supervisor);
143 
144 	_map_script.CloseFile();
145 }
146 
147 
148 
Reset()149 void MapMode::Reset() {
150 	// Reset video engine context properties
151 	VideoManager->SetCoordSys(0.0f, SCREEN_COLS, SCREEN_ROWS, 0.0f);
152 	VideoManager->SetDrawFlags(VIDEO_X_CENTER, VIDEO_Y_BOTTOM, 0);
153 
154 	// Set the active instance pointer to this map
155 	MapMode::_current_instance = this;
156 
157 	// Make the map location known globally to other code that may need to know this information
158 	GlobalManager->SetLocation(MakeUnicodeString(_map_filename), _location_graphic.GetFilename());
159 
160 	if (_music.size() > _current_track && _music[_current_track].GetState() != AUDIO_STATE_PLAYING) {
161 		_music[_current_track].Play();
162 	}
163 
164 	_intro_timer.Run();
165 }
166 
167 
168 
Update()169 void MapMode::Update() {
170 	// TODO: we need to detect if a battle is about to occur and if so, fade the screen gradually from
171 	// map mode into the battle
172 	_dialogue_icon.Update();
173 
174 	// TODO: instead of doing this every frame, see if it can be done only when the _camera pointer is modified
175 	_current_context = _camera->GetContext();
176 
177 	// Process quit and pause events unconditional to the state of map mode
178 	if (InputManager->QuitPress() == true) {
179 		ModeManager->Push(new PauseMode(true));
180 		return;
181 	}
182 	else if (InputManager->PausePress() == true) {
183 		ModeManager->Push(new PauseMode(false));
184 		return;
185 	}
186 
187 	// ---------- (1) Call the map script's update function
188 	if (_update_function)
189 		ScriptCallFunction<void>(_update_function);
190 
191 	// ---------- (2) Update all animated tile images
192 	_tile_supervisor->Update();
193 	_object_supervisor->Update();
194 	_object_supervisor->SortObjects();
195 
196 	// ---------- (3) Update the active state of the map
197 	switch (CurrentState()) {
198 		case STATE_EXPLORE:
199 			_UpdateExplore();
200 			break;
201 		case STATE_SCENE:
202 			break;
203 		case STATE_DIALOGUE:
204 			_UpdateDialogue();
205 			_dialogue_supervisor->Update();
206 			break;
207 		case STATE_TREASURE:
208 			_camera->moving = false;
209 			_treasure_supervisor->Update();
210 			break;
211 		default:
212 			IF_PRINT_WARNING(MAP_DEBUG) << "map was set in an unknown state: " << CurrentState() << endl;
213 			ResetState();
214 			break;
215 	}
216 
217 	// ---------- (4) Update all active map events
218 	_event_supervisor->Update();
219 } // void MapMode::Update()
220 
221 
222 
Draw()223 void MapMode::Draw() {
224 	_CalculateMapFrame();
225 
226 	if (_draw_function)
227 		ScriptCallFunction<void>(_draw_function);
228 	else
229 		_DrawMapLayers();
230 
231 	_DrawGUI();
232 	if (CurrentState() == STATE_DIALOGUE) {
233 		_dialogue_supervisor->Draw();
234 	}
235 } // void MapMode::_Draw()
236 
237 
238 
ResetState()239 void MapMode::ResetState() {
240 	_state_stack.clear();
241 	_state_stack.push_back(STATE_INVALID);
242 }
243 
244 
245 
PushState(MAP_STATE state)246 void MapMode::PushState(MAP_STATE state) {
247 	_state_stack.push_back(state);
248 }
249 
250 
251 
PopState()252 void MapMode::PopState() {
253 	_state_stack.pop_back();
254 	if (_state_stack.empty() == true) {
255 		IF_PRINT_WARNING(MAP_DEBUG) << "stack was empty after operation, reseting state stack" << endl;
256 		_state_stack.push_back(STATE_INVALID);
257 	}
258 }
259 
260 
261 
CurrentState()262 MAP_STATE MapMode::CurrentState() {
263 	if (_state_stack.empty() == true) {
264 		IF_PRINT_WARNING(MAP_DEBUG) << "stack was empty, reseting state stack" << endl;
265 		_state_stack.push_back(STATE_INVALID);
266 	}
267 	return _state_stack.back();
268 }
269 
270 
271 
AddGroundObject(MapObject * obj)272 void MapMode::AddGroundObject(MapObject *obj) {
273 	_object_supervisor->_ground_objects.push_back(obj);
274 	_object_supervisor->_all_objects.insert(make_pair(obj->object_id, obj));
275 }
276 
277 
278 
AddPassObject(MapObject * obj)279 void MapMode::AddPassObject(MapObject *obj) {
280 	_object_supervisor->_pass_objects.push_back(obj);
281 	_object_supervisor->_all_objects.insert(make_pair(obj->object_id, obj));
282 }
283 
284 
285 
AddSkyObject(MapObject * obj)286 void MapMode::AddSkyObject(MapObject *obj) {
287 	_object_supervisor->_sky_objects.push_back(obj);
288 	_object_supervisor->_all_objects.insert(make_pair(obj->object_id, obj));
289 }
290 
291 
292 
AddZone(MapZone * zone)293 void MapMode::AddZone(MapZone *zone) {
294 	_object_supervisor->_zones.push_back(zone);
295 }
296 
297 
298 
IsEnemyLoaded(uint32 id) const299 bool MapMode::IsEnemyLoaded(uint32 id) const {
300 	for (uint32 i = 0; i < _enemies.size(); i++) {
301 		if (_enemies[i]->GetID() == id) {
302 			return true;
303 		}
304 	}
305 	return false;
306 }
307 
PlayMusic(uint32 track_num)308 void MapMode::PlayMusic(uint32 track_num) {
309 	_music[_current_track].Stop();
310 	_current_track = track_num;
311 	_music[_current_track].Play();
312 }
313 
314 // ****************************************************************************
315 // ********** MapMode Private Class Methods
316 // ****************************************************************************
317 
_Load()318 void MapMode::_Load() {
319 	// ---------- (1) Open map script file and read in the basic map properties and tile definitions
320 	if (_map_script.OpenFile(_map_filename) == false) {
321 		return;
322 	}
323 
324 	// Determine the map's tablespacename and then open it. The tablespace is the name of the map file without
325 	// file extension or path information (for example, 'dat/maps/demo.lua' has a tablespace name of 'demo').
326 	int32 period = _map_filename.find(".");
327 	int32 last_slash = _map_filename.find_last_of("/");
328 	_map_tablespace = _map_filename.substr(last_slash + 1, period - (last_slash + 1));
329 	_map_script.OpenTable(_map_tablespace);
330 
331 	// Read the number of map contexts, the name of the map, and load the location graphic image
332 	_num_map_contexts = _map_script.ReadUInt("num_map_contexts");
333 	_map_name = MakeUnicodeString(_map_script.ReadString("map_name"));
334 	if (_location_graphic.Load("img/menus/locations/" + _map_script.ReadString("location_filename")) == false) {
335 		PRINT_ERROR << "failed to load location graphic image: " << _location_graphic.GetFilename() << endl;
336 	}
337 
338 	// ---------- (2) Instruct the supervisor classes to perform their portion of the load operation
339 	_tile_supervisor->Load(_map_script, this);
340 	_object_supervisor->Load(_map_script);
341 
342 	// ---------- (3) Load map sounds and music
343 	vector<string> sound_filenames;
344 	_map_script.ReadStringVector("sound_filenames", sound_filenames);
345 
346 	for (uint32 i = 0; i < sound_filenames.size(); i++) {
347 		_sounds.push_back(SoundDescriptor());
348 		if (_sounds.back().LoadAudio(sound_filenames[i]) == false) {
349 			PRINT_ERROR << "failed to load map sound: " << sound_filenames[i] << endl;
350 			return;
351 		}
352 	}
353 
354 	vector<string> music_filenames;
355 	_map_script.ReadStringVector("music_filenames", music_filenames);
356 	_music.resize(music_filenames.size(), MusicDescriptor());
357 	for (uint32 i = 0; i < music_filenames.size(); i++) {
358 		if (_music[i].LoadAudio(music_filenames[i]) == false) {
359 			PRINT_ERROR << "failed to load map music: " << music_filenames[i] << endl;
360 			return;
361 		}
362 	}
363 
364 	// ---------- (4) Create and store all enemies that may appear on this map
365 	vector<int32> enemy_ids;
366 	_map_script.ReadIntVector("enemy_ids", enemy_ids);
367 	for (uint32 i = 0; i < enemy_ids.size(); i++) {
368 		_enemies.push_back(new GlobalEnemy(enemy_ids[i]));
369 	}
370 
371 	// ---------- (5) Call the map script's custom load function and get a reference to all other script function pointers
372 	ScriptObject map_table(luabind::from_stack(_map_script.GetLuaState(), hoa_script::private_script::STACK_TOP));
373 	ScriptObject function = map_table["Load"];
374 	ScriptCallFunction<void>(function, this);
375 
376 	_update_function = _map_script.ReadFunctionPointer("Update");
377 	_draw_function = _map_script.ReadFunctionPointer("Draw");
378 
379 	// ---------- (6) Prepare all sprites with dialogue
380 	// This is done at this stage because the map script's load function creates the sprite and dialogue objects. Only after
381 	// both sets are created can we determine which sprites have active dialogue.
382 
383 	// TODO: Need to figure out a new function appropriate for this code?
384 	// TEMP: The line below is very bad to do, but is necessary for the UpdateDialogueStatus function to work correctly
385 	_current_instance = this;
386 	for (map<uint16, MapObject*>::iterator i = _object_supervisor->_all_objects.begin(); i != _object_supervisor->_all_objects.end(); i++) {
387 		if (i->second->GetType() == SPRITE_TYPE) {
388 			MapSprite* sprite = dynamic_cast<MapSprite*>(i->second);
389 			sprite->UpdateDialogueStatus();
390 		}
391 	}
392 
393 	_map_script.CloseAllTables();
394 } // void MapMode::_Load()
395 
396 
397 
_UpdateExplore()398 void MapMode::_UpdateExplore() {
399 	// First go to menu mode if the user requested it
400 	if (InputManager->MenuPress()) {
401 		MenuMode *MM = new MenuMode(_map_name, _location_graphic.GetFilename());
402 		ModeManager->Push(MM);
403 		return;
404 	}
405 
406 	// Update the running state of the camera object. Check if the player wishes to continue running and if so,
407 	// update the stamina value if the operation is permitted
408 	_camera->is_running = false;
409 	if (_run_disabled == false && InputManager->CancelState() == true &&
410 		(InputManager->UpState() || InputManager->DownState() || InputManager->LeftState() || InputManager->RightState()))
411 	{
412 		if (_run_forever) {
413 			_camera->is_running = true;
414 		}
415 		else if (_run_stamina > SystemManager->GetUpdateTime() * 2) {
416 			_run_stamina -= (SystemManager->GetUpdateTime() * 2);
417 			_camera->is_running = true;
418 		}
419 		else {
420 			_run_stamina = 0;
421 		}
422 	}
423 	// Regenerate the stamina at 1/2 the consumption rate
424 	else if (_run_stamina < 10000) {
425 		_run_stamina += SystemManager->GetUpdateTime();
426 		if (_run_stamina > 10000)
427 			_run_stamina = 10000;
428 	}
429 
430 	// If the user requested a confirm event, check if there is a nearby object that the player may interact with
431 	// Interactions are currently limited to dialogue with sprites and opening of treasures
432 	if (InputManager->ConfirmPress()) {
433 		MapObject* obj = _object_supervisor->FindNearestObject(_camera);
434 
435 		if (obj && (obj->GetType() == SPRITE_TYPE)) {
436 			MapSprite *sp = reinterpret_cast<MapSprite*>(obj);
437 
438 			if (sp->HasAvailableDialogue()) {
439 				_dialogue_supervisor->BeginDialogue(sp);
440 				return;
441 			}
442 		}
443 		else if (obj && obj->GetType() == TREASURE_TYPE) {
444 			MapTreasure* treasure = reinterpret_cast<MapTreasure*>(obj);
445 
446 			if (treasure->IsEmpty() == false) {
447 			    _camera->moving = false;
448 				treasure->Open();
449 			}
450 		}
451 	}
452 
453 	// Detect movement input from the user
454 	if (InputManager->UpState() || InputManager->DownState() || InputManager->LeftState() || InputManager->RightState()) {
455 		_camera->moving = true;
456 	}
457 	else {
458 		_camera->moving = false;
459 	}
460 
461 	// Determine the direction of movement. Priority of movement is given to: up, down, left, right.
462 	// In the case of diagonal movement, the direction that the sprite should face also needs to be deduced.
463 	if (_camera->moving == true) {
464 		if (InputManager->UpState())
465 		{
466 			if (InputManager->LeftState())
467 				_camera->SetDirection(MOVING_NORTHWEST);
468 			else if (InputManager->RightState())
469 				_camera->SetDirection(MOVING_NORTHEAST);
470 			else
471 				_camera->SetDirection(NORTH);
472 		}
473 		else if (InputManager->DownState())
474 		{
475 			if (InputManager->LeftState())
476 				_camera->SetDirection(MOVING_SOUTHWEST);
477 			else if (InputManager->RightState())
478 				_camera->SetDirection(MOVING_SOUTHEAST);
479 			else
480 				_camera->SetDirection(SOUTH);
481 		}
482 		else if (InputManager->LeftState()) {
483 			_camera->SetDirection(WEST);
484 		}
485 		else if (InputManager->RightState()) {
486 			_camera->SetDirection(EAST);
487 		}
488 	} // if (_camera->moving == true)
489 } // void MapMode::_UpdateExplore()
490 
491 
492 
_UpdateDialogue()493 void MapMode::_UpdateDialogue() {
494 	// Update the running state of the camera object. Check if the player wishes to continue running and if so,
495 	// update the stamina value if the operation is permitted
496 	_camera->is_running = false;
497 	if (_run_disabled == false && InputManager->CancelState() == true &&
498 		(InputManager->UpState() || InputManager->DownState() || InputManager->LeftState() || InputManager->RightState()))
499 	{
500 		if (_run_forever) {
501 			_camera->is_running = true;
502 		}
503 		else if (_run_stamina > SystemManager->GetUpdateTime() * 2) {
504 			_run_stamina -= (SystemManager->GetUpdateTime() * 2);
505 			_camera->is_running = true;
506 		}
507 		else {
508 			_run_stamina = 0;
509 		}
510 	}
511 	// Regenerate the stamina at 1/2 the consumption rate
512 	else if (_run_stamina < 10000) {
513 		_run_stamina += SystemManager->GetUpdateTime();
514 		if (_run_stamina > 10000)
515 			_run_stamina = 10000;
516 	}
517 
518     // TODO: If we want to allow moving during dialogue, this is the place to do it
519 	// Detect movement input from the user
520 //	if (InputManager->UpState() || InputManager->DownState() || InputManager->LeftState() || InputManager->RightState()) {
521 //		_camera->moving = true;
522 //	}
523 //	else {
524 		_camera->moving = false;
525 //	}
526 
527 	// Determine the direction of movement. Priority of movement is given to: up, down, left, right.
528 	// In the case of diagonal movement, the direction that the sprite should face also needs to be deduced.
529 	if (_camera->moving == true) {
530 		if (InputManager->UpState())
531 		{
532 			if (InputManager->LeftState())
533 				_camera->SetDirection(MOVING_NORTHWEST);
534 			else if (InputManager->RightState())
535 				_camera->SetDirection(MOVING_NORTHEAST);
536 			else
537 				_camera->SetDirection(NORTH);
538 		}
539 		else if (InputManager->DownState())
540 		{
541 			if (InputManager->LeftState())
542 				_camera->SetDirection(MOVING_SOUTHWEST);
543 			else if (InputManager->RightState())
544 				_camera->SetDirection(MOVING_SOUTHEAST);
545 			else
546 				_camera->SetDirection(SOUTH);
547 		}
548 		else if (InputManager->LeftState()) {
549 			_camera->SetDirection(WEST);
550 		}
551 		else if (InputManager->RightState()) {
552 			_camera->SetDirection(EAST);
553 		}
554 	} // if (_camera->moving == true)
555 } // void MapMode::_UpdateDialogue()
556 
557 
558 
_CalculateMapFrame()559 void MapMode::_CalculateMapFrame() {
560 	// ---------- (1) Determine the center position coordinates for the camera
561 	float camera_x, camera_y; // Holds the final X, Y coordinates of the camera
562 	float x_pixel_length, y_pixel_length; // The X and Y length values that coorespond to a single pixel in the current coodinate system
563 	float rounded_x_offset, rounded_y_offset; // The X and Y position offsets of the camera, rounded to perfectly align on a pixel boundary
564 
565 	// TODO: the call to GetPixelSize() will return the same result every time so long as the coordinate system did not change. If we never
566 	// change the coordinate system in map mode, then this should be done only once and the calculated values should be saved for re-use.
567 	// However, we've discussed the possiblity of adding a zoom feature to maps, in which case we need to continually re-calculate the pixel size
568 	VideoManager->GetPixelSize(x_pixel_length, y_pixel_length);
569 	rounded_x_offset = FloorToFloatMultiple(_camera->x_offset, x_pixel_length);
570 	rounded_y_offset = FloorToFloatMultiple(_camera->y_offset, y_pixel_length);
571 	camera_x = static_cast<float>(_camera->x_position) + rounded_x_offset;
572 	camera_y = static_cast<float>(_camera->y_position) + rounded_y_offset;
573 
574 	// ---------- (2) Calculate all four screen edges and determine
575 	// Determine the draw coordinates of the top left corner using the camera's current position
576 	_map_frame.tile_x_start = 1.0f - rounded_x_offset;
577 	if (IsOddNumber(_camera->x_position))
578 		_map_frame.tile_x_start -= 1.0f;
579 
580 	_map_frame.tile_y_start = 2.0f - rounded_y_offset;
581 	if (IsOddNumber(_camera->y_position))
582 		_map_frame.tile_y_start -= 1.0f;
583 
584 	// The starting  row and column of tiles to draw is determined by the map camera's position
585 	_map_frame.starting_col = (_camera->x_position / 2) - HALF_TILE_COLS;
586 	_map_frame.starting_row = (_camera->y_position / 2) - HALF_TILE_ROWS;
587 
588 	_map_frame.screen_edges.top    = camera_y - HALF_SCREEN_ROWS;
589 	_map_frame.screen_edges.bottom = camera_y + HALF_SCREEN_ROWS;
590 	_map_frame.screen_edges.left   = camera_x - HALF_SCREEN_COLS;
591 	_map_frame.screen_edges.right  = camera_x + HALF_SCREEN_COLS;
592 
593 	// ---------- (3) Check for boundary conditions and re-adjust as necessary so we don't draw outside the map area
594 
595 	// Usually the map centers on the camera's position, but when the camera becomes too close to
596 	// the edges of the map, we need to modify the drawing properties of the frame.
597 
598 	// Camera exceeds the left boundary of the map
599 	if (_map_frame.starting_col < 0) {
600 		_map_frame.starting_col = 0;
601 		_map_frame.tile_x_start = 1.0f;
602 		_map_frame.screen_edges.left = 0.0f;
603 		_map_frame.screen_edges.right = SCREEN_COLS;
604 	}
605 	// Camera exceeds the right boundary of the map
606 	else if (_map_frame.starting_col + TILE_COLS >= _tile_supervisor->_num_tile_cols) {
607 		_map_frame.starting_col = static_cast<int16>(_tile_supervisor->_num_tile_cols - TILE_COLS);
608 		_map_frame.tile_x_start = 1.0f;
609 		_map_frame.screen_edges.right = static_cast<float>(_object_supervisor->_num_grid_cols);
610 		_map_frame.screen_edges.left = _map_frame.screen_edges.right - SCREEN_COLS;
611 	}
612 
613 	// Camera exceeds the top boundary of the map
614 	if (_map_frame.starting_row < 0) {
615 		_map_frame.starting_row = 0;
616 		_map_frame.tile_y_start = 2.0f;
617 		_map_frame.screen_edges.top = 0.0f;
618 		_map_frame.screen_edges.bottom = SCREEN_ROWS;
619 	}
620 	// Camera exceeds the bottom boundary of the map
621 	else if (_map_frame.starting_row + TILE_ROWS >= _tile_supervisor->_num_tile_rows) {
622 		_map_frame.starting_row = static_cast<int16>(_tile_supervisor->_num_tile_rows - TILE_ROWS);
623 		_map_frame.tile_y_start = 2.0f;
624 		_map_frame.screen_edges.bottom = static_cast<float>(_object_supervisor->_num_grid_rows);
625 		_map_frame.screen_edges.top = _map_frame.screen_edges.bottom - SCREEN_ROWS;
626 	}
627 
628 	// ---------- (4) Determine the number of rows and columns of tiles that need to be drawn
629 
630 	// When the tile images align perfectly with the screen, we can afford to draw one less row or column of tiles
631 	if (IsFloatInRange(_map_frame.tile_x_start, 0.999f, 1.001f)) {
632 		_map_frame.num_draw_cols = TILE_COLS;
633 	}
634 	else {
635 		_map_frame.num_draw_cols = TILE_COLS + 1;
636 	}
637 	if (IsFloatInRange(_map_frame.tile_y_start, 1.999f, 2.001f)) {
638 		_map_frame.num_draw_rows = TILE_ROWS;
639 	}
640 	else {
641 		_map_frame.num_draw_rows = TILE_ROWS + 1;
642 	}
643 
644 	// Comment this out to print out debugging info about each map frame that is drawn
645 // 	printf("--- MAP DRAW INFO ---\n");
646 // 	printf("Starting row, col: [%d, %d]\n", _map_frame.starting_row, _map_frame.starting_col);
647 // 	printf("# draw rows, cols: [%d, %d]\n", _map_frame.num_draw_rows, _map_frame.num_draw_cols);
648 // 	printf("Camera position:   [%f, %f]\n", camera_x, camera_y);
649 // 	printf("Tile draw start:   [%f, %f]\n", _map_frame.tile_x_start, _map_frame.tile_y_start);
650 // 	printf("Edges (T,B,L,R):   [%f, %f, %f, %f]\n", _map_frame.screen_edges.top, _map_frame.screen_edges.bottom,
651 // 		_map_frame.screen_edges.left, _map_frame.screen_edges.right);
652 } // void MapMode::_CalculateMapFrame()
653 
654 
655 
_DrawMapLayers()656 void MapMode::_DrawMapLayers() {
657 	VideoManager->SetCoordSys(0.0f, SCREEN_COLS, SCREEN_ROWS, 0.0f);
658 
659 	_tile_supervisor->DrawLowerLayer(&_map_frame);
660 	_tile_supervisor->DrawMiddleLayer(&_map_frame);
661 
662 	_object_supervisor->DrawGroundObjects(&_map_frame, false); // First draw pass of ground objects
663 	_object_supervisor->DrawPassObjects(&_map_frame);
664 	_object_supervisor->DrawGroundObjects(&_map_frame, true); // Second draw pass of ground objects
665 
666 	_tile_supervisor->DrawUpperLayer(&_map_frame);
667 
668 	_object_supervisor->DrawSkyObjects(&_map_frame);
669 } // void MapMode::_DrawMapLayers()
670 
671 
672 
_DrawGUI()673 void MapMode::_DrawGUI() {
674 	// TODO: figure out what this color represents and create an approximate name for it
675 	const Color unknown(0.0196f, 0.207f, 0.0196f, 1.0f);
676 	const Color lighter_green(0.419f, 0.894f, 0.0f, 1.0f);
677 	const Color light_green(0.0196f, 0.207f, 0.0196f, 1.0f);
678 	const Color medium_green(0.0509f, 0.556f, 0.0509f, 1.0f);
679 	const Color darkish_green(0.352f, 0.4f, 0.352f, 1.0f);
680 	const Color dark_green(0.0196f, 0.207f, 0.0196f, 1.0f);
681 	const Color bright_yellow(0.937f, 1.0f, 0.725f, 1.0f);
682 
683 	// ---------- (1) Draw the introductory location name and graphic if necessary
684 	if (_intro_timer.IsFinished() == false) {
685 		uint32 time = _intro_timer.GetTimeExpired();
686 
687 		Color blend(1.0f, 1.0f, 1.0f, 1.0f);
688 		if (time < 2000) { // Fade in
689 			blend.SetAlpha((static_cast<float>(time) / 2000.0f));
690 		}
691 		else if (time > 5000) { // Fade out
692 			blend.SetAlpha(1.0f - static_cast<float>(time - 5000) / 2000.0f);
693 		}
694 
695 		VideoManager->PushState();
696 		VideoManager->SetCoordSys(0.0f, 1024.0f, 768.0f, 0.0f);
697 		VideoManager->SetDrawFlags(VIDEO_X_CENTER, VIDEO_Y_CENTER, 0);
698 		VideoManager->Move(512.0f, 100.0f);
699 		_location_graphic.Draw(blend);
700 		VideoManager->MoveRelative(0.0f, -80.0f);
701 		VideoManager->Text()->Draw(_map_name, TextStyle("title24", blend, VIDEO_TEXT_SHADOW_DARK));
702 		VideoManager->PopState();
703 	}
704 
705 	// ---------- (2) Draw the stamina bar in the lower right corner
706 	// TODO: the code in this section needs better comments to explain what each coloring step is doing
707 	float fill_size = static_cast<float>(_run_stamina) / 10000.0f;
708 
709 	VideoManager->PushState();
710 	VideoManager->SetCoordSys(0.0f, 1024.0f, 768.0f, 0.0f);
711 	VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
712 
713 	// Draw the background image
714 	VideoManager->Move(780, 747);
715 	_stamina_bar_background.Draw();
716 	VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_NO_BLEND, 0);
717 
718 	// Draw the base color of the bar
719 	VideoManager->Move(800, 740);
720 	VideoManager->DrawRectangle(200 * fill_size, 10, unknown);
721 
722 	// Shade the bar with a faux lighting effect
723 	VideoManager->Move(800,739);
724 	VideoManager->DrawRectangle(200 * fill_size, 2, dark_green);
725 	VideoManager->Move(800, 737);
726 	VideoManager->DrawRectangle(200 * fill_size, 7, darkish_green);
727 
728 	// Only do this if the bar is at least 4 pixels long
729 	if ((200 * fill_size) >= 4) {
730 		VideoManager->Move(801, 739);
731 		VideoManager->DrawRectangle((200 * fill_size) -2, 1, darkish_green);
732 
733 		VideoManager->Move(801, 738);
734 		VideoManager->DrawRectangle(1, 2, medium_green);
735 		VideoManager->Move(800 + (fill_size * 200 - 2), 738); // Automatically reposition to be at moving endcap
736 		VideoManager->DrawRectangle(1, 2, medium_green);
737 	}
738 
739 	VideoManager->Move(800, 736);
740 	VideoManager->DrawRectangle(200 * fill_size, 5, medium_green);
741 
742 	// Only do this if the bar is at least 4 pixels long
743 	if ((200 * fill_size) >= 4) {
744 		VideoManager->Move(801, 735);
745 		VideoManager->DrawRectangle(1, 1, lighter_green);
746 		VideoManager->Move(800 + (fill_size * 200 - 2), 735); // automatically reposition to be at moving endcap
747 		VideoManager->DrawRectangle(1, 1, lighter_green);
748 		VideoManager->Move(800, 734);
749 		VideoManager->DrawRectangle(200 * fill_size, 2, lighter_green);
750 	}
751 
752 	// Only do this if the bar is at least 6 pixels long
753 	if ((200 * fill_size) >= 6) {
754 		VideoManager->Move(802, 733);
755 		VideoManager->DrawRectangle((200 * fill_size) - 4, 1, bright_yellow);
756 	}
757 
758 	if (_run_forever) { // Draw the infinity symbol over the stamina bar
759 		VideoManager->SetDrawFlags(VIDEO_BLEND, 0);
760 		VideoManager->Move(780, 747);
761 		_stamina_bar_infinite_overlay.Draw();
762 	}
763 
764 	VideoManager->PopState();
765 
766 	// ---------- (3) Draw the treasure menu if necessary
767 	if (_treasure_supervisor->IsActive() == true)
768 		_treasure_supervisor->Draw();
769 } // void MapMode::_DrawGUI()
770 
771 } // namespace hoa_map
772 
773