static int ParseStandardProperty(FScanner &scanner, UMapEntry *mape) { // find the next line with content. // this line is no property. scanner.MustGetToken(TK_Identifier); FString pname = scanner.String; scanner.MustGetToken('='); if (!pname.CompareNoCase("levelname")) { scanner.MustGetToken(TK_StringConst); mape->LevelName = scanner.String; } else if (!pname.CompareNoCase("next")) { ParseLumpName(scanner, mape->nextmap); } else if (!pname.CompareNoCase("nextsecret")) { ParseLumpName(scanner, mape->nextsecret); } else if (!pname.CompareNoCase("levelpic")) { ParseLumpName(scanner, mape->levelpic); } else if (!pname.CompareNoCase("skytexture")) { ParseLumpName(scanner, mape->skytexture); } else if (!pname.CompareNoCase("music")) { ParseLumpName(scanner, mape->music); } else if (!pname.CompareNoCase("endpic")) { ParseLumpName(scanner, mape->endpic); } else if (!pname.CompareNoCase("endcast")) { scanner.MustGetBoolToken(); if (scanner.Number) strcpy(mape->endpic, "$CAST"); else strcpy(mape->endpic, "-"); } else if (!pname.CompareNoCase("endbunny")) { scanner.MustGetBoolToken(); if (scanner.Number) strcpy(mape->endpic, "$BUNNY"); else strcpy(mape->endpic, "-"); } else if (!pname.CompareNoCase("endgame")) { scanner.MustGetBoolToken(); if (scanner.Number) strcpy(mape->endpic, "!"); else strcpy(mape->endpic, "-"); } else if (!pname.CompareNoCase("exitpic")) { ParseLumpName(scanner, mape->exitpic); } else if (!pname.CompareNoCase("enterpic")) { ParseLumpName(scanner, mape->enterpic); } else if (!pname.CompareNoCase("nointermission")) { scanner.MustGetBoolToken(); mape->nointermission = scanner.Number; } else if (!pname.CompareNoCase("partime")) { scanner.MustGetValue(false); mape->partime = TICRATE * scanner.Number; } else if (!pname.CompareNoCase("intertext")) { mape->InterText = ParseMultiString(scanner, 1); if (mape->InterText.IsEmpty()) return 0; } else if (!pname.CompareNoCase("intertextsecret")) { mape->InterTextSecret = ParseMultiString(scanner, 1); if (mape->InterTextSecret.IsEmpty()) return 0; } else if (!pname.CompareNoCase("interbackdrop")) { ParseLumpName(scanner, mape->interbackdrop); } else if (!pname.CompareNoCase("intermusic")) { ParseLumpName(scanner, mape->intermusic); } else if (!pname.CompareNoCase("episode")) { FString Episode = ParseMultiString(scanner, 1); if (Episode.IsEmpty()) return 0; if (Episode.Compare("-") == 0) { // clear the given episode for (unsigned i = 0; i < AllEpisodes.Size(); i++) { if (AllEpisodes[i].mEpisodeMap.CompareNoCase(mape->MapName) == 0) { AllEpisodes.Delete(i); break; } } } else { auto split = Episode.Split("\n"); // add the given episode FEpisode epi; epi.mEpisodeName = strbin1(split[1]); epi.mEpisodeMap = mape->MapName; epi.mPicName = split[0]; epi.mShortcut = split[2][0]; unsigned i; for (i = 0; i < AllEpisodes.Size(); i++) { if (AllEpisodes[i].mEpisodeMap.CompareNoCase(mape->MapName) == 0) { AllEpisodes[i] = std::move(epi); break; } } if (i == AllEpisodes.Size()) { AllEpisodes.Push(epi); } } } else if (!pname.CompareNoCase("bossaction")) { scanner.MustGetToken(TK_Identifier); int classnum, special, tag; if (!stricmp(scanner.String, "clear")) { // mark level free of boss actions classnum = special = tag = -1; mape->BossActions.Clear(); } else { FName type = scanner.String; scanner.MustGetToken(','); scanner.MustGetValue(false); int special = scanner.Number; scanner.MustGetToken(','); scanner.MustGetValue(false); int tag = scanner.Number; // allow no 0-tag specials here, unless a level exit. if (tag != 0 || special == 11 || special == 51 || special == 52 || special == 124) { // This cannot be evaluated here because this needs to be done in the context of the level being used. FSpecialAction & bossact = mape->BossActions[mape->BossActions.Reserve(1)]; bossact = { type, special | 0x40000000, {tag} }; }; } } else { // Skip over all unknown properties. do { if (!scanner.CheckValue(true)) { scanner.MustGetAnyToken(); if (scanner.TokenType != TK_Identifier && scanner.TokenType != TK_StringConst && scanner.TokenType != TK_True && scanner.TokenType != TK_False) { scanner.ScriptError("Identifier or value expected"); } } } while (scanner.CheckToken(',')); } return 1; }
FFont::FFont (const char *name, const char *nametemplate, const char *filetemplate, int lfirst, int lcount, int start, int fdlump, int spacewidth, bool notranslate, bool iwadonly) { int i; FTextureID lump; char buffer[12]; int maxyoffs; bool doomtemplate = (nametemplate && (gameinfo.gametype & GAME_DoomChex)) ? strncmp (nametemplate, "STCFN", 5) == 0 : false; DVector2 Scale = { 1, 1 }; noTranslate = notranslate; Lump = fdlump; GlobalKerning = false; FontName = name; Next = FirstFont; FirstFont = this; Cursor = '_'; ActiveColors = 0; SpaceWidth = 0; FontHeight = 0; uint8_t pp = 0; for (auto &p : PatchRemap) p = pp++; translateUntranslated = false; int FixedWidth = 0; maxyoffs = 0; TMap<int, FTexture*> charMap; int minchar = INT_MAX; int maxchar = INT_MIN; // Read the font's configuration. // This will not be done for the default fonts, because they are not atomic and the default content does not need it. TArray<FolderEntry> folderdata; if (filetemplate != nullptr) { FStringf path("fonts/%s/", filetemplate); // If a name template is given, collect data from all resource files. // For anything else, each folder is being treated as an atomic, self-contained unit and mixing from different glyph sets is blocked. Wads.GetLumpsInFolder(path, folderdata, nametemplate == nullptr); //if (nametemplate == nullptr) { FStringf infpath("fonts/%s/font.inf", filetemplate); unsigned index = folderdata.FindEx([=](const FolderEntry &entry) { return infpath.CompareNoCase(entry.name) == 0; }); if (index < folderdata.Size()) { FScanner sc; sc.OpenLumpNum(folderdata[index].lumpnum); while (sc.GetToken()) { sc.TokenMustBe(TK_Identifier); if (sc.Compare("Kerning")) { sc.MustGetValue(false); GlobalKerning = sc.Number; } else if (sc.Compare("Scale")) { sc.MustGetValue(true); Scale.Y = Scale.X = sc.Float; if (sc.CheckToken(',')) { sc.MustGetValue(true); Scale.Y = sc.Float; } } else if (sc.Compare("SpaceWidth")) { sc.MustGetValue(false); SpaceWidth = sc.Number; } else if (sc.Compare("FontHeight")) { sc.MustGetValue(false); FontHeight = sc.Number; } else if (sc.Compare("CellSize")) { sc.MustGetValue(false); FixedWidth = sc.Number; sc.MustGetToken(','); sc.MustGetValue(false); FontHeight = sc.Number; } else if (sc.Compare("Translationtype")) { sc.MustGetToken(TK_Identifier); if (sc.Compare("console")) { TranslationType = 1; } else if (sc.Compare("standard")) { TranslationType = 0; } else { sc.ScriptError("Unknown translation type %s", sc.String); } } } } } } if (FixedWidth > 0) { ReadSheetFont(folderdata, FixedWidth, FontHeight, Scale); Type = Folder; } else { if (nametemplate != nullptr) { if (!iwadonly) { for (i = 0; i < lcount; i++) { int position = lfirst + i; mysnprintf(buffer, countof(buffer), nametemplate, i + start); lump = TexMan.CheckForTexture(buffer, ETextureType::MiscPatch); if (doomtemplate && lump.isValid() && i + start == 121) { // HACKHACK: Don't load STCFN121 in doom(2), because // it's not really a lower-case 'y' but a '|'. // Because a lot of wads with their own font seem to foolishly // copy STCFN121 and make it a '|' themselves, wads must // provide STCFN120 (x) and STCFN122 (z) for STCFN121 to load as a 'y'. if (!TexMan.CheckForTexture("STCFN120", ETextureType::MiscPatch).isValid() || !TexMan.CheckForTexture("STCFN122", ETextureType::MiscPatch).isValid()) { // insert the incorrectly named '|' graphic in its correct position. position = 124; } } if (lump.isValid()) { Type = Multilump; if (position < minchar) minchar = position; if (position > maxchar) maxchar = position; charMap.Insert(position, TexMan.GetTexture(lump)); } } } else { FTexture *texs[256] = {}; if (lcount > 256 - start) lcount = 256 - start; for (i = 0; i < lcount; i++) { TArray<FTextureID> array; mysnprintf(buffer, countof(buffer), nametemplate, i + start); TexMan.ListTextures(buffer, array, true); for (auto entry : array) { FTexture *tex = TexMan.GetTexture(entry, false); if (tex && tex->SourceLump >= 0 && Wads.GetLumpFile(tex->SourceLump) <= Wads.GetIwadNum() && tex->UseType == ETextureType::MiscPatch) { texs[i] = tex; } } } if (doomtemplate) { // Handle the misplaced '|'. if (texs[121 - '!'] && !texs[120 - '!'] && !texs[122 - '!'] && !texs[124 - '!']) { texs[124 - '!'] = texs[121 - '!']; texs[121 - '!'] = nullptr; } } for (i = 0; i < lcount; i++) { if (texs[i]) { int position = lfirst + i; Type = Multilump; if (position < minchar) minchar = position; if (position > maxchar) maxchar = position; charMap.Insert(position, texs[i]); } } } } if (folderdata.Size() > 0) { // all valid lumps must be named with a hex number that represents its Unicode character index. for (auto &entry : folderdata) { char *endp; auto base = ExtractFileBase(entry.name); auto position = strtoll(base.GetChars(), &endp, 16); if ((*endp == 0 || (*endp == '.' && position >= '!' && position < 0xffff))) { auto lump = TexMan.CheckForTexture(entry.name, ETextureType::MiscPatch); if (lump.isValid()) { if ((int)position < minchar) minchar = (int)position; if ((int)position > maxchar) maxchar = (int)position; auto tex = TexMan.GetTexture(lump); tex->SetScale(Scale); charMap.Insert((int)position, tex); Type = Folder; } } } } FirstChar = minchar; LastChar = maxchar; auto count = maxchar - minchar + 1; Chars.Resize(count); int fontheight = 0; for (i = 0; i < count; i++) { auto lump = charMap.CheckKey(FirstChar + i); if (lump != nullptr) { FTexture *pic = *lump; if (pic != nullptr) { int height = pic->GetDisplayHeight(); int yoffs = pic->GetDisplayTopOffset(); if (yoffs > maxyoffs) { maxyoffs = yoffs; } height += abs(yoffs); if (height > fontheight) { fontheight = height; } } pic->SetUseType(ETextureType::FontChar); if (!noTranslate) { Chars[i].OriginalPic = pic; Chars[i].TranslatedPic = new FImageTexture(new FFontChar1(pic->GetImage()), ""); Chars[i].TranslatedPic->CopySize(pic); Chars[i].TranslatedPic->SetUseType(ETextureType::FontChar); TexMan.AddTexture(Chars[i].TranslatedPic); } else { Chars[i].TranslatedPic = pic; } Chars[i].XMove = Chars[i].TranslatedPic->GetDisplayWidth(); } else { Chars[i].TranslatedPic = nullptr; Chars[i].XMove = INT_MIN; } } if (SpaceWidth == 0) // An explicit override from the .inf file must always take precedence { if (spacewidth != -1) { SpaceWidth = spacewidth; } else if ('N' - FirstChar >= 0 && 'N' - FirstChar < count && Chars['N' - FirstChar].TranslatedPic != nullptr) { SpaceWidth = (Chars['N' - FirstChar].XMove + 1) / 2; } else { SpaceWidth = 4; } } if (FontHeight == 0) FontHeight = fontheight; FixXMoves(); } if (!noTranslate) LoadTranslations(); }