1 /*
2  * This file is part of EasyRPG Player.
3  *
4  * EasyRPG Player is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * EasyRPG Player is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 // Headers
19 #include <cctype>
20 #include <sstream>
21 #include <iterator>
22 
23 #include "compiler.h"
24 #include "window_message.h"
25 #include "game_actors.h"
26 #include "game_map.h"
27 #include "game_message.h"
28 #include "game_party.h"
29 #include "game_system.h"
30 #include "game_variables.h"
31 #include "input.h"
32 #include "output.h"
33 #include "player.h"
34 #include "util_macro.h"
35 #include "game_battle.h"
36 #include "bitmap.h"
37 #include "font.h"
38 #include "cache.h"
39 #include "text.h"
40 
41 // FIXME: Off by 1 bug in window base class
42 constexpr int message_animation_frames = 7;
43 
44 namespace {
45 #if defined(EP_DEBUG_MESSAGE) || defined(EP_DEBUG_MESSAGE_TEXT)
46 static int frame_offset = 0;
47 
DebugLogResetFrameCounter()48 void DebugLogResetFrameCounter() {
49 	frame_offset = Main_Data::game_system->GetFrameCounter();
50 }
51 #else
52 void DebugLogResetFrameCounter() { }
53 #endif
54 
55 #ifdef EP_DEBUG_MESSAGE
56 template <typename... Args>
DebugLog(const char * fmt,Args &&...args)57 void DebugLog(const char* fmt, Args&&... args) {
58 	int frames = Main_Data::game_system->GetFrameCounter() - frame_offset;
59 	Output::Debug(fmt, frames, std::forward<Args>(args)...);
60 }
61 #else
62 
63 template <typename... Args>
DebugLog(const char *,Args &&...)64 void DebugLog(const char*, Args&&...) { }
65 #endif
66 
67 #ifdef EP_DEBUG_MESSAGE_TEXT
68 template <typename... Args>
DebugLogText(const char * fmt,Args &&...args)69 void DebugLogText(const char* fmt, Args&&... args) {
70 	int frames = Main_Data::game_system->GetFrameCounter() - frame_offset;
71 	Output::Debug(fmt, frames, std::forward<Args>(args)...);
72 }
73 #else
74 
75 template <typename... Args>
DebugLogText(const char *,Args &&...)76 void DebugLogText(const char*, Args&&...) { }
77 #endif
78 } //namespace
79 
80 // C4428 is nonsense
81 #ifdef _MSC_VER
82 #pragma warning (disable : 4428)
83 #endif
84 
Window_Message(int ix,int iy,int iwidth,int iheight)85 Window_Message::Window_Message(int ix, int iy, int iwidth, int iheight) :
86 	Window_Selectable(ix, iy, iwidth, iheight),
87 	number_input_window(new Window_NumberInput(0, 0)),
88 	gold_window(new Window_Gold(232, 0, 88, 32))
89 {
90 	SetContents(Bitmap::Create(width - 16, height - 16));
91 
92 	// 2k3 transparent message boxes
93 	bool msg_transparent = Player::IsRPG2k3()
94 		// if the flag is set ..
95 		&& (lcf::Data::battlecommands.transparency == lcf::rpg::BattleCommands::Transparency_transparent)
96 		// if we're not in battle, or if we are in battle but not using mode A
97 		&& (!Game_Battle::IsBattleRunning() || lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_traditional)
98 		// RPG_RT < 1.11 bug, map messages were not transparent if the battle type was mode A.
99 		&& (Player::IsRPG2k3E() || lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_traditional);
100 	if (msg_transparent) {
101 		SetBackOpacity(160);
102 	}
103 	gold_window->SetBackOpacity(GetBackOpacity());
104 
105 	SetVisible(false);
106 	// Above other windows
107 	SetZ(Priority_Window + 100);
108 
109 	active = true;
110 	SetIndex(-1);
111 	text_color = Font::ColorDefault;
112 
113 	number_input_window->SetVisible(false);
114 
115 	gold_window->SetVisible(false);
116 
117 	Main_Data::game_system->ClearMessageFace();
118 	Game_Message::SetWindow(this);
119 }
120 
~Window_Message()121 Window_Message::~Window_Message() {
122 	if (Game_Message::GetWindow() == this) {
123 		Game_Message::SetWindow(nullptr);
124 	}
125 }
126 
StartMessageProcessing(PendingMessage pm)127 void Window_Message::StartMessageProcessing(PendingMessage pm) {
128 	text.clear();
129 	pending_message = std::move(pm);
130 
131 	if (!IsVisible()) {
132 		DebugLogResetFrameCounter();
133 	}
134 	DebugLog("{}: MSG START");
135 
136 	if (!pending_message.IsActive()) {
137 		return;
138 	}
139 
140 	const auto& lines = pending_message.GetLines();
141 
142 	int num_lines = 0;
143 	auto append = [&](const std::string& line) {
144 		bool force_page_break = (!line.empty() && line.back() == '\f');
145 
146 		text.append(line, 0, line.size() - force_page_break);
147 		if (line.empty() || text.back() != '\n') {
148 			text.push_back('\n');
149 		}
150 		++num_lines;
151 
152 		if (num_lines == GetMaxLinesPerPage() || force_page_break) {
153 			text.push_back('\f');
154 			num_lines = 0;
155 		}
156 	};
157 
158 	if (pending_message.IsWordWrapped()) {
159 		for (const std::string& line : lines) {
160 			/* TODO: don't take commands like \> \< into account when word-wrapping */
161 			Game_Message::WordWrap(
162 					line,
163 					width - 24,
164 					[&](StringView wrapped_line) {
165 						append(std::string(wrapped_line));
166 					}
167 			);
168 		}
169 	} else {
170 		for (const std::string& line : lines) {
171 			append(line);
172 		}
173 	}
174 
175 	if (text.empty() || text.back() != '\f') {
176 		text.push_back('\f');
177 	}
178 
179 	item_max = min(4, pending_message.GetNumChoices());
180 
181 	text_index = text.data();
182 
183 	DebugLog("{}: MSG TEXT \n{}", text);
184 
185 	auto open_frames = (!IsVisible() && !Game_Battle::IsBattleRunning()) ? message_animation_frames : 0;
186 	SetOpenAnimation(open_frames);
187 	DebugLog("{}: MSG START OPEN {}", open_frames);
188 
189 	InsertNewPage();
190 }
191 
OnFinishPage()192 void Window_Message::OnFinishPage() {
193 	DebugLog("{}: FINISH PAGE");
194 
195 	if (pending_message.GetNumChoices() > 0) {
196 		StartChoiceProcessing();
197 	} else if (pending_message.HasNumberInput()) {
198 		StartNumberInputProcessing();
199 	} else if (!kill_page) {
200 		DebugLog("{}: SET PAUSE");
201 		SetPause(true);
202 	}
203 
204 	line_count = 0;
205 	kill_page = false;
206 	line_char_counter = 0;
207 }
208 
StartChoiceProcessing()209 void Window_Message::StartChoiceProcessing() {
210 	SetIndex(0);
211 }
212 
StartNumberInputProcessing()213 void Window_Message::StartNumberInputProcessing() {
214 	number_input_window->SetMaxDigits(pending_message.GetNumberInputDigits());
215 	if (IsFaceEnabled() && !Main_Data::game_system->IsMessageFaceRightPosition()) {
216 		number_input_window->SetX(LeftMargin + FaceSize + RightFaceMargin);
217 	} else {
218 		number_input_window->SetX(x);
219 	}
220 	number_input_window->SetY(y + contents_y - 2);
221 	number_input_window->SetActive(true);
222 	if (IsVisible() && !IsOpeningOrClosing()) {
223 		number_input_window->SetVisible(true);
224 	}
225 	number_input_window->Update();
226 }
227 
ShowGoldWindow()228 void Window_Message::ShowGoldWindow() {
229 	if (Game_Battle::IsBattleRunning()) {
230 		return;
231 	}
232 	if (!gold_window->IsVisible()) {
233 		gold_window->SetY(y == 0 ? SCREEN_TARGET_HEIGHT - 32 : 0);
234 		gold_window->SetOpenAnimation(message_animation_frames);
235 	} else if (gold_window->IsClosing()) {
236 		gold_window->SetOpenAnimation(0);
237 	}
238 	gold_window->Refresh();
239 }
240 
InsertNewPage()241 void Window_Message::InsertNewPage() {
242 	DebugLog("{}: MSG NEW PAGE");
243 	// Cancel pending face requests for async
244 	// Otherwise they render on the wrong page
245 	face_request_ids.clear();
246 
247 	contents->Clear();
248 	SetIndex(-1);
249 	SetPause(false);
250 	number_input_window->SetActive(false);
251 	number_input_window->SetVisible(false);
252 	kill_page = false;
253 	line_count = 0;
254 	text_color = Font::ColorDefault;
255 	speed = 1;
256 	kill_page = false;
257 	instant_speed = false;
258 	prev_char_printable = false;
259 	prev_char_waited = true;
260 
261 	y = Game_Message::GetRealPosition() * 80;
262 
263 	if (Main_Data::game_system->IsMessageTransparent()) {
264 		SetOpacity(0);
265 		gold_window->SetBackOpacity(0);
266 	} else {
267 		SetOpacity(255);
268 		gold_window->SetBackOpacity(GetBackOpacity());
269 	}
270 
271 	if (IsFaceEnabled()) {
272 		if (!Main_Data::game_system->IsMessageFaceRightPosition()) {
273 			contents_x = LeftMargin + FaceSize + RightFaceMargin;
274 			DrawFace(Main_Data::game_system->GetMessageFaceName(), Main_Data::game_system->GetMessageFaceIndex(), LeftMargin, TopMargin, Main_Data::game_system->IsMessageFaceFlipped());
275 		} else {
276 			contents_x = 0;
277 			DrawFace(Main_Data::game_system->GetMessageFaceName(), Main_Data::game_system->GetMessageFaceIndex(), 248, TopMargin, Main_Data::game_system->IsMessageFaceFlipped());
278 		}
279 	} else {
280 		contents_x = 0;
281 	}
282 
283 	if (pending_message.GetChoiceStartLine() == 0 && pending_message.HasChoices()) {
284 		contents_x += 12;
285 	}
286 
287 	contents_y = 2;
288 
289 	if (pending_message.GetNumberInputStartLine() == 0 && pending_message.HasNumberInput()) {
290 		// If there is an input window on the first line
291 		StartNumberInputProcessing();
292 	}
293 	line_char_counter = 0;
294 
295 	if (pending_message.ShowGoldWindow()) {
296 		ShowGoldWindow();
297 	} else {
298 		// If first character is gold, the gold window appears immediately and animates open with the main window.
299 		auto tret = Utils::TextNext(text_index, (text.data() + text.size()), Player::escape_char);
300 		if (tret && tret.is_escape && tret.ch == '$') {
301 			ShowGoldWindow();
302 		}
303 	}
304 
305 }
306 
InsertNewLine()307 void Window_Message::InsertNewLine() {
308 	DebugLog("{}: MSG NEW LINE");
309 	if (IsFaceEnabled() && !Main_Data::game_system->IsMessageFaceRightPosition()) {
310 		contents_x = LeftMargin + FaceSize + RightFaceMargin;
311 	} else {
312 		contents_x = 0;
313 	}
314 
315 	contents_y += 16;
316 	++line_count;
317 
318 	if (pending_message.HasChoices() && line_count >= pending_message.GetChoiceStartLine()) {
319 		unsigned choice_index = line_count - pending_message.GetChoiceStartLine();
320 		if (pending_message.GetChoiceResetColor()) {
321 			// Check for disabled choices
322 			if (!pending_message.IsChoiceEnabled(choice_index)) {
323 				text_color = Font::ColorDisabled;
324 			} else {
325 				text_color = Font::ColorDefault;
326 			}
327 		}
328 
329 		contents_x += 12;
330 	}
331 	line_char_counter = 0;
332 	prev_char_printable = false;
333 	prev_char_waited = true;
334 }
335 
FinishMessageProcessing()336 void Window_Message::FinishMessageProcessing() {
337 	DebugLog("{}: FINISH MSG");
338 	text.clear();
339 	text_index = text.data();
340 
341 	SetPause(false);
342 	kill_page = false;
343 	line_char_counter = 0;
344 	SetIndex(-1);
345 
346 	pending_message = {};
347 
348 	auto close_frames = Game_Battle::IsBattleRunning() ? 0 : message_animation_frames;
349 
350 	if (number_input_window->IsVisible()) {
351 		number_input_window->SetActive(false);
352 		number_input_window->SetVisible(false);
353 	}
354 
355 	SetCloseAnimation(close_frames);
356 	close_started_this_frame = true;
357 	DebugLog("{}: MSG START CLOSE {}", close_frames);
358 
359 	// RPG_RT updates window open/close at the end of the main loop.
360 	// To simulate this timing, we run base class Update() again once
361 	// to animate the closing by 1 frame.
362 	Window::Update();
363 
364 	if (gold_window->IsVisible()) {
365 		gold_window->SetCloseAnimation(close_frames);
366 		gold_window->Update();
367 	}
368 }
369 
ResetWindow()370 void Window_Message::ResetWindow() {
371 
372 }
373 
Update()374 void Window_Message::Update() {
375 	aop = {};
376 	if (IsOpening()) { DebugLog("{}: MSG OPENING"); }
377 	if (IsClosing()) { DebugLog("{}: MSG CLOSING"); }
378 
379 	close_started_this_frame = false;
380 	close_finished_this_frame = false;
381 
382 	const bool was_closing = IsClosing();
383 
384 	Window_Selectable::Update();
385 	number_input_window->Update();
386 	gold_window->Update();
387 
388 	if (was_closing && !IsClosing()) {
389 		close_finished_this_frame = true;
390 	}
391 
392 	if (!IsVisible()) {
393 		return;
394 	}
395 
396 	if (IsOpeningOrClosing()) {
397 		return;
398 	}
399 
400 	if (wait_count > 0) {
401 		DebugLog("{}: MSG WAIT {}", wait_count);
402 		--wait_count;
403 		return;
404 	}
405 
406 	if (GetPause()) {
407 		DebugLog("{}: MSG PAUSE");
408 		WaitForInput();
409 
410 		if (GetPause()) {
411 			return;
412 		}
413 	}
414 
415 	if (GetIndex() >= 0) {
416 		DebugLog("{}: MSG CHOICE");
417 		InputChoice();
418 		if (GetIndex() >= 0) {
419 			return;
420 		}
421 	}
422 
423 	if (number_input_window->GetActive()) {
424 		DebugLog("{}: MSG NUMBER");
425 		InputNumber();
426 		if (number_input_window->GetActive()) {
427 			return;
428 		}
429 	}
430 
431 	DebugLog("{}: MSG UPD");
432 	UpdateMessage();
433 }
434 
UpdateMessage()435 void Window_Message::UpdateMessage() {
436 	// Message Box Show Message rendering loop
437 	bool instant_speed_forced = false;
438 
439 	if (Player::debug_flag && Input::IsPressed(Input::SHIFT)) {
440 		instant_speed = true;
441 		instant_speed_forced = true;
442 	}
443 
444 	auto system = Cache::SystemOrBlack();
445 	auto font = Font::Default();
446 
447 	while (true) {
448 		const auto* end = text.data() + text.size();
449 
450 		if (wait_count > 0) {
451 			DebugLog("{}: MSG WAIT LOOP {}", wait_count);
452 			--wait_count;
453 			break;
454 		}
455 
456 		if (GetPause() || GetIndex() >= 0 || number_input_window->GetActive()) {
457 			break;
458 		}
459 
460 		if (text_index == end) {
461 			FinishMessageProcessing();
462 			break;
463 		}
464 
465 		auto tret = Utils::TextNext(text_index, end, Player::escape_char);
466 		auto text_prev = text_index;
467 		text_index = tret.next;
468 
469 		if (EP_UNLIKELY(!tret)) {
470 			continue;
471 		}
472 
473 		const auto ch = tret.ch;
474 		if (tret.is_exfont) {
475 			if (!DrawGlyph(*font, *system, ch, true)) {
476 				text_index = text_prev;
477 			}
478 			continue;
479 		}
480 
481 		if (ch == '\f') {
482 			if (text_index != end) {
483 				InsertNewPage();
484 				SetWait(1);
485 			}
486 			continue;
487 		}
488 
489 		if (ch == '\n') {
490 			int wait_frames = 0;
491 			bool end_page = (*text_index == '\f');
492 
493 			if (!instant_speed) {
494 				if (!prev_char_printable) {
495 					wait_frames += 1 + end_page;
496 				}
497 			} else if (end_page) {
498 				// When the page ends and speed is instant, RPG_RT always waits 2 frames.
499 				wait_frames += 2;
500 			}
501 
502 			InsertNewLine();
503 
504 			if (end_page) {
505 				OnFinishPage();
506 			}
507 			SetWait(wait_frames);
508 
509 			if (instant_speed && !instant_speed_forced) {
510 				// instant_speed stops at the end of the line
511 				// unless it was triggered by the shift key.
512 				instant_speed = false;
513 			}
514 			continue;
515 		}
516 
517 		if (Utils::IsControlCharacter(ch)) {
518 			// control characters not handled
519 			continue;
520 		}
521 
522 		if (tret.is_escape && ch != Player::escape_char) {
523 			// Special message codes
524 			switch (ch) {
525 			case 'c':
526 			case 'C':
527 				{
528 					// Color
529 					auto pres = Game_Message::ParseColor(text_index, end, Player::escape_char, true);
530 					auto value = pres.value;
531 					text_index = pres.next;
532 					DebugLogText("{}: MSG Color \\c[{}]", value);
533 					SetWaitForNonPrintable(0);
534 					text_color = value > 19 ? 0 : value;
535 				}
536 				break;
537 			case 's':
538 			case 'S':
539 				{
540 					// Speed modifier
541 					auto pres = Game_Message::ParseSpeed(text_index, end, Player::escape_char, true);
542 					text_index = pres.next;
543 					DebugLogText("{}: MSG Speed \\s[{}]", pres.value);
544 					SetWaitForNonPrintable(0);
545 					speed = Utils::Clamp(pres.value, 1, 20);
546 				}
547 				break;
548 			case '_':
549 				// Insert half size space
550 				contents_x += Font::Default()->GetSize(" ").width / 2;
551 				DebugLogText("{}: MSG HalfWait \\_");
552 				SetWaitForCharacter(1);
553 				break;
554 			case '$':
555 				// Show Gold Window
556 				ShowGoldWindow();
557 				DebugLogText("{}: MSG Gold \\$");
558 				SetWaitForNonPrintable(speed);
559 				break;
560 			case '!':
561 				// Text pause
562 				DebugLogText("{}: MSG Pause \\!");
563 				SetWaitForNonPrintable(0);
564 				SetPause(true);
565 				break;
566 			case '^':
567 				// Force message close
568 				// The close happens at the end of the message, not where
569 				// the ^ is encountered
570 				DebugLogText("{}: MSG Kill Page \\^");
571 				kill_page = true;
572 				SetWaitForNonPrintable(speed);
573 				break;
574 			case '>':
575 				// Instant speed start
576 				DebugLogText("{}: MSG Instant Speed Start \\>");
577 				SetWaitForNonPrintable(0);
578 				instant_speed = true;
579 				break;
580 			case '<':
581 				// Instant speed stop - also cancels shift key and forces a delay.
582 				instant_speed = false;
583 				instant_speed_forced = false;
584 				DebugLogText("{}: MSG Instant Speed Stop \\<");
585 				SetWaitForNonPrintable(speed);
586 				break;
587 			case '.':
588 				// 1/4 second sleep
589 				// Despite documentation saying 1/4 second, RPG_RT waits for 16 frames.
590 				// RPG_RT also has a bug(??) where speeds >= 17 slow this down by 1 more frame per speed.
591 				SetWaitForNonPrintable(16 + Utils::Clamp(speed - 16, 0, 4));
592 				DebugLogText("{}: MSG Quick Sleep \\.");
593 				break;
594 			case '|':
595 				// Second sleep
596 				// Despite documentation saying 1 second, RPG_RT waits for 61 frames.
597 				SetWaitForNonPrintable(61);
598 				DebugLogText("{}: MSG Sleep \\|");
599 				break;
600 			default:
601 				// Unknown characters will not display anything but do wait.
602 				SetWaitForNonPrintable(speed);
603 				break;
604 			}
605 			continue;
606 		}
607 
608 		if (!DrawGlyph(*font, *system, ch, false)) {
609 			text_index = text_prev;
610 			continue;
611 		}
612 	}
613 }
614 
DrawGlyph(Font & font,const Bitmap & system,char32_t glyph,bool is_exfont)615 bool Window_Message::DrawGlyph(Font& font, const Bitmap& system, char32_t glyph, bool is_exfont) {
616 	if (is_exfont) {
617 		DebugLogText("{}: MSG DrawGlyph Exfont {}", static_cast<uint32_t>(glyph));
618 	} else {
619 		if (glyph < 128) {
620 			DebugLogText("{}: MSG DrawGlyph ASCII {}", static_cast<char>(glyph));
621 		} else {
622 			DebugLogText("{}: MSG DrawGlyph UTF32 {#:X}", static_cast<uint32_t>(glyph));
623 		}
624 	}
625 
626 	// RPG_RT compatible for half-width (6) and full-width (12)
627 	// generalizes the algo for even bigger glyphs
628 	auto get_width = [](int w) {
629 		return (w > 0) ? (w - 1) / 6 + 1 : 0;
630 	};
631 
632 	// Wide characters cause an extra wait if the last printed character did not wait.
633 	if (prev_char_printable && !prev_char_waited) {
634 		auto& wide_font = is_exfont ? *Font::exfont : font;
635 		auto rect = wide_font.GetSize(glyph);
636 		auto width = get_width(rect.width);
637 		if (width >= 2) {
638 			prev_char_waited = true;
639 			++line_char_counter;
640 			SetWait(1);
641 			return false;
642 		}
643 	}
644 
645 	auto rect = Text::Draw(*contents, contents_x, contents_y, font, system, text_color, glyph, is_exfont);
646 
647 	int glyph_width = rect.width;
648 	contents_x += glyph_width;
649 	int width = get_width(glyph_width);
650 	SetWaitForCharacter(width);
651 
652 	return true;
653 }
654 
IncrementLineCharCounter(int width)655 void Window_Message::IncrementLineCharCounter(int width) {
656 	// For speed 1, RPG_RT prints 2 half width chars every frame. This
657 	// resets anytime we print a full width character or another
658 	// character with a different speed.
659 	// To emulate this, we increment by 2 and clear the low bit anytime
660 	// we're not a speed 1 half width char.
661 	if (width == 1 && speed <= 1) {
662 		line_char_counter++;
663 	} else {
664 		line_char_counter = (line_char_counter & ~1) + 2;
665 	}
666 }
667 
UpdateCursorRect()668 void Window_Message::UpdateCursorRect() {
669 	if (index >= 0) {
670 		int x_pos = 2;
671 		int y_pos = (pending_message.GetChoiceStartLine() + index) * 16;
672 		int width = contents->GetWidth() - 4;
673 
674 		if (IsFaceEnabled()) {
675 			if (!Main_Data::game_system->IsMessageFaceRightPosition()) {
676 				x_pos += LeftMargin + FaceSize + RightFaceMargin;
677 			}
678 			width = width - LeftMargin - FaceSize - RightFaceMargin;
679 		}
680 
681 		SetCursorRect(Rect(x_pos, y_pos, width, 16));
682 	} else {
683 		SetCursorRect(Rect());
684 	}
685 }
686 
WaitForInput()687 void Window_Message::WaitForInput() {
688 	if (Input::IsTriggered(Input::DECISION) ||
689 			Input::IsTriggered(Input::CANCEL)) {
690 		SetPause(false);
691 	}
692 }
693 
InputChoice()694 void Window_Message::InputChoice() {
695 	int choice_result = -1;
696 
697 	if (Input::IsTriggered(Input::CANCEL)) {
698 		if (pending_message.GetChoiceCancelType() > 0) {
699 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
700 			choice_result = pending_message.GetChoiceCancelType() - 1; // Cancel
701 		}
702 	} else if (Input::IsTriggered(Input::DECISION)) {
703 		if (!pending_message.IsChoiceEnabled(index)) {
704 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer));
705 			return;
706 		}
707 
708 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
709 		choice_result = index;
710 	}
711 
712 	if (choice_result >= 0) {
713 		auto& continuation = pending_message.GetChoiceContinuation();
714 		if (continuation) {
715 			aop = continuation(choice_result);
716 		}
717 		// This disables choices
718 		index = -1;
719 	}
720 }
721 
InputNumber()722 void Window_Message::InputNumber() {
723 	number_input_window->SetVisible(true);
724 	if (Input::IsTriggered(Input::DECISION)) {
725 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
726 		Main_Data::game_variables->Set(pending_message.GetNumberInputVariable(), number_input_window->GetNumber());
727 		Game_Map::SetNeedRefresh(true);
728 		number_input_window->SetNumber(0);
729 		number_input_window->SetActive(false);
730 	}
731 }
732 
SetWaitForNonPrintable(int frames)733 void Window_Message::SetWaitForNonPrintable(int frames) {
734 	if (!instant_speed) {
735 		if (speed <= 1) {
736 			frames += (line_char_counter & 1);
737 		}
738 		SetWait(frames);
739 	}
740 	prev_char_waited = (instant_speed || frames > 0);
741 	prev_char_printable = false;
742 	// Non printables only contribute to character count after the first printable..
743 	if (line_char_counter > 0) {
744 		IncrementLineCharCounter(1);
745 	}
746 }
747 
SetWaitForCharacter(int width)748 void Window_Message::SetWaitForCharacter(int width) {
749 	int frames = 0;
750 	if (!instant_speed && width > 0) {
751 		bool is_last_for_page = (text.data() + text.size() - text_index) < 2 || (*text_index == '\n' && *(text_index + 1) == '\f');
752 
753 		if (is_last_for_page) {
754 			// RPG_RT always waits 2 frames for last character on the page.
755 			// FIXME: Exfonts / wide last on page?
756 			frames = 2;
757 		} else {
758 			if (speed > 1) {
759 				frames = speed * width / 2 + 1;
760 			} else {
761 				frames = width / 2;
762 				if (width & 1) {
763 					bool is_last_for_line = (*text_index == '\n');
764 
765 					// RPG_RT waits for every even character. Also always waits
766 					// for the last character.
767 					frames += (line_char_counter & 1) || is_last_for_line;
768 				}
769 			}
770 		}
771 	}
772 	prev_char_waited = (instant_speed || frames > 0);
773 	prev_char_printable = true;
774 	SetWait(frames);
775 	IncrementLineCharCounter(width);
776 }
777 
SetWait(int frames)778 void Window_Message::SetWait(int frames) {
779 	assert(speed >= 1 && speed <= 20);
780 	DebugLogText("{}: MSG SetWait {}", frames);
781 	wait_count = frames;
782 }
783 
IsFaceEnabled() const784 bool Window_Message::IsFaceEnabled() const {
785 	return pending_message.IsFaceEnabled() && !Main_Data::game_system->GetMessageFaceName().empty();
786 }
787 
788