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.
221 lines
9.5 KiB
221 lines
9.5 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/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..."), |
|
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."), |
|
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 } }, UiFlags::AlignCenter, 1, 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
|
|
|