Exemple #1
0
void SBarInfo::ParseMugShotBlock(FScanner &sc, FMugShotState &state)
{
	sc.MustGetToken('{');
	while(!sc.CheckToken('}'))
	{
		FMugShotFrame frame;
		bool multiframe = false;
		if(sc.CheckToken('{'))
			multiframe = true;
		do
		{
			sc.MustGetToken(TK_Identifier);
			if(strlen(sc.String) > 5)
				sc.ScriptError("MugShot frames cannot exceed 5 characters.");
			frame.Graphic.Push(sc.String);
		}
		while(multiframe && sc.CheckToken(','));
		if(multiframe)
			sc.MustGetToken('}');
		bool negative = sc.CheckToken('-');
		sc.MustGetToken(TK_IntConst);
		frame.Delay = (negative ? -1 : 1)*sc.Number;
		sc.MustGetToken(';');
		state.Frames.Push(frame);
	}
}
static void ParseEnum (FScanner &sc, PSymbolTable *symt, PClass *cls)
{
	int currvalue = 0;

	sc.MustGetToken('{');
	while (!sc.CheckToken('}'))
	{
		sc.MustGetToken(TK_Identifier);
		FName symname = sc.String;
		if (sc.CheckToken('='))
		{
			FxExpression *expr = ParseExpression (sc, cls);
			currvalue = expr->EvalExpression(NULL).GetInt();
			delete expr;
		}
		PSymbolConst *sym = new PSymbolConst(symname);
		sym->ValueType = VAL_Int;
		sym->Value = currvalue;
		if (symt->AddSymbol (sym) == NULL)
		{
			delete sym;
			sc.ScriptMessage ("'%s' is already defined in '%s'.",
				symname.GetChars(), cls? cls->TypeName.GetChars() : "Global");
			FScriptPosition::ErrorCounter++;
		}
		// This allows a comma after the last value but doesn't enforce it.
		if (sc.CheckToken('}')) break;
		sc.MustGetToken(',');
		currvalue++;
	}
	sc.MustGetToken(';');
}
Exemple #3
0
static void ParseActionDef (FScanner &sc, PClassActor *cls)
{
	unsigned int error = 0;
	FName funcname;
	TArray<PType *> rets;
	
	if (sc.LumpNum == -1 || Wads.GetLumpFile(sc.LumpNum) > 0)
	{
		sc.ScriptMessage ("Action functions can only be imported by internal class and actor definitions!");
		FScriptPosition::ErrorCounter++;
	}

	sc.MustGetToken(TK_Native);
	// check for a return value
	do
	{
		if (sc.CheckToken(TK_Int) || sc.CheckToken(TK_Bool))
		{
			rets.Push(TypeSInt32);
		}
		else if (sc.CheckToken(TK_State))
		{
			rets.Push(TypeState);
		}
		else if (sc.CheckToken(TK_Float))
		{
			rets.Push(TypeFloat64);
		}
	}
	while (sc.CheckToken(','));
	sc.MustGetToken(TK_Identifier);
	funcname = sc.String;
	ParseFunctionDef(sc, cls, funcname, rets, VARF_Method | VARF_Action);
}
Exemple #4
0
static FString ParseMultiString(FScanner &scanner, int error)
{
	FString build;
	
	if (scanner.CheckToken(TK_Identifier))
	{
		if (!stricmp(scanner.String, "clear"))
		{
			return "-";
		}
		else
		{
			scanner.ScriptError("Either 'clear' or string constant expected");
		}
	}
	
	do
	{
		scanner.MustGetToken(TK_StringConst);
		if (build.Len() > 0) build += "\n";
		build += scanner.String;
	} 
	while (scanner.CheckToken(','));
	return build;
}
Exemple #5
0
		void		GetCoordinates(FScanner &sc, bool fullScreenOffsets, SBarInfoCoordinate &x, SBarInfoCoordinate &y)
		{
			bool negative = false;
			bool relCenter = false;
			SBarInfoCoordinate *coords[2] = {&x, &y};
			for(int i = 0;i < 2;i++)
			{
				negative = false;
				relCenter = false;
				if(i > 0)
					sc.MustGetToken(',');
			
				// [-]INT center
				negative = sc.CheckToken('-');
				sc.MustGetToken(TK_IntConst);
				coords[i]->Set(negative ? -sc.Number : sc.Number, false);
				if(sc.CheckToken('+'))
				{
					sc.MustGetToken(TK_Identifier);
					if(!sc.Compare("center"))
						sc.ScriptError("Expected 'center' but got '%s' instead.", sc.String);
					relCenter = true;
				}
				if(fullScreenOffsets)
				{
					coords[i]->SetRelCenter(relCenter);
				}
			}

			//if(!fullScreenOffsets)
			//	y.SetCoord((negative ? -sc.Number : sc.Number) - (200 - script->height));
		}
