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_dialogue.cpp
12 *** \author  Tyler Olsen, roots@allacrost.org
13 *** \brief   Source file for map mode dialogue.
14 *** ***************************************************************************/
15 
16 // Allacrost utilities
17 #include "utils.h"
18 
19 // Allacrost engines
20 #include "audio.h"
21 #include "input.h"
22 #include "mode_manager.h"
23 
24 // Allacrost globals
25 #include "global.h"
26 
27 // Other game mode headers
28 #include "menu.h"
29 
30 // Local map mode headers
31 #include "map.h"
32 #include "map_dialogue.h"
33 #include "map_events.h"
34 #include "map_objects.h"
35 #include "map_sprites.h"
36 
37 using namespace std;
38 using namespace hoa_utils;
39 using namespace hoa_audio;
40 using namespace hoa_video;
41 using namespace hoa_gui;
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_global;
47 using namespace hoa_menu;
48 
49 namespace hoa_map {
50 
51 namespace private_map {
52 
53 ///////////////////////////////////////////////////////////////////////////////
54 // MapDialogue Class Functions
55 ///////////////////////////////////////////////////////////////////////////////
56 
MapDialogue(uint32 id)57 MapDialogue::MapDialogue(uint32 id) :
58 	_dialogue_id(id),
59 	_times_seen(0),
60 	_max_views(-1),
61 	_line_count(0),
62 	_current_line(0),
63 	_blocked(false),
64 	_save_state(true),
65 	_event_name("")
66 {
67 	// Look up the event for this dialogue to see whether it has already been read before or not
68 	// Either create the event or retrieve the number of times the dialogue has been seen.
69 	_event_name = GetEventName();
70 	GlobalEventGroup& event_group = *(MapMode::CurrentInstance()->GetMapEventGroup());
71 
72 	if (event_group.DoesEventExist(_event_name) == false) {
73 		event_group.AddNewEvent(_event_name, 0);
74 	}
75 	else {
76 		SetTimesSeen(event_group.GetEvent(_event_name));
77 	}
78 }
79 
80 
~MapDialogue()81 MapDialogue::~MapDialogue() {
82 	for (uint32 i = 0; i < _options.size(); i++) {
83 		if (_options[i] != NULL) {
84 			delete _options[i];
85 		}
86 	}
87 }
88 
89 
90 
AddText(std::string text,uint32 speaker_id,int32 next_line,uint32 event,bool display_timer)91 void MapDialogue::AddText(std::string text, uint32 speaker_id, int32 next_line, uint32 event, bool display_timer) {
92 	_text.push_back(MakeUnicodeString(text));
93 	_speakers.push_back(speaker_id);
94 	_next_lines.push_back(next_line);
95 	_options.push_back(NULL);
96 	_events.push_back(event);
97 	_line_count++;
98 
99 	if (display_timer == true) {
100 		// TODO: replace 5000 with a function call that will calculate the display time based on text length and player's speed setting
101 		_display_times.push_back(5000);
102 	}
103 	else {
104 		_display_times.push_back(-1);
105 	}
106 }
107 
108 
109 
AddOption(string text,int32 next_line,uint32 event)110 void MapDialogue::AddOption(string text, int32 next_line, uint32 event) {
111 	int32 current_line = _line_count - 1; // Current line that options will belong to.
112 
113 	// If the line the options will be added to currently has no options, create a new instance of the MapDialogueOptions class to store the options in.
114 	if (_options[current_line] == NULL) {
115 		MapDialogueOptions* option = new MapDialogueOptions();
116 		_options[current_line] = option;
117 	}
118 
119 	_options[current_line]->AddOption(MakeUnicodeString(text), next_line, event);
120 }
121 
122 
123 
ReadNextLine(int32 line)124 bool MapDialogue::ReadNextLine(int32 line) {
125 	bool end_dialogue = false;
126 
127 	// If argument is negative, there is no next line to read so end the dialogue
128 	if (line < 0) {
129 		end_dialogue = true;
130 	}
131 	// End the dialogue in this case as well to avoid crashing
132 	else if (line >= static_cast<int32>(_line_count)) {
133 		IF_PRINT_WARNING(MAP_DEBUG) << "function argument exceeded number of lines in dialogue: " << line << endl;
134 		end_dialogue = true;
135 	}
136 	else {
137 		_current_line = line;
138 	}
139 
140 	if (end_dialogue == true) {
141 		_current_line = 0;
142 		IncrementTimesSeen();
143 		MapMode::CurrentInstance()->GetMapEventGroup()->SetEvent(_event_name, _times_seen);
144 		return false;
145 	}
146 	else {
147 		return true;
148 	}
149 }
150 
151 ///////////////////////////////////////////////////////////////////////////////
152 // MapDialogueOptions Functions
153 ///////////////////////////////////////////////////////////////////////////////
154 
AddOption(ustring text,int32 next_line,uint32 event)155 void MapDialogueOptions::AddOption(ustring text, int32 next_line, uint32 event) {
156 	if (_text.size() >= MAX_OPTIONS) {
157 		IF_PRINT_WARNING(MAP_DEBUG) << "dialogue option box already contains too many options. The new option will not be added." << endl;
158 		return;
159 	}
160 
161 	_text.push_back(text);
162 	_next_lines.push_back(next_line);
163 	_events.push_back(event);
164 }
165 
166 ///////////////////////////////////////////////////////////////////////////////
167 // DialogueWindow class methods
168 ///////////////////////////////////////////////////////////////////////////////
169 
DialogueWindow()170 DialogueWindow::DialogueWindow() {
171 	if (_parchment_image.Load("img/menus/black_sleet_parch.png") == false)
172 		cerr << "MAP ERROR: failed to load image: " << _parchment_image.GetFilename() << endl;
173 
174 	if (_nameplate_image.Load("img/menus/dialogue_nameplate.png") == false)
175 		cerr << "MAP ERROR: failed to load image: " << _nameplate_image.GetFilename() << endl;
176 
177 	VideoManager->PushState();
178 	VideoManager->SetCoordSys(0, 1024, 768, 0);
179 
180 	_display_textbox.SetDisplaySpeed(30);
181 	_display_textbox.SetPosition(260.0f, 596.0f);
182 	_display_textbox.SetDimensions(700.0f, 126.0f);
183 	_display_textbox.SetTextStyle(TextStyle("text20", Color::black, VIDEO_TEXT_SHADOW_LIGHT));
184 	_display_textbox.SetDisplayMode(VIDEO_TEXT_FADECHAR);
185 	_display_textbox.SetAlignment(VIDEO_X_LEFT, VIDEO_Y_TOP);
186 	_display_textbox.SetTextAlignment(VIDEO_X_LEFT, VIDEO_Y_TOP);
187 
188 	_display_options.SetPosition(300.0f, 630.0f);
189 	_display_options.SetDimensions(660.0f, 90.0f, 1, 255, 1, 3);
190 	_display_options.SetOptionAlignment(VIDEO_X_LEFT, VIDEO_Y_CENTER);
191 	_display_options.SetTextStyle(TextStyle("title20", Color::black, VIDEO_TEXT_SHADOW_LIGHT));
192 	_display_options.SetSelectMode(VIDEO_SELECT_SINGLE);
193 	_display_options.SetCursorOffset(-55.0f, -25.0f);
194 	_display_options.SetVerticalWrapMode(VIDEO_WRAP_MODE_NONE);
195 	_display_options.SetSelection(0);
196 
197 	VideoManager->PopState();
198 }
199 
200 
201 
~DialogueWindow()202 DialogueWindow::~DialogueWindow() {
203 	MenuWindow::Destroy();
204 }
205 
206 
207 
Initialize()208 void DialogueWindow::Initialize() {
209 	// FIXME: Should this be here?  I don't think so, but if it does, get it working properly.
210 	// currently, it does nothing except flood the debug output!
211 //	MenuWindow::Show();
212 }
213 
214 
215 
Reset()216 void DialogueWindow::Reset() {
217 //	MenuWindow::Hide();
218 	_display_textbox.ClearText();
219 	_display_options.ClearOptions();
220 }
221 
222 
223 
Draw(ustring * name,StillImage * portrait)224 void DialogueWindow::Draw(ustring* name, StillImage* portrait) {
225 //	MenuWindow::Draw();
226 
227 	// Temporarily change the coordinate system to 1024x768 and draw the contents of the dialogue window
228 	VideoManager->PushState();
229 	VideoManager->SetCoordSys(0.0f, 1024.0f, 768.0f, 0.0f);
230 	VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, 0);
231 
232 	VideoManager->Move(18.0f, 744.0f);
233 	_parchment_image.Draw();
234 
235 // 	VideoManager->Move(47.0f, 726.0f);
236 // 	if (name != NULL)
237 // 		_nameplate_image.Draw();
238 
239 	VideoManager->SetDrawFlags(VIDEO_X_CENTER, VIDEO_Y_BOTTOM, 0);
240 	VideoManager->MoveRelative(120.0f, -20.0f);
241 
242 	if (name != NULL)
243 		VideoManager->Text()->Draw(*name, TextStyle("title22", Color::black, VIDEO_TEXT_SHADOW_LIGHT));
244 
245 	if (portrait != NULL) {
246 		VideoManager->MoveRelative(0.0f, -20.0f);
247 		portrait->Draw();
248 	}
249 
250 	_display_textbox.Draw();
251 	_display_options.Draw();
252 
253 	VideoManager->PopState();
254 }
255 
256 ///////////////////////////////////////////////////////////////////////////////
257 // DialogueSupervisor class methods
258 ///////////////////////////////////////////////////////////////////////////////
259 
DialogueSupervisor()260 DialogueSupervisor::DialogueSupervisor() :
261 	_state(DIALOGUE_STATE_LINE),
262 	_current_dialogue(NULL),
263 	_current_options(NULL),
264 	_line_timer(-1),
265 	_dialogue_window()
266 {}
267 
268 
269 
~DialogueSupervisor()270 DialogueSupervisor::~DialogueSupervisor() {
271 	// Update the times seen count before deleting each dialogue
272 	for (map<uint32, MapDialogue*>::iterator i = _all_dialogues.begin(); i != _all_dialogues.end(); i++) {
273 		MapMode::CurrentInstance()->GetMapEventGroup()->SetEvent(i->second->GetEventName(), i->second->GetTimesSeen());
274 		delete i->second;
275 	}
276 	_all_dialogues.clear();
277 }
278 
279 
280 
AddDialogue(MapDialogue * dialogue)281 void DialogueSupervisor::AddDialogue(MapDialogue* dialogue) {
282 	if (dialogue == NULL) {
283 		IF_PRINT_WARNING(MAP_DEBUG) << "function argument was NULL" << endl;
284 		return;
285 	}
286 
287 	if (GetDialogue(dialogue->GetDialogueID()) != NULL) {
288 		IF_PRINT_WARNING(MAP_DEBUG) << "a dialogue was already registered with this ID: " << dialogue->GetDialogueID() << endl;
289 		delete dialogue;
290 		return;
291 	}
292 	else {
293 		_all_dialogues.insert(make_pair(dialogue->GetDialogueID(), dialogue));
294 	}
295 }
296 
297 
298 
AddSpriteReference(uint32 dialogue_id,uint32 sprite_id)299 void DialogueSupervisor::AddSpriteReference(uint32 dialogue_id, uint32 sprite_id) {
300 	map<uint32, vector<uint32> >::iterator entry = _sprite_references.find(dialogue_id);
301 
302 	if (entry == _sprite_references.end()) {
303 		vector<uint32> new_entry(1, sprite_id);
304 		_sprite_references.insert(make_pair(dialogue_id, new_entry));
305 	}
306 	else {
307 		entry->second.push_back(sprite_id);
308 	}
309 }
310 
311 
312 
BeginDialogue(uint32 dialogue_id)313 void DialogueSupervisor::BeginDialogue(uint32 dialogue_id) {
314 	MapDialogue* dialogue = GetDialogue(dialogue_id);
315 
316 	if (dialogue == NULL) {
317 		IF_PRINT_WARNING(MAP_DEBUG) << "could not begin dialogue because none existed for id# " << dialogue_id << endl;
318 		return;
319 	}
320 
321 	if (_current_dialogue != NULL) {
322 		IF_PRINT_WARNING(MAP_DEBUG) << "beginning a new dialogue while another dialogue is still active" << endl;
323 	}
324 
325 	_current_dialogue = dialogue;
326 	_current_options = _current_dialogue->GetCurrentOptions();
327 	_line_timer = _current_dialogue->GetCurrentTime();
328 	_dialogue_window.Initialize();
329 	_dialogue_window._display_textbox.SetDisplayText(_current_dialogue->GetCurrentText());
330 	MapMode::CurrentInstance()->PushState(STATE_DIALOGUE);
331 }
332 
333 
334 
BeginDialogue(MapSprite * sprite)335 void DialogueSupervisor::BeginDialogue(MapSprite* sprite) {
336 	if (sprite == NULL) {
337 		IF_PRINT_WARNING(MAP_DEBUG) << "NULL argument passed to function" << endl;
338 		return;
339 	}
340 
341 	if (sprite->HasAvailableDialogue() == false) {
342 		IF_PRINT_WARNING(MAP_DEBUG) << "sprite argument had no available dialogue" << endl;
343 		return;
344 	}
345 
346 	MapDialogue* next_dialogue = GetDialogue(sprite->GetNextDialogueID());
347 	if (next_dialogue == NULL) {
348 		IF_PRINT_WARNING(MAP_DEBUG) << "the next dialogue referenced by the sprite argument was invalid" << endl;
349 		return;
350 	}
351 
352 	if (next_dialogue->IsAvailable() == false) {
353 		IF_PRINT_WARNING(MAP_DEBUG) << "the next dialogue referenced by the sprite was not available" << endl;
354 		return;
355 	}
356 
357 	// Prepare the state of the sprite and map camera for the dialogue
358 	sprite->SaveState();
359 	sprite->moving = false;
360 	sprite->SetDirection(CalculateOppositeDirection(MapMode::CurrentInstance()->GetCamera()->GetDirection()));
361 	sprite->IncrementNextDialogue();
362 	// TODO: Is the line below necessary to do? Shouldn't the camera stop on its own (if its pointing to the player's character)?
363 	MapMode::CurrentInstance()->GetCamera()->moving = false;
364 	BeginDialogue(next_dialogue->GetDialogueID());
365 }
366 
367 
368 
EndDialogue()369 void DialogueSupervisor::EndDialogue() {
370 	if (_current_dialogue == NULL) {
371 		IF_PRINT_WARNING(MAP_DEBUG) << "tried to end a dialogue when none was active" << endl;
372 		return;
373 	}
374 
375 	AnnounceDialogueUpdate(_current_dialogue->GetDialogueID());
376 
377 	_dialogue_window.Reset();
378 	_current_dialogue = NULL;
379 	_current_options = NULL;
380 	_line_timer = -1;
381 	MapMode::CurrentInstance()->PopState();
382 }
383 
384 
385 
GetDialogue(uint32 dialogue_id)386 MapDialogue* DialogueSupervisor::GetDialogue(uint32 dialogue_id) {
387 	if (_all_dialogues.find(dialogue_id) != _all_dialogues.end())
388 		return _all_dialogues[dialogue_id];
389 	else
390 		return NULL;
391 }
392 
393 
394 
AnnounceDialogueUpdate(uint32 dialogue_id)395 void DialogueSupervisor::AnnounceDialogueUpdate(uint32 dialogue_id) {
396 	map<uint32, vector<uint32> >::iterator entry = _sprite_references.find(dialogue_id);
397 
398 	// Note that we don't print a warning if no entry was found, because the case where a dialogue exists
399 	// but is not referenced by any sprites is a valid one
400 	if (entry == _sprite_references.end())
401 		return;
402 
403 	// Update the dialogue status of all sprites that reference this dialogue
404 	for (uint32 i = 0; i < entry->second.size(); i++) {
405 		MapSprite* referee = static_cast<MapSprite*>(MapMode::CurrentInstance()->GetObjectSupervisor()->GetObject(entry->second[i]));
406 		if (referee == NULL) {
407 			IF_PRINT_WARNING(MAP_DEBUG) << "map sprite: " << entry->second[i] << " references dialogue: " << dialogue_id << " but sprite object did not exist"<< endl;
408 		}
409 		else {
410 			referee->UpdateDialogueStatus();
411 		}
412 	}
413 }
414 
415 
416 
Update()417 void DialogueSupervisor::Update() {
418 	if (_current_dialogue == NULL) {
419 		IF_PRINT_WARNING(MAP_DEBUG) << "attempted to update dialogue supervisor when no dialogue was active" << endl;
420 		return;
421 	}
422 
423 	switch (_state) {
424 		case DIALOGUE_STATE_LINE:
425 			_UpdateLine();
426 			break;
427 		case DIALOGUE_STATE_OPTION:
428 			_UpdateOptions();
429 			break;
430 		default:
431 			IF_PRINT_WARNING(MAP_DEBUG) << "dialogue supervisor was in an unknown state: " << _state << endl;
432 			_state = DIALOGUE_STATE_LINE;
433 			break;
434 	}
435 
436 	// FIXME: This is disabled to prevent problems where a dialogue is necessary or has other things attached.
437 	// For instance: in the opening map it was possible to cancel the dialogue and be stuck there.
438 	// Possible fix: advancing to a 'necessary part' of the dialogue
439 	// Possible fix: allowing dialogues to be specified as 'non-cancelable'
440 	if (0 && InputManager->CancelPress()) {
441 		_state = DIALOGUE_STATE_LINE;
442 		_RestoreSprites();
443 		EndDialogue();
444 	}
445 
446 } // void DialogueSupervisor::Update()
447 
448 
449 
Draw()450 void DialogueSupervisor::Draw() {
451 	if (_current_dialogue == NULL) {
452 		IF_PRINT_WARNING(MAP_DEBUG) << "attempted to draw dialogue window when no dialogue was active" << endl;
453 		return;
454 	}
455 
456 	// TODO: Check if speaker ID is 0 and if so, call Draw function with NULL arguments
457 	MapSprite* speaker = reinterpret_cast<MapSprite*>(MapMode::CurrentInstance()->GetObjectSupervisor()->GetObject(_current_dialogue->GetCurrentSpeaker()));
458 	_dialogue_window.Draw(&speaker->GetName(), speaker->GetFacePortrait());
459 } // void DialogueSupervisor::Draw()
460 
461 
462 
_UpdateLine()463 void DialogueSupervisor::_UpdateLine() {
464 	_dialogue_window._display_textbox.Update();
465 
466 	// TODO: there is potential for dead-lock here. Lines that have (or do not have) a display time, have player options,
467 	// and/or have the input blocking property set can cause a lock-up.
468 
469 	// Update the display timer if it is enabled for this dialogue
470 	if (_line_timer > 0) {
471 		_line_timer -= SystemManager->GetUpdateTime();
472 
473 		if (_line_timer <= 0) {
474 			if (_current_options != NULL) {
475 				_state = DIALOGUE_STATE_OPTION;
476 				_ConstructOptions();
477 			}
478 			else {
479 				_FinishLine(_current_dialogue->GetCurrentNextLine());
480 			}
481 		}
482 	}
483 
484 	// If this dialogue does not allow user input, we are finished
485 	if (_current_dialogue->IsBlocked() == true)
486 		return;
487 
488 	if (InputManager->ConfirmPress()) {
489 		// If the line is not yet finished displaying, display the rest of the text
490 		if (_dialogue_window._display_textbox.IsFinished() == false) {
491 			_dialogue_window._display_textbox.ForceFinish();
492 		}
493 		// Proceed to option selection if the line has options
494 		else if (_current_dialogue->CurrentLineHasOptions() == true) {
495 			_state = DIALOGUE_STATE_OPTION;
496 			_ConstructOptions();
497 		}
498 		else {
499 			_FinishLine(_current_dialogue->GetCurrentNextLine());
500 		}
501 	}
502 
503 	// TODO: Handle cancel presses to allow backtracking through the dialogue
504 } // void DialogueSupervisor::_UpdateLine()
505 
506 
507 
_UpdateOptions()508 void DialogueSupervisor::_UpdateOptions() {
509 	_dialogue_window._display_options.Update();
510 
511 	// Execute the event for the current selection if applicable, then return the next line of dialogue for this selection
512 	if (InputManager->ConfirmPress()) {
513 		_dialogue_window._display_options.InputConfirm();
514 
515 		int32 selected_option = _dialogue_window._display_options.GetSelection();
516 
517 		if (_current_options->_events[selected_option] != 0) {
518 			MapMode::CurrentInstance()->GetEventSupervisor()->StartEvent(_current_options->_events[selected_option]);
519 		}
520 
521 		_FinishLine(_current_options->_next_lines[selected_option]);
522 	}
523 
524 	// TODO: handle cancel press to return to previous lines
525 
526 	else if (InputManager->UpPress()) {
527 		_dialogue_window._display_options.InputUp();
528 	}
529 
530 	else if (InputManager->DownPress()) {
531 		_dialogue_window._display_options.InputDown();
532 	}
533 } // void DialogueSupervisor::_UpdateOptions()
534 
535 
536 
_ConstructOptions()537 void DialogueSupervisor::_ConstructOptions() {
538 	for (vector<ustring>::iterator i = _current_options->_text.begin(); i != _current_options->_text.end(); i++) {
539 		_dialogue_window._display_options.AddOption(*i);
540 	}
541 	_dialogue_window._display_options.SetSelection(0);
542 }
543 
544 
545 
_FinishLine(int32 next_line)546 void DialogueSupervisor::_FinishLine(int32 next_line) {
547 	_dialogue_window._display_textbox.ClearText();
548 	_dialogue_window._display_options.ClearOptions();
549 	_state = DIALOGUE_STATE_LINE;
550 
551 	// Execute any scripted events that should occur after this line of dialogue has finished
552 	if (_current_dialogue->GetCurrentEvent() != 0) {
553 		MapMode::CurrentInstance()->GetEventSupervisor()->StartEvent(_current_dialogue->GetCurrentEvent());
554 	}
555 
556 	// Check if there are more lines of dialogue and continue on to the next line if available
557 	if (_current_dialogue->ReadNextLine(next_line) == true) {
558 		_current_options = _current_dialogue->GetCurrentOptions();
559 		_line_timer = _current_dialogue->GetCurrentTime();
560 		_dialogue_window._display_textbox.SetDisplayText(_current_dialogue->GetCurrentText());
561 		return;
562 	}
563 
564 	// If this point in the function is reached, the last line of dialogue has ben read
565 	// Restore the status of the sprites that participated in this dialogue if necessary
566 	if (_current_dialogue->IsSaveState()) {
567 		_RestoreSprites();
568 	}
569 
570 	EndDialogue();
571 } // void DialogueSupervisor::_FinishLine()
572 
573 
574 
_RestoreSprites()575 void DialogueSupervisor::_RestoreSprites() {
576 	// We only want to call the RestoreState function *once* for each speaker, so first we have to construct a list of pointers
577 	// for all speakers without duplication (i.e. the case where a speaker spoke more than one line of dialogue).
578 
579 	set<MapSprite*> participants;
580 	for (uint32 i = 0; i < _current_dialogue->GetLineCount(); i++) {
581 		participants.insert(static_cast<MapSprite*>(MapMode::CurrentInstance()->GetObjectSupervisor()->GetObject(_current_dialogue->GetLineSpeaker(i))));
582 	}
583 
584 	for (set<MapSprite*>::iterator i = participants.begin(); i != participants.end(); i++) {
585 		if ((*i)->IsStateSaved() == true)
586 			(*i)->RestoreState();
587 	}
588 }
589 
590 } // namespace private_map
591 
592 } // namespace hoa_map
593