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.

224 lines
9.6 KiB

/**
* @file diablo_msg.cpp
*
* Implementation of in-game message functions.
*/
#include <algorithm>
#include <cstdint>
#include <deque>
#include "diablo_msg.hpp"
#include "DiabloUI/ui_flags.hpp"
#include "engine/clx_sprite.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/primitive_render.hpp"
#include "engine/render/text_render.hpp"
#include "panels/info_box.hpp"
#include "utils/algorithm/container.hpp"
#include "utils/language.h"
#include "utils/str_split.hpp"
namespace devilution {
namespace {
struct MessageEntry {
std::string text;
uint32_t duration; // Duration in milliseconds
};
std::deque<MessageEntry> DiabloMessages;
uint32_t msgStartTime;
std::vector<std::string> TextLines;
int OuterHeight;
int LineWidth;
int LineHeight;
void InitDiabloMsg()
{
TextLines.clear();
if (DiabloMessages.empty())
return;
LineWidth = 418;
const std::string_view text = DiabloMessages.front().text;
const std::string wrapped = WordWrapString(text, LineWidth, GameFont12);
for (const std::string_view line : SplitByChar(wrapped, '\n')) {
LineWidth = std::max(LineWidth, GetLineWidth(text, GameFont12));
TextLines.emplace_back(line);
}
msgStartTime = SDL_GetTicks();
LineHeight = GetLineHeight(text, GameFont12);
OuterHeight = static_cast<int>((TextLines.size()) * LineHeight) + 42;
}
} // namespace
/** Maps from error_id to error message. */
const char *const MsgStrings[] = {
"",
N_("Game saved"),
N_("No multiplayer functions in demo"),
N_("Direct Sound Creation Failed"),
N_("Not available in shareware version"),
N_("Not enough space to save"),
N_("No Pause in town"),
N_("Copying to a hard disk is recommended"),
N_("Multiplayer sync problem"),
N_("No pause in multiplayer"),
N_("Loading..."),
N_("Saving..."),
5 years ago
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Some are weakened as one grows strong"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "New strength is forged through destruction"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Those who defend seldom attack"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "The sword of justice is swift and sharp"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "While the spirit is vigilant the body thrives"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "The powers of mana refocused renews"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Time cannot diminish the power of steel"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Magic is not always what it seems to be"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "What once was opened now is closed"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Intensity comes at the cost of wisdom"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Arcane power brings destruction"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "That which cannot be held cannot be harmed"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Crimson and Azure become as the sun"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Knowledge and wisdom at the cost of self"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Drink and be refreshed"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Wherever you go, there you are"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Energy comes at the cost of wisdom"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Riches abound when least expected"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Where avarice fails, patience gains reward"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Blessed by a benevolent companion!"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "The hands of men may be guided by fate"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Strength is bolstered by heavenly faith"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "The essence of life flows from within"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "The way is made clear when viewed from above"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Salvation comes at the cost of wisdom"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Mysteries are revealed in the light of reason"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Those who are last may yet be first"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Generosity brings its own rewards"),
N_("You must be at least level 8 to use this."),
N_("You must be at least level 13 to use this."),
N_("You must be at least level 17 to use this."),
5 years ago
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Arcane knowledge gained!"),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "That which does not kill you..."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Knowledge is power."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Give and you shall receive."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Some experience is gained by touch."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "There's no place like home."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "Spiritual energy is restored."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "You feel more agile."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "You feel stronger."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "You feel wiser."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "You feel refreshed."),
N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "That which can break will."),
};
void InitDiabloMsg(diablo_message e, uint32_t duration /*= 3500*/)
{
InitDiabloMsg(LanguageTranslate(MsgStrings[e]), duration);
}
void InitDiabloMsg(std::string_view msg, uint32_t duration /*= 3500*/)
{
if (c_find_if(DiabloMessages, [&msg](const MessageEntry &entry) { return entry.text == msg; })
!= DiabloMessages.end())
return;
DiabloMessages.push_back({ std::string(msg), duration });
if (DiabloMessages.size() == 1) {
InitDiabloMsg();
}
}
bool IsDiabloMsgAvailable()
{
return !DiabloMessages.empty();
}
void CancelCurrentDiabloMsg()
{
if (!DiabloMessages.empty()) {
DiabloMessages.pop_front();
InitDiabloMsg();
}
}
void ClrDiabloMsg()
{
DiabloMessages.clear();
}
void DrawDiabloMsg(const Surface &out)
{
const ClxSpriteList sprites = *pSTextSlidCels;
const ClxSprite borderCornerTopLeftSprite = sprites[0];
const ClxSprite borderCornerBottomLeftSprite = sprites[1];
const ClxSprite borderCornerBottomRightSprite = sprites[2];
const ClxSprite borderCornerTopRightSprite = sprites[3];
const ClxSprite borderTopSprite = sprites[4];
const ClxSprite borderLeftSprite = sprites[5];
const ClxSprite borderBottomSprite = sprites[6];
const ClxSprite borderRightSprite = sprites[7];
const int borderPartWidth = 12;
const int borderPartHeight = 12;
const int textPaddingX = 5;
const int borderThickness = 3;
const int outerHeight = std::min<int>(out.h(), OuterHeight);
const int outerWidth = std::min<int>(out.w(), LineWidth + 2 * borderThickness + textPaddingX);
const int innerWidth = outerWidth - 2 * borderThickness;
const int lineWidth = innerWidth - textPaddingX;
const int innerHeight = outerHeight - 2 * borderThickness;
const Point topLeft { (out.w() - outerWidth) / 2, (out.h() - outerHeight) / 2 };
const int innerXBegin = topLeft.x + borderThickness;
const int innerXEnd = innerXBegin + innerWidth;
const int innerYBegin = topLeft.y + borderThickness;
const int innerYEnd = innerYBegin + innerHeight;
const int borderRightX = innerXEnd - (borderPartWidth - borderThickness);
const int borderBottomY = innerYEnd - (borderPartHeight - borderThickness);
RenderClxSprite(out, borderCornerTopLeftSprite, topLeft);
RenderClxSprite(out, borderCornerBottomLeftSprite, { topLeft.x, borderBottomY });
RenderClxSprite(out, borderCornerBottomRightSprite, { borderRightX, borderBottomY });
RenderClxSprite(out, borderCornerTopRightSprite, { borderRightX, topLeft.y });
const Surface horizontalBorderOut = out.subregionX(topLeft.x, outerWidth - borderPartWidth);
for (int x = borderPartWidth; x < horizontalBorderOut.w(); x += borderPartWidth) {
RenderClxSprite(horizontalBorderOut, borderTopSprite, { x, topLeft.y });
RenderClxSprite(horizontalBorderOut, borderBottomSprite, { x, borderBottomY });
}
const Surface verticalBorderOut = out.subregionY(topLeft.y, outerHeight - borderPartHeight);
for (int y = borderPartHeight; y < verticalBorderOut.h(); y += borderPartHeight) {
RenderClxSprite(verticalBorderOut, borderLeftSprite, { topLeft.x, y });
RenderClxSprite(verticalBorderOut, borderRightSprite, { borderRightX, y });
}
DrawHalfTransparentRectTo(out, innerXBegin, innerYBegin, innerWidth, innerHeight);
const int textX = innerXBegin + textPaddingX;
int textY = innerYBegin + (innerHeight - LineHeight * static_cast<int>(TextLines.size())) / 2;
for (const std::string &line : TextLines) {
DrawString(out, line, { { textX, textY }, { lineWidth, LineHeight } },
{ .flags = UiFlags::AlignCenter, .lineHeight = LineHeight });
textY += LineHeight;
}
// Calculate the time the current message has been displayed
const uint32_t messageElapsedTime = SDL_GetTicks() - msgStartTime;
// Check if the current message's duration has passed
if (!DiabloMessages.empty() && messageElapsedTime >= DiabloMessages.front().duration) {
DiabloMessages.pop_front();
InitDiabloMsg();
}
}
} // namespace devilution