Exemple #6
0
void SBarInfo::getCoordinates(FScanner &sc, bool fullScreenOffsets, int &x, int &y)
{
	bool negative = false;
	bool relCenter = false;
	int *coords[2] = {&x, &y};
	for(int i = 0;i < 2;i++)
	{
		negative = false;
		relCenter = false;
		if(i > 0)
			sc.MustGetToken(',');

		// [-]INT center
		negative = sc.CheckToken('-');
		sc.MustGetToken(TK_IntConst);
		*coords[i] = negative ? -sc.Number : sc.Number;
		if(sc.CheckToken('+'))
		{
			sc.MustGetToken(TK_Identifier);
			if(!sc.Compare("center"))
				sc.ScriptError("Expected 'center' but got '%s' instead.", sc.String);
			relCenter = true;
		}
		if(fullScreenOffsets)
		{
			if(relCenter)
				*coords[i] |= SBarInfoCoordinate::REL_CENTER;
			else
				*coords[i] &= ~SBarInfoCoordinate::REL_CENTER;
		}
	}

	if(!fullScreenOffsets)
		y = (negative ? -sc.Number : sc.Number) - (200 - this->height);
}
Exemple #7
0
static void PSR_FindEndBlock(FScanner &sc)
{
	int depth = 1;
	do
	{
		if(sc.CheckToken('}'))
			--depth;
		else if(sc.CheckToken('{'))
			++depth;
		else
			sc.MustGetAnyToken();
	}
	while(depth);
}
Exemple #8
0
static bool PSR_FindAndEnterBlock(FScanner &sc, const char* keyword)
{
	// Finds a block with a given keyword and then enter it (opening brace)
	// Should be closed with PSR_FindEndBlock
	while(sc.GetToken())
	{
		if(sc.TokenType == '}')
		{
			sc.UnGet();
			return false;
		}

		sc.TokenMustBe(TK_StringConst);
		if(!sc.Compare(keyword))
		{
			if(!sc.CheckToken(TK_StringConst))
				PSR_SkipBlock(sc);
		}
		else
		{
			sc.MustGetToken('{');
			return true;
		}
	}
	return false;
}
Exemple #9
0
static TArray<FString> PSR_ReadBaseInstalls(FScanner &sc)
{
	TArray<FString> result;

	// Get a list of possible install directories.
	while(sc.GetToken())
	{
		if(sc.TokenType == '}')
			break;

		sc.TokenMustBe(TK_StringConst);
		FString key(sc.String);
		if(key.Left(18).CompareNoCase("BaseInstallFolder_") == 0)
		{
			sc.MustGetToken(TK_StringConst);
			result.Push(FString(sc.String) + "/steamapps/common");
		}
		else
		{
			if(sc.CheckToken('{'))
				PSR_FindEndBlock(sc);
			else
				sc.MustGetToken(TK_StringConst);
		}
	}

	return result;
}
static void ParseConstant (FScanner &sc, PSymbolTable *symt, PClassActor *cls)
{
	// Read the type and make sure it's int or float.
	if (sc.CheckToken(TK_Int) || sc.CheckToken(TK_Float))
	{
		int type = sc.TokenType;
		sc.MustGetToken(TK_Identifier);
		FName symname = sc.String;
		sc.MustGetToken('=');
		FxExpression *expr = ParseExpression (sc, cls, true);
		sc.MustGetToken(';');

		if (!expr->isConstant())
		{
			sc.ScriptMessage("Constant definition is not a constant");
			FScriptPosition::ErrorCounter++;
		}
		else
		{
			ExpVal val = static_cast<FxConstant *>(expr)->GetValue();
			delete expr;
			PSymbolConstNumeric *sym;
			if (type == TK_Int)
			{
				sym = new PSymbolConstNumeric(symname, TypeSInt32);
				sym->Value = val.GetInt();
			}
			else
			{
				sym = new PSymbolConstNumeric(symname, TypeFloat64);
				sym->Float = val.GetFloat();
			}
			if (symt->AddSymbol (sym) == NULL)
			{
				delete sym;
				sc.ScriptMessage ("'%s' is already defined in '%s'.",
					symname.GetChars(), cls? cls->TypeName.GetChars() : "Global");
				FScriptPosition::ErrorCounter++;
			}
		}
	}
	else
	{
		sc.ScriptMessage("Numeric type required for constant");
		FScriptPosition::ErrorCounter++;
	}
}
Exemple #11
0
void gl_ParseHardwareShader(FScanner &sc, int deflump)
{
	int type = FTexture::TEX_Any;
	bool disable_fullbright=false;
	bool thiswad = false;
	bool iwad = false;
	int maplump = -1;
	FString maplumpname;
	float speed = 1.f;

	sc.MustGetString();
	if (sc.Compare("texture")) type = FTexture::TEX_Wall;
	else if (sc.Compare("flat")) type = FTexture::TEX_Flat;
	else if (sc.Compare("sprite")) type = FTexture::TEX_Sprite;
	else sc.UnGet();

	sc.MustGetString();
	FTextureID no = TexMan.CheckForTexture(sc.String, type);
	FTexture *tex = TexMan[no];

	sc.MustGetToken('{');
	while (!sc.CheckToken('}'))
	{
		sc.MustGetString();
		if (sc.Compare("shader"))
		{
			sc.MustGetString();
			maplumpname = sc.String;
		}
		else if (sc.Compare("speed"))
		{
			sc.MustGetFloat();
			speed = float(sc.Float);
		}
	}
	if (!tex)
	{
		return;
	}

	if (maplumpname.IsNotEmpty())
	{
		if (tex->bWarped != 0)
		{
			Printf("Cannot combine warping with hardware shader on texture '%s'\n", tex->Name.GetChars());
			return;
		}
		tex->gl_info.shaderspeed = speed; 
		for(unsigned i=0;i<usershaders.Size();i++)
		{
			if (!usershaders[i].CompareNoCase(maplumpname))
			{
				tex->gl_info.shaderindex = i + FIRST_USER_SHADER;
				return;
			}
		}
		tex->gl_info.shaderindex = usershaders.Push(maplumpname) + FIRST_USER_SHADER;
	}	
}
//==========================================================================
//***
// DoActionSpecials
// handles action specials as code pointers
//
//==========================================================================
bool DoActionSpecials(FScanner &sc, FState & state, Baggage &bag)
{
	int i;
	int min_args, max_args;
	FString specname = sc.String;

	int special = P_FindLineSpecial(sc.String, &min_args, &max_args);

	if (special > 0 && min_args >= 0)
	{

		int paramindex=PrepareStateParameters(&state, 6, bag.Info->Class);

		StateParams.Set(paramindex, new FxConstant(special, sc));

		// Make this consistent with all other parameter parsing
		if (sc.CheckToken('('))
		{
			for (i = 0; i < 5;)
			{
				StateParams.Set(paramindex+i+1, ParseExpression (sc, bag.Info->Class));
				i++;
				if (!sc.CheckToken (',')) break;
			}
			sc.MustGetToken (')');
		}
		else i=0;

		if (i < min_args)
		{
			sc.ScriptError ("Too few arguments to %s", specname.GetChars());
		}
		if (i > max_args)
		{
			sc.ScriptError ("Too many arguments to %s", specname.GetChars());
		}

		state.SetAction(FindGlobalActionFunction("A_CallSpecial"), false);
		return true;
	}
	return false;
}
Exemple #13
0
static int ParseMapEntry(FScanner &scanner, UMapEntry *val)
{
	scanner.MustGetToken(TK_Identifier);

	val->MapName = scanner.String;
	scanner.MustGetToken('{');
	while(!scanner.CheckToken('}'))
	{
		ParseStandardProperty(scanner, val);
	}
	return 1;
}
static void ParseConstant (FScanner &sc, PSymbolTable * symt, PClass *cls)
{
	// Read the type and make sure it's int or float.
	if (sc.CheckToken(TK_Int) || sc.CheckToken(TK_Float))
	{
		int type = sc.TokenType;
		sc.MustGetToken(TK_Identifier);
		FName symname = sc.String;
		sc.MustGetToken('=');
		FxExpression *expr = ParseExpression (sc, cls);
		sc.MustGetToken(';');

		ExpVal val = expr->EvalExpression(NULL);
		delete expr;
		PSymbolConst *sym = new PSymbolConst(symname);
		if (type == TK_Int)
		{
			sym->ValueType = VAL_Int;
			sym->Value = val.GetInt();
		}
		else
		{
			sym->ValueType = VAL_Float;
			sym->Float = val.GetFloat();
		}
		if (symt->AddSymbol (sym) == NULL)
		{
			delete sym;
			sc.ScriptMessage ("'%s' is already defined in '%s'.",
				symname.GetChars(), cls? cls->TypeName.GetChars() : "Global");
			FScriptPosition::ErrorCounter++;
		}
	}
	else
	{
		sc.ScriptMessage("Numeric type required for constant");
		FScriptPosition::ErrorCounter++;
	}
}
static void ParseEnum (FScanner &sc, PSymbolTable *symt, PClassActor *cls)
{
	int currvalue = 0;

	sc.MustGetToken('{');
	while (!sc.CheckToken('}'))
	{
		sc.MustGetToken(TK_Identifier);
		FName symname = sc.String;
		if (sc.CheckToken('='))
		{
			FxExpression *expr = ParseExpression (sc, cls, true);
			if (!expr->isConstant())
			{
				sc.ScriptMessage("'%s' must be constant", symname.GetChars());
				FScriptPosition::ErrorCounter++;
			}
			else
			{
				currvalue = static_cast<FxConstant *>(expr)->GetValue().GetInt();
			}
			delete expr;
		}
		PSymbolConstNumeric *sym = new PSymbolConstNumeric(symname, TypeSInt32);
		sym->Value = currvalue;
		if (symt->AddSymbol (sym) == NULL)
		{
			delete sym;
			sc.ScriptMessage ("'%s' is already defined in '%s'.",
				symname.GetChars(), cls? cls->TypeName.GetChars() : "Global");
			FScriptPosition::ErrorCounter++;
		}
		// This allows a comma after the last value but doesn't enforce it.
		if (sc.CheckToken('}')) break;
		sc.MustGetToken(',');
		currvalue++;
	}
	sc.MustGetToken(';');
}
Exemple #16
0
int SBarInfo::getSignedInteger(FScanner &sc)
{
	if(sc.CheckToken('-'))
	{
		sc.MustGetToken(TK_IntConst);
		return -sc.Number;
	}
	else
	{
		sc.MustGetToken(TK_IntConst);
		return sc.Number;
	}
}
Exemple #17
0
		EColorRange	GetTranslation(FScanner &sc)
		{
			if (!sc.CheckToken(TK_Null)) sc.MustGetToken(TK_Identifier);
			EColorRange returnVal = CR_UNTRANSLATED;
			FString namedTranslation; //we must send in "[translation]"
			const BYTE *trans_ptr;
			namedTranslation.Format("[%s]", sc.String);
			trans_ptr = (const BYTE *)(&namedTranslation[0]);
			if((returnVal = V_ParseFontColor(trans_ptr, CR_UNTRANSLATED, CR_UNTRANSLATED)) == CR_UNDEFINED)
			{
				sc.ScriptError("Missing definition for color %s.", sc.String);
			}
			return returnVal;
		}
