diff --git a/rwengine/include/engine/GameState.hpp b/rwengine/include/engine/GameState.hpp index 83fd6216..1683e16e 100644 --- a/rwengine/include/engine/GameState.hpp +++ b/rwengine/include/engine/GameState.hpp @@ -7,6 +7,7 @@ #include #include #include +#include class GameWorld; class GameObject; @@ -159,42 +160,6 @@ struct TextDisplayData glm::vec4 colourBG; }; -struct OnscreenText -{ - std::string id; - std::string osTextString; - float osTextStart; - float osTextTime; - unsigned short osTextStyle; - std::string osTextVar; - - enum /*TextStyle*/ - { - /// Used for subtitles - HighPriority = 0, - /// Mission completed message - CenterBig = 1, - /// Right aligned mission names - MissionName = 2, - /// Help text (top left, black background) - Help = 12 - }; - - OnscreenText(const std::string& id, - const std::string& string, - float start, - float time, - unsigned short style, - const std::string& var = "") - : id (id) - , osTextString(string) - , osTextStart(start) - , osTextTime(time) - , osTextStyle(style) - , osTextVar(var) - { } -}; - /** * Stores information about where the game can generate vehicles. */ @@ -353,11 +318,9 @@ struct GameState std::map specialCharacters; std::map specialModels; - /** - * Stores long-lasting on screen messages - */ - std::vector text; - + /// Handles on screen text behaviour + ScreenText text; + TextDisplayData nextText; /** * Stores temporary, one-tick messages diff --git a/rwengine/include/engine/ScreenText.hpp b/rwengine/include/engine/ScreenText.hpp new file mode 100644 index 00000000..571ffc46 --- /dev/null +++ b/rwengine/include/engine/ScreenText.hpp @@ -0,0 +1,160 @@ +#ifndef _RWENGINE_SCREENTEXT_HPP_ +#define _RWENGINE_SCREENTEXT_HPP_ +#include +#include +#include +#include +#include +#include + +enum class ScreenTextType +{ + /// Big text will be rendered according to the proscribed style. + /// Adding a 2nd big text will cause the first to terminate. + Big = 0, + /// See Big, will wait for any Big text to finish. + BigLowPriority = 1, + /// Will be cleared by the clear help opcode + Help = 2, + /// Automatically cleared after each tick, for generic text. + Immediate = 3, + /// High priority cutscene text + HighPriority = 4, + /// + _Count = 5 +}; +constexpr unsigned int ScreenTypeTextCount = static_cast(ScreenTextType::_Count); + +/** + * @brief The ScreenTextEntry struct + * + * Text string and fading information. + */ +struct ScreenTextEntry +{ + /// After processing numbers + std::string text; + /// in the virtual 640x480 screen space + glm::vec2 position; + /// Font number + int font; + /// Font size + int size; + /// Background colour (or, if a == 0, shadow offset) + glm::u8vec4 colourBG; + /// Foreground colour + glm::u8vec3 colourFG; + /// Alignment (left = 0, center = 1, right = 2) + unsigned char alignment; + /// Onscreen duration + int durationMS; + /// The amount of time onscreen so far + int displayedMS; + /// Wrap width + int wrapX; + /// ID used to reference the text. + std::string id; + + static ScreenTextEntry makeBig(const std::string& id, + const std::string& str, + int style, + int durationMS); + + static ScreenTextEntry makeHighPriority(const std::string& id, + const std::string& str, + int durationMS); + + static ScreenTextEntry makeHelp(const std::string& id, + const std::string& str); +}; + +/** + * @brief The ScreenText class + * + * Logic for on-screen game text rendering + * + * There are 4 text pool types: + * - Big Text + * - (low priority) Big Text + * - Help Text + * - Immediate Text + * + * Only one Big Text can be drawn at a time, adding a second will + * cause the first to end prematurely. Low priority big text will + * only display if there is no regular big text. + * + * Help text. + * + * Immediate text is only rendered once, the text is removed at the + * start of each tick and must be re-added to keep it on screen. + */ +class ScreenText +{ + using EntryList = + std::vector; + using EntryQueues = + std::array; +public: + + /// + /// \brief tick Apply display and fading rules to the text + /// \param dt + /// + void tick(float dt); + + template + void addText(Args&&...args) + { + static_assert(static_cast(Q) < ScreenTypeTextCount, "Queue out of range"); + m_textQueues[static_cast(Q)].emplace_back(std::forward(args...)); + } + + template + const EntryList& getText() const + { + static_assert(static_cast(Q) < ScreenTypeTextCount, "Queue out of range"); + return m_textQueues[static_cast(Q)]; + } + + template + void clear() + { + static_assert(static_cast(Q) < ScreenTypeTextCount, "Queue out of range"); + m_textQueues[static_cast(Q)].clear(); + } + + template + void remove(const std::string& id) + { + static_assert(static_cast(Q) < ScreenTypeTextCount, "Queue out of range"); + auto& list = m_textQueues[static_cast(Q)]; + list.erase( + std::remove_if(list.begin(), list.end(), + [&id](const ScreenTextEntry& e){ return e.id == id; }), + list.end() + ); + } + + const EntryQueues& getAllText() const + { + return m_textQueues; + } + + template + static std::string format(std::string format, Args&&...args) + { + const std::array vals = { args... }; + size_t x = 0, val = 0; + // We're only looking for numerical replacement markers + while ((x = format.find("~1~")) != std::string::npos && val < vals.size()) + { + format = format.substr(0, x) + vals[val++] + format.substr(x + 3); + } + return format; + } + +private: + EntryQueues m_textQueues; +}; + +#endif diff --git a/rwengine/include/render/TextRenderer.hpp b/rwengine/include/render/TextRenderer.hpp index 7ed3a43e..8dfc8670 100644 --- a/rwengine/include/render/TextRenderer.hpp +++ b/rwengine/include/render/TextRenderer.hpp @@ -32,17 +32,19 @@ public: int font; /// Message to be displayed (including markup) std::string text; - /// Extra text parameter - std::string varText; /// On screen position glm::vec2 screenPosition; /// font size float size; /// Base colour - glm::vec3 baseColour; + glm::u8vec3 baseColour; + /// Background colour + glm::u8vec4 backgroundColour; /// Horizontal Alignment TextAlignemnt align; - + /// Wrap width + int wrapX; + TextInfo(); }; @@ -70,4 +72,4 @@ private: GeometryBuffer gb; DrawBuffer db; -}; \ No newline at end of file +}; diff --git a/rwengine/src/engine/ScreenText.cpp b/rwengine/src/engine/ScreenText.cpp new file mode 100644 index 00000000..f2cebdcc --- /dev/null +++ b/rwengine/src/engine/ScreenText.cpp @@ -0,0 +1,112 @@ +#include + +void ScreenText::tick(float dt) +{ + int millis = dt * 1000; + + // Remove all the immedate text + m_textQueues[static_cast(ScreenTextType::Immediate)].clear(); + + for (unsigned int t = 0; t < m_textQueues.size(); ++t) + { + for (unsigned int i = 0; i < m_textQueues[t].size();) + { + auto& big = m_textQueues[t][i]; + + big.displayedMS += millis; + if (big.displayedMS >= big.durationMS) + { + m_textQueues[t].erase(m_textQueues[t].begin()+i); + } + else + { + ++i; + } + } + } +} + +ScreenTextEntry ScreenTextEntry::makeBig(const std::string& id, const std::string& str, int style, int durationMS) +{ + switch(style) { + case 1: + return { + str, + {320.f, 400.f}, + 1, + 50, + { 3, 3, 0, 0}, + {20, 20, 200}, + 1, + durationMS, + 0, + 600, + id + }; + case 2: + return { + str, + {620.f, 380.f}, + 1, + 30, + { 3, 3, 0, 0}, + {205, 162, 7}, + 2, + durationMS, + 0, + 600, + id + }; + default: + RW_ERROR("Unhandled text style"); + break; + } + + return { + "Error", + {320.f, 400.f}, + 1, + 50, + {20, 20, 0, 0}, + {20, 20, 200}, + 1, + durationMS, + 0, + 600, + id + }; +} + +ScreenTextEntry ScreenTextEntry::makeHighPriority(const std::string& id, const std::string& str, int durationMS) +{ + return { + str, + {320.f, 420.f}, + 2, + 18, + {0, 0, 0, 0}, + {255, 255, 255}, + 1, + durationMS, + 0, + 50, + id + }; +} + +ScreenTextEntry ScreenTextEntry::makeHelp(const std::string& id, const std::string& str) +{ + return { + str, + {20.f, 20.f}, + 2, + 18, + { 0, 0, 0, 255}, + {255, 255, 255}, + 0, + 5000, + 0, + 35, + id + }; +} diff --git a/rwengine/src/render/TextRenderer.cpp b/rwengine/src/render/TextRenderer.cpp index ee784978..f1bfe01c 100644 --- a/rwengine/src/render/TextRenderer.cpp +++ b/rwengine/src/render/TextRenderer.cpp @@ -187,13 +187,17 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti) glm::vec2 coord( 0.f, 0.f ); glm::vec2 alignment = ti.screenPosition; + // We should track real size not just chars. + auto lineLength = 0; glm::vec2 ss( ti.size ); - glm::vec3 colour = ti.baseColour; + glm::vec3 colour = glm::vec3(ti.baseColour) * (1/255.f); + glm::vec4 colourBG = glm::vec4(ti.backgroundColour) * (1/255.f); std::vector geo; float maxWidth = 0.f; + float maxHeight = 0.f; auto text = ti.text; @@ -206,11 +210,6 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti) { switch( text[i+1] ) { - case '1': - case 'a': - text.erase(text.begin()+i, text.begin()+i+3); - text.insert(i, ti.varText); - break; case 'k': { text.erase(text.begin()+i, text.begin()+i+3); // Extract the key name from the /next/ markup @@ -218,7 +217,7 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti) auto keyname = text.substr(i+1, keyend-i-1); // Since we don't have a key map yet, just print out the name text.erase(text.begin()+i, text.begin()+keyend); - text.insert(i, "("+keyname+")"); + text.insert(i, keyname); } break; case 'w': text.erase(text.begin()+i, text.begin()+i+3); @@ -238,6 +237,26 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti) { continue; } + + // If we're not at the start of the column, check if the current word + // will need to be wrapped + if (ti.wrapX > 0 && coord.x > 0.f && !std::isspace(c)) + { + auto wend = std::find_if(std::begin(text)+i, + std::end(text), + [](char x) { return std::isspace(x); }); + if (wend != std::end(text)) + { + auto word = std::distance(std::begin(text)+i, wend); + if (lineLength + word >= ti.wrapX) + { + coord.x = 0; + coord.y += ss.y; + maxHeight = coord.y + ss.y; + lineLength = 0; + } + } + } auto& data = glyphData[glyph]; auto tex = indexToCoord(ti.font, glyph); @@ -248,11 +267,14 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti) // Handle special chars. if( c == '\n' ) { - coord.x = ti.screenPosition.x; + coord.x = 0.f; coord.y += ss.y; + maxHeight = coord.y + ss.y; + lineLength = 0; continue; } - + lineLength ++; + glm::vec2 p = coord; coord.x += ss.x; maxWidth = std::max(coord.x, maxWidth); @@ -277,6 +299,17 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti) } alignment.y -= ti.size * 0.2f; + + // If we need to, draw the background. + if (colourBG.a > 0.f) + { + renderer->drawColour( + colourBG, + glm::vec4( + ti.screenPosition - (ss/3.f), + glm::vec2(maxWidth, maxHeight)+(ss/2.f))); + + } renderer->getRenderer()->setUniform(textShader, "proj", renderer->getRenderer()->get2DProjection()); renderer->getRenderer()->setUniformTexture(textShader, "fontTexture", 0); diff --git a/rwengine/src/script/modules/GameModule.cpp b/rwengine/src/script/modules/GameModule.cpp index 0416f66b..b5c62962 100644 --- a/rwengine/src/script/modules/GameModule.cpp +++ b/rwengine/src/script/modules/GameModule.cpp @@ -19,45 +19,56 @@ #include #include #include +#include #include #include #include + +// Helper function to format script numbers to strings +// for use in the text printing opcodes. +std::string formatValue(const SCMOpcodeParameter& p) +{ + switch (p.type) { + case TFloat16: + return std::to_string(p.real); + default: + return std::to_string(p.integerValue()); + } + return ""; +} + void game_print_big(const ScriptArguments& args) { std::string id(args[0].string); std::string str = args.getWorld()->data->texts.text(id); + unsigned short time = args[1].integer; unsigned short style = args[2].integer; - args.getWorld()->state->text.emplace_back( - id, - str, - args.getWorld()->getGameTime(), - args[1].integer / 1000.f, - style - ); + args.getWorld()->state->text.addText( + ScreenTextEntry::makeBig( + id, str, style, time + )); } void game_print_now(const ScriptArguments& args) { std::string id(args[0].string); std::string str = args.getWorld()->data->texts.text(id); + int time = args[1].integer; int flags = args[2].integer; RW_UNUSED(flags); - RW_UNIMPLEMENTED("game_print_now(): flags"); - args.getWorld()->state->text.emplace_back( - id, - str, - args.getWorld()->getGameTime(), - args[1].integer / 1000.f, - 0 - ); + RW_UNIMPLEMENTED("Unclear what style should be used"); + args.getWorld()->state->text.addText( + ScreenTextEntry::makeHighPriority( + id, str, time + )); } void game_clear_prints(const ScriptArguments& args) { - args.getWorld()->state->text.clear(); + args.getWorld()->state->text.clear(); } void game_get_time(const ScriptArguments& args) @@ -326,20 +337,15 @@ void game_get_runtime(const ScriptArguments& args) void game_print_big_with_number(const ScriptArguments& args) { std::string id(args[0].string); - std::string str = args.getWorld()->data->texts.text(id); - - int number = args[1].integer; - + std::string str = + ScreenText::format( + args.getWorld()->data->texts.text(id), + formatValue(args[1])); unsigned short style = args[3].integer; - - args.getWorld()->state->text.push_back({ - id, - str, - args.getWorld()->getGameTime(), - args[2].integer / 1000.f, - style, - std::to_string(number), - }); + args.getWorld()->state->text.addText( + ScreenTextEntry::makeBig( + id, str, style, 5000 + )); } void game_disable_roads(const ScriptArguments& args) @@ -821,17 +827,7 @@ void game_clear_print(const ScriptArguments& args) { std::string id(args[0].string); - for( size_t i = 0; i < args.getWorld()->state->text.size(); ) - { - if( args.getWorld()->state->text[i].id == id ) - { - args.getWorld()->state->text.erase(args.getWorld()->state->text.begin() + i); - } - else - { - i++; - } - } + args.getWorld()->state->text.remove(id); } bool game_did_game_save(const ScriptArguments& args) @@ -851,30 +847,16 @@ void game_display_help(const ScriptArguments& args) { std::string id(args[0].string); std::string str = args.getWorld()->data->texts.text(id); - unsigned short style = OnscreenText::Help; - args.getWorld()->state->text.push_back({ - id, - str, - args.getWorld()->getGameTime(), - 2.5f, - style - }); + + args.getWorld()->state->text.addText( + ScreenTextEntry::makeHelp( + id, str + )); } void game_clear_help(const ScriptArguments& args) { - for( size_t i = 0; i < args.getWorld()->state->text.size(); ) - { - auto& texts = args.getWorld()->state->text; - if( texts[i].osTextStyle == OnscreenText::Help ) - { - texts.erase(texts.begin() + i); - } - else - { - i++; - } - } + args.getWorld()->state->text.clear(); } bool game_can_player_move(const ScriptArguments& args) diff --git a/rwgame/DrawUI.cpp b/rwgame/DrawUI.cpp index 50792ce6..6ab131ff 100644 --- a/rwgame/DrawUI.cpp +++ b/rwgame/DrawUI.cpp @@ -16,11 +16,11 @@ constexpr size_t ui_ammoSize = 14; constexpr size_t ui_ammoHeight = 16; constexpr size_t ui_armourOffset = ui_textSize * 3; constexpr size_t ui_maxWantedLevel = 6; -#define RGB_COLOR(r,g,b) r/255.f, g/255.f, b/255.f -const glm::vec3 ui_timeColour(RGB_COLOR(196, 165, 119)); -const glm::vec3 ui_moneyColour(RGB_COLOR(89, 113, 147)); -const glm::vec3 ui_healthColour(RGB_COLOR(187, 102, 47)); -const glm::vec3 ui_armourColour(RGB_COLOR(123, 136, 93)); +const glm::u8vec3 ui_timeColour(196, 165, 119); +const glm::u8vec3 ui_moneyColour(89, 113, 147); +const glm::u8vec3 ui_healthColour(187, 102, 47); +const glm::u8vec3 ui_armourColour(123, 136, 93); +const glm::u8vec3 ui_shadowColour(0, 0, 0); const float ui_mapSize = 150.f; const float ui_worldSizeMin = 200.f; const float ui_worldSizeMax = 300.f; @@ -75,7 +75,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, GameRenderer* re ti.text = ss.str(); } - ti.baseColour = glm::vec3(0.f, 0.f, 0.f); + ti.baseColour = ui_shadowColour; ti.screenPosition = glm::vec2(infoTextX + 1.f, infoTextY+1.f); render->text.renderText(ti); @@ -86,7 +86,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, GameRenderer* re infoTextY += ui_textHeight; ti.text = "$" + std::to_string(world->state->playerInfo.money); - ti.baseColour = glm::vec3(0.f, 0.f, 0.f); + ti.baseColour = ui_shadowColour; ti.screenPosition = glm::vec2(infoTextX + 1.f, infoTextY+1.f); render->text.renderText(ti); @@ -102,7 +102,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, GameRenderer* re << (int)player->getCharacter()->getCurrentState().health; ti.text = ss.str(); } - ti.baseColour = glm::vec3(0.f, 0.f, 0.f); + ti.baseColour = ui_shadowColour; ti.screenPosition = glm::vec2(infoTextX + 1.f, infoTextY+1.f); render->text.renderText(ti); @@ -116,7 +116,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, GameRenderer* re ss << "[" << std::setw(3) << std::setfill('0') << (int)player->getCharacter()->getCurrentState().armour; ti.text = ss.str(); - ti.baseColour = glm::vec3(0.f, 0.f, 0.f); + ti.baseColour = ui_shadowColour; ti.screenPosition = glm::vec2(infoTextX + 1.f - ui_armourOffset, infoTextY+1.f); render->text.renderText(ti); @@ -130,7 +130,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, GameRenderer* re s += "]"; } ti.text = s; - ti.baseColour = glm::vec3(0.f, 0.f, 0.f); + ti.baseColour = ui_shadowColour; ti.screenPosition = glm::vec2(wantedX + 1.f, wantedY + 1.f); render->text.renderText(ti); @@ -183,7 +183,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, GameRenderer* re ti.text = std::to_string(slotInfo.bulletsClip) + "-" + std::to_string(slotInfo.bulletsTotal); - ti.baseColour = glm::vec3(0.f, 0.f, 0.f); + ti.baseColour = ui_shadowColour; ti.font = 2; ti.size = ui_ammoSize; ti.align = TextRenderer::TextInfo::Center; @@ -204,13 +204,58 @@ void drawHUD(ViewCamera& currentView, PlayerController* player, GameWorld* world void drawOnScreenText(GameWorld* world, GameRenderer* renderer) { - const glm::ivec2& vp = renderer->getRenderer()->getViewport(); + const auto vp = glm::vec2(renderer->getRenderer()->getViewport()); TextRenderer::TextInfo ti; ti.font = 2; ti.screenPosition = glm::vec2( 10.f, 10.f ); ti.size = 20.f; - + + auto& alltext = world->state->text.getAllText(); + + for(auto& l : alltext) + { + for (auto& t : l) + { + ti.size = t.size; + ti.font = t.font; + ti.text = t.text; + ti.wrapX = t.wrapX; + ti.screenPosition = (t.position/glm::vec2(640.f, 480.f)) * vp; + switch(t.alignment) { + case 0: + ti.align = TextRenderer::TextInfo::Left; + break; + case 1: + ti.align = TextRenderer::TextInfo::Center; + break; + case 2: + ti.align = TextRenderer::TextInfo::Right; + break; + } + + // Check for the background type + if (t.colourBG.a == 0) + { + ti.baseColour = glm::vec3(0.f); + ti.screenPosition += glm::vec2(t.colourBG.x, t.colourBG.y); + ti.backgroundColour = {0, 0, 0, 0}; + + renderer->text.renderText(ti); + + ti.screenPosition -= glm::vec2(t.colourBG.x, t.colourBG.y); + } + else if(t.colourBG.a > 0) + { + ti.backgroundColour = t.colourBG; + } + + ti.baseColour = t.colourFG; + renderer->text.renderText(ti); + } + } + +#if 0 for(OnscreenText& t : world->state->text) { glm::vec2 shadowOffset( 0, 0 ); @@ -271,15 +316,5 @@ void drawOnScreenText(GameWorld* world, GameRenderer* renderer) renderer->text.renderText(ti); } - - for(auto& t : world->state->texts) { - ti.font = 2; - ti.screenPosition = t.position / glm::vec2(640, 480); - ti.screenPosition *= vp; - ti.baseColour = glm::vec3(t.colourFG); - ti.size = 20.f; - ti.text = t.text; - - renderer->text.renderText(ti); - } +#endif } diff --git a/rwgame/MenuSystem.hpp b/rwgame/MenuSystem.hpp index 6a9b257c..55b1590a 100644 --- a/rwgame/MenuSystem.hpp +++ b/rwgame/MenuSystem.hpp @@ -36,11 +36,11 @@ public: ti.size = getHeight(); if( ! active ) { - ti.baseColour = glm::vec3(1.f, 1.f, 1.f); + ti.baseColour = glm::u8vec3(255); } else { - ti.baseColour = glm::vec3(1.f, 1.f, 0.f); + ti.baseColour = glm::u8vec3(255, 255, 0); } r->text.renderText(ti); basis.y += getHeight(); diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index e26a8882..e7e6f37c 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -438,20 +438,8 @@ void RWGame::tick(float dt) } world->destroyQueuedObjects(); - state->texts.clear(); - for( int i = 0; i < state->text.size(); ) - { - auto& text = state->text[i]; - if( world->getGameTime() > text.osTextStart + text.osTextTime ) - { - state->text.erase(state->text.begin() + i); - } - else - { - i++; - } - } + state->text.tick(dt); world->dynamicsWorld->stepSimulation(dt, 2, dt); @@ -656,6 +644,7 @@ void RWGame::renderDebugStats(float time, Renderer::ProfileInfo& worldRenderTime ti.font = 2; ti.screenPosition = glm::vec2( 10.f, 10.f ); ti.size = 15.f; + ti.baseColour = glm::u8vec3(255); renderer->text.renderText(ti); /*while( engine->log.size() > 0 && engine->log.front().time + 10.f < engine->gameTime ) { @@ -752,6 +741,7 @@ void RWGame::renderProfile() ti.align = TextRenderer::TextInfo::Left; ti.font = 2; ti.size = lineHeight - 2.f; + ti.baseColour = glm::u8vec3(255); std::function renderEntry = [&](const perf::ProfileEntry& entry, int depth) { int g = 0; diff --git a/rwgame/debugstate.cpp b/rwgame/debugstate.cpp index cf2f5f32..034f6473 100644 --- a/rwgame/debugstate.cpp +++ b/rwgame/debugstate.cpp @@ -195,6 +195,7 @@ void DebugState::draw(GameRenderer* r) ti.font = 2; ti.screenPosition = glm::vec2( 10.f, 10.f ); ti.size = 15.f; + ti.baseColour = glm::u8vec3(255); r->text.renderText(ti); State::draw(r); diff --git a/rwgame/loadingstate.cpp b/rwgame/loadingstate.cpp index 845b56e6..8b98d802 100644 --- a/rwgame/loadingstate.cpp +++ b/rwgame/loadingstate.cpp @@ -55,5 +55,6 @@ void LoadingState::draw(GameRenderer* r) ti.size = 25.f; ti.screenPosition = glm::vec2( 50.f, size.y - ti.size - 50.f ); ti.font = 2; + ti.baseColour = glm::u8vec3(255); r->text.renderText(ti); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 670feb60..7f29e0df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -44,7 +44,8 @@ include_directories("${CMAKE_SOURCE_DIR}/tests") find_package(Boost COMPONENTS unit_test_framework REQUIRED) -include_directories("${CMAKE_SOURCE_DIR}/rwengine/include" "${CMAKE_SOURCE_DIR}/rwgame" ${BULLET_INCLUDE_DIR}) +include_directories("${CMAKE_SOURCE_DIR}/rwengine/include" "${CMAKE_SOURCE_DIR}/rwgame") +include_directories(${BULLET_INCLUDE_DIR} SYSTEM) target_link_libraries(run_tests rwengine diff --git a/tests/test_text.cpp b/tests/test_text.cpp index b29ed9a7..cd73f5af 100644 --- a/tests/test_text.cpp +++ b/tests/test_text.cpp @@ -2,6 +2,7 @@ #include "test_globals.hpp" #include #include +#include BOOST_AUTO_TEST_SUITE(TextTests) @@ -20,4 +21,150 @@ BOOST_AUTO_TEST_CASE(load_test) } } +BOOST_AUTO_TEST_CASE(big_test) +{ + // Check that makeBig creates a text in the right place + { + auto big = ScreenTextEntry::makeBig( + "TEST_1", + "Test String", + 1, + 5000); + + BOOST_CHECK_EQUAL("TEST_1", big.id); + BOOST_CHECK_EQUAL("Test String", big.text); + BOOST_CHECK_EQUAL(5000, big.durationMS); + BOOST_CHECK_EQUAL(0, big.displayedMS); + BOOST_CHECK_EQUAL(1, big.alignment); + BOOST_CHECK_EQUAL(50, big.size); + } + { + auto big = ScreenTextEntry::makeBig( + "TEST_1", + "Test String", + 2, + 5000); + + BOOST_CHECK_EQUAL("Test String", big.text); + BOOST_CHECK_EQUAL(5000, big.durationMS); + BOOST_CHECK_EQUAL(0, big.displayedMS); + BOOST_CHECK_EQUAL(2, big.alignment); + BOOST_CHECK_EQUAL(30, big.size); + } +} + +BOOST_AUTO_TEST_CASE(help_test) +{ + auto help = ScreenTextEntry::makeHelp( + "TEST_1", + "Test Help"); + + BOOST_CHECK_EQUAL("Test Help", help.text); + BOOST_CHECK_EQUAL(5000, help.durationMS); + BOOST_CHECK_EQUAL(0, help.displayedMS); + BOOST_CHECK_EQUAL(18, help.size); +} + +BOOST_AUTO_TEST_CASE(queue_test) +{ + // Check that creating a test puts it on the right queue. + + ScreenText st; + + st.addText( + ScreenTextEntry::makeBig( + "TEST_1", + "Test String", + 2, + 5000)); + st.addText( + ScreenTextEntry::makeHighPriority( + "TEST_1", + "Test String", + 5000)); + + BOOST_REQUIRE(st.getText().size() == 1); + BOOST_CHECK_EQUAL("Test String", st.getText()[0].text); + BOOST_CHECK_EQUAL(5000, st.getText()[0].durationMS); + BOOST_CHECK_EQUAL(0, st.getText()[0].displayedMS); + + BOOST_CHECK_EQUAL(1, st.getText().size()); + + st.tick(6.f); + + BOOST_CHECK_EQUAL(0, st.getText().size()); + BOOST_CHECK_EQUAL(0, st.getText().size()); +} + +BOOST_AUTO_TEST_CASE(clear_test) +{ + ScreenText st; + + st.addText( + ScreenTextEntry::makeBig( + "TEST_1", + "Test String", + 2, + 5000)); + + BOOST_CHECK_EQUAL(1, st.getText().size()); + + st.clear(); + + BOOST_CHECK_EQUAL(0, st.getText().size()); +} + +BOOST_AUTO_TEST_CASE(format_test) +{ + // Test formating of string codes into strings. + const auto codeStr1 = "Hello ~1~ world"; + const auto codeStr2 = "~1~Hello ~1~ world~1~"; + const auto codeStr3 = "Hello world~1~"; + + auto f1 = ScreenText::format(codeStr1, "r"); + BOOST_CHECK_EQUAL(f1, "Hello r world"); + + auto f2 = ScreenText::format(codeStr2, "k", "h"); + BOOST_CHECK_EQUAL(f2, "kHello h world~1~"); + + auto f3 = ScreenText::format(codeStr3, "x"); + BOOST_CHECK_EQUAL(f3, "Hello worldx"); + + auto f4 = ScreenText::format(codeStr3, "x", "k"); + BOOST_CHECK_EQUAL(f4, "Hello worldx"); +} + +BOOST_AUTO_TEST_CASE(format_remove) +{ + // Test removing an identified string from the list + ScreenText st; + + st.addText( + ScreenTextEntry::makeBig( + "TEST_2", + "Test String", + 2, + 5000)); + + st.addText( + ScreenTextEntry::makeBig( + "TEST_1", + "Test String", + 2, + 5000)); + + st.addText( + ScreenTextEntry::makeBig( + "TEST_1", + "Test String", + 2, + 5000)); + + BOOST_CHECK_EQUAL(3, st.getText().size()); + + st.remove("TEST_1"); + + BOOST_CHECK_EQUAL(1, st.getText().size()); +} + BOOST_AUTO_TEST_SUITE_END()