2016-03-01 15:47:10 +00:00
|
|
|
/*
|
|
|
|
** thingdef-parse.cpp
|
|
|
|
**
|
|
|
|
** Actor definitions - all parser related code
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
** Copyright 2002-2007 Christoph Oelckers
|
|
|
|
** Copyright 2004-2007 Randy Heit
|
|
|
|
** All rights reserved.
|
|
|
|
**
|
|
|
|
** Redistribution and use in source and binary forms, with or without
|
|
|
|
** modification, are permitted provided that the following conditions
|
|
|
|
** are met:
|
|
|
|
**
|
|
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer.
|
|
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
|
|
** documentation and/or other materials provided with the distribution.
|
|
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
|
|
** derived from this software without specific prior written permission.
|
|
|
|
** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be
|
|
|
|
** covered by the terms of the GNU General Public License as published by
|
|
|
|
** the Free Software Foundation; either version 2 of the License, or (at
|
|
|
|
** your option) any later version.
|
|
|
|
**
|
|
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "doomtype.h"
|
|
|
|
#include "actor.h"
|
|
|
|
#include "a_pickups.h"
|
|
|
|
#include "sc_man.h"
|
|
|
|
#include "thingdef.h"
|
|
|
|
#include "a_morph.h"
|
|
|
|
#include "cmdlib.h"
|
|
|
|
#include "templates.h"
|
|
|
|
#include "v_palette.h"
|
|
|
|
#include "doomerrors.h"
|
|
|
|
#include "i_system.h"
|
2016-10-15 08:43:02 +00:00
|
|
|
#include "codegeneration/codegen.h"
|
2016-03-01 15:47:10 +00:00
|
|
|
#include "w_wad.h"
|
|
|
|
#include "v_video.h"
|
|
|
|
#include "version.h"
|
|
|
|
#include "v_text.h"
|
|
|
|
#include "m_argv.h"
|
|
|
|
|
|
|
|
void ParseOldDecoration(FScanner &sc, EDefinitionType def);
|
2016-10-12 18:42:41 +00:00
|
|
|
EXTERN_CVAR(Bool, strictdecorate);
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// DecoDerivedClass
|
|
|
|
//
|
|
|
|
// Create a derived class and performs some additional sanity checks
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
PClassActor *DecoDerivedClass(const FScriptPosition &sc, PClassActor *parent, FName typeName)
|
|
|
|
{
|
|
|
|
PClassActor *type = static_cast<PClassActor *>(parent->CreateDerivedClass(typeName, parent->Size));
|
|
|
|
if (type == nullptr)
|
|
|
|
{
|
|
|
|
FString newname = typeName.GetChars();
|
|
|
|
FString sourcefile = sc.FileName;
|
|
|
|
|
|
|
|
sourcefile.Substitute(":", "@");
|
|
|
|
newname << '@' << sourcefile;
|
|
|
|
if (strictdecorate)
|
|
|
|
{
|
|
|
|
sc.Message(MSG_ERROR, "Tried to define class '%s' more than once.", typeName.GetChars());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Due to backwards compatibility issues this cannot be an unconditional error.
|
|
|
|
sc.Message(MSG_WARNING, "Tried to define class '%s' more than once. Renaming class to '%s'", typeName.GetChars(), newname.GetChars());
|
|
|
|
}
|
|
|
|
type = static_cast<PClassActor *>(parent->CreateDerivedClass(newname, parent->Size));
|
|
|
|
if (type == nullptr)
|
|
|
|
{
|
|
|
|
// This we cannot handle cleanly anymore. Let's just abort and forget about the odd mod out that was this careless.
|
|
|
|
sc.Message(MSG_FATAL, "Tried to define class '%s' more than twice in the same file.", typeName.GetChars());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return type;
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ParseParameter
|
|
|
|
//
|
|
|
|
// Parses a parameter - either a default in a function declaration
|
|
|
|
// or an argument in a function call.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FxExpression *ParseParameter(FScanner &sc, PClassActor *cls, PType *type, bool constant)
|
|
|
|
{
|
|
|
|
FxExpression *x = NULL;
|
|
|
|
int v;
|
|
|
|
|
|
|
|
if (type == TypeSound)
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
x = new FxConstant(FSoundID(sc.String), sc);
|
|
|
|
}
|
2016-07-05 22:32:26 +00:00
|
|
|
else if (type == TypeBool || type == TypeSInt32 || type == TypeFloat64)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
x = ParseExpression (sc, cls, constant);
|
|
|
|
if (constant && !x->isConstant())
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("Default parameter must be constant.");
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
2016-07-05 22:32:26 +00:00
|
|
|
// Do automatic coercion between bools, ints and floats.
|
|
|
|
if (type == TypeBool)
|
|
|
|
{
|
|
|
|
x = new FxBoolCast(x);
|
|
|
|
}
|
|
|
|
else if (type == TypeSInt32)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-10-15 19:35:31 +00:00
|
|
|
x = new FxIntCast(x, true);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
x = new FxFloatCast(x);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (type == TypeName || type == TypeString)
|
|
|
|
{
|
|
|
|
sc.SetEscape(true);
|
|
|
|
sc.MustGetString();
|
|
|
|
sc.SetEscape(false);
|
|
|
|
if (type == TypeName)
|
|
|
|
{
|
|
|
|
x = new FxConstant(sc.String[0] ? FName(sc.String) : NAME_None, sc);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
x = new FxConstant(strbin1(sc.String), sc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (type == TypeColor)
|
|
|
|
{
|
|
|
|
sc.MustGetString ();
|
|
|
|
if (sc.Compare("none"))
|
|
|
|
{
|
|
|
|
v = -1;
|
|
|
|
}
|
|
|
|
else if (sc.Compare(""))
|
|
|
|
{
|
|
|
|
v = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int c = V_GetColor (NULL, sc.String);
|
|
|
|
// 0 needs to be the default so we have to mark the color.
|
|
|
|
v = MAKEARGB(1, RPART(c), GPART(c), BPART(c));
|
|
|
|
}
|
|
|
|
ExpVal val;
|
|
|
|
val.Type = TypeColor;
|
|
|
|
val.Int = v;
|
|
|
|
x = new FxConstant(val, sc);
|
|
|
|
}
|
|
|
|
else if (type == TypeState)
|
|
|
|
{
|
|
|
|
// This forces quotation marks around the state name.
|
2016-08-05 15:26:33 +00:00
|
|
|
if (sc.CheckToken(TK_StringConst))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-08-05 15:26:33 +00:00
|
|
|
if (sc.String[0] == 0 || sc.Compare("None"))
|
|
|
|
{
|
2016-11-06 22:10:23 +00:00
|
|
|
x = new FxConstant((FState*)nullptr, sc);
|
2016-08-05 15:26:33 +00:00
|
|
|
}
|
|
|
|
else if (sc.Compare("*"))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-11-06 22:10:23 +00:00
|
|
|
sc.ScriptError("Invalid state name '*'");
|
2016-08-05 15:26:33 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
x = new FxMultiNameState(sc.String, sc);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
2016-08-05 15:26:33 +00:00
|
|
|
else if (!constant)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-08-05 15:26:33 +00:00
|
|
|
x = new FxRuntimeStateIndex(ParseExpression(sc, cls));
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
2016-08-05 15:26:33 +00:00
|
|
|
else sc.MustGetToken(TK_StringConst); // This is for the error.
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else if (type->GetClass() == RUNTIME_CLASS(PClassPointer))
|
|
|
|
{ // Actor name
|
|
|
|
sc.SetEscape(true);
|
|
|
|
sc.MustGetString();
|
|
|
|
sc.SetEscape(false);
|
2016-10-16 17:42:22 +00:00
|
|
|
x = new FxClassTypeCast(static_cast<PClassPointer *>(type), new FxConstant(FName(sc.String), sc));
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
assert(false && "Unknown parameter type");
|
|
|
|
x = NULL;
|
|
|
|
}
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ActorConstDef
|
|
|
|
//
|
|
|
|
// Parses a constant definition.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
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(';');
|
|
|
|
|
2016-08-04 17:58:57 +00:00
|
|
|
if (expr == nullptr)
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("Error while resolving constant definition");
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
|
|
|
else if (!expr->isConstant())
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
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++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ActorEnumDef
|
|
|
|
//
|
|
|
|
// Parses an enum definition.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
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);
|
2016-08-04 17:58:57 +00:00
|
|
|
if (expr != nullptr)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-08-04 17:58:57 +00:00
|
|
|
if (!expr->isConstant())
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("'%s' must be constant", symname.GetChars());
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
currvalue = static_cast<FxConstant *>(expr)->GetValue().GetInt();
|
|
|
|
}
|
|
|
|
delete expr;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-08-04 17:58:57 +00:00
|
|
|
sc.ScriptMessage("Error while resolving expression of '%s'", symname.GetChars());
|
|
|
|
FScriptPosition::ErrorCounter++;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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(';');
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ParseUserVariable
|
|
|
|
//
|
|
|
|
// Parses a user variable declaration.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2016-03-30 02:48:57 +00:00
|
|
|
// Read the type and make sure it's acceptable.
|
2016-03-01 15:47:10 +00:00
|
|
|
sc.MustGetAnyToken();
|
2016-04-04 00:15:00 +00:00
|
|
|
if (sc.TokenType != TK_Int && sc.TokenType != TK_Float)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-04-04 00:15:00 +00:00
|
|
|
sc.ScriptMessage("User variables must be of type 'int' or 'float'");
|
2016-03-01 15:47:10 +00:00
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
2016-04-04 00:15:00 +00:00
|
|
|
type = sc.TokenType == TK_Int ? (PType *)TypeSInt32 : (PType *)TypeFloat64;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
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);
|
2016-08-04 17:58:57 +00:00
|
|
|
if (expr == nullptr)
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("Error while resolving array size");
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
maxelems = 1;
|
|
|
|
}
|
|
|
|
else if (!expr->isConstant())
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
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(';');
|
|
|
|
|
2016-04-03 20:48:09 +00:00
|
|
|
PField *sym = cls->AddField(symname, type, 0);
|
2016-04-04 01:25:07 +00:00
|
|
|
if (sym == NULL)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
sc.ScriptMessage ("'%s' is already defined in '%s'.",
|
|
|
|
symname.GetChars(), cls ? cls->TypeName.GetChars() : "Global");
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Parses a flag name
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
static void ParseActorFlag (FScanner &sc, Baggage &bag, int mod)
|
|
|
|
{
|
|
|
|
sc.MustGetString ();
|
|
|
|
|
|
|
|
FString part1 = sc.String;
|
|
|
|
const char *part2 = NULL;
|
|
|
|
if (sc.CheckString ("."))
|
|
|
|
{
|
|
|
|
sc.MustGetString ();
|
|
|
|
part2 = sc.String;
|
|
|
|
}
|
|
|
|
HandleActorFlag(sc, bag, part1, part2, mod);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Processes a flag. Also used by olddecorations.cpp
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
void HandleActorFlag(FScanner &sc, Baggage &bag, const char *part1, const char *part2, int mod)
|
|
|
|
{
|
|
|
|
FFlagDef *fd;
|
|
|
|
|
|
|
|
if ( (fd = FindFlag (bag.Info, part1, part2)) )
|
|
|
|
{
|
|
|
|
AActor *defaults = (AActor*)bag.Info->Defaults;
|
|
|
|
if (fd->structoffset == -1) // this is a deprecated flag that has been changed into a real property
|
|
|
|
{
|
|
|
|
HandleDeprecatedFlags(defaults, bag.Info, mod=='+', fd->flagbit);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ModActorFlag(defaults, fd, mod == '+');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (part2 == NULL)
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("\"%s\" is an unknown flag\n", part1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("\"%s.%s\" is an unknown flag\n", part1, part2);
|
|
|
|
}
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// [MH] parses a morph style expression
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
struct FParseValue
|
|
|
|
{
|
|
|
|
const char *Name;
|
|
|
|
int Flag;
|
|
|
|
};
|
|
|
|
|
|
|
|
int ParseFlagExpressionString(FScanner &sc, const FParseValue *vals)
|
|
|
|
{
|
|
|
|
// May be given flags by number...
|
|
|
|
if (sc.CheckNumber())
|
|
|
|
{
|
|
|
|
sc.MustGetNumber();
|
|
|
|
return sc.Number;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ... else should be flags by name.
|
|
|
|
// NOTE: Later this should be removed and a normal expression used.
|
|
|
|
// The current DECORATE parser can't handle this though.
|
|
|
|
bool gotparen = sc.CheckString("(");
|
|
|
|
int style = 0;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
style |= vals[sc.MustMatchString(&vals->Name, sizeof (*vals))].Flag;
|
|
|
|
}
|
|
|
|
while (sc.CheckString("|"));
|
|
|
|
if (gotparen)
|
|
|
|
{
|
|
|
|
sc.MustGetStringName(")");
|
|
|
|
}
|
|
|
|
|
|
|
|
return style;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int ParseMorphStyle (FScanner &sc)
|
|
|
|
{
|
|
|
|
static const FParseValue morphstyles[]={
|
|
|
|
{ "MRF_ADDSTAMINA", MORPH_ADDSTAMINA},
|
|
|
|
{ "MRF_FULLHEALTH", MORPH_FULLHEALTH},
|
|
|
|
{ "MRF_UNDOBYTOMEOFPOWER", MORPH_UNDOBYTOMEOFPOWER},
|
|
|
|
{ "MRF_UNDOBYCHAOSDEVICE", MORPH_UNDOBYCHAOSDEVICE},
|
|
|
|
{ "MRF_FAILNOTELEFRAG", MORPH_FAILNOTELEFRAG},
|
|
|
|
{ "MRF_FAILNOLAUGH", MORPH_FAILNOLAUGH},
|
|
|
|
{ "MRF_WHENINVULNERABLE", MORPH_WHENINVULNERABLE},
|
|
|
|
{ "MRF_LOSEACTUALWEAPON", MORPH_LOSEACTUALWEAPON},
|
|
|
|
{ "MRF_NEWTIDBEHAVIOUR", MORPH_NEWTIDBEHAVIOUR},
|
|
|
|
{ "MRF_UNDOBYDEATH", MORPH_UNDOBYDEATH},
|
|
|
|
{ "MRF_UNDOBYDEATHFORCED", MORPH_UNDOBYDEATHFORCED},
|
|
|
|
{ "MRF_UNDOBYDEATHSAVES", MORPH_UNDOBYDEATHSAVES},
|
|
|
|
{ "MRF_UNDOALWAYS", MORPH_UNDOALWAYS },
|
2016-07-22 13:21:15 +00:00
|
|
|
{ "MRF_TRANSFERTRANSLATION", MORPH_TRANSFERTRANSLATION },
|
2016-03-01 15:47:10 +00:00
|
|
|
{ NULL, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
return ParseFlagExpressionString(sc, morphstyles);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ParseThingActivation (FScanner &sc)
|
|
|
|
{
|
|
|
|
static const FParseValue activationstyles[]={
|
|
|
|
|
|
|
|
{ "THINGSPEC_Default", THINGSPEC_Default},
|
|
|
|
{ "THINGSPEC_ThingActs", THINGSPEC_ThingActs},
|
|
|
|
{ "THINGSPEC_ThingTargets", THINGSPEC_ThingTargets},
|
|
|
|
{ "THINGSPEC_TriggerTargets", THINGSPEC_TriggerTargets},
|
|
|
|
{ "THINGSPEC_MonsterTrigger", THINGSPEC_MonsterTrigger},
|
|
|
|
{ "THINGSPEC_MissileTrigger", THINGSPEC_MissileTrigger},
|
|
|
|
{ "THINGSPEC_ClearSpecial", THINGSPEC_ClearSpecial},
|
|
|
|
{ "THINGSPEC_NoDeathSpecial", THINGSPEC_NoDeathSpecial},
|
|
|
|
{ "THINGSPEC_TriggerActs", THINGSPEC_TriggerActs},
|
|
|
|
{ "THINGSPEC_Activate", THINGSPEC_Activate},
|
|
|
|
{ "THINGSPEC_Deactivate", THINGSPEC_Deactivate},
|
|
|
|
{ "THINGSPEC_Switch", THINGSPEC_Switch},
|
|
|
|
{ NULL, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
return ParseFlagExpressionString(sc, activationstyles);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// For getting a state address from the parent
|
|
|
|
// No attempts have been made to add new functionality here
|
|
|
|
// This is strictly for keeping compatibility with old WADs!
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static FState *CheckState(FScanner &sc, PClass *type)
|
|
|
|
{
|
|
|
|
int v = 0;
|
|
|
|
|
|
|
|
if (sc.GetString() && !sc.Crossed)
|
|
|
|
{
|
|
|
|
if (sc.Compare("0")) return NULL;
|
|
|
|
else if (sc.Compare("PARENT"))
|
|
|
|
{
|
|
|
|
FState *state = NULL;
|
|
|
|
sc.MustGetString();
|
|
|
|
|
|
|
|
PClassActor *info = dyn_cast<PClassActor>(type->ParentClass);
|
|
|
|
|
|
|
|
if (info != NULL)
|
|
|
|
{
|
|
|
|
state = info->FindState(FName(sc.String));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sc.GetString ())
|
|
|
|
{
|
|
|
|
if (sc.Compare ("+"))
|
|
|
|
{
|
|
|
|
sc.MustGetNumber ();
|
|
|
|
v = sc.Number;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.UnGet ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state == NULL && v==0)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
if (v != 0 && state==NULL)
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("Attempt to get invalid state from actor %s\n", type->ParentClass->TypeName.GetChars());
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
state += v;
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("Invalid state assignment");
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Parses an actor property's parameters and calls the handler
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static bool ParsePropertyParams(FScanner &sc, FPropertyInfo *prop, AActor *defaults, Baggage &bag)
|
|
|
|
{
|
|
|
|
static TArray<FPropParam> params;
|
|
|
|
static TArray<FString> strings;
|
|
|
|
|
|
|
|
params.Clear();
|
|
|
|
strings.Clear();
|
|
|
|
params.Reserve(1);
|
|
|
|
params[0].i = 0;
|
|
|
|
if (prop->params[0] != '0')
|
|
|
|
{
|
|
|
|
const char * p = prop->params;
|
|
|
|
bool nocomma;
|
|
|
|
bool optcomma;
|
|
|
|
while (*p)
|
|
|
|
{
|
|
|
|
FPropParam conv;
|
|
|
|
FPropParam pref;
|
|
|
|
|
|
|
|
nocomma = false;
|
|
|
|
conv.s = NULL;
|
|
|
|
pref.s = NULL;
|
|
|
|
pref.i = -1;
|
2016-04-04 20:13:36 +00:00
|
|
|
bag.ScriptPosition = sc;
|
2016-03-01 15:47:10 +00:00
|
|
|
switch ((*p) & 223)
|
|
|
|
{
|
|
|
|
case 'X': // Expression in parentheses or number.
|
|
|
|
{
|
|
|
|
FxExpression *x = NULL;
|
|
|
|
|
|
|
|
if (sc.CheckString ("("))
|
|
|
|
{
|
2016-09-19 01:36:51 +00:00
|
|
|
conv.i = -1;
|
|
|
|
params.Push(conv);
|
2016-10-26 12:04:49 +00:00
|
|
|
x = ParseExpression(sc, bag.Info);
|
2016-03-01 15:47:10 +00:00
|
|
|
sc.MustGetStringName(")");
|
2016-09-19 01:36:51 +00:00
|
|
|
conv.exp = x;
|
|
|
|
params.Push(conv);
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.MustGetNumber();
|
2016-09-19 01:36:51 +00:00
|
|
|
conv.i = sc.Number;
|
|
|
|
params.Push(conv);
|
|
|
|
conv.exp = nullptr;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'I':
|
|
|
|
sc.MustGetNumber();
|
|
|
|
conv.i = sc.Number;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'F':
|
|
|
|
sc.MustGetFloat();
|
|
|
|
conv.d = sc.Float;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'Z': // an optional string. Does not allow any numerical value.
|
|
|
|
if (sc.CheckFloat())
|
|
|
|
{
|
|
|
|
nocomma = true;
|
|
|
|
sc.UnGet();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// fall through
|
|
|
|
|
|
|
|
case 'S':
|
|
|
|
sc.MustGetString();
|
|
|
|
conv.s = strings[strings.Reserve(1)] = sc.String;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'T':
|
|
|
|
sc.MustGetString();
|
|
|
|
conv.s = strings[strings.Reserve(1)] = strbin1(sc.String);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'C':
|
|
|
|
if (sc.CheckNumber ())
|
|
|
|
{
|
|
|
|
int R, G, B;
|
|
|
|
R = clamp (sc.Number, 0, 255);
|
|
|
|
sc.CheckString (",");
|
|
|
|
sc.MustGetNumber ();
|
|
|
|
G = clamp (sc.Number, 0, 255);
|
|
|
|
sc.CheckString (",");
|
|
|
|
sc.MustGetNumber ();
|
|
|
|
B = clamp (sc.Number, 0, 255);
|
|
|
|
conv.i = MAKERGB(R, G, B);
|
|
|
|
pref.i = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.MustGetString ();
|
|
|
|
conv.s = strings[strings.Reserve(1)] = sc.String;
|
|
|
|
pref.i = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'M': // special case. An expression-aware parser will not need this.
|
|
|
|
conv.i = ParseMorphStyle(sc);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'N': // special case. An expression-aware parser will not need this.
|
|
|
|
conv.i = ParseThingActivation(sc);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'L': // Either a number or a list of strings
|
|
|
|
if (sc.CheckNumber())
|
|
|
|
{
|
|
|
|
pref.i = 0;
|
|
|
|
conv.i = sc.Number;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pref.i = 1;
|
|
|
|
params.Push(pref);
|
|
|
|
params[0].i++;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
sc.MustGetString ();
|
|
|
|
conv.s = strings[strings.Reserve(1)] = sc.String;
|
|
|
|
params.Push(conv);
|
|
|
|
params[0].i++;
|
|
|
|
}
|
|
|
|
while (sc.CheckString(","));
|
|
|
|
goto endofparm;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
assert(false);
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
if (pref.i != -1)
|
|
|
|
{
|
|
|
|
params.Push(pref);
|
|
|
|
params[0].i++;
|
|
|
|
}
|
|
|
|
params.Push(conv);
|
|
|
|
params[0].i++;
|
|
|
|
endofparm:
|
|
|
|
p++;
|
|
|
|
// Hack for some properties that have to allow comma less
|
|
|
|
// parameter lists for compatibility.
|
|
|
|
if ((optcomma = (*p == '_')))
|
|
|
|
p++;
|
|
|
|
|
|
|
|
if (nocomma)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (*p == 0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (*p >= 'a')
|
|
|
|
{
|
|
|
|
if (!sc.CheckString(","))
|
|
|
|
{
|
|
|
|
if (optcomma)
|
|
|
|
{
|
|
|
|
if (!sc.CheckFloat()) break;
|
|
|
|
else sc.UnGet();
|
|
|
|
}
|
|
|
|
else break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!optcomma) sc.MustGetStringName(",");
|
|
|
|
else sc.CheckString(",");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// call the handler
|
|
|
|
try
|
|
|
|
{
|
|
|
|
prop->Handler(defaults, bag.Info, bag, ¶ms[0]);
|
|
|
|
}
|
|
|
|
catch (CRecoverableError &error)
|
|
|
|
{
|
|
|
|
sc.ScriptError("%s", error.GetMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Parses an actor property
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void ParseActorProperty(FScanner &sc, Baggage &bag)
|
|
|
|
{
|
|
|
|
static const char *statenames[] = {
|
|
|
|
"Spawn", "See", "Melee", "Missile", "Pain", "Death", "XDeath", "Burn",
|
|
|
|
"Ice", "Raise", "Crash", "Crush", "Wound", "Disintegrate", "Heal", NULL };
|
|
|
|
|
|
|
|
strlwr (sc.String);
|
|
|
|
|
|
|
|
FString propname = sc.String;
|
|
|
|
|
|
|
|
if (sc.CheckString ("."))
|
|
|
|
{
|
|
|
|
sc.MustGetString ();
|
|
|
|
propname += '.';
|
|
|
|
strlwr (sc.String);
|
|
|
|
propname += sc.String;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.UnGet ();
|
|
|
|
}
|
|
|
|
|
|
|
|
FPropertyInfo *prop = FindProperty(propname);
|
|
|
|
|
|
|
|
if (prop != NULL)
|
|
|
|
{
|
|
|
|
if (bag.Info->IsDescendantOf(*prop->cls))
|
|
|
|
{
|
|
|
|
ParsePropertyParams(sc, prop, (AActor *)bag.Info->Defaults, bag);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-10-10 22:56:47 +00:00
|
|
|
sc.ScriptMessage("'%s' requires an actor of type '%s'\n", propname.GetChars(), (*prop->cls)->TypeName.GetChars());
|
2016-03-01 15:47:10 +00:00
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (MatchString(propname, statenames) != -1)
|
|
|
|
{
|
|
|
|
bag.statedef.SetStateLabel(propname, CheckState (sc, bag.Info));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-10-10 22:56:47 +00:00
|
|
|
sc.ScriptError("'%s' is an unknown actor property\n", propname.GetChars());
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-12 18:42:41 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Starts a new actor definition
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
PClassActor *CreateNewActor(const FScriptPosition &sc, FName typeName, FName parentName, bool native)
|
|
|
|
{
|
|
|
|
PClassActor *replacee = NULL;
|
|
|
|
PClassActor *ti = NULL;
|
|
|
|
|
|
|
|
PClassActor *parent = RUNTIME_CLASS(AActor);
|
|
|
|
|
|
|
|
if (parentName != NAME_None)
|
|
|
|
{
|
|
|
|
parent = PClass::FindActor(parentName);
|
|
|
|
|
|
|
|
PClassActor *p = parent;
|
|
|
|
while (p != NULL)
|
|
|
|
{
|
|
|
|
if (p->TypeName == typeName)
|
|
|
|
{
|
|
|
|
sc.Message(MSG_ERROR, "'%s' inherits from a class with the same name", typeName.GetChars());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
p = dyn_cast<PClassActor>(p->ParentClass);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parent == NULL)
|
|
|
|
{
|
|
|
|
sc.Message(MSG_ERROR, "Parent type '%s' not found in %s", parentName.GetChars(), typeName.GetChars());
|
|
|
|
parent = RUNTIME_CLASS(AActor);
|
|
|
|
}
|
|
|
|
else if (!parent->IsDescendantOf(RUNTIME_CLASS(AActor)))
|
|
|
|
{
|
|
|
|
sc.Message(MSG_ERROR, "Parent type '%s' is not an actor in %s", parentName.GetChars(), typeName.GetChars());
|
|
|
|
parent = RUNTIME_CLASS(AActor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (native)
|
|
|
|
{
|
|
|
|
ti = PClass::FindActor(typeName);
|
|
|
|
if (ti == NULL)
|
|
|
|
{
|
|
|
|
extern void DumpTypeTable();
|
|
|
|
DumpTypeTable();
|
|
|
|
sc.Message(MSG_ERROR, "Unknown native actor '%s'", typeName.GetChars());
|
|
|
|
goto create;
|
|
|
|
}
|
|
|
|
else if (ti != RUNTIME_CLASS(AActor) && ti->ParentClass->NativeClass() != parent->NativeClass())
|
|
|
|
{
|
|
|
|
sc.Message(MSG_ERROR, "Native class '%s' does not inherit from '%s'", typeName.GetChars(), parentName.GetChars());
|
|
|
|
parent = RUNTIME_CLASS(AActor);
|
|
|
|
goto create;
|
|
|
|
}
|
|
|
|
else if (ti->Defaults != NULL)
|
|
|
|
{
|
|
|
|
sc.Message(MSG_ERROR, "Redefinition of internal class '%s'", typeName.GetChars());
|
|
|
|
goto create;
|
|
|
|
}
|
|
|
|
ti->InitializeNativeDefaults();
|
|
|
|
ti->ParentClass->DeriveData(ti);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
create:
|
|
|
|
ti = DecoDerivedClass(sc, parent, typeName);
|
|
|
|
}
|
|
|
|
|
|
|
|
ti->Replacee = ti->Replacement = NULL;
|
|
|
|
ti->DoomEdNum = -1;
|
|
|
|
return ti;
|
|
|
|
}
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Starts a new actor definition
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
static PClassActor *ParseActorHeader(FScanner &sc, Baggage *bag)
|
|
|
|
{
|
|
|
|
FName typeName;
|
|
|
|
FName parentName;
|
|
|
|
FName replaceName;
|
|
|
|
bool native = false;
|
|
|
|
int DoomEdNum = -1;
|
|
|
|
|
|
|
|
// Get actor name
|
|
|
|
sc.MustGetString();
|
|
|
|
|
|
|
|
char *colon = strchr(sc.String, ':');
|
|
|
|
if (colon != NULL)
|
|
|
|
{
|
|
|
|
*colon++ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
typeName = sc.String;
|
|
|
|
|
|
|
|
// Do some tweaking so that a definition like 'Actor:Parent' which is read as a single token is recognized as well
|
|
|
|
// without having resort to C-mode (which disallows periods in actor names.)
|
|
|
|
if (colon == NULL)
|
|
|
|
{
|
|
|
|
sc.MustGetString ();
|
|
|
|
if (sc.String[0]==':')
|
|
|
|
{
|
|
|
|
colon = sc.String + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (colon != NULL)
|
|
|
|
{
|
|
|
|
if (colon[0] == 0)
|
|
|
|
{
|
|
|
|
sc.MustGetString ();
|
|
|
|
colon = sc.String;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (colon == NULL)
|
|
|
|
{
|
|
|
|
sc.UnGet();
|
|
|
|
}
|
|
|
|
|
|
|
|
parentName = colon;
|
|
|
|
|
|
|
|
// Check for "replaces"
|
|
|
|
if (sc.CheckString ("replaces"))
|
|
|
|
{
|
|
|
|
// Get actor name
|
|
|
|
sc.MustGetString ();
|
|
|
|
replaceName = sc.String;
|
|
|
|
|
|
|
|
if (replaceName == typeName)
|
|
|
|
{
|
|
|
|
sc.ScriptMessage ("Cannot replace class %s with itself", typeName.GetChars());
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, after the actor names have been parsed, it is time to switch to C-mode
|
|
|
|
// for the rest of the actor definition.
|
|
|
|
sc.SetCMode (true);
|
|
|
|
if (sc.CheckNumber())
|
|
|
|
{
|
|
|
|
if (sc.Number>=-1 && sc.Number<32768) DoomEdNum = sc.Number;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// does not need to be fatal.
|
|
|
|
sc.ScriptMessage ("DoomEdNum must be in the range [-1,32767]");
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sc.CheckString("native"))
|
|
|
|
{
|
|
|
|
native = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
PClassActor *info = CreateNewActor(sc, typeName, parentName, native);
|
|
|
|
info->DoomEdNum = DoomEdNum > 0 ? DoomEdNum : -1;
|
|
|
|
info->SourceLumpName = Wads.GetLumpFullPath(sc.LumpNum);
|
|
|
|
|
2016-10-12 18:42:41 +00:00
|
|
|
if (!info->SetReplacement(replaceName))
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("Replaced type '%s' not found for %s", replaceName.GetChars(), info->TypeName.GetChars());
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
ResetBaggage (bag, info == RUNTIME_CLASS(AActor) ? NULL : static_cast<PClassActor *>(info->ParentClass));
|
|
|
|
bag->Info = info;
|
|
|
|
bag->Lumpnum = sc.LumpNum;
|
|
|
|
#ifdef _DEBUG
|
|
|
|
bag->ClassName = typeName;
|
|
|
|
#endif
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
catch (CRecoverableError &err)
|
|
|
|
{
|
|
|
|
sc.ScriptError("%s", err.GetMessage());
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Reads an actor definition
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
static void ParseActor(FScanner &sc)
|
|
|
|
{
|
|
|
|
PClassActor *info = NULL;
|
|
|
|
Baggage bag;
|
|
|
|
|
2016-11-07 10:53:49 +00:00
|
|
|
bag.fromDecorate = true;
|
2016-03-01 15:47:10 +00:00
|
|
|
info = ParseActorHeader(sc, &bag);
|
|
|
|
sc.MustGetToken('{');
|
|
|
|
while (sc.MustGetAnyToken(), sc.TokenType != '}')
|
|
|
|
{
|
|
|
|
switch (sc.TokenType)
|
|
|
|
{
|
|
|
|
case TK_Const:
|
|
|
|
ParseConstant (sc, &info->Symbols, info);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TK_Enum:
|
|
|
|
ParseEnum (sc, &info->Symbols, info);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TK_Var:
|
|
|
|
ParseUserVariable (sc, &info->Symbols, info);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TK_Identifier:
|
|
|
|
ParseActorProperty(sc, bag);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TK_States:
|
|
|
|
if (bag.StateSet)
|
|
|
|
{
|
|
|
|
sc.ScriptMessage("'%s' contains multiple state declarations", bag.Info->TypeName.GetChars());
|
|
|
|
FScriptPosition::ErrorCounter++;
|
|
|
|
}
|
|
|
|
ParseStates(sc, bag.Info, (AActor *)bag.Info->Defaults, bag);
|
|
|
|
bag.StateSet = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '+':
|
|
|
|
case '-':
|
|
|
|
ParseActorFlag(sc, bag, sc.TokenType);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
sc.ScriptError("Unexpected '%s' in definition of '%s'", sc.String, bag.Info->TypeName.GetChars());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-10-12 18:42:41 +00:00
|
|
|
if (bag.DropItemSet)
|
|
|
|
{
|
|
|
|
bag.Info->SetDropItems(bag.DropItemList);
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
info->Finalize(bag.statedef);
|
|
|
|
}
|
|
|
|
catch (CRecoverableError &err)
|
|
|
|
{
|
|
|
|
sc.ScriptError("%s", err.GetMessage());
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
sc.SetCMode (false);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Reads a damage definition
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void ParseDamageDefinition(FScanner &sc)
|
|
|
|
{
|
|
|
|
sc.SetCMode (true); // This may be 100% irrelevant for such a simple syntax, but I don't know
|
|
|
|
|
|
|
|
// Get DamageType
|
|
|
|
|
|
|
|
sc.MustGetString();
|
|
|
|
FName damageType = sc.String;
|
|
|
|
|
|
|
|
DamageTypeDefinition dtd;
|
|
|
|
|
|
|
|
sc.MustGetToken('{');
|
|
|
|
while (sc.MustGetAnyToken(), sc.TokenType != '}')
|
|
|
|
{
|
|
|
|
if (sc.Compare("FACTOR"))
|
|
|
|
{
|
|
|
|
sc.MustGetFloat();
|
2016-03-22 15:35:41 +00:00
|
|
|
dtd.DefaultFactor = sc.Float;
|
2016-03-24 00:46:11 +00:00
|
|
|
if (dtd.DefaultFactor == 0) dtd.ReplaceFactor = true;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else if (sc.Compare("REPLACEFACTOR"))
|
|
|
|
{
|
|
|
|
dtd.ReplaceFactor = true;
|
|
|
|
}
|
|
|
|
else if (sc.Compare("NOARMOR"))
|
|
|
|
{
|
|
|
|
dtd.NoArmor = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sc.ScriptError("Unexpected data (%s) in damagetype definition.", sc.String);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dtd.Apply(damageType);
|
|
|
|
|
|
|
|
sc.SetCMode (false); // (set to true earlier in function)
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ParseDecorate
|
|
|
|
//
|
|
|
|
// Parses a single DECORATE lump
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void ParseDecorate (FScanner &sc)
|
|
|
|
{
|
|
|
|
// Get actor class name.
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
FScanner::SavedPos pos = sc.SavePos();
|
|
|
|
if (!sc.GetToken ())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (sc.TokenType)
|
|
|
|
{
|
|
|
|
case TK_Include:
|
|
|
|
{
|
|
|
|
sc.MustGetString();
|
|
|
|
// This check needs to remain overridable for testing purposes.
|
|
|
|
if (Wads.GetLumpFile(sc.LumpNum) == 0 && !Args->CheckParm("-allowdecoratecrossincludes"))
|
|
|
|
{
|
|
|
|
int includefile = Wads.GetLumpFile(Wads.CheckNumForFullName(sc.String, true));
|
|
|
|
if (includefile != 0)
|
|
|
|
{
|
|
|
|
I_FatalError("File %s is overriding core lump %s.",
|
|
|
|
Wads.GetWadFullName(includefile), sc.String);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
FScanner newscanner;
|
|
|
|
newscanner.Open(sc.String);
|
|
|
|
ParseDecorate(newscanner);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case TK_Const:
|
|
|
|
ParseConstant (sc, &GlobalSymbols, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TK_Enum:
|
|
|
|
ParseEnum (sc, &GlobalSymbols, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ';':
|
|
|
|
// ';' is the start of a comment in the non-cmode parser which
|
|
|
|
// is used to parse parts of the DECORATE lump. If we don't add
|
|
|
|
// a check here the user will only get weird non-informative
|
|
|
|
// error messages if a semicolon is found.
|
|
|
|
sc.ScriptError("Unexpected ';'");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TK_Identifier:
|
|
|
|
// 'ACTOR' cannot be a keyword because it is also needed as a class identifier
|
|
|
|
// so let's do a special case for this.
|
|
|
|
if (sc.Compare("ACTOR"))
|
|
|
|
{
|
|
|
|
ParseActor (sc);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (sc.Compare("PICKUP"))
|
|
|
|
{
|
|
|
|
ParseOldDecoration (sc, DEF_Pickup);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (sc.Compare("BREAKABLE"))
|
|
|
|
{
|
|
|
|
ParseOldDecoration (sc, DEF_BreakableDecoration);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (sc.Compare("PROJECTILE"))
|
|
|
|
{
|
|
|
|
ParseOldDecoration (sc, DEF_Projectile);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (sc.Compare("DAMAGETYPE"))
|
|
|
|
{
|
|
|
|
ParseDamageDefinition(sc);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
sc.RestorePos(pos);
|
|
|
|
ParseOldDecoration(sc, DEF_Decoration);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-12 22:53:59 +00:00
|
|
|
|
|
|
|
void ParseAllDecorate()
|
|
|
|
{
|
2016-10-13 18:45:52 +00:00
|
|
|
int lastlump = 0, lump;
|
2016-10-12 22:53:59 +00:00
|
|
|
|
|
|
|
while ((lump = Wads.FindLump("DECORATE", &lastlump)) != -1)
|
|
|
|
{
|
|
|
|
FScanner sc(lump);
|
|
|
|
ParseDecorate(sc);
|
|
|
|
}
|
|
|
|
}
|