Exemple #18
0
		void	Parse(FScanner &sc, bool fullScreenOffsets)
		{
			this->fullScreenOffsets = fullScreenOffsets;
			if(sc.CheckToken(','))
			{
				while(sc.CheckToken(TK_Identifier))
				{
					if(sc.Compare("forcescaled"))
						forceScaled = true;
					else if(sc.Compare("fullscreenoffsets"))
						this->fullScreenOffsets = true;
					else
						sc.ScriptError("Unkown flag '%s'.", sc.String);
					if(!sc.CheckToken('|') && !sc.CheckToken(','))
					{
						SBarInfoCommandFlowControl::Parse(sc, this->fullScreenOffsets);
						return;
					}
				}
				sc.MustGetToken(TK_FloatConst);
				alpha = sc.Float;
			}
			SBarInfoCommandFlowControl::Parse(sc, this->fullScreenOffsets);
		}
Exemple #19
0
		void Parse(FScanner &sc, bool fullScreenOffsets)
		{
			bool negate = false;
			if(sc.CheckToken(TK_Identifier))
			{
				if(sc.Compare("not"))
					negate = true;
				else
					sc.UnGet();
			}

			ParseNegatable(sc, fullScreenOffsets);

			SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);

			if(negate)
				Negate();
		}
Exemple #20
0
		void ParseBlock(TDeletingArray<SBarInfoCommand *> &commands, FScanner &sc, bool fullScreenOffsets)
		{
			if(sc.CheckToken('{'))
			{
				while(SBarInfoCommand *cmd = NextCommand(sc))
				{
					cmd->Parse(sc, fullScreenOffsets);
					commands.Push(cmd);
				}
			}
			else
			{
				if(SBarInfoCommand *cmd = NextCommand(sc))
				{
					cmd->Parse(sc, fullScreenOffsets);
					commands.Push(cmd);
				}
				else
					sc.ScriptError("Missing command for flow control statement.");
			}
		}
