/** * @file error.cpp * * Implementation of in-game message functions. */ #include #include "error.h" #include "DiabloUI/ui_flags.hpp" #include "engine/render/clx_render.hpp" #include "engine/render/text_render.hpp" #include "panels/info_box.hpp" #include "stores.h" #include "utils/language.h" namespace devilution { namespace { std::deque DiabloMessages; std::vector TextLines; uint32_t msgdelay; int ErrorWindowHeight = 54; const int LineHeight = 12; const int LineWidth = 418; void InitNextLines() { msgdelay = SDL_GetTicks(); TextLines.clear(); const std::string paragraphs = WordWrapString(DiabloMessages.front(), LineWidth, GameFont12, 1); 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; } ErrorWindowHeight = std::max(54, static_cast((TextLines.size() * LineHeight) + 42)); } } // namespace /** Maps from error_id to error message. */ const char *const MsgStrings[] = { "", N_("No automap available in town"), 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) { InitDiabloMsg(LanguageTranslate(MsgStrings[e])); } void InitDiabloMsg(string_view msg) { if (DiabloMessages.size() >= MAX_SEND_STR_LEN) return; if (std::find(DiabloMessages.begin(), DiabloMessages.end(), msg) != DiabloMessages.end()) return; DiabloMessages.push_back(std::string(msg)); if (DiabloMessages.size() == 1) InitNextLines(); } bool IsDiabloMsgAvailable() { return !DiabloMessages.empty(); } void CancelCurrentDiabloMsg() { msgdelay = 0; } void ClrDiabloMsg() { DiabloMessages.clear(); } void DrawDiabloMsg(const Surface &out) { auto &uiRectanglePosition = GetUIRectangle().position; int dialogStartY = ((gnScreenHeight - GetMainPanel().size.height) / 2) - (ErrorWindowHeight / 2) + 9; ClxDraw(out, { uiRectanglePosition.x + 101, dialogStartY }, (*pSTextSlidCels)[0]); ClxDraw(out, { uiRectanglePosition.x + 101, dialogStartY + ErrorWindowHeight - 6 }, (*pSTextSlidCels)[1]); ClxDraw(out, { uiRectanglePosition.x + 527, dialogStartY + ErrorWindowHeight - 6 }, (*pSTextSlidCels)[2]); ClxDraw(out, { uiRectanglePosition.x + 527, dialogStartY }, (*pSTextSlidCels)[3]); int sx = uiRectanglePosition.x + 109; for (int i = 0; i < 35; i++) { ClxDraw(out, { sx, dialogStartY }, (*pSTextSlidCels)[4]); ClxDraw(out, { sx, dialogStartY + ErrorWindowHeight - 6 }, (*pSTextSlidCels)[6]); sx += 12; } int drawnYborder = 12; while ((drawnYborder + 12) < ErrorWindowHeight) { ClxDraw(out, { uiRectanglePosition.x + 101, dialogStartY + drawnYborder }, (*pSTextSlidCels)[5]); ClxDraw(out, { uiRectanglePosition.x + 527, dialogStartY + drawnYborder }, (*pSTextSlidCels)[7]); drawnYborder += 12; } DrawHalfTransparentRectTo(out, uiRectanglePosition.x + 104, dialogStartY - 8, 432, ErrorWindowHeight); int lineNumber = 0; for (auto &line : TextLines) { DrawString(out, line, { { uiRectanglePosition.x + 109, dialogStartY + 12 + lineNumber * LineHeight }, { LineWidth, LineHeight } }, UiFlags::AlignCenter, 1, LineHeight); lineNumber += 1; } if (msgdelay > 0 && msgdelay <= SDL_GetTicks() - 3500) { msgdelay = 0; } if (msgdelay == 0) { DiabloMessages.pop_front(); if (!DiabloMessages.empty()) InitNextLines(); } } } // namespace devilution