1 ///////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2011 by The Allacrost Project
3 //            Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 //                         All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See http://www.gnu.org/copyleft/gpl.html for details.
9 ///////////////////////////////////////////////////////////////////////////////
10 
11 /** ****************************************************************************
12 *** \file    dialogue.cpp
13 *** \author  Tyler Olsen, roots@allacrost.org
14 *** \author  Yohann Ferreira, yohann ferreira orange fr
15 *** \brief   Source file for common dialogue code.
16 *** ***************************************************************************/
17 
18 #include "dialogue.h"
19 
20 #include "common/global/global.h"
21 
22 #include "common.h"
23 #include "common/gui/gui.h"
24 
25 #include "engine/video/video.h"
26 #include "engine/input.h"
27 #include "engine/mode_manager.h"
28 
29 using namespace vt_system;
30 using namespace vt_utils;
31 using namespace vt_video;
32 using namespace vt_gui;
33 
34 namespace vt_common
35 {
36 
37 ///////////////////////////////////////////////////////////////////////////////
38 // Dialogue Class Functions
39 ///////////////////////////////////////////////////////////////////////////////
40 
Dialogue(const std::string & dialogue_id)41 Dialogue::Dialogue(const std::string& dialogue_id) :
42     _dialogue_id(dialogue_id),
43     _line_count(0)
44 {}
45 
~Dialogue()46 Dialogue::~Dialogue()
47 {
48     for(uint32_t i = 0; i < _options.size(); i++) {
49         if(_options[i] != nullptr) {
50             delete _options[i];
51             _options[i] = nullptr;
52         }
53     }
54 }
55 
Create(DialogueSupervisor * dialogue_supervisor,const std::string & dialogue_id)56 Dialogue* Dialogue::Create(DialogueSupervisor* dialogue_supervisor, const std::string& dialogue_id)
57 {
58     Dialogue* dialogue = new Dialogue(dialogue_id);
59 
60     // As the object is created through scripting, we register it
61     // to the common dialogue supervisor.
62     // NOTE: We don't do it in the constructor as SpriteDialogue are children
63     // of this class
64     dialogue_supervisor->AddDialogue(dialogue);
65 
66     return dialogue;
67 }
68 
AddLine(const std::string & text)69 void Dialogue::AddLine(const std::string& text)
70 {
71     AddLine(text, DIALOGUE_NEXT_LINE);
72 }
73 
AddLine(const std::string & text,int32_t next_line)74 void Dialogue::AddLine(const std::string& text, int32_t next_line)
75 {
76     ++_line_count;
77     _text.push_back(MakeUnicodeString(text));
78     _next_lines.push_back(next_line);
79     _display_times.push_back(DIALOGUE_NO_TIMER);
80     _options.push_back(nullptr);
81 }
82 
AddLine(const std::string & text,const std::string & speaker_id)83 void Dialogue::AddLine(const std::string& text, const std::string& speaker_id)
84 {
85     AddLineTimed(text, speaker_id, DIALOGUE_NEXT_LINE, DIALOGUE_NO_TIMER);
86 }
87 
AddLine(const std::string & text,const std::string & speaker_id,int32_t next_line)88 void Dialogue::AddLine(const std::string& text, const std::string& speaker_id, int32_t next_line)
89 {
90     AddLineTimed(text, speaker_id, next_line, DIALOGUE_NO_TIMER);
91 }
92 
AddLineTimed(const std::string & text,uint32_t display_time)93 void Dialogue::AddLineTimed(const std::string& text, uint32_t display_time)
94 {
95     AddLineTimed(text, DIALOGUE_NEXT_LINE, display_time);
96 }
97 
AddLineTimed(const std::string & text,int32_t next_line,uint32_t display_time)98 void Dialogue::AddLineTimed(const std::string& text, int32_t next_line, uint32_t display_time)
99 {
100     ++_line_count;
101     _text.push_back(MakeUnicodeString(text));
102     _next_lines.push_back(next_line);
103     _display_times.push_back(display_time);
104     _options.push_back(nullptr);
105 }
106 
AddLineTimed(const std::string & text,const std::string & speaker_id,uint32_t display_time)107 void Dialogue::AddLineTimed(const std::string& text, const std::string& speaker_id, uint32_t display_time)
108 {
109     AddLineTimed(text, speaker_id, DIALOGUE_NEXT_LINE, display_time);
110 }
111 
AddLineTimed(const std::string & text,const std::string & speaker_id,int32_t next_line,uint32_t display_time)112 void Dialogue::AddLineTimed(const std::string &text, const std::string& speaker_id, int32_t next_line, uint32_t display_time)
113 {
114     AddLineTimed(text, next_line, display_time);
115     _speakers.push_back(speaker_id);
116 }
117 
AddOption(const std::string & text)118 void Dialogue::AddOption(const std::string& text)
119 {
120     AddOption(text, DIALOGUE_NEXT_LINE);
121 }
122 
AddOption(const std::string & text,int32_t next_line)123 void Dialogue::AddOption(const std::string& text, int32_t next_line)
124 {
125     if(_line_count == 0) {
126         IF_PRINT_WARNING(COMMON_DEBUG) << "Attempted to add an option to a dialogue with no lines" << std::endl;
127         return;
128     }
129 
130     uint32_t current_line = _line_count - 1;
131 
132     // If the line the options will be added to currently has no options, create a new instance of the CommonDialogueOptions class to store the options in.
133     if(_options[current_line] == nullptr) {
134         _options[current_line] = new DialogueOptions();
135     }
136     _options[current_line]->AddOption(text, next_line);
137 }
138 
Validate()139 bool Dialogue::Validate()
140 {
141     // Valid dialogues need to have at least one line
142     if(_line_count == 0) {
143         IF_PRINT_WARNING(COMMON_DEBUG) << "Validation failed for dialogue #" << _dialogue_id << ": no lines" << std::endl;
144         return false;
145     }
146 
147     // Check that all next lines with positive values point to valid indeces
148     for(uint32_t i = 0; i < _line_count; ++i) {
149         if((_next_lines[i] >= 0) && (static_cast<uint32_t>(_next_lines[i]) >= _line_count)) {
150             IF_PRINT_WARNING(COMMON_DEBUG) << "Validation failed for dialogue #" << _dialogue_id
151                                            << ": next line referred to an invalid line index: " << _next_lines[i] << std::endl;
152             return false;
153         }
154 
155         // If this line has options, we have to examine the next line argument for each option as well
156         if(_options[i] != 0) {
157             if(_options[i]->GetNumberOptions() == 0) {
158                 PRINT_WARNING << "Validation failed for dialogue #" << _dialogue_id
159                               << ": line had options declared but no options defined" << std::endl;
160                 return false;
161             }
162 
163             for(uint32_t j = 0; j < _options[i]->GetNumberOptions(); ++j) {
164                 int32_t option_next_line = _options[i]->GetOptionNextLine(j);
165                 if((option_next_line >= 0) && (static_cast<uint32_t>(option_next_line) >= _line_count)) {
166                     IF_PRINT_WARNING(COMMON_DEBUG) << "Validation failed for dialogue #" << _dialogue_id
167                                                    << ": option's next line referred to an invalid line index: "
168                                                    << option_next_line << std::endl;
169                     return false;
170                 }
171             }
172         }
173     }
174 
175     // Construct containers that hold all unique sprite and event ids for this dialogue
176     std::set<std::string> speaker_ids;
177     for(uint32_t i = 0; i < _line_count; ++i) {
178         speaker_ids.insert(_speakers[i]);
179     }
180 
181     // Check that all sprites and events referenced by the dialogue exist
182     // FIXME: Move the check into the dialogue supervisor
183     /*
184     for(std::set<uint32_t>::iterator i = speaker_ids.begin(); i != speaker_ids.end(); ++i) {
185         if(BattleMode::CurrentInstance()->GetDialogueSupervisor()->GetSpeaker(*i) == nullptr) {
186             PRINT_WARNING << "Validation failed for dialogue #" << _dialogue_id
187                           << ": dialogue referenced invalid speaker with id: " << *i << std::endl;
188             return false;
189         }
190     }
191     */
192 
193     return true;
194 }
195 
196 ///////////////////////////////////////////////////////////////////////////////
197 // DialogueOptions Functions
198 ///////////////////////////////////////////////////////////////////////////////
199 
AddOption(const std::string & text)200 void DialogueOptions::AddOption(const std::string& text)
201 {
202     AddOption(text, DIALOGUE_NEXT_LINE);
203 }
204 
AddOption(const std::string & text,int32_t next_line)205 void DialogueOptions::AddOption(const std::string& text, int32_t next_line)
206 {
207     _text.push_back(MakeUnicodeString(text));
208     _next_lines.push_back(next_line);
209 }
210 
211 ///////////////////////////////////////////////////////////////////////////////
212 // DialogueWindow class methods
213 ///////////////////////////////////////////////////////////////////////////////
214 
DialogueWindow()215 DialogueWindow::DialogueWindow() :
216     _pos(512.0f, 512.0f),
217     _indicator_symbol(DIALOGUE_NO_INDICATOR),
218     _blink_time(0),
219     _blink_state(true),
220     _portrait_image(nullptr)
221 {
222     //TODO: Makes this part of the themes
223     if(_parchment_image.Load("data/gui/black_sleet_parch.png") == false)
224         PRINT_ERROR << "failed to load dialogue image: " << _parchment_image.GetFilename() << std::endl;
225 
226     if(_nameplate_image.Load("data/gui/dialogue_nameplate.png") == false)
227         PRINT_ERROR << "failed to load dialogue image: " << _nameplate_image.GetFilename() << std::endl;
228 
229     if(_next_line_image.Load("data/gui/dialogue_cont_arrow.png") == false)
230         PRINT_ERROR << "failed to load dialogue image: " << _next_line_image.GetFilename() << std::endl;
231 
232     if(_last_line_image.Load("data/gui/dialogue_last_ind.png") == false)
233         PRINT_ERROR << "failed to load dialogue image: " << _last_line_image.GetFilename() << std::endl;
234 
235     VideoManager->PushState();
236     VideoManager->SetStandardCoordSys();
237 
238     _display_textbox.SetDisplaySpeed(SystemManager->GetMessageSpeed());
239     _display_textbox.SetPosition(260.0f, 596.0f);
240     _display_textbox.SetDimensions(640.0f, 126.0f);
241     _display_textbox.SetTextStyle(TextStyle("text20", Color::black, VIDEO_TEXT_SHADOW_LIGHT));
242     _display_textbox.SetDisplayMode(VIDEO_TEXT_FADECHAR);
243     _display_textbox.SetAlignment(VIDEO_X_CENTER, VIDEO_Y_CENTER);
244     _display_textbox.SetTextAlignment(VIDEO_X_LEFT, VIDEO_Y_TOP);
245 
246     _display_optionbox.SetPosition(300.0f, 630.0f);
247     _display_optionbox.SetDimensions(640.0f, 90.0f, 1, 255, 1, 4);
248     _display_optionbox.SetOptionAlignment(VIDEO_X_LEFT, VIDEO_Y_CENTER);
249     _display_optionbox.SetTextStyle(TextStyle("title20", Color::black, VIDEO_TEXT_SHADOW_LIGHT));
250     _display_optionbox.SetSelectMode(VIDEO_SELECT_SINGLE);
251     _display_optionbox.SetCursorOffset(-55.0f, -25.0f);
252     _display_optionbox.SetVerticalWrapMode(VIDEO_WRAP_MODE_NONE);
253 
254     _name_text.SetStyle(TextStyle("title22", Color::black, VIDEO_TEXT_SHADOW_LIGHT));
255 
256     VideoManager->PopState();
257 }
258 
SetPosition(float pos_x,float pos_y)259 void DialogueWindow::SetPosition(float pos_x, float pos_y)
260 {
261     _pos.x = pos_x;
262     _pos.y = pos_y;
263 
264     _display_textbox.SetPosition(_pos.x + 80.0f, _pos.y - 110.0f);
265     _display_optionbox.SetPosition(_pos.x - 220.0f, _pos.y - 112.0f);
266 }
267 
Clear()268 void DialogueWindow::Clear()
269 {
270     _display_textbox.ClearText();
271     _display_optionbox.ClearOptions();
272     _name_text.Clear();
273     _portrait_image = nullptr;
274 }
275 
Draw()276 void DialogueWindow::Draw()
277 {
278     VideoManager->PushState();
279     VideoManager->SetStandardCoordSys();
280     VideoManager->SetDrawFlags(VIDEO_X_CENTER, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
281 
282     VideoManager->Move(_pos.x, _pos.y);
283     _parchment_image.Draw();
284 
285     VideoManager->MoveRelative(-370.0f, -45.0f);
286     if(_portrait_image)
287         _portrait_image->Draw();
288 
289     if (!_name_text.GetString().empty()) {
290         VideoManager->MoveRelative(0.0f, 30.0f);
291         _nameplate_image.Draw();
292 
293         VideoManager->MoveRelative(0.0f, -6.0f);
294         _name_text.Draw();
295     }
296     else {
297         VideoManager->MoveRelative(0.0f, 24.0f);
298     }
299 
300     VideoManager->MoveRelative(0.0f, 5.0f);
301     _blink_time += SystemManager->GetUpdateTime();
302     if(_blink_time > 500) {
303         _blink_time -= 500;
304         _blink_state = _blink_state ? false : true;
305     }
306 
307     if(_indicator_symbol == DIALOGUE_NEXT_INDICATOR && _blink_state) {
308         VideoManager->MoveRelative(830.0f, 0.0f);
309         _next_line_image.Draw();
310     } else if(_indicator_symbol == DIALOGUE_LAST_INDICATOR && _blink_state) {
311         VideoManager->MoveRelative(830.0f, 0.0f);
312         _last_line_image.Draw();
313     }
314 
315     _display_textbox.Draw();
316     _display_optionbox.Draw();
317 
318     VideoManager->PopState();
319 }
320 
321 ///////////////////////////////////////////////////////////////////////////////
322 // DialogueSupervisor class methods
323 ///////////////////////////////////////////////////////////////////////////////
324 
DialogueSupervisor()325 DialogueSupervisor::DialogueSupervisor() :
326     _state(DIALOGUE_STATE_INACTIVE),
327     _current_dialogue(nullptr),
328     _current_options(nullptr),
329     _line_timer(),
330     _line_counter(0),
331     _dialogue_window()
332 {
333     _dialogue_window.SetPosition(512.0f, 170.0f);
334 }
335 
~DialogueSupervisor()336 DialogueSupervisor::~DialogueSupervisor()
337 {
338     // Delete all dialogues
339     for(std::map<std::string, Dialogue *>::iterator it = _dialogues.begin(); it != _dialogues.end(); ++it) {
340         delete it->second;
341     }
342     _dialogues.clear();
343     _speakers.clear();
344 }
345 
SetDialoguePosition(float x,float y)346 void DialogueSupervisor::SetDialoguePosition(float x, float y)
347 {
348     _dialogue_window.SetPosition(x, y);
349 }
350 
Update()351 void DialogueSupervisor::Update()
352 {
353     if(_current_dialogue == nullptr)
354         return;
355 
356     _line_timer.Update();
357 
358     switch(_state) {
359     case DIALOGUE_STATE_LINE:
360         _UpdateLine();
361         break;
362     case DIALOGUE_STATE_OPTION:
363         _UpdateOptions();
364         break;
365     default:
366         PRINT_WARNING << "Dialogue supervisor was in an unknown state: " << _state << std::endl;
367         _state = DIALOGUE_STATE_LINE;
368         break;
369     }
370 }
371 
Draw()372 void DialogueSupervisor::Draw()
373 {
374     _dialogue_window.Draw();
375 }
376 
AddDialogue(Dialogue * dialogue)377 void DialogueSupervisor::AddDialogue(Dialogue* dialogue)
378 {
379     if(dialogue == nullptr) {
380         IF_PRINT_WARNING(COMMON_DEBUG) << "function received nullptr argument" << std::endl;
381         return;
382     }
383 
384     if(GetDialogue(dialogue->GetDialogueID()) != nullptr) {
385         IF_PRINT_WARNING(COMMON_DEBUG) << "a dialogue was already registered with this ID: " << dialogue->GetDialogueID() << std::endl;
386         delete dialogue;
387         return;
388     }
389     _dialogues.insert(std::make_pair(dialogue->GetDialogueID(), dialogue));
390 }
391 
AddSpeaker(const std::string & speaker_id,const std::string & name,const std::string & portrait)392 void DialogueSupervisor::AddSpeaker(const std::string& speaker_id, const std::string& name, const std::string& portrait)
393 {
394     if(_speakers.find(speaker_id) != _speakers.end()) {
395         PRINT_WARNING << "Speaker already existed with requested id: " << speaker_id << std::endl;
396         return;
397     }
398 
399     Speaker new_speaker;
400     new_speaker.name = MakeUnicodeString(name);
401     if(!portrait.empty()) {
402         if(!new_speaker.portrait.Load(portrait)) {
403             IF_PRINT_WARNING(COMMON_DEBUG) << "invalid image filename for new portrait: " << portrait << std::endl;
404         }
405         // Make sure the portrait doesn't go over the screen edge.
406         if(new_speaker.portrait.GetHeight() > 130.0f)
407             new_speaker.portrait.SetHeightKeepRatio(130.0f);
408     }
409 
410     _speakers[speaker_id] = new_speaker;
411 }
412 
ChangeSpeakerName(const std::string & speaker_id,const std::string & name)413 void DialogueSupervisor::ChangeSpeakerName(const std::string& speaker_id, const std::string& name)
414 {
415     std::map<std::string, Speaker>::iterator it = _speakers.find(speaker_id);
416     if(it == _speakers.end()) {
417         PRINT_WARNING << "No speaker found with requested id: " << speaker_id << std::endl;
418         return;
419     }
420 
421     it->second.name = MakeUnicodeString(name);
422 
423     if(_current_dialogue != nullptr) {
424         if(_current_dialogue->GetLineSpeaker(_line_counter) == speaker_id) {
425             _dialogue_window.GetNameText().SetText(it->second.name);
426         }
427     }
428 }
429 
ChangeSpeakerPortrait(const std::string & speaker_id,const std::string & portrait)430 void DialogueSupervisor::ChangeSpeakerPortrait(const std::string& speaker_id, const std::string& portrait)
431 {
432     if(portrait.empty())
433         return;
434 
435     std::map<std::string, Speaker>::iterator it = _speakers.find(speaker_id);
436     if(it == _speakers.end()) {
437         PRINT_WARNING << "No speaker found with requested id: " << speaker_id << std::endl;
438         return;
439     }
440 
441     // Note: we don't have to also check whether or not the active portrait on the dialogue window needs to be
442     // updated since the dialogue window simply retains a pointer to the image object. We only update the StillImage
443     // class object contents in this function, not its address.
444     if(!it->second.portrait.Load(portrait)) {
445         PRINT_WARNING << "Invalid image filename for new portrait: " << portrait << std::endl;
446         return;
447     }
448 }
449 
StartDialogue(const std::string & dialogue_id)450 void DialogueSupervisor::StartDialogue(const std::string& dialogue_id)
451 {
452     Dialogue *dialogue = GetDialogue(dialogue_id);
453 
454     if(dialogue == nullptr) {
455         PRINT_WARNING << "Could not begin dialogue because none existed for id: " << dialogue_id << std::endl;
456         return;
457     }
458 
459     if(_current_dialogue != nullptr) {
460         PRINT_WARNING << "beginning a new dialogue while another dialogue is still active" << std::endl;
461     }
462 
463     _line_counter = 0;
464     _current_dialogue = dialogue;
465     _current_options = _current_dialogue->GetLineOptions(_line_counter);
466 
467     _BeginLine();
468 }
469 
EndDialogue()470 void DialogueSupervisor::EndDialogue()
471 {
472     if(_current_dialogue == nullptr) {
473         IF_PRINT_WARNING(COMMON_DEBUG) << "Tried to end a dialogue when none was active" << std::endl;
474         return;
475     }
476 
477     _current_dialogue = nullptr;
478     _current_options = nullptr;
479     _line_timer.Finish();
480 }
481 
ForceNextLine()482 void DialogueSupervisor::ForceNextLine()
483 {
484     if(_current_dialogue == nullptr) {
485         IF_PRINT_WARNING(COMMON_DEBUG) << "function called when no dialogue was active" << std::endl;
486         return;
487     }
488 
489     _EndLine();
490 }
491 
GetDialogue(const std::string & dialogue_id)492 Dialogue* DialogueSupervisor::GetDialogue(const std::string& dialogue_id)
493 {
494     std::map<std::string, Dialogue *>::iterator it = _dialogues.find(dialogue_id);
495     if(it == _dialogues.end())
496         return nullptr;
497 
498     return it->second;
499 }
500 
GetSpeaker(const std::string & speaker_id)501 Speaker* DialogueSupervisor::GetSpeaker(const std::string& speaker_id)
502 {
503     std::map<std::string, Speaker>::iterator it = _speakers.find(speaker_id);
504     if(it != _speakers.end())
505         return &(it->second);
506 
507     return nullptr;
508 }
509 
_UpdateLine()510 void DialogueSupervisor::_UpdateLine()
511 {
512     _dialogue_window.GetDisplayTextBox().Update();
513 
514     if(_current_options != nullptr) {
515         if(_dialogue_window.GetDisplayTextBox().IsFinished()) {
516             _state = DIALOGUE_STATE_OPTION;
517             return;
518         }
519     }
520 
521     // If the line has a valid display time and the timer is finished, move on to the next line
522     if((_line_timer.GetDuration() > 0) && (_line_timer.IsFinished())) {
523         _EndLine();
524         return;
525     }
526 
527     // Set the correct indicator
528     if(_current_options || !_dialogue_window.GetDisplayTextBox().IsFinished()) {
529         _dialogue_window.SetIndicator(DIALOGUE_NO_INDICATOR);
530     } else if(_line_counter == _current_dialogue->GetLineCount() - 1) {
531         if (_dialogue_window.SetIndicator(DIALOGUE_LAST_INDICATOR))
532             vt_global::GlobalManager->Media().PlaySound("line_complete");
533     } else {
534         if (_dialogue_window.SetIndicator(DIALOGUE_NEXT_INDICATOR))
535             vt_global::GlobalManager->Media().PlaySound("line_complete");
536     }
537 
538     // If the current mode is accepting input, we can handle it.
539     if (!vt_mode_manager::ModeManager->GetTop()->AcceptUserInputInDialogues())
540         return;
541 
542     if(vt_input::InputManager->ConfirmPress()) {
543         // If the line is not yet finished displaying, display the rest of the text
544         if(_dialogue_window.GetDisplayTextBox().IsFinished() == false) {
545             _dialogue_window.GetDisplayTextBox().ForceFinish();
546         }
547         // Proceed to option selection if the line has options
548         else if(_current_options != nullptr) {
549             _state = DIALOGUE_STATE_OPTION;
550         } else {
551             _EndLine();
552         }
553     }
554 }
555 
_UpdateOptions()556 void DialogueSupervisor::_UpdateOptions()
557 {
558     _dialogue_window.GetDisplayOptionBox().Update();
559     vt_global::GlobalMedia& media = vt_global::GlobalManager->Media();
560 
561     if(vt_input::InputManager->ConfirmPress()) {
562         _dialogue_window.GetDisplayOptionBox().InputConfirm();
563         media.PlaySound("confirm");
564         _EndLine();
565     }
566 
567     else if(vt_input::InputManager->UpPress()) {
568         _dialogue_window.GetDisplayOptionBox().InputUp();
569         media.PlaySound("bump");
570     }
571 
572     else if(vt_input::InputManager->DownPress()) {
573         _dialogue_window.GetDisplayOptionBox().InputDown();
574         media.PlaySound("bump");
575     }
576 }
577 
_BeginLine()578 void DialogueSupervisor::_BeginLine()
579 {
580     _state = DIALOGUE_STATE_LINE;
581     _current_options = _current_dialogue->GetLineOptions(_line_counter);
582 
583     // Initialize the line timer
584     if(_current_dialogue->GetLineDisplayTime(_line_counter) >= 0) {
585         _line_timer.Initialize(_current_dialogue->GetLineDisplayTime(_line_counter));
586         _line_timer.Run();
587     }
588     // If the line has no timer specified, set the line time to zero and put the timer in the finished state
589     else {
590         _line_timer.Initialize(0);
591         _line_timer.Finish();
592     }
593 
594     // Setup the text and graphics for the dialogue window
595     _dialogue_window.Clear();
596     _dialogue_window.GetDisplayTextBox().SetDisplayText(_current_dialogue->GetLineText(_line_counter));
597     if(_current_options != nullptr) {
598         for(uint32_t i = 0; i < _current_options->GetNumberOptions(); ++i) {
599             _dialogue_window.GetDisplayOptionBox().AddOption(_current_options->GetOptionText(i));
600         }
601 
602         _dialogue_window.GetDisplayOptionBox().SetSelection(0);
603     }
604 
605     Speaker* line_speaker = GetSpeaker(_current_dialogue->GetLineSpeaker(_line_counter));
606     if(line_speaker == nullptr) {
607         IF_PRINT_WARNING(COMMON_DEBUG) << "dialogue #" << _current_dialogue->GetDialogueID()
608                                        << " referenced a speaker that did not exist with id: " << _current_dialogue->GetLineSpeaker(_line_counter) << std::endl;
609         _dialogue_window.GetNameText().SetText("");
610         _dialogue_window.SetPortraitImage(nullptr);
611     } else {
612         _dialogue_window.GetNameText().SetText(line_speaker->name);
613         _dialogue_window.SetPortraitImage(&(line_speaker->portrait));
614     }
615 }
616 
_EndLine()617 void DialogueSupervisor::_EndLine()
618 {
619     // Determine the next line to read
620     int32_t next_line = _current_dialogue->GetLineNextLine(_line_counter);
621     // If this line had options, the selected option next line overrides the line's next line that we set above
622     if(_current_options != nullptr) {
623         uint32_t selected_option = _dialogue_window.GetDisplayOptionBox().GetSelection();
624         next_line = _current_options->GetOptionNextLine(selected_option);
625     }
626 
627     // --- Case 1: Explicitly setting the next line. Warn and end the dialogue if the line to move to is invalid
628     if(next_line >= 0) {
629         if(static_cast<uint32_t>(next_line) >= _current_dialogue->GetLineCount()) {
630             IF_PRINT_WARNING(COMMON_DEBUG) << "dialogue #" << _current_dialogue->GetDialogueID()
631                                            << " tried to set dialogue to invalid line. Current/next line values: {" << _line_counter
632                                            << ", " << next_line << "}" << std::endl;
633             next_line = DIALOGUE_END;
634         }
635     }
636     // --- Case 2: Request to incrementing the current line. If we're incrementing past the last line, end the dialogue
637     else if(next_line == DIALOGUE_NEXT_LINE) {
638         next_line = _line_counter + 1;
639         if(static_cast<uint32_t>(next_line) >= _current_dialogue->GetLineCount())
640             next_line = DIALOGUE_END;
641     }
642     // --- Case 3: Request to end the current dialogue
643     else if(next_line == DIALOGUE_END) {
644         // Do nothing
645     }
646     // --- Case 4: Unknown negative value. Warn and end dialogue
647     else {
648         IF_PRINT_WARNING(COMMON_DEBUG) << "dialogue #" << _current_dialogue->GetDialogueID()
649                                        << " unknown next line control value: " << next_line << std::endl;
650         next_line = DIALOGUE_END;
651     }
652 
653     // Now either end the dialogue or move on to the next line
654     if(next_line == DIALOGUE_END) {
655         EndDialogue();
656     } else {
657         _line_counter = next_line;
658         _BeginLine();
659     }
660 }
661 
662 } // namespace vt_common
663