You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
4.7 KiB
188 lines
4.7 KiB
/** |
|
* @file minitext.cpp |
|
* |
|
* Implementation of scrolling dialog text. |
|
*/ |
|
#include <cstdint> |
|
#include <optional> |
|
#include <string> |
|
#include <string_view> |
|
#include <vector> |
|
|
|
#include "DiabloUI/ui_flags.hpp" |
|
#include "control.h" |
|
#include "engine.h" |
|
#include "engine/clx_sprite.hpp" |
|
#include "engine/dx.h" |
|
#include "engine/load_cel.hpp" |
|
#include "engine/render/clx_render.hpp" |
|
#include "engine/render/text_render.hpp" |
|
#include "playerdat.hpp" |
|
#include "textdat.h" |
|
#include "utils/language.h" |
|
#include "utils/timer.hpp" |
|
|
|
namespace devilution { |
|
|
|
bool qtextflag; |
|
|
|
namespace { |
|
|
|
/** Vertical speed of the scrolling text in ms/px */ |
|
int qtextSpd; |
|
/** Start time of scrolling */ |
|
uint32_t ScrollStart; |
|
/** Graphics for the window border */ |
|
OptionalOwnedClxSpriteList pTextBoxCels; |
|
|
|
/** Pixels for a line of text and the empty space under it. */ |
|
const int LineHeight = 38; |
|
|
|
std::vector<std::string> TextLines; |
|
|
|
void LoadText(std::string_view text) |
|
{ |
|
TextLines.clear(); |
|
|
|
const std::string paragraphs = WordWrapString(text, 543, GameFont30); |
|
|
|
size_t previous = 0; |
|
while (true) { |
|
size_t next = paragraphs.find('\n', previous); |
|
TextLines.emplace_back(paragraphs.substr(previous, next - previous)); |
|
if (next == std::string::npos) |
|
break; |
|
previous = next + 1; |
|
} |
|
} |
|
|
|
/** |
|
* @brief Calculate the speed the current text should scroll to match the given audio |
|
* @param nSFX The index of the sound in the sgSFX table |
|
* @return ms/px |
|
*/ |
|
uint32_t CalculateTextSpeed(SfxID nSFX) |
|
{ |
|
const auto numLines = static_cast<uint32_t>(TextLines.size()); |
|
|
|
#ifndef NOSOUND |
|
uint32_t sfxFrames = GetSFXLength(nSFX); |
|
#else |
|
// Sound is disabled -- estimate length from the number of lines. |
|
uint32_t sfxFrames = numLines * 3000; |
|
#endif |
|
assert(sfxFrames != 0); |
|
|
|
uint32_t textHeight = LineHeight * numLines; |
|
textHeight += LineHeight * 5; // adjust so when speaker is done two line are left |
|
assert(textHeight != 0); |
|
|
|
return sfxFrames / textHeight; |
|
} |
|
|
|
int CalculateTextPosition() |
|
{ |
|
const uint32_t currTime = GetMillisecondsSinceStartup(); |
|
|
|
const int y = (currTime - ScrollStart) / qtextSpd - 260; |
|
|
|
const auto textHeight = static_cast<int>(LineHeight * TextLines.size()); |
|
if (y >= textHeight) |
|
qtextflag = false; |
|
|
|
return y; |
|
} |
|
|
|
/** |
|
* @brief Draw the current text in the quest dialog window |
|
*/ |
|
void DrawQTextContent(const Surface &out) |
|
{ |
|
int y = CalculateTextPosition(); |
|
|
|
const int sx = GetUIRectangle().position.x + 48; |
|
const int sy = 0 - (y % LineHeight); |
|
|
|
const unsigned int skipLines = y / LineHeight; |
|
|
|
for (int i = 0; i < 8; i++) { |
|
const unsigned int lineNumber = skipLines + i; |
|
if (lineNumber >= TextLines.size()) { |
|
continue; |
|
} |
|
|
|
const std::string &line = TextLines[lineNumber]; |
|
if (line.empty()) { |
|
continue; |
|
} |
|
|
|
DrawString(out, line, { { sx, sy + i * LineHeight }, { 543, LineHeight } }, |
|
{ .flags = UiFlags::FontSize30 | UiFlags::ColorGold }); |
|
} |
|
} |
|
|
|
} // namespace |
|
|
|
void FreeQuestText() |
|
{ |
|
pTextBoxCels = std::nullopt; |
|
} |
|
|
|
void InitQuestText() |
|
{ |
|
pTextBoxCels = LoadCel("data\\textbox", 591); |
|
} |
|
|
|
void InitQTextMsg(_speech_id m) |
|
{ |
|
SfxID sfxnr = Speeches[m].sfxnr; |
|
const SfxID *classSounds = herosounds[static_cast<size_t>(MyPlayer->_pClass)]; |
|
switch (sfxnr) { |
|
case SfxID::Warrior1: |
|
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::ChamberOfBoneLore)]; |
|
break; |
|
case SfxID::Warrior10: |
|
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::ValorLore)]; |
|
break; |
|
case SfxID::Warrior11: |
|
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::HallsOfTheBlindLore)]; |
|
break; |
|
case SfxID::Warrior12: |
|
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::WarlordOfBloodLore)]; |
|
break; |
|
case SfxID::Warrior54: |
|
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::InSpirituSanctum)]; |
|
break; |
|
case SfxID::Warrior55: |
|
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::PraedictumOtium)]; |
|
break; |
|
case SfxID::Warrior56: |
|
sfxnr = classSounds[static_cast<size_t>(HeroSpeech::EfficioObitusUtInimicus)]; |
|
break; |
|
default: |
|
break; |
|
} |
|
if (Speeches[m].scrlltxt) { |
|
QuestLogIsOpen = false; |
|
LoadText(_(Speeches[m].txtstr)); |
|
qtextflag = true; |
|
qtextSpd = CalculateTextSpeed(sfxnr); |
|
ScrollStart = GetMillisecondsSinceStartup(); |
|
} |
|
PlaySFX(sfxnr); |
|
} |
|
|
|
void DrawQTextBack(const Surface &out) |
|
{ |
|
const Point uiPosition = GetUIRectangle().position; |
|
ClxDraw(out, uiPosition + Displacement { 24, 327 }, (*pTextBoxCels)[0]); |
|
DrawHalfTransparentRectTo(out, uiPosition.x + 27, uiPosition.y + 28, 585, 297); |
|
} |
|
|
|
void DrawQText(const Surface &out) |
|
{ |
|
DrawQTextBack(out); |
|
DrawQTextContent(out.subregionY(GetUIRectangle().position.y + 49, 260)); |
|
} |
|
|
|
} // namespace devilution
|
|
|