bool FIntermissionActionTextscreen::ParseKey(FScanner &sc)
{
	if (sc.Compare("Position"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_IntConst);
		mTextX = sc.Number;
		sc.MustGetToken(',');
		sc.MustGetToken(TK_IntConst);
		mTextY = sc.Number;
		return true;
	}
	else if (sc.Compare("TextLump"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_StringConst);
		int lump = Wads.CheckNumForFullName(sc.String, true);
		if (lump > 0)
		{
			mText = Wads.ReadLump(lump).GetString();
		}
		else
		{
			// only print an error if coming from a PWAD
			if (Wads.GetLumpFile(sc.LumpNum) > 1)
				sc.ScriptMessage("Unknown text lump '%s'", sc.String);
			mText.Format("Unknown text lump '%s'", sc.String);
		}
		return true;
	}
	else if (sc.Compare("Text"))
	{
		sc.MustGetToken('=');
		do
		{
			sc.MustGetToken(TK_StringConst);
			mText << sc.String << '\n';
		}
		while (sc.CheckToken(','));
		return true;
	}
	else if (sc.Compare("TextColor"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_StringConst);
		mTextColor = V_FindFontColor(sc.String);
		return true;
	}
	else if (sc.Compare("TextDelay"))
	{
		sc.MustGetToken('=');
		if (!sc.CheckToken('-'))
		{
			sc.MustGetFloat();
			mTextDelay = int(sc.Float*TICRATE);
		}
		else
		{
			sc.MustGetToken(TK_IntConst);
			mTextDelay = sc.Number;
		}
		return true;
	}
	else if (sc.Compare("textspeed"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_IntConst);
		mTextSpeed = sc.Number;
		return true;
	}
	else return Super::ParseKey(sc);
}
Exemple #22
0
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;
}
static void ParseUserVariable (FScanner &sc, PSymbolTable *symt, PClassActor *cls)
{
	PType *type;
	int maxelems = 1;

	// Only non-native classes may have user variables.
	if (!cls->bRuntimeClass)
	{
		sc.ScriptError("Native classes may not have user variables");
	}

	// Read the type and make sure it's acceptable.
	sc.MustGetAnyToken();
	if (sc.TokenType != TK_Int && sc.TokenType != TK_Float)
	{
		sc.ScriptMessage("User variables must be of type 'int' or 'float'");
		FScriptPosition::ErrorCounter++;
	}
	type = sc.TokenType == TK_Int ? (PType *)TypeSInt32 : (PType *)TypeFloat64;

	sc.MustGetToken(TK_Identifier);
	// For now, restrict user variables to those that begin with "user_" to guarantee
	// no clashes with internal member variable names.
	if (sc.StringLen < 6 || strnicmp("user_", sc.String, 5) != 0)
	{
		sc.ScriptMessage("User variable names must begin with \"user_\"");
		FScriptPosition::ErrorCounter++;
	}

	FName symname = sc.String;

	// We must ensure that we do not define duplicates, even when they come from a parent table.
	if (symt->FindSymbol(symname, true) != NULL)
	{
		sc.ScriptMessage ("'%s' is already defined in '%s' or one of its ancestors.",
			symname.GetChars(), cls ? cls->TypeName.GetChars() : "Global");
		FScriptPosition::ErrorCounter++;
		return;
	}

	if (sc.CheckToken('['))
	{
		FxExpression *expr = ParseExpression(sc, cls, true);
		if (!expr->isConstant())
		{
			sc.ScriptMessage("Array size must be a constant");
			FScriptPosition::ErrorCounter++;
			maxelems = 1;
		}
		else
		{
			maxelems = static_cast<FxConstant *>(expr)->GetValue().GetInt();
		}
		sc.MustGetToken(']');
		if (maxelems <= 0)
		{
			sc.ScriptMessage("Array size must be positive");
			FScriptPosition::ErrorCounter++;
			maxelems = 1;
		}
		type = NewArray(type, maxelems);
	}
	sc.MustGetToken(';');

	PField *sym = cls->AddField(symname, type, 0);
	if (sym == NULL)
	{
		sc.ScriptMessage ("'%s' is already defined in '%s'.",
			symname.GetChars(), cls ? cls->TypeName.GetChars() : "Global");
		FScriptPosition::ErrorCounter++;
	}
}
static void ParseArgListDef(FScanner &sc, PClassActor *cls,
	TArray<PType *> &args, TArray<DWORD> &argflags)
{
	if (!sc.CheckToken(')'))
	{
		while (sc.TokenType != ')')
		{
			int flags = 0;
			PType *type = NULL;
			PClass *restrict = NULL;

			// Retrieve flags before type name
			for (;;)
			{
				if (sc.CheckToken(TK_Coerce) || sc.CheckToken(TK_Native))
				{
				}
				else
				{
					break;
				}
			}
			// Read the variable type
			sc.MustGetAnyToken();
			switch (sc.TokenType)
			{
			case TK_Bool:
				type = TypeBool;
				break;

			case TK_Int:
				type = TypeSInt32;
				break;

			case TK_Float:
				type = TypeFloat64;
				break;

			case TK_Sound:		type = TypeSound;		break;
			case TK_String:		type = TypeString;		break;
			case TK_Name:		type = TypeName;		break;
			case TK_State:		type = TypeState;		break;
			case TK_Color:		type = TypeColor;		break;
			case TK_Class:
				sc.MustGetToken('<');
				sc.MustGetToken(TK_Identifier);	// Get class name
				restrict = PClass::FindClass(sc.String);
				if (restrict == NULL)
				{
					sc.ScriptMessage("Unknown class type %s", sc.String);
					FScriptPosition::ErrorCounter++;
				}
				else
				{
					type = NewClassPointer(restrict);
				}
				sc.MustGetToken('>');
				break;
			case TK_Ellipsis:
				// Making the final type NULL signals a varargs function.
				type = NULL;
				sc.MustGetToken(')');
				sc.UnGet();
				break;
			default:
				sc.ScriptMessage ("Unknown variable type %s", sc.TokenName(sc.TokenType, sc.String).GetChars());
				type = TypeSInt32;
				FScriptPosition::ErrorCounter++;
				break;
			}
			// Read the optional variable name
			if (!sc.CheckToken(',') && !sc.CheckToken(')'))
			{
				sc.MustGetToken(TK_Identifier);
			}
			else
			{
				sc.UnGet();
			}

			if (sc.CheckToken('='))
			{
				flags |= VARF_Optional;
				FxExpression *def = ParseParameter(sc, cls, type, true);
				delete def;
			}

			args.Push(type);
			argflags.Push(flags);
			sc.MustGetAnyToken();
			if (sc.TokenType != ',' && sc.TokenType != ')')
			{
				sc.ScriptError ("Expected ',' or ')' but got %s instead", sc.TokenName(sc.TokenType, sc.String).GetChars());
			}
		}
	}
	sc.MustGetToken(';');
}
static void ParseNativeVariable (FScanner &sc, PSymbolTable * symt, PClass *cls)
{
	FExpressionType valuetype;

	if (sc.LumpNum == -1 || Wads.GetLumpFile(sc.LumpNum) > 0)
	{
		sc.ScriptMessage ("variables can only be imported by internal class and actor definitions!");
		FScriptPosition::ErrorCounter++;
	}

	// Read the type and make sure it's int or float.
	sc.MustGetAnyToken();
	switch (sc.TokenType)
	{
	case TK_Int:
		valuetype = VAL_Int;
		break;

	case TK_Float:
		valuetype = VAL_Float;
		break;

	case TK_Angle_t:
		valuetype = VAL_Angle;
		break;

	case TK_Fixed_t:
		valuetype = VAL_Fixed;
		break;

	case TK_Bool:
		valuetype = VAL_Bool;
		break;

	case TK_Identifier:
		valuetype = VAL_Object;
		// Todo: Object type
		sc.ScriptError("Object type variables not implemented yet!");
		break;

	default:
		sc.ScriptError("Invalid variable type %s", sc.String);
		return;
	}

	sc.MustGetToken(TK_Identifier);
	FName symname = sc.String;
	if (sc.CheckToken('['))
	{
		FxExpression *expr = ParseExpression (sc, cls);
		int maxelems = expr->EvalExpression(NULL).GetInt();
		delete expr;
		sc.MustGetToken(']');
		valuetype.MakeArray(maxelems);
	}
	sc.MustGetToken(';');

	const FVariableInfo *vi = FindVariable(symname, cls);
	if (vi == NULL)
	{
		sc.ScriptError("Unknown native variable '%s'", symname.GetChars());
	}

	PSymbolVariable *sym = new PSymbolVariable(symname);
	sym->offset = vi->address;	// todo
	sym->ValueType = valuetype;
	sym->bUserVar = false;

	if (symt->AddSymbol (sym) == NULL)
	{
		delete sym;
		sc.ScriptMessage ("'%s' is already defined in '%s'.",
			symname.GetChars(), cls? cls->TypeName.GetChars() : "Global");
		FScriptPosition::ErrorCounter++;
	}
}
bool FIntermissionActionScroller::ParseKey(FScanner &sc)
{
	struct ScrollType
	{
		const char *Name;
		EScrollDir Type;
	}
	const ST[] = {
		{ "Left", SCROLL_Left },
		{ "Right", SCROLL_Right },
		{ "Up", SCROLL_Up },
		{ "Down", SCROLL_Down },
		{ NULL, SCROLL_Left }
	};

	if (sc.Compare("ScrollDirection"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_Identifier);
		int v = sc.MatchString(&ST[0].Name, sizeof(ST[0]));
		if (v != -1) mScrollDir = ST[v].Type;
		return true;
	}
	else if (sc.Compare("InitialDelay"))
	{
		sc.MustGetToken('=');
		if (!sc.CheckToken('-'))
		{
			sc.MustGetFloat();
			mScrollDelay = int(sc.Float*TICRATE);
		}
		else
		{
			sc.MustGetToken(TK_IntConst);
			mScrollDelay = sc.Number;
		}
		return true;
	}
	else if (sc.Compare("ScrollTime"))
	{
		sc.MustGetToken('=');
		if (!sc.CheckToken('-'))
		{
			sc.MustGetFloat();
			mScrollTime = int(sc.Float*TICRATE);
		}
		else
		{
			sc.MustGetToken(TK_IntConst);
			mScrollTime = sc.Number;
		}
		return true;
	}
	else if (sc.Compare("Background2"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_StringConst);
		mSecondPic = sc.String;
		return true;
	}
	else return Super::ParseKey(sc);
}
static void ParseActionDef (FScanner &sc, PClass *cls)
{
	enum
	{
		OPTIONAL = 1
	};

	unsigned int error = 0;
	const AFuncDesc *afd;
	FName funcname;
	FString args;
	TArray<FxExpression *> DefaultParams;
	bool hasdefaults = false;
	
	if (sc.LumpNum == -1 || Wads.GetLumpFile(sc.LumpNum) > 0)
	{
		sc.ScriptMessage ("Action functions can only be imported by internal class and actor definitions!");
		++error;
	}

	sc.MustGetToken(TK_Native);
	sc.MustGetToken(TK_Identifier);
	funcname = sc.String;
	afd = FindFunction(sc.String);
	if (afd == NULL)
	{
		sc.ScriptMessage ("The function '%s' has not been exported from the executable.", sc.String);
		++error;
	}
	sc.MustGetToken('(');
	if (!sc.CheckToken(')'))
	{
		while (sc.TokenType != ')')
		{
			int flags = 0;
			char type = '@';

			// Retrieve flags before type name
			for (;;)
			{
				if (sc.CheckToken(TK_Coerce) || sc.CheckToken(TK_Native))
				{
				}
				else
				{
					break;
				}
			}
			// Read the variable type
			sc.MustGetAnyToken();
			switch (sc.TokenType)
			{
			case TK_Bool:
			case TK_Int:
				type = 'x';
				break;

			case TK_Float:
				type = 'y';
				break;

			case TK_Sound:		type = 's';		break;
			case TK_String:		type = 't';		break;
			case TK_Name:		type = 't';		break;
			case TK_State:		type = 'l';		break;
			case TK_Color:		type = 'c';		break;
			case TK_Class:
				sc.MustGetToken('<');
				sc.MustGetToken(TK_Identifier);	// Skip class name, since the parser doesn't care
				sc.MustGetToken('>');
				type = 'm';
				break;
			case TK_Ellipsis:
				type = '+';
				sc.MustGetToken(')');
				sc.UnGet();
				break;
			default:
				sc.ScriptMessage ("Unknown variable type %s", sc.TokenName(sc.TokenType, sc.String).GetChars());
				type = 'x';
				FScriptPosition::ErrorCounter++;
				break;
			}
			// Read the optional variable name
			if (!sc.CheckToken(',') && !sc.CheckToken(')'))
			{
				sc.MustGetToken(TK_Identifier);
			}
			else
			{
				sc.UnGet();
			}

			FxExpression *def;
			if (sc.CheckToken('='))
			{
				hasdefaults = true;
				flags |= OPTIONAL;
				def = ParseParameter(sc, cls, type, true);
			}
			else
			{
				def = NULL;
			}
			DefaultParams.Push(def);

			if (!(flags & OPTIONAL) && type != '+')
			{
				type -= 'a' - 'A';
			}
			args += type;
			sc.MustGetAnyToken();
			if (sc.TokenType != ',' && sc.TokenType != ')')
			{
				sc.ScriptError ("Expected ',' or ')' but got %s instead", sc.TokenName(sc.TokenType, sc.String).GetChars());
			}
		}
	}
	sc.MustGetToken(';');
	if (afd != NULL)
	{
		PSymbolActionFunction *sym = new PSymbolActionFunction(funcname);
		sym->Arguments = args;
		sym->Function = afd->Function;
		if (hasdefaults)
		{
			sym->defaultparameterindex = StateParams.Size();
			for(unsigned int i = 0; i < DefaultParams.Size(); i++)
			{
				StateParams.Add(DefaultParams[i], cls, true);
			}
		}
		else
		{
			sym->defaultparameterindex = -1;
		}
		if (error)
		{
			FScriptPosition::ErrorCounter += error;
		}
		else if (cls->Symbols.AddSymbol (sym) == NULL)
		{
			delete sym;
			sc.ScriptMessage ("'%s' is already defined in class '%s'.",
				funcname.GetChars(), cls->TypeName.GetChars());
			FScriptPosition::ErrorCounter++;
		}
	}
}
static void ParseUserVariable (FScanner &sc, PSymbolTable *symt, PClass *cls)
{
	FExpressionType valuetype;

	// Only non-native classes may have user variables.
	if (!cls->bRuntimeClass)
	{
		sc.ScriptError("Native classes may not have user variables");
	}

	// Read the type and make sure it's int.
	sc.MustGetAnyToken();
	if (sc.TokenType != TK_Int)
	{
		sc.ScriptMessage("User variables must be of type int");
		FScriptPosition::ErrorCounter++;
	}
	valuetype = VAL_Int;

	sc.MustGetToken(TK_Identifier);
	// For now, restrict user variables to those that begin with "user_" to guarantee
	// no clashes with internal member variable names.
	if (sc.StringLen < 6 || strnicmp("user_", sc.String, 5) != 0)
	{
		sc.ScriptMessage("User variable names must begin with \"user_\"");
		FScriptPosition::ErrorCounter++;
	}



	FName symname = sc.String;
	if (sc.CheckToken('['))
	{
		FxExpression *expr = ParseExpression(sc, cls);
		int maxelems = expr->EvalExpression(NULL).GetInt();
		delete expr;
		sc.MustGetToken(']');
		if (maxelems <= 0)
		{
			sc.ScriptMessage("Array size must be positive");
			FScriptPosition::ErrorCounter++;
			maxelems = 1;
		}
		valuetype.MakeArray(maxelems);
	}
	sc.MustGetToken(';');

	// We must ensure that we do not define duplicates, even when they come from a parent table.
	if (symt->FindSymbol(symname, true) != NULL)
	{
		sc.ScriptMessage ("'%s' is already defined in '%s' or one of its ancestors.",
			symname.GetChars(), cls ? cls->TypeName.GetChars() : "Global");
		FScriptPosition::ErrorCounter++;
		return;
	}

	PSymbolVariable *sym = new PSymbolVariable(symname);
	sym->offset = cls->Extend(sizeof(int) * (valuetype.Type == VAL_Array ? valuetype.size : 1));
	sym->ValueType = valuetype;
	sym->bUserVar = true;
	if (symt->AddSymbol(sym) == NULL)
	{
		delete sym;
		sc.ScriptMessage ("'%s' is already defined in '%s'.",
			symname.GetChars(), cls ? cls->TypeName.GetChars() : "Global");
		FScriptPosition::ErrorCounter++;
	}
}
bool FIntermissionAction::ParseKey(FScanner &sc)
{
	if (sc.Compare("music"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_StringConst);
		mMusic = sc.String;
		mMusicOrder = 0;
		if (sc.CheckToken(','))
		{
			sc.MustGetToken(TK_IntConst);
			mMusicOrder = sc.Number;
		}
		return true;
	}
	else if (sc.Compare("cdmusic"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_IntConst);
		mCdTrack = sc.Number;
		mCdId = 0;
		if (sc.CheckToken(','))
		{
			sc.MustGetToken(TK_IntConst);
			mCdId = sc.Number;
		}
		return true;
	}
	else if (sc.Compare("Time"))
	{
		sc.MustGetToken('=');
		if (!sc.CheckToken('-'))
		{
			sc.MustGetFloat();
			mDuration = int(sc.Float*TICRATE);
		}
		else
		{
			sc.MustGetToken(TK_IntConst);
			mDuration = sc.Number;
		}
		return true;
	}
	else if (sc.Compare("Background"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_StringConst);
		mBackground = sc.String;
		mFlatfill = 0;
		if (sc.CheckToken(','))
		{
			sc.MustGetToken(TK_IntConst);
			mFlatfill = !!sc.Number;
			if (sc.CheckToken(','))
			{
				sc.MustGetToken(TK_StringConst);
				mPalette = sc.String;
			}
		}
		return true;
	}
	else if (sc.Compare("Sound"))
	{
		sc.MustGetToken('=');
		sc.MustGetToken(TK_StringConst);
		mSound = sc.String;
		return true;
	}
	else if (sc.Compare("Draw"))
	{
		FIntermissionPatch *pat = &mOverlays[mOverlays.Reserve(1)];
		sc.MustGetToken('=');
		sc.MustGetToken(TK_StringConst);
		pat->mName = sc.String;
		sc.MustGetToken(',');
		sc.MustGetToken(TK_IntConst);
		pat->x = sc.Number;
		sc.MustGetToken(',');
		sc.MustGetToken(TK_IntConst);
		pat->y = sc.Number;
		pat->mCondition = NAME_None;
		return true;
	}
	else if (sc.Compare("DrawConditional"))
	{
		FIntermissionPatch *pat = &mOverlays[mOverlays.Reserve(1)];
		sc.MustGetToken('=');
		sc.MustGetToken(TK_StringConst);
		pat->mCondition = sc.String;
		sc.MustGetToken(',');
		sc.MustGetToken(TK_StringConst);
		pat->mName = sc.String;
		sc.MustGetToken(',');
		sc.MustGetToken(TK_IntConst);
		pat->x = sc.Number;
		sc.MustGetToken(',');
		sc.MustGetToken(TK_IntConst);
		pat->y = sc.Number;
		return true;
	}
	else return false;
}
Exemple #30
0
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();
}