Browse Source

Render quest and menu text using common text rendering

pull/2031/head
Anders Jenbo 5 years ago
parent
commit
6acb49bea4
  1. 44
      Source/engine/render/text_render.cpp
  2. 10
      Source/engine/render/text_render.hpp
  3. 35
      Source/gmenu.cpp
  4. 2
      Source/help.cpp
  5. 194
      Source/minitext.cpp
  6. 2
      Source/scrollrt.cpp
  7. 776
      Source/textdat.cpp

44
Source/engine/render/text_render.cpp

@ -12,6 +12,8 @@
namespace devilution {
namespace {
/**
* Maps ASCII character code to font index, as used by the
* small, medium and large sized fonts; which corresponds to smaltext.cel,
@ -116,8 +118,6 @@ const uint8_t fontkern[3][68] = {
}
};
namespace {
enum text_color : uint8_t {
COL_WHITE,
COL_BLUE,
@ -126,48 +126,46 @@ enum text_color : uint8_t {
COL_BLACK,
};
int LineHeights[3] = { 12, 43, 50 };
int LineHeights[3] = { 12, 38, 50 };
/** Graphics for the fonts */
std::array<std::optional<CelSprite>, 3> fonts;
uint8_t fontColorTableGold[256];
uint8_t fontColorTableBlue[256];
uint8_t fontColorTableRed[256];
static void PrintChar(const CelOutputBuffer &out, int sx, int sy, int nCel, text_color col)
static void DrawChar(const CelOutputBuffer &out, Point point, GameFontTables size, int nCel, text_color color)
{
switch (col) {
switch (color) {
case COL_WHITE:
CelDrawTo(out, sx, sy, *pPanelText, nCel);
CelDrawTo(out, point.x, point.y, *fonts[size], nCel);
return;
case COL_BLUE:
CelDrawLightTo(out, sx, sy, *pPanelText, nCel, fontColorTableBlue);
CelDrawLightTo(out, point.x, point.y, *fonts[size], nCel, fontColorTableBlue);
break;
case COL_RED:
CelDrawLightTo(out, sx, sy, *pPanelText, nCel, fontColorTableRed);
CelDrawLightTo(out, point.x, point.y, *fonts[size], nCel, fontColorTableRed);
break;
case COL_GOLD:
CelDrawLightTo(out, sx, sy, *pPanelText, nCel, fontColorTableGold);
CelDrawLightTo(out, point.x, point.y, *fonts[size], nCel, fontColorTableGold);
break;
case COL_BLACK:
light_table_index = 15;
CelDrawLightTo(out, sx, sy, *pPanelText, nCel, nullptr);
CelDrawLightTo(out, point.x, point.y, *fonts[size], nCel, nullptr);
return;
}
}
} // namespace
std::optional<CelSprite> pPanelText;
/** Graphics for the medium size font */
std::optional<CelSprite> pMedTextCels;
std::optional<CelSprite> BigTGold_cel;
std::optional<CelSprite> pSPentSpn2Cels;
void InitText()
{
pPanelText = LoadCel("CtrlPan\\SmalText.CEL", 13);
pMedTextCels = LoadCel("Data\\MedTextS.CEL", 22);
BigTGold_cel = LoadCel("Data\\BigTGold.CEL", 46);
fonts[GameFontSmall] = LoadCel("CtrlPan\\SmalText.CEL", 13);
fonts[GameFontMed] = LoadCel("Data\\MedTextS.CEL", 22);
fonts[GameFontBig] = LoadCel("Data\\BigTGold.CEL", 46);
pSPentSpn2Cels = LoadCel("Data\\PentSpn2.CEL", 12);
@ -199,9 +197,9 @@ void InitText()
void FreeText()
{
pPanelText = std::nullopt;
pMedTextCels = std::nullopt;
BigTGold_cel = std::nullopt;
fonts[GameFontSmall] = std::nullopt;
fonts[GameFontMed] = std::nullopt;
fonts[GameFontBig] = std::nullopt;
pSPentSpn2Cels = std::nullopt;
}
@ -237,7 +235,7 @@ int AdjustSpacingToFitHorizontally(int &lineWidth, int maxSpacing, int character
return maxSpacing - spacingRedux;
}
void WordWrapGameString(char *text, size_t width, size_t size, int spacing)
void WordWrapGameString(char *text, size_t width, GameFontTables size, int spacing)
{
const size_t textLength = strlen(text);
size_t lineStart = 0;
@ -347,7 +345,7 @@ int DrawString(const CelOutputBuffer &out, const char *text, const SDL_Rect &rec
sx += w - lineWidth;
}
if (frame != 0) {
PrintChar(out, sx, sy, frame, color);
DrawChar(out, { sx, sy }, size, frame, color);
}
if (text[i] != '\n')
sx += symbolWidth + spacing;

10
Source/engine/render/text_render.hpp

@ -20,14 +20,6 @@ enum GameFontTables : uint8_t {
GameFontBig,
};
extern const uint8_t gbFontTransTbl[256];
extern const uint8_t fontframe[3][128];
extern const uint8_t fontkern[3][68];
extern std::optional<CelSprite> pPanelText;
extern std::optional<CelSprite> pMedTextCels;
extern std::optional<CelSprite> BigTGold_cel;
extern std::optional<CelSprite> pSPentSpn2Cels;
void InitText();
@ -42,7 +34,7 @@ void FreeText();
* @return Line width in pixels
*/
int GetLineWidth(const char *text, GameFontTables size = GameFontSmall, int spacing = 1, int *charactersInLine = nullptr);
void WordWrapGameString(char *text, size_t width, size_t size = GameFontSmall, int spacing = 1);
void WordWrapGameString(char *text, size_t width, GameFontTables size = GameFontSmall, int spacing = 1);
int DrawString(const CelOutputBuffer &out, const char *text, const SDL_Rect &rect, uint16_t flags = 0, int spacing = 1, int lineHeight = -1, bool drawTextCursor = false);
int PentSpn2Spin();

35
Source/gmenu.cpp

@ -33,26 +33,13 @@ void (*gmenu_current_option)();
TMenuItem *sgpCurrentMenu;
int sgCurrentMenuIdx;
static void gmenu_print_text(const CelOutputBuffer &out, int x, int y, const char *pszStr)
{
BYTE c;
while (*pszStr) {
c = gbFontTransTbl[(BYTE)*pszStr++];
c = fontframe[GameFontBig][c];
if (c != 0)
CelDrawLightTo(out, x, y, *BigTGold_cel, c, nullptr);
x += fontkern[GameFontBig][c] + 2;
}
}
void gmenu_draw_pause(const CelOutputBuffer &out)
{
if (currlevel != 0)
RedBack(out);
if (sgpCurrentMenu == nullptr) {
light_table_index = 0;
gmenu_print_text(out, 252 + PANEL_LEFT, 176, _("Pause"));
DrawString(out, _("Pause"), { PANEL_LEFT + 252, 176, 0, 0 }, UIS_MED, 2);
}
}
@ -181,22 +168,20 @@ static int gmenu_get_lfont(TMenuItem *pItem)
static void gmenu_draw_menu_item(const CelOutputBuffer &out, TMenuItem *pItem, int y)
{
DWORD w, x, nSteps, step, pos;
w = gmenu_get_lfont(pItem);
int w = gmenu_get_lfont(pItem);
if ((pItem->dwFlags & GMENU_SLIDER) != 0) {
x = 16 + w / 2;
int x = 16 + w / 2;
CelDrawTo(out, x + PANEL_LEFT, y - 10, *optbar_cel, 1);
step = pItem->dwFlags & 0xFFF;
nSteps = (pItem->dwFlags & 0xFFF000) >> 12;
if (nSteps < 2)
nSteps = 2;
pos = step * 256 / nSteps;
int step = pItem->dwFlags & 0xFFF;
int nSteps = std::max<int>((pItem->dwFlags & 0xFFF000) >> 12, 2);
int pos = step * 256 / nSteps;
gmenu_clear_buffer(out, x + 2 + PANEL_LEFT, y - 12, pos + 13, 28);
CelDrawTo(out, x + 2 + pos + PANEL_LEFT, y - 12, *option_cel, 1);
}
x = gnScreenWidth / 2 - w / 2;
light_table_index = (pItem->dwFlags & GMENU_ENABLED) ? 0 : 15;
gmenu_print_text(out, x, y, _(pItem->pszStr));
int x = (gnScreenWidth - w) / 2;
uint16_t style = (pItem->dwFlags & GMENU_ENABLED) ? UIS_SILVER : UIS_BLACK;
DrawString(out, _(pItem->pszStr), { x, y, 0, 0 }, style | UIS_HUGE, 2);
if (pItem == sgpCurrItem) {
CelDrawTo(out, x - 54, y + 1, *PentSpin_cel, PentSpn2Spin());
CelDrawTo(out, x + 4 + w, y + 1, *PentSpin_cel, PentSpn2Spin());

2
Source/help.cpp

@ -145,7 +145,7 @@ void DrawHelp(const CelOutputBuffer &out)
style = UIS_RED;
}
DrawString(out, &line[offset], { sx, sy + i * 12, 577, 0 }, style);
DrawString(out, &line[offset], { sx, sy + i * 12, 577, 12 }, style);
}
PrintSString(out, 0, 23, _("Press ESC to end or the arrow keys to scroll."), UIS_GOLD | UIS_CENTER);

194
Source/minitext.cpp

@ -3,6 +3,8 @@
*
* Implementation of scrolling dialog text.
*/
#include <string>
#include <vector>
#include "control.h"
#include "dx.h"
@ -12,6 +14,7 @@
#include "textdat.h"
#include "utils/language.h"
#include "utils/stdcompat/optional.hpp"
#include "utils/stdcompat/string_view.hpp"
namespace devilution {
@ -20,77 +23,36 @@ bool qtextflag;
namespace {
/** Current y position of text in px */
int qtexty;
/** Pointer to the current text being displayed */
const char *qtextptr;
/** Vertical speed of the scrolling text in ms/px */
int qtextSpd;
/** Time of last rendering of the text */
Uint32 sgLastScroll;
/** Start time of scrolling */
Uint32 ScrollStart;
/** Graphics for the window border */
std::optional<CelSprite> pTextBoxCels;
/** Pixels for a line of text and the empty space under it. */
const int lineHeight = 38;
const int LineHeight = 38;
/**
* @brief Build a single line of text from the given text stream
* @param text The original text
* @param line The buffer to insert the line in to
* @return Indicate that the end of the text was reached
*/
bool BuildLine(const char *text, char line[128])
std::vector<std::string> TextLines;
void LoadText(const char *text)
{
int lineWidth = 0;
int l = 0;
while (*text != '\n' && *text != '|' && lineWidth < 543) {
uint8_t c = gbFontTransTbl[(uint8_t)*text];
text++;
if (c != '\0') {
line[l] = c;
lineWidth += fontkern[GameFontMed][fontframe[GameFontMed][c]] + 2;
} else {
l--;
}
l++;
}
line[l] = '\0';
if (*text == '|') {
line[l] = '\0';
return true;
}
TextLines.clear();
if (*text != '\n') {
while (line[l] != ' ' && l > 0) {
line[l] = '\0';
l--;
}
}
char tempstr[1536]; // Longest test is about 768 chars * 2 for unicode
strcpy(tempstr, text);
return false;
}
WordWrapGameString(tempstr, 543, GameFontMed, 2);
const string_view paragraphs = tempstr;
/**
* @brief Calculate the number of line required by the given text
* @return Number of lines
*/
int GetLinesInText(const char *text)
{
char line[128];
int lines = 0;
bool doneflag = false;
while (!doneflag) {
doneflag = BuildLine(text, line);
text += strlen(line);
if (*text == '\n')
text++;
lines++;
size_t previous = 0;
while (true) {
size_t next = paragraphs.find("\n", previous);
TextLines.emplace_back(paragraphs.substr(previous, next));
if (next == std::string::npos)
break;
previous = next + 1;
}
return lines;
}
/**
@ -98,99 +60,62 @@ int GetLinesInText(const char *text)
* @param nSFX The index of the sound in the sgSFX table
* @return ms/px
*/
int CalcTextSpeed(int nSFX)
int CalculateTextSpeed(int nSFX)
{
int TextHeight;
Uint32 SfxFrames;
const int numLines = GetLinesInText(qtextptr);
const int numLines = TextLines.size();
#ifndef NOSOUND
SfxFrames = GetSFXLength(nSFX);
Uint32 SfxFrames = GetSFXLength(nSFX);
assert(SfxFrames != 0);
#else
// Sound is disabled -- estimate length from the number of lines.
SfxFrames = numLines * 3000;
Uint32 SfxFrames = numLines * 3000;
#endif
TextHeight = lineHeight * numLines;
TextHeight += lineHeight * 5; // adjust so when speaker is done two line are left
int textHeight = LineHeight * numLines;
textHeight += LineHeight * 5; // adjust so when speaker is done two line are left
return SfxFrames / TextHeight;
return SfxFrames / textHeight;
}
/**
* @brief Print a character
* @param sx Back buffer coordinate
* @param sy Back buffer coordinate
* @param cel CEL sprite
* @param nCel CEL frame number
*/
void PrintQTextChr(int sx, int sy, const CelSprite &cel, int nCel)
int CalculateTextPosition()
{
const int start_y = 49 + UI_OFFSET_Y;
const CelOutputBuffer &buf = GlobalBackBuffer().subregionY(start_y, 260);
CelDrawTo(buf, sx, sy - start_y, cel, nCel);
}
Uint32 currTime = SDL_GetTicks();
/**
* @brief Draw the current text in the quest dialog window
* @return the start of the text currently being rendered
*/
void ScrollQTextContent(const char *pnl)
{
for (Uint32 currTime = SDL_GetTicks(); sgLastScroll + qtextSpd < currTime; sgLastScroll += qtextSpd) {
qtexty--;
if (qtexty <= 49 + UI_OFFSET_Y) {
qtexty += 38;
qtextptr = pnl;
if (*pnl == '|') {
qtextflag = false;
}
break;
}
}
int y = (currTime - ScrollStart) / qtextSpd - 260;
int textHeight = LineHeight * TextLines.size();
if (y >= textHeight)
qtextflag = false;
return y;
}
/**
* @brief Draw the current text in the quest dialog window
*/
static void DrawQTextContent()
void DrawQTextContent(const CelOutputBuffer &out)
{
// TODO: Draw to the given `out` buffer.
const char *text, *pnl;
char line[128];
text = qtextptr;
pnl = nullptr;
int tx = 48 + PANEL_X;
int ty = qtexty;
bool doneflag = false;
while (!doneflag) {
doneflag = BuildLine(text, line);
for (int i = 0; line[i]; i++) {
text++;
uint8_t c = fontframe[GameFontMed][gbFontTransTbl[(uint8_t)line[i]]];
if (*text == '\n') {
text++;
}
if (c != 0) {
PrintQTextChr(tx, ty, *pMedTextCels, c);
}
tx += fontkern[GameFontMed][c] + 2;
}
if (pnl == nullptr) {
pnl = text;
int y = CalculateTextPosition();
const int sx = PANEL_X + 48;
const int sy = LineHeight / 2 - (y % LineHeight);
const unsigned int skipLines = y / LineHeight;
for (int i = 0; i < 8; i++) {
const int lineNumber = skipLines + i;
if (lineNumber < 0 || lineNumber >= (int)TextLines.size()) {
continue;
}
tx = 48 + PANEL_X;
ty += lineHeight;
if (ty > 341 + UI_OFFSET_Y) {
doneflag = true;
const char *line = TextLines[lineNumber].c_str();
if (line[0] == '\0') {
continue;
}
}
ScrollQTextContent(pnl);
DrawString(out, line, { sx, sy + i * LineHeight, 543, LineHeight }, UIS_MED, 2);
}
}
} // namespace
@ -220,11 +145,10 @@ void InitQTextMsg(_speech_id m)
{
if (alltext[m].scrlltxt) {
questlog = false;
qtextptr = _(alltext[m].txtstr);
LoadText(_(alltext[m].txtstr));
qtextflag = true;
qtexty = 340 + UI_OFFSET_Y;
qtextSpd = CalcTextSpeed(alltext[m].sfxnr);
sgLastScroll = SDL_GetTicks();
qtextSpd = CalculateTextSpeed(alltext[m].sfxnr);
ScrollStart = SDL_GetTicks();
}
PlaySFX(alltext[m].sfxnr);
}
@ -238,7 +162,7 @@ void DrawQTextBack(const CelOutputBuffer &out)
void DrawQText(const CelOutputBuffer &out)
{
DrawQTextBack(out);
DrawQTextContent();
DrawQTextContent(out.subregionY(UI_OFFSET_Y + 49, 260));
}
} // namespace devilution

2
Source/scrollrt.cpp

@ -1412,7 +1412,7 @@ static void DrawFPS(const CelOutputBuffer &out)
DWORD tc, frames;
char String[12];
if (frameflag && gbActive && pPanelText) {
if (frameflag && gbActive) {
frameend++;
tc = SDL_GetTicks();
frames = tc - framestart;

776
Source/textdat.cpp

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save