diff --git a/src/common/2d/v_drawtext.cpp b/src/common/2d/v_drawtext.cpp index 539d72a04..5c06457b4 100644 --- a/src/common/2d/v_drawtext.cpp +++ b/src/common/2d/v_drawtext.cpp @@ -44,6 +44,7 @@ #include "vm.h" #include "printf.h" + int ListGetInt(VMVa_List &tags); diff --git a/src/g_statusbar/base_sbar.cpp b/src/g_statusbar/base_sbar.cpp index fc00d107a..cd0fb577d 100644 --- a/src/g_statusbar/base_sbar.cpp +++ b/src/g_statusbar/base_sbar.cpp @@ -42,19 +42,24 @@ #include "cmdlib.h" #include "texturemanager.h" #include "c_cvars.h" +#include "v_font.h" +#include "utf8.h" +#include "v_text.h" #include "vm.h" FGameTexture* CrosshairImage; static int CrosshairNum; -IMPLEMENT_CLASS(DStatusBarCore, false, false) +IMPLEMENT_CLASS(DStatusBarCore, true, false) +IMPLEMENT_CLASS(DHUDFont, true, false); CVAR(Color, crosshaircolor, 0xff0000, CVAR_ARCHIVE); CVAR(Int, crosshairhealth, 1, CVAR_ARCHIVE); CVAR(Float, crosshairscale, 1.0, CVAR_ARCHIVE); CVAR(Bool, crosshairgrow, false, CVAR_ARCHIVE); +EXTERN_CVAR(Bool, vid_fps) @@ -228,16 +233,437 @@ void FormatNumber(int number, int minsize, int maxsize, int flags, const FString else fmt.Format("%s%*d", prefix.GetChars(), minsize, number); } -DEFINE_ACTION_FUNCTION_NATIVE(DStatusBarCore, FormatNumber, FormatNumber) +void DStatusBarCore::ValidateResolution(int& hres, int& vres) const { - PARAM_PROLOGUE; - PARAM_INT(number); - PARAM_INT(minsize); - PARAM_INT(maxsize); - PARAM_INT(flags); - PARAM_STRING(prefix); - FString fmt; - FormatNumber(number, minsize, maxsize, flags, prefix, &fmt); - ACTION_RETURN_STRING(fmt); + if (hres == 0) + { + static const int HORIZONTAL_RESOLUTION_DEFAULT = 320; + hres = HORIZONTAL_RESOLUTION_DEFAULT; + } + + if (vres == 0) + { + static const int VERTICAL_RESOLUTION_DEFAULT = 200; + vres = VERTICAL_RESOLUTION_DEFAULT; + } } + +//============================================================================ +// +// draw stuff +// +//============================================================================ + +void DStatusBarCore::StatusbarToRealCoords(double& x, double& y, double& w, double& h) const +{ + if (SBarScale.X == -1 || ForcedScale) + { + int hres = HorizontalResolution; + int vres = VerticalResolution; + ValidateResolution(hres, vres); + + VirtualToRealCoords(twod, x, y, w, h, hres, vres, true, true); + } + else + { + x = ST_X + x * SBarScale.X; + y = ST_Y + y * SBarScale.Y; + w *= SBarScale.X; + h *= SBarScale.Y; + } +} + +//============================================================================ +// +// draw stuff +// +//============================================================================ + +void DStatusBarCore::DrawGraphic(FTextureID texture, double x, double y, int flags, double Alpha, double boxwidth, double boxheight, double scaleX, double scaleY) +{ + if (!texture.isValid()) + return; + + FGameTexture* tex = TexMan.GetGameTexture(texture, !(flags & DI_DONTANIMATE)); + + double texwidth = tex->GetDisplayWidth() * scaleX; + double texheight = tex->GetDisplayHeight() * scaleY; + + if (boxwidth > 0 || boxheight > 0) + { + if (!(flags & DI_FORCEFILL)) + { + double scale1 = 1., scale2 = 1.; + + if (boxwidth > 0 && (boxwidth < texwidth || (flags & DI_FORCESCALE))) + { + scale1 = boxwidth / texwidth; + } + if (boxheight != -1 && (boxheight < texheight || (flags & DI_FORCESCALE))) + { + scale2 = boxheight / texheight; + } + + if (flags & DI_FORCESCALE) + { + if (boxwidth <= 0 || (boxheight > 0 && scale2 < scale1)) + scale1 = scale2; + } + else scale1 = MIN(scale1, scale2); + + boxwidth = texwidth * scale1; + boxheight = texheight * scale1; + } + } + else + { + boxwidth = texwidth; + boxheight = texheight; + } + + // resolve auto-alignment before making any adjustments to the position values. + if (!(flags & DI_SCREEN_MANUAL_ALIGN)) + { + if (x < 0) flags |= DI_SCREEN_RIGHT; + else flags |= DI_SCREEN_LEFT; + if (y < 0) flags |= DI_SCREEN_BOTTOM; + else flags |= DI_SCREEN_TOP; + } + + Alpha *= this->Alpha; + if (Alpha <= 0) return; + x += drawOffset.X; + y += drawOffset.Y; + + switch (flags & DI_ITEM_HMASK) + { + case DI_ITEM_HCENTER: x -= boxwidth / 2; break; + case DI_ITEM_RIGHT: x -= boxwidth; break; + case DI_ITEM_HOFFSET: x -= tex->GetDisplayLeftOffset() * boxwidth / texwidth; break; + } + + switch (flags & DI_ITEM_VMASK) + { + case DI_ITEM_VCENTER: y -= boxheight / 2; break; + case DI_ITEM_BOTTOM: y -= boxheight; break; + case DI_ITEM_VOFFSET: y -= tex->GetDisplayTopOffset() * boxheight / texheight; break; + } + + if (!fullscreenOffsets) + { + StatusbarToRealCoords(x, y, boxwidth, boxheight); + } + else + { + double orgx, orgy; + + switch (flags & DI_SCREEN_HMASK) + { + default: orgx = 0; break; + case DI_SCREEN_HCENTER: orgx = twod->GetWidth() / 2; break; + case DI_SCREEN_RIGHT: orgx = twod->GetWidth(); break; + } + + switch (flags & DI_SCREEN_VMASK) + { + default: orgy = 0; break; + case DI_SCREEN_VCENTER: orgy = twod->GetHeight() / 2; break; + case DI_SCREEN_BOTTOM: orgy = twod->GetHeight(); break; + } + + // move stuff in the top right corner a bit down if the fps counter is on. + if ((flags & (DI_SCREEN_HMASK | DI_SCREEN_VMASK)) == DI_SCREEN_RIGHT_TOP && vid_fps) y += 10; + + DVector2 Scale = GetHUDScale(); + + x *= Scale.X; + y *= Scale.Y; + boxwidth *= Scale.X; + boxheight *= Scale.Y; + x += orgx; + y += orgy; + } + DrawTexture(twod, tex, x, y, + DTA_TopOffset, 0, + DTA_LeftOffset, 0, + DTA_DestWidthF, boxwidth, + DTA_DestHeightF, boxheight, + DTA_TranslationIndex, (flags & DI_TRANSLATABLE) ? GetTranslation() : 0, + DTA_ColorOverlay, (flags & DI_DIM) ? MAKEARGB(170, 0, 0, 0) : 0, + DTA_Alpha, Alpha, + DTA_AlphaChannel, !!(flags & DI_ALPHAMAPPED), + DTA_FillColor, (flags & DI_ALPHAMAPPED) ? 0 : -1, + DTA_FlipX, !!(flags & DI_MIRROR), + TAG_DONE); +} + + +//============================================================================ +// +// draw a string +// +//============================================================================ + +void DStatusBarCore::DrawString(FFont* font, const FString& cstring, double x, double y, int flags, double Alpha, int translation, int spacing, EMonospacing monospacing, int shadowX, int shadowY, double scaleX, double scaleY) +{ + bool monospaced = monospacing != EMonospacing::Off; + double dx = 0; + int spacingparm = monospaced ? -spacing : spacing; + + switch (flags & DI_TEXT_ALIGN) + { + default: + break; + case DI_TEXT_ALIGN_RIGHT: + dx = font->StringWidth(cstring, spacingparm); + break; + case DI_TEXT_ALIGN_CENTER: + dx = font->StringWidth(cstring, spacingparm) / 2; + break; + } + + // Take text scale into account + x -= dx * scaleX; + + const uint8_t* str = (const uint8_t*)cstring.GetChars(); + const EColorRange boldTranslation = EColorRange(translation ? translation - 1 : NumTextColors - 1); + int fontcolor = translation; + double orgx = 0, orgy = 0; + DVector2 Scale; + + if (fullscreenOffsets) + { + Scale = GetHUDScale(); + shadowX *= (int)Scale.X; + shadowY *= (int)Scale.Y; + + switch (flags & DI_SCREEN_HMASK) + { + default: orgx = 0; break; + case DI_SCREEN_HCENTER: orgx = twod->GetWidth() / 2; break; + case DI_SCREEN_RIGHT: orgx = twod->GetWidth(); break; + } + + switch (flags & DI_SCREEN_VMASK) + { + default: orgy = 0; break; + case DI_SCREEN_VCENTER: orgy = twod->GetHeight() / 2; break; + case DI_SCREEN_BOTTOM: orgy = twod->GetHeight(); break; + } + + // move stuff in the top right corner a bit down if the fps counter is on. + if ((flags & (DI_SCREEN_HMASK | DI_SCREEN_VMASK)) == DI_SCREEN_RIGHT_TOP && vid_fps) y += 10; + } + else + { + Scale = { 1.,1. }; + } + int ch; + while (ch = GetCharFromString(str), ch != '\0') + { + if (ch == ' ') + { + x += monospaced ? spacing : font->GetSpaceWidth() + spacing; + continue; + } + else if (ch == TEXTCOLOR_ESCAPE) + { + EColorRange newColor = V_ParseFontColor(str, translation, boldTranslation); + if (newColor != CR_UNDEFINED) + fontcolor = newColor; + continue; + } + + int width; + auto c = font->GetChar(ch, fontcolor, &width); + if (c == NULL) //missing character. + { + continue; + } + + if (!monospaced) //If we are monospaced lets use the offset + x += (c->GetDisplayLeftOffset() + 1); //ignore x offsets since we adapt to character size + + double rx, ry, rw, rh; + rx = x + drawOffset.X; + ry = y + drawOffset.Y; + rw = c->GetDisplayWidth(); + rh = c->GetDisplayHeight(); + + if (monospacing == EMonospacing::CellCenter) + rx += (spacing - rw) / 2; + else if (monospacing == EMonospacing::CellRight) + rx += (spacing - rw); + + if (!fullscreenOffsets) + { + StatusbarToRealCoords(rx, ry, rw, rh); + } + else + { + rx *= Scale.X; + ry *= Scale.Y; + rw *= Scale.X; + rh *= Scale.Y; + + rx += orgx; + ry += orgy; + } + + // Apply text scale + rw *= scaleX; + rh *= scaleY; + + // This is not really such a great way to draw shadows because they can overlap with previously drawn characters. + // This may have to be changed to draw the shadow text up front separately. + if ((shadowX != 0 || shadowY != 0) && !(flags & DI_NOSHADOW)) + { + DrawChar(twod, font, CR_UNTRANSLATED, rx + shadowX, ry + shadowY, ch, + DTA_DestWidthF, rw, + DTA_DestHeightF, rh, + DTA_Alpha, (Alpha * 0.4), + DTA_FillColor, 0, + TAG_DONE); + } + DrawChar(twod, font, fontcolor, rx, ry, ch, + DTA_DestWidthF, rw, + DTA_DestHeightF, rh, + DTA_Alpha, Alpha, + TAG_DONE); + + dx = monospaced + ? spacing + : width + spacing - (c->GetDisplayLeftOffset() + 1); + + // Take text scale into account + x += dx * scaleX; + } +} + +void SBar_DrawString(DStatusBarCore* self, DHUDFont* font, const FString& string, double x, double y, int flags, int trans, double alpha, int wrapwidth, int linespacing, double scaleX, double scaleY) +{ + if (font == nullptr) ThrowAbortException(X_READ_NIL, nullptr); + if (!twod->HasBegun2D()) ThrowAbortException(X_OTHER, "Attempt to draw to screen outside a draw function"); + + // resolve auto-alignment before making any adjustments to the position values. + if (!(flags & DI_SCREEN_MANUAL_ALIGN)) + { + if (x < 0) flags |= DI_SCREEN_RIGHT; + else flags |= DI_SCREEN_LEFT; + if (y < 0) flags |= DI_SCREEN_BOTTOM; + else flags |= DI_SCREEN_TOP; + } + + if (wrapwidth > 0) + { + auto brk = V_BreakLines(font->mFont, int(wrapwidth * scaleX), string, true); + for (auto& line : brk) + { + self->DrawString(font->mFont, line.Text, x, y, flags, alpha, trans, font->mSpacing, font->mMonospacing, font->mShadowX, font->mShadowY, scaleX, scaleY); + y += (font->mFont->GetHeight() + linespacing) * scaleY; + } + } + else + { + self->DrawString(font->mFont, string, x, y, flags, alpha, trans, font->mSpacing, font->mMonospacing, font->mShadowX, font->mShadowY, scaleX, scaleY); + } +} + + +//============================================================================ +// +// draw stuff +// +//============================================================================ + +void DStatusBarCore::TransformRect(double& x, double& y, double& w, double& h, int flags) +{ + // resolve auto-alignment before making any adjustments to the position values. + if (!(flags & DI_SCREEN_MANUAL_ALIGN)) + { + if (x < 0) flags |= DI_SCREEN_RIGHT; + else flags |= DI_SCREEN_LEFT; + if (y < 0) flags |= DI_SCREEN_BOTTOM; + else flags |= DI_SCREEN_TOP; + } + + x += drawOffset.X; + y += drawOffset.Y; + + if (!fullscreenOffsets) + { + StatusbarToRealCoords(x, y, w, h); + } + else + { + double orgx, orgy; + + switch (flags & DI_SCREEN_HMASK) + { + default: orgx = 0; break; + case DI_SCREEN_HCENTER: orgx = twod->GetWidth() / 2; break; + case DI_SCREEN_RIGHT: orgx = twod->GetWidth(); break; + } + + switch (flags & DI_SCREEN_VMASK) + { + default: orgy = 0; break; + case DI_SCREEN_VCENTER: orgy = twod->GetHeight() / 2; break; + case DI_SCREEN_BOTTOM: orgy = twod->GetHeight(); break; + } + + // move stuff in the top right corner a bit down if the fps counter is on. + if ((flags & (DI_SCREEN_HMASK | DI_SCREEN_VMASK)) == DI_SCREEN_RIGHT_TOP && vid_fps) y += 10; + + DVector2 Scale = GetHUDScale(); + + x *= Scale.X; + y *= Scale.Y; + w *= Scale.X; + h *= Scale.Y; + x += orgx; + y += orgy; + } +} + + +//============================================================================ +// +// draw stuff +// +//============================================================================ + +void DStatusBarCore::Fill(PalEntry color, double x, double y, double w, double h, int flags) +{ + double Alpha = color.a * this->Alpha / 255; + if (Alpha <= 0) return; + + TransformRect(x, y, w, h, flags); + + int x1 = int(x); + int y1 = int(y); + int ww = int(x + w - x1); // account for scaling to non-integers. Truncating the values separately would fail for cases like + int hh = int(y + h - y1); // y=3.5, height = 5.5 where adding both values gives a larger integer than adding the two integers. + + Dim(twod, color, float(Alpha), x1, y1, ww, hh); +} + + +//============================================================================ +// +// draw stuff +// +//============================================================================ + +void DStatusBarCore::SetClipRect(double x, double y, double w, double h, int flags) +{ + TransformRect(x, y, w, h, flags); + int x1 = int(x); + int y1 = int(y); + int ww = int(x + w - x1); // account for scaling to non-integers. Truncating the values separately would fail for cases like + int hh = int(y + h - y1); // y=3.5, height = 5.5 where adding both values gives a larger integer than adding the two integers. + twod->SetClipRect(x1, y1, ww, hh); +} + + diff --git a/src/g_statusbar/base_sbar.h b/src/g_statusbar/base_sbar.h index db0de04e7..794df9425 100644 --- a/src/g_statusbar/base_sbar.h +++ b/src/g_statusbar/base_sbar.h @@ -1,16 +1,171 @@ #pragma once #include "dobject.h" +#include "textureid.h" +#include "zstring.h" +#include "v_draw.h" + class FGameTexture; +class FFont; extern FGameTexture* CrosshairImage; void ST_LoadCrosshair(int num, bool alwaysload); void ST_UnloadCrosshair(); void ST_DrawCrosshair(int phealth, double xpos, double ypos, double scale); +enum DI_Flags +{ + DI_SKIPICON = 0x1, + DI_SKIPALTICON = 0x2, + DI_SKIPSPAWN = 0x4, + DI_SKIPREADY = 0x8, + DI_ALTICONFIRST = 0x10, + DI_TRANSLATABLE = 0x20, + DI_FORCESCALE = 0x40, + DI_DIM = 0x80, + DI_DRAWCURSORFIRST = 0x100, // only for DrawInventoryBar. + DI_ALWAYSSHOWCOUNT = 0x200, // only for DrawInventoryBar. + DI_DIMDEPLETED = 0x400, + DI_DONTANIMATE = 0x800, // do not animate the texture + DI_MIRROR = 0x1000, // flip the texture horizontally, like a mirror + + DI_SCREEN_AUTO = 0, // decide based on given offsets. + DI_SCREEN_MANUAL_ALIGN = 0x4000, // If this is on, the following flags will have an effect + + DI_SCREEN_TOP = DI_SCREEN_MANUAL_ALIGN, + DI_SCREEN_VCENTER = 0x8000 | DI_SCREEN_MANUAL_ALIGN, + DI_SCREEN_BOTTOM = 0x10000 | DI_SCREEN_MANUAL_ALIGN, + DI_SCREEN_VOFFSET = 0x18000 | DI_SCREEN_MANUAL_ALIGN, + DI_SCREEN_VMASK = 0x18000 | DI_SCREEN_MANUAL_ALIGN, + + DI_SCREEN_LEFT = DI_SCREEN_MANUAL_ALIGN, + DI_SCREEN_HCENTER = 0x20000 | DI_SCREEN_MANUAL_ALIGN, + DI_SCREEN_RIGHT = 0x40000 | DI_SCREEN_MANUAL_ALIGN, + DI_SCREEN_HOFFSET = 0x60000 | DI_SCREEN_MANUAL_ALIGN, + DI_SCREEN_HMASK = 0x60000 | DI_SCREEN_MANUAL_ALIGN, + + DI_SCREEN_LEFT_TOP = DI_SCREEN_TOP | DI_SCREEN_LEFT, + DI_SCREEN_RIGHT_TOP = DI_SCREEN_TOP | DI_SCREEN_RIGHT, + DI_SCREEN_LEFT_BOTTOM = DI_SCREEN_BOTTOM | DI_SCREEN_LEFT, + DI_SCREEN_RIGHT_BOTTOM = DI_SCREEN_BOTTOM | DI_SCREEN_RIGHT, + DI_SCREEN_CENTER = DI_SCREEN_VCENTER | DI_SCREEN_HCENTER, + DI_SCREEN_CENTER_BOTTOM = DI_SCREEN_BOTTOM | DI_SCREEN_HCENTER, + DI_SCREEN_OFFSETS = DI_SCREEN_HOFFSET | DI_SCREEN_VOFFSET, + + DI_ITEM_AUTO = 0, // equivalent with bottom center, which is the default alignment. + + DI_ITEM_TOP = 0x80000, + DI_ITEM_VCENTER = 0x100000, + DI_ITEM_BOTTOM = 0, // this is the default vertical alignment + DI_ITEM_VOFFSET = 0x180000, + DI_ITEM_VMASK = 0x180000, + + DI_ITEM_LEFT = 0x200000, + DI_ITEM_HCENTER = 0, // this is the deafault horizontal alignment + DI_ITEM_RIGHT = 0x400000, + DI_ITEM_HOFFSET = 0x600000, + DI_ITEM_HMASK = 0x600000, + + DI_ITEM_LEFT_TOP = DI_ITEM_TOP | DI_ITEM_LEFT, + DI_ITEM_RIGHT_TOP = DI_ITEM_TOP | DI_ITEM_RIGHT, + DI_ITEM_LEFT_BOTTOM = DI_ITEM_BOTTOM | DI_ITEM_LEFT, + DI_ITEM_RIGHT_BOTTOM = DI_ITEM_BOTTOM | DI_ITEM_RIGHT, + DI_ITEM_CENTER = DI_ITEM_VCENTER | DI_ITEM_HCENTER, + DI_ITEM_CENTER_BOTTOM = DI_ITEM_BOTTOM | DI_ITEM_HCENTER, + DI_ITEM_OFFSETS = DI_ITEM_HOFFSET | DI_ITEM_VOFFSET, + + DI_TEXT_ALIGN_LEFT = 0, + DI_TEXT_ALIGN_RIGHT = 0x800000, + DI_TEXT_ALIGN_CENTER = 0x1000000, + DI_TEXT_ALIGN = 0x1800000, + + DI_ALPHAMAPPED = 0x2000000, + DI_NOSHADOW = 0x4000000, + DI_ALWAYSSHOWCOUNTERS = 0x8000000, + DI_ARTIFLASH = 0x10000000, + DI_FORCEFILL = 0x20000000, + + // These 2 flags are only used by SBARINFO so these duplicate other flags not used by SBARINFO + DI_DRAWINBOX = DI_TEXT_ALIGN_RIGHT, + DI_ALTERNATEONFAIL = DI_TEXT_ALIGN_CENTER, + +}; + +//============================================================================ +// +// encapsulates all settings a HUD font may need +// +//============================================================================ + +class DHUDFont : public DObject +{ + // this blocks CreateNew on this class which is the intent here. + DECLARE_ABSTRACT_CLASS(DHUDFont, DObject); + +public: + FFont* mFont; + int mSpacing; + EMonospacing mMonospacing; + int mShadowX; + int mShadowY; + + DHUDFont(FFont* f, int sp, EMonospacing ms, int sx, int sy) + : mFont(f), mSpacing(sp), mMonospacing(ms), mShadowX(sx), mShadowY(sy) + {} +}; + + + class DStatusBarCore : public DObject { DECLARE_CLASS(DStatusBarCore, DObject) + +protected: + + +public: + + enum EAlign + { + TOP = 0, + VCENTER = 1, + BOTTOM = 2, + VOFFSET = 3, + VMASK = 3, + + LEFT = 0, + HCENTER = 4, + RIGHT = 8, + HOFFSET = 12, + HMASK = 12, + + CENTER = VCENTER | HCENTER, + CENTER_BOTTOM = BOTTOM | HCENTER + }; + + int ST_X; + int ST_Y; + int SBarTop; + DVector2 SBarScale; + int RelTop; + int HorizontalResolution, VerticalResolution; + double Alpha = 1.; + DVector2 drawOffset = { 0,0 }; // can be set by subclasses to offset drawing operations + double drawClip[4] = { 0,0,0,0 }; // defines a clipping rectangle (not used yet) + bool fullscreenOffsets = false; // current screen is displayed with fullscreen behavior. + bool ForcedScale = false; + + + virtual DVector2 GetHUDScale() const = 0; + virtual uint32_t GetTranslation() const { return 0; } + void ValidateResolution(int& hres, int& vres) const; + void StatusbarToRealCoords(double& x, double& y, double& w, double& h) const; + void DrawGraphic(FTextureID texture, double x, double y, int flags, double Alpha, double boxwidth, double boxheight, double scaleX, double scaleY); + void DrawString(FFont* font, const FString& cstring, double x, double y, int flags, double Alpha, int translation, int spacing, EMonospacing monospacing, int shadowX, int shadowY, double scaleX, double scaleY); + void TransformRect(double& x, double& y, double& w, double& h, int flags = 0); + void Fill(PalEntry color, double x, double y, double w, double h, int flags = 0); + void SetClipRect(double x, double y, double w, double h, int flags = 0); + }; \ No newline at end of file diff --git a/src/g_statusbar/sbar.h b/src/g_statusbar/sbar.h index d15411b9c..ae08b425b 100644 --- a/src/g_statusbar/sbar.h +++ b/src/g_statusbar/sbar.h @@ -332,30 +332,6 @@ enum }; -//============================================================================ -// -// encapsulates all settings a HUD font may need -// -//============================================================================ - -class DHUDFont : public DObject -{ - // this blocks CreateNew on this class which is the intent here. - DECLARE_ABSTRACT_CLASS(DHUDFont, DObject); - -public: - FFont *mFont; - int mSpacing; - EMonospacing mMonospacing; - int mShadowX; - int mShadowY; - - DHUDFont(FFont *f, int sp, EMonospacing ms, int sx, int sy) - : mFont(f), mSpacing(sp), mMonospacing(ms), mShadowX(sx), mShadowY(sy) - {} -}; - - class DBaseStatusBar : public DStatusBarCore { friend class DSBarInfo; @@ -392,24 +368,6 @@ public: }; - enum EAlign - { - TOP = 0, - VCENTER = 1, - BOTTOM = 2, - VOFFSET = 3, - VMASK = 3, - - LEFT = 0, - HCENTER = 4, - RIGHT = 8, - HOFFSET = 12, - HMASK = 12, - - CENTER = VCENTER | HCENTER, - CENTER_BOTTOM = BOTTOM | HCENTER - }; - DBaseStatusBar (); void SetSize(int reltop = 32, int hres = 320, int vres = 200, int hhres = -1, int hvres = -1); void OnDestroy() override; @@ -435,7 +393,7 @@ public: void DrawBottomStuff (EHudState state); void DrawTopStuff (EHudState state); void AttachToPlayer(player_t *player); - DVector2 GetHUDScale() const; + DVector2 GetHUDScale() const override; virtual void FlashCrosshair (); void NewGame (); virtual void ScreenSizeChanged (); @@ -444,21 +402,14 @@ public: virtual bool MustDrawLog(EHudState state); virtual void SetMugShotState (const char *state_name, bool wait_till_done=false, bool reset=false); void DrawLog(); - uint32_t GetTranslation() const; + uint32_t GetTranslation() const override; void CreateAltHUD(); void DrawAltHUD(); - void DrawGraphic(FTextureID texture, double x, double y, int flags, double Alpha, double boxwidth, double boxheight, double scaleX, double scaleY); - void DrawString(FFont *font, const FString &cstring, double x, double y, int flags, double Alpha, int translation, int spacing, EMonospacing monospacing, int shadowX, int shadowY, double scaleX, double scaleY); - void TransformRect(double &x, double &y, double &w, double &h, int flags = 0); - void Fill(PalEntry color, double x, double y, double w, double h, int flags = 0); - void SetClipRect(double x, double y, double w, double h, int flags = 0); - void BeginStatusBar(int resW, int resH, int relTop, bool forceScaled); void BeginHUD(int resW, int resH, double Alpha, bool forceScaled = false); bool ForceHUDScale(bool on) { std::swap(ForcedScale, on); return on; } // This is for SBARINFO which should not use BeginStatusBar or BeginHUD. - void StatusbarToRealCoords(double &x, double &y, double &w, double &h) const; int GetTopOfStatusbar() const { return SBarTop; @@ -481,15 +432,8 @@ public: void DrawCrosshair (); // Sizing info for ths status bar. - int ST_X; - int ST_Y; - int SBarTop; - DVector2 SBarScale; - int RelTop; - int HorizontalResolution, VerticalResolution; bool Scaled; // This needs to go away. DVector2 defaultScale; // factor for fully scaled fullscreen display. - bool ForcedScale = false; bool Centering; bool FixedOrigin; @@ -502,10 +446,6 @@ public: player_t *CPlayer; - double Alpha = 1.; - DVector2 drawOffset = { 0,0 }; // can be set by subclasses to offset drawing operations - double drawClip[4] = { 0,0,0,0 }; // defines a clipping rectangle (not used yet) - bool fullscreenOffsets = false; // current screen is displayed with fullscreen behavior. FMugShot mugshot; private: @@ -544,82 +484,4 @@ void C_MidPrint(FFont* font, const char* message, bool bold = false); -enum DI_Flags -{ - DI_SKIPICON = 0x1, - DI_SKIPALTICON = 0x2, - DI_SKIPSPAWN = 0x4, - DI_SKIPREADY = 0x8, - DI_ALTICONFIRST = 0x10, - DI_TRANSLATABLE = 0x20, - DI_FORCESCALE = 0x40, - DI_DIM = 0x80, - DI_DRAWCURSORFIRST = 0x100, // only for DrawInventoryBar. - DI_ALWAYSSHOWCOUNT = 0x200, // only for DrawInventoryBar. - DI_DIMDEPLETED = 0x400, - DI_DONTANIMATE = 0x800, // do not animate the texture - DI_MIRROR = 0x1000, // flip the texture horizontally, like a mirror - - DI_SCREEN_AUTO = 0, // decide based on given offsets. - DI_SCREEN_MANUAL_ALIGN = 0x4000, // If this is on, the following flags will have an effect - - DI_SCREEN_TOP = DI_SCREEN_MANUAL_ALIGN, - DI_SCREEN_VCENTER = 0x8000 | DI_SCREEN_MANUAL_ALIGN, - DI_SCREEN_BOTTOM = 0x10000 | DI_SCREEN_MANUAL_ALIGN, - DI_SCREEN_VOFFSET = 0x18000 | DI_SCREEN_MANUAL_ALIGN, - DI_SCREEN_VMASK = 0x18000 | DI_SCREEN_MANUAL_ALIGN, - - DI_SCREEN_LEFT = DI_SCREEN_MANUAL_ALIGN, - DI_SCREEN_HCENTER = 0x20000 | DI_SCREEN_MANUAL_ALIGN, - DI_SCREEN_RIGHT = 0x40000 | DI_SCREEN_MANUAL_ALIGN, - DI_SCREEN_HOFFSET = 0x60000 | DI_SCREEN_MANUAL_ALIGN, - DI_SCREEN_HMASK = 0x60000 | DI_SCREEN_MANUAL_ALIGN, - - DI_SCREEN_LEFT_TOP = DI_SCREEN_TOP|DI_SCREEN_LEFT, - DI_SCREEN_RIGHT_TOP = DI_SCREEN_TOP|DI_SCREEN_RIGHT, - DI_SCREEN_LEFT_BOTTOM = DI_SCREEN_BOTTOM|DI_SCREEN_LEFT, - DI_SCREEN_RIGHT_BOTTOM = DI_SCREEN_BOTTOM|DI_SCREEN_RIGHT, - DI_SCREEN_CENTER = DI_SCREEN_VCENTER|DI_SCREEN_HCENTER, - DI_SCREEN_CENTER_BOTTOM = DI_SCREEN_BOTTOM|DI_SCREEN_HCENTER, - DI_SCREEN_OFFSETS = DI_SCREEN_HOFFSET|DI_SCREEN_VOFFSET, - - DI_ITEM_AUTO = 0, // equivalent with bottom center, which is the default alignment. - - DI_ITEM_TOP = 0x80000, - DI_ITEM_VCENTER = 0x100000, - DI_ITEM_BOTTOM = 0, // this is the default vertical alignment - DI_ITEM_VOFFSET = 0x180000, - DI_ITEM_VMASK = 0x180000, - - DI_ITEM_LEFT = 0x200000, - DI_ITEM_HCENTER = 0, // this is the deafault horizontal alignment - DI_ITEM_RIGHT = 0x400000, - DI_ITEM_HOFFSET = 0x600000, - DI_ITEM_HMASK = 0x600000, - - DI_ITEM_LEFT_TOP = DI_ITEM_TOP|DI_ITEM_LEFT, - DI_ITEM_RIGHT_TOP = DI_ITEM_TOP|DI_ITEM_RIGHT, - DI_ITEM_LEFT_BOTTOM = DI_ITEM_BOTTOM|DI_ITEM_LEFT, - DI_ITEM_RIGHT_BOTTOM = DI_ITEM_BOTTOM|DI_ITEM_RIGHT, - DI_ITEM_CENTER = DI_ITEM_VCENTER|DI_ITEM_HCENTER, - DI_ITEM_CENTER_BOTTOM = DI_ITEM_BOTTOM|DI_ITEM_HCENTER, - DI_ITEM_OFFSETS = DI_ITEM_HOFFSET|DI_ITEM_VOFFSET, - - DI_TEXT_ALIGN_LEFT = 0, - DI_TEXT_ALIGN_RIGHT = 0x800000, - DI_TEXT_ALIGN_CENTER = 0x1000000, - DI_TEXT_ALIGN = 0x1800000, - - DI_ALPHAMAPPED = 0x2000000, - DI_NOSHADOW = 0x4000000, - DI_ALWAYSSHOWCOUNTERS = 0x8000000, - DI_ARTIFLASH = 0x10000000, - DI_FORCEFILL = 0x20000000, - - // These 2 flags are only used by SBARINFO so these duplicate other flags not used by SBARINFO - DI_DRAWINBOX = DI_TEXT_ALIGN_RIGHT, - DI_ALTERNATEONFAIL = DI_TEXT_ALIGN_CENTER, - -}; - #endif /* __SBAR_H__ */ diff --git a/src/g_statusbar/shared_sbar.cpp b/src/g_statusbar/shared_sbar.cpp index 4ca762448..f2a4d92ce 100644 --- a/src/g_statusbar/shared_sbar.cpp +++ b/src/g_statusbar/shared_sbar.cpp @@ -71,7 +71,6 @@ #define XHAIRPICKUPSIZE (2+XHAIRSHRINKSIZE) #define POWERUPICONSIZE 32 -IMPLEMENT_CLASS(DHUDFont, true, false); IMPLEMENT_CLASS(DBaseStatusBar, false, true) IMPLEMENT_POINTERS_START(DBaseStatusBar) @@ -401,21 +400,6 @@ DBaseStatusBar::DBaseStatusBar () CreateAltHUD(); } -static void ValidateResolution(int &hres, int &vres) -{ - if (hres == 0) - { - static const int HORIZONTAL_RESOLUTION_DEFAULT = 320; - hres = HORIZONTAL_RESOLUTION_DEFAULT; - } - - if (vres == 0) - { - static const int VERTICAL_RESOLUTION_DEFAULT = 200; - vres = VERTICAL_RESOLUTION_DEFAULT; - } -} - void DBaseStatusBar::SetSize(int reltop, int hres, int vres, int hhres, int hvres) { ValidateResolution(hres, vres); @@ -1494,424 +1478,6 @@ uint32_t DBaseStatusBar::GetTranslation() const return TRANSLATION(TRANSLATION_Players, int(CPlayer - players)); } -//============================================================================ -// -// draw stuff -// -//============================================================================ - -void DBaseStatusBar::StatusbarToRealCoords(double &x, double &y, double &w, double &h) const -{ - if (SBarScale.X == -1 || ForcedScale) - { - int hres = HorizontalResolution; - int vres = VerticalResolution; - ValidateResolution(hres, vres); - - VirtualToRealCoords(twod, x, y, w, h, hres, vres, true, true); - } - else - { - x = ST_X + x * SBarScale.X; - y = ST_Y + y * SBarScale.Y; - w *= SBarScale.X; - h *= SBarScale.Y; - } -} - -//============================================================================ -// -// draw stuff -// -//============================================================================ - -void DBaseStatusBar::DrawGraphic(FTextureID texture, double x, double y, int flags, double Alpha, double boxwidth, double boxheight, double scaleX, double scaleY) -{ - if (!texture.isValid()) - return; - - FGameTexture *tex = TexMan.GetGameTexture(texture, !(flags & DI_DONTANIMATE)); - - double texwidth = tex->GetDisplayWidth() * scaleX; - double texheight = tex->GetDisplayHeight() * scaleY; - - if (boxwidth > 0 || boxheight > 0) - { - if (!(flags & DI_FORCEFILL)) - { - double scale1 = 1., scale2 = 1.; - - if (boxwidth > 0 && (boxwidth < texwidth || (flags & DI_FORCESCALE))) - { - scale1 = boxwidth / texwidth; - } - if (boxheight != -1 && (boxheight < texheight || (flags & DI_FORCESCALE))) - { - scale2 = boxheight / texheight; - } - - if (flags & DI_FORCESCALE) - { - if (boxwidth <= 0 || (boxheight > 0 && scale2 < scale1)) - scale1 = scale2; - } - else scale1 = MIN(scale1, scale2); - - boxwidth = texwidth * scale1; - boxheight = texheight * scale1; - } - } - else - { - boxwidth = texwidth; - boxheight = texheight; - } - - // resolve auto-alignment before making any adjustments to the position values. - if (!(flags & DI_SCREEN_MANUAL_ALIGN)) - { - if (x < 0) flags |= DI_SCREEN_RIGHT; - else flags |= DI_SCREEN_LEFT; - if (y < 0) flags |= DI_SCREEN_BOTTOM; - else flags |= DI_SCREEN_TOP; - } - - Alpha *= this->Alpha; - if (Alpha <= 0) return; - x += drawOffset.X; - y += drawOffset.Y; - - switch (flags & DI_ITEM_HMASK) - { - case DI_ITEM_HCENTER: x -= boxwidth / 2; break; - case DI_ITEM_RIGHT: x -= boxwidth; break; - case DI_ITEM_HOFFSET: x -= tex->GetDisplayLeftOffset() * boxwidth / texwidth; break; - } - - switch (flags & DI_ITEM_VMASK) - { - case DI_ITEM_VCENTER: y -= boxheight / 2; break; - case DI_ITEM_BOTTOM: y -= boxheight; break; - case DI_ITEM_VOFFSET: y -= tex->GetDisplayTopOffset() * boxheight / texheight; break; - } - - if (!fullscreenOffsets) - { - StatusbarToRealCoords(x, y, boxwidth, boxheight); - } - else - { - double orgx, orgy; - - switch (flags & DI_SCREEN_HMASK) - { - default: orgx = 0; break; - case DI_SCREEN_HCENTER: orgx = twod->GetWidth() / 2; break; - case DI_SCREEN_RIGHT: orgx = twod->GetWidth(); break; - } - - switch (flags & DI_SCREEN_VMASK) - { - default: orgy = 0; break; - case DI_SCREEN_VCENTER: orgy = twod->GetHeight() / 2; break; - case DI_SCREEN_BOTTOM: orgy = twod->GetHeight(); break; - } - - // move stuff in the top right corner a bit down if the fps counter is on. - if ((flags & (DI_SCREEN_HMASK|DI_SCREEN_VMASK)) == DI_SCREEN_RIGHT_TOP && vid_fps) y += 10; - - DVector2 Scale = GetHUDScale(); - - x *= Scale.X; - y *= Scale.Y; - boxwidth *= Scale.X; - boxheight *= Scale.Y; - x += orgx; - y += orgy; - } - DrawTexture(twod, tex, x, y, - DTA_TopOffset, 0, - DTA_LeftOffset, 0, - DTA_DestWidthF, boxwidth, - DTA_DestHeightF, boxheight, - DTA_TranslationIndex, (flags & DI_TRANSLATABLE) ? GetTranslation() : 0, - DTA_ColorOverlay, (flags & DI_DIM) ? MAKEARGB(170, 0, 0, 0) : 0, - DTA_Alpha, Alpha, - DTA_AlphaChannel, !!(flags & DI_ALPHAMAPPED), - DTA_FillColor, (flags & DI_ALPHAMAPPED) ? 0 : -1, - DTA_FlipX, !!(flags & DI_MIRROR), - TAG_DONE); -} - - -//============================================================================ -// -// draw a string -// -//============================================================================ - -void DBaseStatusBar::DrawString(FFont *font, const FString &cstring, double x, double y, int flags, double Alpha, int translation, int spacing, EMonospacing monospacing, int shadowX, int shadowY, double scaleX, double scaleY) -{ - bool monospaced = monospacing != EMonospacing::Off; - double dx = 0; - int spacingparm = monospaced ? -spacing : spacing; - - switch (flags & DI_TEXT_ALIGN) - { - default: - break; - case DI_TEXT_ALIGN_RIGHT: - dx = font->StringWidth(cstring, spacingparm); - break; - case DI_TEXT_ALIGN_CENTER: - dx = font->StringWidth(cstring, spacingparm) / 2; - break; - } - - // Take text scale into account - x -= dx * scaleX; - - const uint8_t* str = (const uint8_t*)cstring.GetChars(); - const EColorRange boldTranslation = EColorRange(translation ? translation - 1 : NumTextColors - 1); - int fontcolor = translation; - double orgx = 0, orgy = 0; - DVector2 Scale; - - if (fullscreenOffsets) - { - Scale = GetHUDScale(); - shadowX *= (int)Scale.X; - shadowY *= (int)Scale.Y; - - switch (flags & DI_SCREEN_HMASK) - { - default: orgx = 0; break; - case DI_SCREEN_HCENTER: orgx = twod->GetWidth() / 2; break; - case DI_SCREEN_RIGHT: orgx = twod->GetWidth(); break; - } - - switch (flags & DI_SCREEN_VMASK) - { - default: orgy = 0; break; - case DI_SCREEN_VCENTER: orgy = twod->GetHeight() / 2; break; - case DI_SCREEN_BOTTOM: orgy = twod->GetHeight(); break; - } - - // move stuff in the top right corner a bit down if the fps counter is on. - if ((flags & (DI_SCREEN_HMASK | DI_SCREEN_VMASK)) == DI_SCREEN_RIGHT_TOP && vid_fps) y += 10; - } - else - { - Scale = { 1.,1. }; - } - int ch; - while (ch = GetCharFromString(str), ch != '\0') - { - if (ch == ' ') - { - x += monospaced ? spacing : font->GetSpaceWidth() + spacing; - continue; - } - else if (ch == TEXTCOLOR_ESCAPE) - { - EColorRange newColor = V_ParseFontColor(str, translation, boldTranslation); - if (newColor != CR_UNDEFINED) - fontcolor = newColor; - continue; - } - - int width; - auto c = font->GetChar(ch, fontcolor, &width); - if (c == NULL) //missing character. - { - continue; - } - - if (!monospaced) //If we are monospaced lets use the offset - x += (c->GetDisplayLeftOffset() + 1); //ignore x offsets since we adapt to character size - - double rx, ry, rw, rh; - rx = x + drawOffset.X; - ry = y + drawOffset.Y; - rw = c->GetDisplayWidth(); - rh = c->GetDisplayHeight(); - - if (monospacing == EMonospacing::CellCenter) - rx += (spacing - rw) / 2; - else if (monospacing == EMonospacing::CellRight) - rx += (spacing - rw); - - if (!fullscreenOffsets) - { - StatusbarToRealCoords(rx, ry, rw, rh); - } - else - { - rx *= Scale.X; - ry *= Scale.Y; - rw *= Scale.X; - rh *= Scale.Y; - - rx += orgx; - ry += orgy; - } - - // Apply text scale - rw *= scaleX; - rh *= scaleY; - - // This is not really such a great way to draw shadows because they can overlap with previously drawn characters. - // This may have to be changed to draw the shadow text up front separately. - if ((shadowX != 0 || shadowY != 0) && !(flags & DI_NOSHADOW)) - { - DrawChar(twod, font, CR_UNTRANSLATED, rx + shadowX, ry + shadowY, ch, - DTA_DestWidthF, rw, - DTA_DestHeightF, rh, - DTA_Alpha, (Alpha * HR_SHADOW), - DTA_FillColor, 0, - TAG_DONE); - } - DrawChar(twod, font, fontcolor, rx, ry, ch, - DTA_DestWidthF, rw, - DTA_DestHeightF, rh, - DTA_Alpha, Alpha, - TAG_DONE); - - dx = monospaced - ? spacing - : width + spacing - (c->GetDisplayLeftOffset() + 1); - - // Take text scale into account - x += dx * scaleX; - } -} - -void SBar_DrawString(DBaseStatusBar *self, DHUDFont *font, const FString &string, double x, double y, int flags, int trans, double alpha, int wrapwidth, int linespacing, double scaleX, double scaleY) -{ - if (font == nullptr) ThrowAbortException(X_READ_NIL, nullptr); - if (!twod->HasBegun2D()) ThrowAbortException(X_OTHER, "Attempt to draw to screen outside a draw function"); - - // resolve auto-alignment before making any adjustments to the position values. - if (!(flags & DI_SCREEN_MANUAL_ALIGN)) - { - if (x < 0) flags |= DI_SCREEN_RIGHT; - else flags |= DI_SCREEN_LEFT; - if (y < 0) flags |= DI_SCREEN_BOTTOM; - else flags |= DI_SCREEN_TOP; - } - - if (wrapwidth > 0) - { - auto brk = V_BreakLines(font->mFont, int(wrapwidth * scaleX), string, true); - for (auto &line : brk) - { - self->DrawString(font->mFont, line.Text, x, y, flags, alpha, trans, font->mSpacing, font->mMonospacing, font->mShadowX, font->mShadowY, scaleX, scaleY); - y += (font->mFont->GetHeight() + linespacing) * scaleY; - } - } - else - { - self->DrawString(font->mFont, string, x, y, flags, alpha, trans, font->mSpacing, font->mMonospacing, font->mShadowX, font->mShadowY, scaleX, scaleY); - } -} - - -//============================================================================ -// -// draw stuff -// -//============================================================================ - -void DBaseStatusBar::TransformRect(double &x, double &y, double &w, double &h, int flags) -{ - // resolve auto-alignment before making any adjustments to the position values. - if (!(flags & DI_SCREEN_MANUAL_ALIGN)) - { - if (x < 0) flags |= DI_SCREEN_RIGHT; - else flags |= DI_SCREEN_LEFT; - if (y < 0) flags |= DI_SCREEN_BOTTOM; - else flags |= DI_SCREEN_TOP; - } - - x += drawOffset.X; - y += drawOffset.Y; - - if (!fullscreenOffsets) - { - StatusbarToRealCoords(x, y, w, h); - } - else - { - double orgx, orgy; - - switch (flags & DI_SCREEN_HMASK) - { - default: orgx = 0; break; - case DI_SCREEN_HCENTER: orgx = twod->GetWidth() / 2; break; - case DI_SCREEN_RIGHT: orgx = twod->GetWidth(); break; - } - - switch (flags & DI_SCREEN_VMASK) - { - default: orgy = 0; break; - case DI_SCREEN_VCENTER: orgy = twod->GetHeight() / 2; break; - case DI_SCREEN_BOTTOM: orgy = twod->GetHeight(); break; - } - - // move stuff in the top right corner a bit down if the fps counter is on. - if ((flags & (DI_SCREEN_HMASK | DI_SCREEN_VMASK)) == DI_SCREEN_RIGHT_TOP && vid_fps) y += 10; - - DVector2 Scale = GetHUDScale(); - - x *= Scale.X; - y *= Scale.Y; - w *= Scale.X; - h *= Scale.Y; - x += orgx; - y += orgy; - } -} - - -//============================================================================ -// -// draw stuff -// -//============================================================================ - -void DBaseStatusBar::Fill(PalEntry color, double x, double y, double w, double h, int flags) -{ - double Alpha = color.a * this->Alpha / 255; - if (Alpha <= 0) return; - - TransformRect(x, y, w, h, flags); - - int x1 = int(x); - int y1 = int(y); - int ww = int(x + w - x1); // account for scaling to non-integers. Truncating the values separately would fail for cases like - int hh = int(y + h - y1); // y=3.5, height = 5.5 where adding both values gives a larger integer than adding the two integers. - - Dim(twod, color, float(Alpha), x1, y1, ww, hh); -} - - -//============================================================================ -// -// draw stuff -// -//============================================================================ - -void DBaseStatusBar::SetClipRect(double x, double y, double w, double h, int flags) -{ - TransformRect(x, y, w, h, flags); - int x1 = int(x); - int y1 = int(y); - int ww = int(x + w - x1); // account for scaling to non-integers. Truncating the values separately would fail for cases like - int hh = int(y + h - y1); // y=3.5, height = 5.5 where adding both values gives a larger integer than adding the two integers. - twod->SetClipRect(x1, y1, ww, hh); -} - - //============================================================================ // // CCMD showpop diff --git a/src/scripting/vmthunks.cpp b/src/scripting/vmthunks.cpp index b28c06de4..40a4b30db 100644 --- a/src/scripting/vmthunks.cpp +++ b/src/scripting/vmthunks.cpp @@ -2160,7 +2160,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(DBaseStatusBar, DrawImage, SBar_DrawImage) return 0; } -void SBar_DrawString(DBaseStatusBar *self, DHUDFont *font, const FString &string, double x, double y, int flags, int trans, double alpha, int wrapwidth, int linespacing, double scaleX, double scaleY); +void SBar_DrawString(DStatusBarCore *self, DHUDFont *font, const FString &string, double x, double y, int flags, int trans, double alpha, int wrapwidth, int linespacing, double scaleX, double scaleY); DEFINE_ACTION_FUNCTION_NATIVE(DBaseStatusBar, DrawString, SBar_DrawString) { @@ -2241,6 +2241,22 @@ DEFINE_ACTION_FUNCTION_NATIVE(DBaseStatusBar, SetClipRect, SBar_SetClipRect) return 0; } +void FormatNumber(int number, int minsize, int maxsize, int flags, const FString& prefix, FString* result); + +DEFINE_ACTION_FUNCTION_NATIVE(DStatusBarCore, FormatNumber, FormatNumber) +{ + PARAM_PROLOGUE; + PARAM_INT(number); + PARAM_INT(minsize); + PARAM_INT(maxsize); + PARAM_INT(flags); + PARAM_STRING(prefix); + FString fmt; + FormatNumber(number, minsize, maxsize, flags, prefix, &fmt); + ACTION_RETURN_STRING(fmt); +} + + static void GetGlobalACSString(int index, FString *result) { *result = primaryLevel->Behaviors.LookupString(ACS_GlobalVars[index]);