diff --git a/src/scripting/backend/codegen.cpp b/src/scripting/backend/codegen.cpp index 5c495ee20..c59b7bbc5 100644 --- a/src/scripting/backend/codegen.cpp +++ b/src/scripting/backend/codegen.cpp @@ -58,6 +58,7 @@ extern FRandom pr_exrandom; FMemArena FxAlloc(65536); +int utf8_decode(const char *src, int *size); struct FLOP { @@ -304,6 +305,36 @@ static bool AreCompatiblePointerTypes(PType *dest, PType *source, bool forcompar return false; } + +//========================================================================== +// +// +// +//========================================================================== + +static FxExpression *StringConstToChar(FxExpression *basex) +{ + if (!basex->isConstant()) return false; + // Allow single character string literals be convertible to integers. + // This serves as workaround for not being able to use single quoted literals because those are taken for names. + ExpVal constval = static_cast(basex)->GetValue(); + FString str = constval.GetString(); + if (str.Len() == 1) + { + return new FxConstant(str[0], basex->ScriptPosition); + } + else if (str.Len() > 1) + { + // If the string is UTF-8, allow a single character UTF-8 sequence. + int size; + int c = utf8_decode(str.GetChars(), &size); + if (c >= 0 && size == str.Len()) + { + return new FxConstant(str[0], basex->ScriptPosition); + } + } + return nullptr; +} //========================================================================== // // @@ -908,6 +939,7 @@ FxExpression *FxIntCast::Resolve(FCompileContext &ctx) { CHECKRESOLVED(); SAFE_RESOLVE(basex, ctx); + int c; if (basex->ValueType->GetRegType() == REGT_INT) { @@ -952,6 +984,17 @@ FxExpression *FxIntCast::Resolve(FCompileContext &ctx) return this; } + else if (basex->ValueType == TypeString && basex->isConstant()) + { + FxExpression *x = StringConstToChar(basex); + if (x) + { + x->ValueType = ValueType; + basex = nullptr; + delete this; + return x; + } + } ScriptPosition.Message(MSG_ERROR, "Numeric type expected"); delete this; return nullptr; @@ -3302,6 +3345,18 @@ FxExpression *FxCompareRel::Resolve(FCompileContext& ctx) return nullptr; } + FxExpression *x; + if (left->IsNumeric() && right->ValueType == TypeString && (x = StringConstToChar(right))) + { + delete right; + right = x; + } + else if (right->IsNumeric() && left->ValueType == TypeString && (x = StringConstToChar(left))) + { + delete left; + left = x; + } + if (left->ValueType == TypeString || right->ValueType == TypeString) { if (left->ValueType != TypeString) @@ -3528,6 +3583,17 @@ FxExpression *FxCompareEq::Resolve(FCompileContext& ctx) if (left->ValueType != right->ValueType) // identical types are always comparable, if they can be placed in a register, so we can save most checks if this is the case. { + FxExpression *x; + if (left->IsNumeric() && right->ValueType == TypeString && (x = StringConstToChar(right))) + { + delete right; + right = x; + } + else if (right->IsNumeric() && left->ValueType == TypeString && (x = StringConstToChar(left))) + { + delete left; + left = x; + } // Special cases: Compare strings and names with names, sounds, colors, state labels and class types. // These are all types a string can be implicitly cast into, so for convenience, so they should when doing a comparison. if ((left->ValueType == TypeString || left->ValueType == TypeName) && diff --git a/src/scripting/thingdef.cpp b/src/scripting/thingdef.cpp index 82e358638..a8503e64e 100644 --- a/src/scripting/thingdef.cpp +++ b/src/scripting/thingdef.cpp @@ -206,10 +206,9 @@ PFunction *FindClassMemberFunction(PStruct *selfcls, PStruct *funccls, FName nam // private access is only allowed if the symbol table belongs to the class in which the current function is being defined. sc.Message(MSG_ERROR, "%s is declared private and not accessible", symbol->SymbolName.GetChars()); } - else if ((funcsym->Variants[0].Flags & VARF_Protected) && (!cls_ctx || !cls_target || !cls_ctx->IsDescendantOf((PClass*)cls_target))) + else if ((funcsym->Variants[0].Flags & VARF_Protected) && symtable != &funccls->Symbols && (!cls_ctx || !cls_target || !cls_ctx->IsDescendantOf((PClass*)cls_target))) { sc.Message(MSG_ERROR, "%s is declared protected and not accessible", symbol->SymbolName.GetChars()); - return nullptr; } else if (funcsym->Variants[0].Flags & VARF_Deprecated) { diff --git a/src/textures/texturemanager.cpp b/src/textures/texturemanager.cpp index 4714a7eea..432cc2419 100644 --- a/src/textures/texturemanager.cpp +++ b/src/textures/texturemanager.cpp @@ -1199,7 +1199,7 @@ DEFINE_ACTION_FUNCTION(_TexMan, GetSize) { PARAM_PROLOGUE; PARAM_INT(texid); - auto tex = TexMan[FSetTextureID(texid)]; + auto tex = TexMan.ByIndex(texid); int x, y; if (tex != nullptr) { @@ -1212,11 +1212,17 @@ DEFINE_ACTION_FUNCTION(_TexMan, GetSize) return MIN(numret, 2); } +//========================================================================== +// +// +// +//========================================================================== + DEFINE_ACTION_FUNCTION(_TexMan, GetScaledSize) { PARAM_PROLOGUE; PARAM_INT(texid); - auto tex = TexMan[FSetTextureID(texid)]; + auto tex = TexMan.ByIndex(texid); if (tex != nullptr) { ACTION_RETURN_VEC2(DVector2(tex->GetScaledWidthDouble(), tex->GetScaledHeightDouble())); @@ -1224,6 +1230,24 @@ DEFINE_ACTION_FUNCTION(_TexMan, GetScaledSize) ACTION_RETURN_VEC2(DVector2(-1, -1)); } +//========================================================================== +// +// +// +//========================================================================== + +DEFINE_ACTION_FUNCTION(_TexMan, CheckRealHeight) +{ + PARAM_PROLOGUE; + PARAM_INT(texid); + auto tex = TexMan.ByIndex(texid); + if (tex != nullptr) + { + ACTION_RETURN_INT(tex->CheckRealHeight()); + } + ACTION_RETURN_INT(-1); +} + //========================================================================== // // FTextureID::operator+ diff --git a/src/wi_stuff.cpp b/src/wi_stuff.cpp index c8a82deb1..67d77cce7 100644 --- a/src/wi_stuff.cpp +++ b/src/wi_stuff.cpp @@ -648,22 +648,22 @@ private: struct FPatchInfo { FFont *mFont; - FTexture *mPatch; + FTextureID mPatch; EColorRange mColor; void Init(FGIFont &gifont) { if (gifont.color == NAME_Null) { - mPatch = TexMan[gifont.fontname]; // "entering" - mColor = mPatch == NULL ? CR_UNTRANSLATED : CR_UNDEFINED; + mPatch = TexMan.CheckForTexture(gifont.fontname, FTexture::TEX_MiscPatch); + mColor = mPatch.isValid() ? CR_UNTRANSLATED : CR_UNDEFINED; mFont = NULL; } else { mFont = V_GetFont(gifont.fontname); mColor = V_FindFontColor(gifont.color); - mPatch = NULL; + mPatch.SetNull(); } if (mFont == NULL) { @@ -748,7 +748,7 @@ public: FPatchInfo finished; FPatchInfo entering; - FTextureID Sp_secret; // "secret" + FTextureID P_secret; // "secret" FTextureID Kills; // "Kills", "Scrt", "Items", "Frags" FTextureID Secret; FTextureID Items; @@ -851,10 +851,11 @@ public: const char *string = GStrings(stringname); int midx = screen->GetWidth() / 2; - if (pinfo->mPatch != NULL) + if (pinfo->mPatch.isValid()) { - screen->DrawTexture(pinfo->mPatch, midx - pinfo->mPatch->GetScaledWidth()*CleanXfac/2, y, DTA_CleanNoMove, true, TAG_DONE); - return y + (pinfo->mPatch->GetScaledHeight() * CleanYfac); + FTexture *tex = TexMan[pinfo->mPatch]; + screen->DrawTexture(tex, midx - tex->GetScaledWidth()*CleanXfac/2, y, DTA_CleanNoMove, true, TAG_DONE); + return y + (tex->GetScaledHeight() * CleanYfac); } else { @@ -1883,7 +1884,7 @@ public: screen->DrawTexture (TexMan[Items], SP_STATSX, SP_STATSY+lh, DTA_Clean, true, TAG_DONE); WI_drawPercent (IntermissionFont, 320 - SP_STATSX, SP_STATSY+lh, cnt_items[0], wbs->maxitems); - screen->DrawTexture (TexMan[Sp_secret], SP_STATSX, SP_STATSY+2*lh, DTA_Clean, true, TAG_DONE); + screen->DrawTexture (TexMan[P_secret], SP_STATSX, SP_STATSY+2*lh, DTA_Clean, true, TAG_DONE); WI_drawPercent (IntermissionFont, 320 - SP_STATSX, SP_STATSY+2*lh, cnt_secret[0], wbs->maxsecret); screen->DrawTexture (TexMan[Timepic], SP_TIMEX, SP_TIMEY, DTA_Clean, true, TAG_DONE); @@ -2024,7 +2025,7 @@ public: { Kills = TexMan.CheckForTexture("WIOSTK", FTexture::TEX_MiscPatch); // "kills" Secret = TexMan.CheckForTexture("WIOSTS", FTexture::TEX_MiscPatch); // "scrt" - Sp_secret = TexMan.CheckForTexture("WISCRT2", FTexture::TEX_MiscPatch); // "secret" + P_secret = TexMan.CheckForTexture("WISCRT2", FTexture::TEX_MiscPatch); // "secret" Items = TexMan.CheckForTexture("WIOSTI", FTexture::TEX_MiscPatch); // "items" Timepic = TexMan.CheckForTexture("WITIME", FTexture::TEX_MiscPatch); // "time" Sucks = TexMan.CheckForTexture("WISUCKS", FTexture::TEX_MiscPatch); // "sucks" @@ -2147,3 +2148,41 @@ DEFINE_FIELD_X(WBStartStruct, wbstartstruct_t, sucktime); DEFINE_FIELD_X(WBStartStruct, wbstartstruct_t, totaltime); DEFINE_FIELD_X(WBStartStruct, wbstartstruct_t, pnum); DEFINE_FIELD_X(WBStartStruct, wbstartstruct_t, plyr); + +DEFINE_FIELD(FIntermissionScreen, acceleratestage); +DEFINE_FIELD(FIntermissionScreen, playerready); +DEFINE_FIELD(FIntermissionScreen, me); +DEFINE_FIELD(FIntermissionScreen, bcnt); +DEFINE_FIELD(FIntermissionScreen, state); +DEFINE_FIELD(FIntermissionScreen, wbs); +DEFINE_FIELD(FIntermissionScreen, Plrs); +DEFINE_FIELD(FIntermissionScreen, cnt); +DEFINE_FIELD(FIntermissionScreen, cnt_kills); +DEFINE_FIELD(FIntermissionScreen, cnt_items); +DEFINE_FIELD(FIntermissionScreen, cnt_secret); +DEFINE_FIELD(FIntermissionScreen, cnt_frags); +DEFINE_FIELD(FIntermissionScreen, cnt_deaths); +DEFINE_FIELD(FIntermissionScreen, cnt_time); +DEFINE_FIELD(FIntermissionScreen, cnt_total_time); +DEFINE_FIELD(FIntermissionScreen, cnt_par); +DEFINE_FIELD(FIntermissionScreen, cnt_pause); +DEFINE_FIELD(FIntermissionScreen, total_frags); +DEFINE_FIELD(FIntermissionScreen, total_deaths); +DEFINE_FIELD(FIntermissionScreen, noautostartmap); +DEFINE_FIELD(FIntermissionScreen, dofrags); +DEFINE_FIELD(FIntermissionScreen, ng_state); +DEFINE_FIELD(FIntermissionScreen, shadowalpha); +DEFINE_FIELD(FIntermissionScreen, mapname); +DEFINE_FIELD(FIntermissionScreen, finished); +DEFINE_FIELD(FIntermissionScreen, entering); +DEFINE_FIELD(FIntermissionScreen, P_secret); +DEFINE_FIELD(FIntermissionScreen, Kills); +DEFINE_FIELD(FIntermissionScreen, Secret); +DEFINE_FIELD(FIntermissionScreen, Items); +DEFINE_FIELD(FIntermissionScreen, Timepic); +DEFINE_FIELD(FIntermissionScreen, Par); +DEFINE_FIELD(FIntermissionScreen, Sucks); +DEFINE_FIELD(FIntermissionScreen, lnametexts); +DEFINE_FIELD(FIntermissionScreen, snl_pointeron); +DEFINE_FIELD(FIntermissionScreen, player_deaths); +DEFINE_FIELD(FIntermissionScreen, sp_state); diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 84b0f9df5..2dd8eaa8e 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -27,6 +27,7 @@ version "2.4" #include "zscript/menu/conversationmenu.txt" #include "zscript/statscreen/types.txt" +#include "zscript/statscreen/statscreen.txt" #include "zscript/inventory/inventory.txt" #include "zscript/inventory/inv_misc.txt" diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index ecdd011ff..82b05f755 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -87,6 +87,7 @@ struct TexMan native static void ReplaceTextures(String from, String to, int flags); native static int, int GetSize(TextureID tex); native static Vector2 GetScaledSize(TextureID tex); + native static int CheckRealHeight(TextureID tex); } enum DrawTextureTags diff --git a/wadsrc/static/zscript/statscreen/statscreen.txt b/wadsrc/static/zscript/statscreen/statscreen.txt new file mode 100644 index 000000000..03423527a --- /dev/null +++ b/wadsrc/static/zscript/statscreen/statscreen.txt @@ -0,0 +1,386 @@ + +struct PatchInfo +{ + Font mFont; + TextureID mPatch; + int mColor; + + void Init(GIFont gifont) + { + if (gifont.color == 'Null') + { + mPatch = TexMan.CheckForTexture(gifont.fontname, TexMan.Type_MiscPatch); + mColor = mPatch.isValid() ? Font.CR_UNTRANSLATED : Font.CR_UNDEFINED; + mFont = NULL; + } + else + { + mFont = Font.GetFont(gifont.fontname); + mColor = Font.FindFontColor(gifont.color); + mPatch.SetInvalid(); + } + if (mFont == NULL) + { + mFont = BigFont; + } + } +}; + + +// Will be made a class later, but for now needs to mirror the internal version. +struct IntermissionScreen native +{ + enum EValues + { + // GLOBAL LOCATIONS + WI_TITLEY = 2, + + // SINGPLE-PLAYER STUFF + SP_STATSX = 50, + SP_STATSY = 50, + + SP_TIMEX = 8, + SP_TIMEY = (200 - 32), + + // NET GAME STUFF + NG_STATSY = 50, + }; + + + + // States for single-player + enum ESPState + { + SP_KILLS = 0, + SP_ITEMS = 2, + SP_SECRET = 4, + SP_FRAGS = 6, + SP_TIME = 8, + }; + + const SHOWNEXTLOCDELAY = 4; // in seconds + + //FInterBackground *bg; + native int acceleratestage; // used to accelerate or skip a stage + native bool playerready[MAXPLAYERS]; + native int me; // wbs.pnum + native int bcnt; + native int state; // specifies current state + native wbstartstruct wbs; // contains information passed into intermission + native wbplayerstruct Plrs[MAXPLAYERS]; // wbs.plyr[] + native int cnt; // used for general timing + native int cnt_kills[MAXPLAYERS]; + native int cnt_items[MAXPLAYERS]; + native int cnt_secret[MAXPLAYERS]; + native int cnt_frags[MAXPLAYERS]; + native int cnt_deaths[MAXPLAYERS]; + native int cnt_time; + native int cnt_total_time; + native int cnt_par; + native int cnt_pause; + native int total_frags; + native int total_deaths; + native bool noautostartmap; + native int dofrags; + native int ng_state; + native float shadowalpha; + + // + // GRAPHICS + // + + native PatchInfo mapname; + native PatchInfo finished; + native PatchInfo entering; + + native TextureID p_secret; + native TextureID kills; + native TextureID secret; + native TextureID items; + native TextureID timepic; + native TextureID par; + native TextureID sucks; + + // [RH] Info to dynamically generate the level name graphics + native String lnametexts[2]; + + + native bool snl_pointeron; + + native int player_deaths[MAXPLAYERS]; + native int sp_state; + + + //==================================================================== + // + // Draws a single character with a shadow + // + //==================================================================== + + protected int DrawCharPatch(Font fnt, int charcode, int x, int y, int translation = Font.CR_UNTRANSLATED, bool nomove = false) + { + int width = fnt.GetCharWidth(charcode); + screen.DrawChar(fnt, translation, x, y, charcode, nomove ? DTA_CleanNoMove : DTA_Clean, true); + return x - width; + } + + //==================================================================== + // + // Draws a level name with the big font + // + // x is no longer passed as a parameter because the text is now broken into several lines + // if it is too long + // + //==================================================================== + + protected int DrawName(int y, TextureID tex, String levelname) + { + // draw + if (tex.isValid()) + { + int w,h; + [w, h] = TexMan.GetSize(tex); + let size = TexMan.GetScaledSize(tex); + screen.DrawTexture(tex, true, (screen.GetWidth() - size.X * CleanXfac) /2, y, DTA_CleanNoMove, true); + if (h > 50) + { // Fix for Deus Vult II and similar wads that decide to make these hugely tall + // patches with vast amounts of empty space at the bottom. + size.Y = TexMan.CheckRealHeight(tex) * size.Y / h; + } + return y + (h + BigFont.GetHeight()/4) * CleanYfac; + } + else if (levelname.Length() > 0) + { + int h = 0; + int lumph = mapname.mFont.GetHeight() * CleanYfac; + + BrokenLines lines = mapname.mFont.BreakLines(levelname, screen.GetWidth() / CleanXfac); + + int count = lines.Count(); + for (int i = 0; i < count; i++) + { + screen.DrawText(mapname.mFont, mapname.mColor, (screen.GetWidth() - lines.StringWidth(i) * CleanXfac) / 2, y + h, lines.StringAt(i), DTA_CleanNoMove, true); + h += lumph; + } + return y + h + lumph/4; + } + return 0; + } + + //==================================================================== + // + // Draws a text, either as patch or as string from the string table + // + //==================================================================== + + protected int DrawPatchText(int y, PatchInfo pinfo, String stringname) + { + String string = Stringtable.Localize(stringname); + int midx = screen.GetWidth() / 2; + + if (pinfo.mPatch.isValid()) + { + let size = TexMan.GetScaledSize(pinfo.mPatch); + screen.DrawTexture(pinfo.mPatch, true, midx - size.X * CleanXfac/2, y, DTA_CleanNoMove, true); + return y + (size.Y * CleanYfac); + } + else + { + screen.DrawText(pinfo.mFont, pinfo.mColor, midx - pinfo.mFont.StringWidth(string) * CleanXfac/2, y, string, DTA_CleanNoMove, true); + return y + pinfo.mFont.GetHeight() * CleanYfac; + } + } + + + //==================================================================== + // + // Draws " Finished!" + // + // Either uses the specified patch or the big font + // A level name patch can be specified for all games now, not just Doom. + // + //==================================================================== + + protected int drawLF () + { + int y = WI_TITLEY * CleanYfac; + + y = DrawName(y, wbs.LName0, lnametexts[0]); + + // Adjustment for different font sizes for map name and 'finished'. + y -= ((mapname.mFont.GetHeight() - finished.mFont.GetHeight()) * CleanYfac) / 4; + + // draw "Finished!" + if (y < (NG_STATSY - finished.mFont.GetHeight()*3/4) * CleanYfac) + { + // don't draw 'finished' if the level name is too tall + y = DrawPatchText(y, finished, "$WI_FINISHED"); + } + return y; + } + + + //==================================================================== + // + // Draws "Entering " + // + // Either uses the specified patch or the big font + // A level name patch can be specified for all games now, not just Doom. + // + //==================================================================== + + protected void drawEL () + { + int y = WI_TITLEY * CleanYfac; + + y = DrawPatchText(y, entering, "$WI_ENTERING"); + y += entering.mFont.GetHeight() * CleanYfac / 4; + DrawName(y, wbs.LName1, lnametexts[1]); + } + + + //==================================================================== + // + // Draws a number. + // If digits > 0, then use that many digits minimum, + // otherwise only use as many as necessary. + // x is the right edge of the number. + // Returns new x position, that is, the left edge of the number. + // + //==================================================================== + protected int drawNum (Font fnt, int x, int y, int n, int digits, bool leadingzeros = true, int translation = Font.CR_UNTRANSLATED) + { + int fntwidth = fnt.StringWidth("3"); + String text; + int len; + bool nomove = fnt != IntermissionFont; + + if (nomove) + { + fntwidth *= CleanXfac; + } + text = String.Format("%d", n); + len = text.Length(); + if (leadingzeros) + { + int filldigits = digits - len; + for(int i = 0; i < filldigits; i++) + { + text = "0" .. text; + } + len = text.Length(); + } + + for(int text_p = len-1; text_p >= 0; text_p--) + { + // Digits are centered in a box the width of the '3' character. + // Other characters (specifically, '-') are right-aligned in their cell. + int c = text.CharCodeAt(text_p); + if (c >= "0" && c <= "9") + { + x -= fntwidth; + DrawCharPatch(fnt, c, x + (fntwidth - fnt.GetCharWidth(c)) / 2, y, translation, nomove); + } + else + { + DrawCharPatch(fnt, c, x - fnt.GetCharWidth(c), y, translation, nomove); + x -= fntwidth; + } + } + if (len < digits) + { + x -= fntwidth * (digits - len); + } + return x; + } + + //==================================================================== + // + // + // + //==================================================================== + + protected void drawPercent (Font fnt, int x, int y, int p, int b, bool show_total = true, int color = Font.CR_UNTRANSLATED) + { + if (p < 0) + return; + + if (wi_percents) + { + if (fnt != IntermissionFont) + { + x -= fnt.StringWidth("%") * CleanXfac; + } + else + { + x -= fnt.StringWidth("%"); + } + screen.DrawText(fnt, color, x, y, "%", fnt != IntermissionFont ? DTA_CleanNoMove : DTA_Clean, true); + if (fnt != IntermissionFont) + { + x -= 2*CleanXfac; + } + drawNum(fnt, x, y, b == 0 ? 100 : p * 100 / b, -1, false, color); + } + else + { + if (show_total) + { + x = drawNum(fnt, x, y, b, 2, false); + x -= fnt.StringWidth("/"); + screen.DrawText (IntermissionFont, color, x, y, "/", DTA_Clean, true); + } + drawNum (fnt, x, y, p, -1, false, color); + } + } + + //==================================================================== + // + // Display level completion time and par, or "sucks" message if overflow. + // + //==================================================================== + protected void drawTime (int x, int y, int t, bool no_sucks=false) + { + bool sucky; + + if (t < 0) + return; + + sucky = !no_sucks && t >= wbs.sucktime * 60 * 60 && wbs.sucktime > 0; + + if (sucky) + { // "sucks" + if (Sucks.isValid()) + { + let size = TexMan.GetScaledSize(Sucks); + screen.DrawTexture (Sucks, true, x - size.X, y - size.Y - 2, DTA_Clean, true); + } + else + { + screen.DrawText (BigFont, Font.CR_UNTRANSLATED, x - BigFont.StringWidth("SUCKS"), y - IntermissionFont.GetHeight() - 2, "SUCKS", DTA_Clean, true); + } + } + + int hours = t / 3600; + t -= hours * 3600; + int minutes = t / 60; + t -= minutes * 60; + int seconds = t; + + // Why were these offsets hard coded? Half the WADs with custom patches + // I tested screwed up miserably in this function! + int num_spacing = IntermissionFont.GetCharWidth("3"); + int colon_spacing = IntermissionFont.GetCharWidth(":"); + + x = drawNum (IntermissionFont, x, y, seconds, 2) - 1; + DrawCharPatch (IntermissionFont, ":", x -= colon_spacing, y); + x = drawNum (IntermissionFont, x, y, minutes, 2, hours!=0); + if (hours) + { + DrawCharPatch (IntermissionFont, ":", x -= colon_spacing, y); + drawNum (IntermissionFont, x, y, hours, 2); + } + } + + +}