// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2023 by James Robert Roman // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- #include #include "doomdef.h" // skincolornum_t #include "doomtype.h" #include "hu_stuff.h" #include "i_time.h" #include "k_hud.h" #include "m_fixed.h" #include "r_draw.h" #include "v_draw.hpp" #include "v_video.h" #include "w_wad.h" #include "z_zone.h" using srb2::Draw; using Chain = Draw::Chain; int Draw::TextElement::width() const { return font_ ? font_width(*font_, default_font_flags(*font_) | flags_.value_or(0), string_.c_str()) / FRACUNIT : 0; } Draw::TextElement& Draw::TextElement::parse(std::string_view raw) { static const std::unordered_map translation = { #define BUTTON(str, lower_bits) \ {str, 0xB0 | lower_bits},\ {str "_animated", 0xA0 | lower_bits},\ {str "_pressed", 0x90 | lower_bits} BUTTON("up", 0x00), BUTTON("down", 0x01), BUTTON("right", 0x02), BUTTON("left", 0x03), BUTTON("r", 0x07), BUTTON("l", 0x08), BUTTON("start", 0x09), BUTTON("a", 0x0A), BUTTON("b", 0x0B), BUTTON("c", 0x0C), BUTTON("x", 0x0D), BUTTON("y", 0x0E), BUTTON("z", 0x0F), #undef BUTTON {"white", 0x80}, {"purple", 0x81}, {"yellow", 0x82}, {"green", 0x83}, {"blue", 0x84}, {"red", 0x85}, {"gray", 0x86}, {"orange", 0x87}, {"sky", 0x88}, {"lavender", 0x89}, {"gold", 0x8A}, {"aqua", 0x8B}, {"magenta", 0x8C}, {"pink", 0x8D}, {"brown", 0x8E}, {"tan", 0x8F}, }; string_.clear(); string_.reserve(raw.size()); using std::size_t; using std::string_view; for (;;) { size_t p = raw.find('<'); // Copy characters until the start tag string_.append(raw.substr(0, p)); if (p == raw.npos) { break; // end of string } raw.remove_prefix(p); // Find end tag p = raw.find('>'); if (p == raw.npos) { break; // no end tag } string_view code = raw.substr(1, p - 1); if (auto it = translation.find(code); it != translation.end()) { string_.push_back(it->second); // replace with character code } else { string_.append(raw.substr(0, p + 1)); // code not found, leave text verbatim } raw.remove_prefix(p + 1); // past end of tag } return *this; } void Chain::patch(patch_t* patch) const { const auto _ = Clipper(*this); const bool stretchH = stretch_ == Stretch::kWidth || stretch_ == Stretch::kBoth; const bool stretchV = stretch_ == Stretch::kHeight || stretch_ == Stretch::kBoth; const fixed_t h = stretchH ? FloatToFixed(width_ / patch->width) : FRACUNIT; const fixed_t v = stretchV ? FloatToFixed(height_ / patch->height) : FRACUNIT; V_DrawStretchyFixedPatch(FloatToFixed(x_), FloatToFixed(y_), h * scale_, v * scale_, flags_, patch, colormap_); } void Chain::thumbnail(UINT16 mapnum) const { const auto _ = Clipper(*this); K_DrawMapThumbnail(FloatToFixed(x_), FloatToFixed(y_), FloatToFixed(width_), flags_, mapnum, colormap_); } void Chain::fill(UINT8 color) const { const auto _ = Clipper(*this); V_DrawFill(x_, y_, width_, height_, color|(flags_ & ~0xFF)); } void Chain::string(const char* str, INT32 flags, Font font) const { const auto _ = Clipper(*this); flags |= default_font_flags(font); fixed_t x = FloatToFixed(x_); fixed_t y = FloatToFixed(y_); switch (align_) { case Align::kLeft: break; case Align::kCenter: x -= (font_width(font, flags, str) / 2) * scale_; break; case Align::kRight: x -= font_width(font, flags, str) * scale_; break; } V_DrawStringScaled(x, y, FloatToFixed(scale_), FRACUNIT, FRACUNIT, flags, colormap_, font_to_fontno(font), str); } namespace { patch_t** get_button_patch(Draw::Button type, int ver) { switch (type) { #define X(x) \ case Draw::Button::x:\ return kp_button_ ## x X(a)[ver]; X(b)[ver]; X(c)[ver]; X(x)[ver]; X(y)[ver]; X(z)[ver]; X(start); X(l); X(r); X(up); X(down); X(right); X(left); #undef X } return nullptr; }; }; // namespace void Chain::button_(Button type, int ver, std::optional press) const { const auto _ = Clipper(*this); if (press) { K_drawButton(FloatToFixed(x_), FloatToFixed(y_), flags_, get_button_patch(type, ver), *press); } else { K_drawButtonAnim(x_, y_, flags_, get_button_patch(type, ver), I_GetTime()); } } void Chain::sticker(patch_t* end_graphic, UINT8 color) const { const auto _ = Clipper(*this); INT32 x = x_; INT32 y = y_; INT32 width = width_; INT32 flags = flags_ | V_FLIP; auto fill = [&](int x, int width) { V_DrawFill(x, y, width, SHORT(end_graphic->height), color | (flags_ & ~0xFF)); }; if (align_ == Align::kRight) { width = -(width); flags ^= V_FLIP; fill(x + width, -(width)); } else { fill(x, width); } V_DrawScaledPatch(x + width, y, flags, end_graphic); if (align_ == Align::kCenter) { V_DrawScaledPatch(x, y, flags ^ V_FLIP, end_graphic); } } Chain::Clipper::Clipper(const Chain& chain) { V_SaveClipRect(&save_); if (chain.clip_) { V_SetClipRect( FloatToFixed(chain.clipx1_), FloatToFixed(chain.clipy1_), FloatToFixed(chain.clipx2_ - chain.clipx1_), FloatToFixed(chain.clipy2_ - chain.clipy1_), chain.flags_ ); } } Chain::Clipper::~Clipper() { V_RestoreClipRect(&save_); } patch_t* Draw::cache_patch(const char* name) { return static_cast(W_CachePatchName(name, PU_CACHE)); } int Draw::font_to_fontno(Font font) { switch (font) { case Font::kThin: return TINY_FONT; case Font::kGamemode: return GM_FONT; case Font::kConsole: return HU_FONT; case Font::kFreeplay: return KART_FONT; case Font::kZVote: return OPPRF_FONT; case Font::kPing: return PINGF_FONT; case Font::kTimer: return TIMER_FONT; case Font::kThinTimer: return TINYTIMER_FONT; case Font::kMenu: return MENU_FONT; case Font::kMedium: return MED_FONT; case Font::kRollingNum: return ROLNUM_FONT; case Font::kRollingNum4P: return RO4NUM_FONT; } return TINY_FONT; }; INT32 Draw::default_font_flags(Font font) { INT32 flags = 0; (void)font; return flags; }; fixed_t Draw::font_width(Font font, INT32 flags, const char* string) { return V_StringScaledWidth(FRACUNIT, FRACUNIT, FRACUNIT, flags, font_to_fontno(font), string); }