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