gave translations a dedicated scripted type.

This is needed for implementing reliable serialization of custom translations. As long as they are merely ints they cannot be restored on loading a savegame because the serialization code does not know that these variables are special.
This commit is contained in:
Christoph Oelckers 2023-11-09 17:22:46 +01:00
parent 3781c43aec
commit f0c9b1765e
19 changed files with 341 additions and 34 deletions

View file

@ -182,6 +182,7 @@ xx(Voidptr)
xx(StateLabel)
xx(SpriteID)
xx(TextureID)
xx(TranslationID)
xx(Overlay)
xx(IsValid)
xx(IsNull)
@ -272,6 +273,7 @@ xx(BuiltinFRandom)
xx(BuiltinNameToClass)
xx(BuiltinClassCast)
xx(BuiltinFunctionPtrCast)
xx(BuiltinFindTranslation)
xx(ScreenJobRunner)
xx(Action)

View file

@ -42,6 +42,46 @@ struct FRemapTable
private:
};
struct FTranslationID
{
public:
FTranslationID() = default;
private:
constexpr FTranslationID(int id) : ID(id)
{
}
public:
static constexpr FTranslationID fromInt(int i)
{
return FTranslationID(i);
}
FTranslationID(const FTranslationID& other) = default;
FTranslationID& operator=(const FTranslationID& other) = default;
bool operator !=(FTranslationID other) const
{
return ID != other.ID;
}
bool operator ==(FTranslationID other) const
{
return ID == other.ID;
}
bool operator ==(int other) const = delete;
bool operator !=(int other) const = delete;
constexpr int index() const
{
return ID;
}
constexpr bool isvalid() const
{
return ID > 0;
}
private:
int ID;
};
// A class that initializes unusued pointers to NULL. This is used so that when
// the TAutoGrowArray below is expanded, the new elements will be NULLed.
class FRemapTablePtr

View file

@ -167,7 +167,7 @@ std2:
'vector2' { RET(TK_Vector2); }
'vector3' { RET(TK_Vector3); }
'map' { RET(TK_Map); }
'mapiterator' { RET(TK_MapIterator); }
'mapiterator' { RET(ParseVersion >= MakeVersion(4, 10, 0)? TK_MapIterator : TK_Identifier); }
'array' { RET(TK_Array); }
'function' { RET(ParseVersion >= MakeVersion(4, 12, 0)? TK_FunctionType : TK_Identifier); }
'in' { RET(TK_In); }

View file

@ -1196,6 +1196,21 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTe
return arc;
}
//==========================================================================
//
//
//
//==========================================================================
FSerializer& Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval)
{
int v = value.index();
int* defv = (int*)defval;
Serialize(arc, key, v, defv);
value = FTranslationID::fromInt(v);
return arc;
}
//==========================================================================
//
// This never uses defval and instead uses 'null' as default

View file

@ -20,6 +20,7 @@ class FSoundID;
union FRenderStyle;
class DObject;
class FTextureID;
struct FTranslationID;
inline bool nullcmp(const void *buffer, size_t length)
{
@ -240,6 +241,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundI
FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString *def);
FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def);
FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &sid, struct ModelOverride *def);
FSerializer& Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval);
void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p);

View file

@ -43,11 +43,13 @@
#include "texturemanager.h"
#include "m_random.h"
#include "v_font.h"
#include "palettecontainer.h"
extern FRandom pr_exrandom;
FMemArena FxAlloc(65536);
CompileEnvironment compileEnvironment;
int R_FindCustomTranslation(FName name);
struct FLOP
{
@ -161,6 +163,12 @@ void FCompileContext::CheckReturn(PPrototype *proto, FScriptPosition &pos)
PType* expected = ReturnProto->ReturnTypes[i];
PType* actual = proto->ReturnTypes[i];
if (swapped) std::swap(expected, actual);
// this must pass for older ZScripts.
if (Version < MakeVersion(4, 12, 0))
{
if (expected == TypeTranslationID) expected = TypeSInt32;
if (actual == TypeTranslationID) actual = TypeSInt32;
}
if (expected != actual && !AreCompatiblePointerTypes(expected, actual))
{ // Incompatible
@ -993,6 +1001,19 @@ FxExpression *FxIntCast::Resolve(FCompileContext &ctx)
if (basex->ValueType->GetRegType() == REGT_INT)
{
if (basex->ValueType == TypeTranslationID)
{
// translation IDs must be entirely incompatible with ints, not even allowing an explicit conversion,
// but since the type was only introduced in version 4.12, older ZScript versions must allow this conversion.
if (ctx.Version < MakeVersion(4, 12, 0))
{
FxExpression* x = basex;
x->ValueType = ValueType;
basex = nullptr;
delete this;
return x;
}
}
if (basex->ValueType->isNumeric() || Explicit) // names can be converted to int, but only with an explicit type cast.
{
FxExpression *x = basex;
@ -1006,7 +1027,7 @@ FxExpression *FxIntCast::Resolve(FCompileContext &ctx)
// Ugh. This should abort, but too many mods fell into this logic hole somewhere, so this serious error needs to be reduced to a warning. :(
// At least in ZScript, MSG_OPTERROR always means to report an error, not a warning so the problem only exists in DECORATE.
if (!basex->isConstant())
ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got a name");
ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got a %s", basex->ValueType->DescriptiveName());
else ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got \"%s\"", static_cast<FxConstant*>(basex)->GetValue().GetName().GetChars());
FxExpression * x = new FxConstant(0, ScriptPosition);
delete this;
@ -1127,7 +1148,8 @@ FxExpression *FxFloatCast::Resolve(FCompileContext &ctx)
{
// Ugh. This should abort, but too many mods fell into this logic hole somewhere, so this seroious error needs to be reduced to a warning. :(
// At least in ZScript, MSG_OPTERROR always means to report an error, not a warning so the problem only exists in DECORATE.
if (!basex->isConstant()) ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got a name");
if (!basex->isConstant())
ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got a %s", basex->ValueType->DescriptiveName());
else ScriptPosition.Message(MSG_OPTERROR, "Numeric type expected, got \"%s\"", static_cast<FxConstant*>(basex)->GetValue().GetName().GetChars());
FxExpression *x = new FxConstant(0.0, ScriptPosition);
delete this;
@ -1534,6 +1556,107 @@ ExpEmit FxSoundCast::Emit(VMFunctionBuilder *build)
return to;
}
//==========================================================================
//
//
//
//==========================================================================
FxTranslationCast::FxTranslationCast(FxExpression* x)
: FxExpression(EFX_TranslationCast, x->ScriptPosition)
{
basex = x;
ValueType = TypeTranslationID;
}
//==========================================================================
//
//
//
//==========================================================================
FxTranslationCast::~FxTranslationCast()
{
SAFE_DELETE(basex);
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression* FxTranslationCast::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(basex, ctx);
if (basex->ValueType->isInt())
{
// 0 is a valid constant for translations, meaning 'no translation at all'. note that this conversion ONLY allows a constant!
if (basex->isConstant() && static_cast<FxConstant*>(basex)->GetValue().GetInt() == 0)
{
FxExpression* x = basex;
x->ValueType = TypeTranslationID;
basex = nullptr;
delete this;
return x;
}
if (ctx.Version < MakeVersion(4, 12, 0))
{
// only allow this conversion as a fallback
FxExpression* x = basex;
x->ValueType = TypeTranslationID;
basex = nullptr;
delete this;
return x;
}
}
else if (basex->ValueType == TypeString || basex->ValueType == TypeName)
{
if (basex->isConstant())
{
ExpVal constval = static_cast<FxConstant*>(basex)->GetValue();
FxExpression* x = new FxConstant(R_FindCustomTranslation(constval.GetName()), ScriptPosition);
x->ValueType = TypeTranslationID;
delete this;
return x;
}
else if (basex->ValueType == TypeString)
{
basex = new FxNameCast(basex, true);
basex = basex->Resolve(ctx);
}
return this;
}
ScriptPosition.Message(MSG_ERROR, "Cannot convert to translation ID");
delete this;
return nullptr;
}
//==========================================================================
//
//
//
//==========================================================================
ExpEmit FxTranslationCast::Emit(VMFunctionBuilder* build)
{
ExpEmit to(build, REGT_POINTER);
VMFunction* callfunc;
auto sym = FindBuiltinFunction(NAME_BuiltinFindTranslation);
assert(sym);
callfunc = sym->Variants[0].Implementation;
FunctionCallEmitter emitters(callfunc);
emitters.AddParameter(build, basex);
emitters.AddReturn(REGT_INT);
return emitters.EmitCall(build);
}
//==========================================================================
//
//
@ -1766,6 +1889,14 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
delete this;
return x;
}
else if (ValueType == TypeTranslationID)
{
FxExpression* x = new FxTranslationCast(basex);
x = x->Resolve(ctx);
basex = nullptr;
delete this;
return x;
}
else if (ValueType == TypeColor)
{
FxExpression *x = new FxColorCast(basex);
@ -4639,6 +4770,7 @@ ExpEmit FxConcat::Emit(VMFunctionBuilder *build)
else if (left->ValueType == TypeColor) cast = CAST_Co2S;
else if (left->ValueType == TypeSpriteID) cast = CAST_SID2S;
else if (left->ValueType == TypeTextureID) cast = CAST_TID2S;
else if (left->ValueType == TypeTranslationID) cast = CAST_U2S;
else if (op1.RegType == REGT_POINTER) cast = CAST_P2S;
else if (op1.RegType == REGT_INT) cast = CAST_I2S;
else assert(false && "Bad type for string concatenation");
@ -4672,6 +4804,7 @@ ExpEmit FxConcat::Emit(VMFunctionBuilder *build)
else if (right->ValueType == TypeColor) cast = CAST_Co2S;
else if (right->ValueType == TypeSpriteID) cast = CAST_SID2S;
else if (right->ValueType == TypeTextureID) cast = CAST_TID2S;
else if (right->ValueType == TypeTranslationID) cast = CAST_U2S;
else if (op2.RegType == REGT_POINTER) cast = CAST_P2S;
else if (op2.RegType == REGT_INT) cast = CAST_I2S;
else assert(false && "Bad type for string concatenation");
@ -5143,11 +5276,24 @@ FxExpression *FxConditional::Resolve(FCompileContext& ctx)
ValueType = truex->ValueType;
else if (falsex->IsPointer() && truex->ValueType == TypeNullPtr)
ValueType = falsex->ValueType;
// translation IDs need a bit of glue for compatibility and the 0 literal.
else if (truex->IsInteger() && falsex->ValueType == TypeTranslationID)
{
truex = new FxTranslationCast(truex);
truex = truex->Resolve(ctx);
ValueType = ctx.Version < MakeVersion(4, 12, 0)? TypeSInt32 : TypeTranslationID;
}
else if (falsex->IsInteger() && truex->ValueType == TypeTranslationID)
{
falsex = new FxTranslationCast(falsex);
falsex = falsex->Resolve(ctx);
ValueType = ctx.Version < MakeVersion(4, 12, 0) ? TypeSInt32 : TypeTranslationID;
}
else
ValueType = TypeVoid;
//else if (truex->ValueType != falsex->ValueType)
if (ValueType->GetRegType() == REGT_NIL)
if (truex == nullptr || falsex == nullptr || ValueType->GetRegType() == REGT_NIL)
{
ScriptPosition.Message(MSG_ERROR, "Incompatible types for ?: operator");
delete this;
@ -8279,6 +8425,7 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
MethodName == NAME_Name ? TypeName :
MethodName == NAME_SpriteID ? TypeSpriteID :
MethodName == NAME_TextureID ? TypeTextureID :
MethodName == NAME_TranslationID ? TypeTranslationID :
MethodName == NAME_State ? TypeState :
MethodName == NAME_Color ? TypeColor : (PType*)TypeSound;

View file

@ -232,6 +232,7 @@ enum EFxType
EFX_StringCast,
EFX_ColorCast,
EFX_SoundCast,
EFX_TranslationCast,
EFX_TypeCast,
EFX_PlusSign,
EFX_MinusSign,
@ -715,6 +716,19 @@ public:
ExpEmit Emit(VMFunctionBuilder *build);
};
class FxTranslationCast : public FxExpression
{
FxExpression* basex;
public:
FxTranslationCast(FxExpression* x);
~FxTranslationCast();
FxExpression* Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder* build);
};
class FxFontCast : public FxExpression
{
FxExpression *basex;

View file

@ -41,6 +41,7 @@
#include "printf.h"
#include "textureid.h"
#include "maps.h"
#include "palettecontainer.h"
FTypeTable TypeTable;
@ -58,6 +59,7 @@ PName *TypeName;
PSound *TypeSound;
PColor *TypeColor;
PTextureID *TypeTextureID;
PTranslationID* TypeTranslationID;
PSpriteID *TypeSpriteID;
PStatePointer *TypeState;
PPointer *TypeFont;
@ -322,6 +324,7 @@ void PType::StaticInit()
TypeTable.AddType(TypeNullPtr = new PPointer, NAME_Pointer);
TypeTable.AddType(TypeSpriteID = new PSpriteID, NAME_SpriteID);
TypeTable.AddType(TypeTextureID = new PTextureID, NAME_TextureID);
TypeTable.AddType(TypeTranslationID = new PTranslationID, NAME_TranslationID);
TypeVoidPtr = NewPointer(TypeVoid, false);
TypeRawFunction = new PPointer;
@ -1321,6 +1324,48 @@ bool PTextureID::ReadValue(FSerializer &ar, const char *key, void *addr) const
return true;
}
/* PTranslationID ******************************************************************/
//==========================================================================
//
// PTranslationID Default Constructor
//
//==========================================================================
PTranslationID::PTranslationID()
: PInt(sizeof(FTranslationID), true, false)
{
mDescriptiveName = "TranslationID";
Flags |= TYPE_IntNotInt;
static_assert(sizeof(FTranslationID) == alignof(FTranslationID), "TranslationID not properly aligned");
}
//==========================================================================
//
// PTranslationID :: WriteValue
//
//==========================================================================
void PTranslationID::WriteValue(FSerializer& ar, const char* key, const void* addr) const
{
FTranslationID val = *(FTranslationID*)addr;
ar(key, val);
}
//==========================================================================
//
// PTranslationID :: ReadValue
//
//==========================================================================
bool PTranslationID::ReadValue(FSerializer& ar, const char* key, void* addr) const
{
FTranslationID val;
ar(key, val);
*(FTranslationID*)addr = val;
return true;
}
/* PSound *****************************************************************/
//==========================================================================

View file

@ -383,6 +383,15 @@ public:
bool ReadValue(FSerializer &ar, const char *key, void *addr) const override;
};
class PTranslationID : public PInt
{
public:
PTranslationID();
void WriteValue(FSerializer& ar, const char* key, const void* addr) const override;
bool ReadValue(FSerializer& ar, const char* key, void* addr) const override;
};
class PColor : public PInt
{
public:
@ -712,6 +721,7 @@ extern PName *TypeName;
extern PSound *TypeSound;
extern PColor *TypeColor;
extern PTextureID *TypeTextureID;
extern PTranslationID* TypeTranslationID;
extern PSpriteID *TypeSpriteID;
extern PStruct* TypeVector2;
extern PStruct* TypeVector3;

View file

@ -1886,6 +1886,12 @@ PType *ZCCCompiler::DetermineType(PType *outertype, ZCC_TreeNode *field, FName n
retval = TypeTextureID;
break;
case NAME_TranslationID:
retval = TypeTranslationID;
break;
default:
retval = ResolveUserType(btype, btype->UserType, outertype ? &outertype->Symbols : nullptr, false);
break;

View file

@ -1372,3 +1372,38 @@ DEFINE_ACTION_FUNCTION_NATIVE(DObject, FindFunction, FindFunctionPointer)
ACTION_RETURN_POINTER(FindFunctionPointer(cls, fn.GetIndex()));
}
int R_FindCustomTranslation(FName name);
static int ZFindTranslation(int intname)
{
return R_FindCustomTranslation(ENamedName(intname));
}
static int MakeTransID(int g, int s)
{
return TRANSLATION(g, s);
}
DEFINE_ACTION_FUNCTION_NATIVE(_Translation, GetID, ZFindTranslation)
{
PARAM_PROLOGUE;
PARAM_INT(t);
ACTION_RETURN_INT(ZFindTranslation(t));
}
// same as above for the compiler which needs a class to look this up.
DEFINE_ACTION_FUNCTION_NATIVE(DObject, BuiltinFindTranslation, ZFindTranslation)
{
PARAM_PROLOGUE;
PARAM_INT(t);
ACTION_RETURN_INT(ZFindTranslation(t));
}
DEFINE_ACTION_FUNCTION_NATIVE(_Translation, MakeID, MakeTransID)
{
PARAM_PROLOGUE;
PARAM_INT(g);
PARAM_INT(t);
ACTION_RETURN_INT(MakeTransID(g, t));
}

View file

@ -666,13 +666,6 @@ int R_FindCustomTranslation(FName name)
return (t != nullptr)? *t : -1;
}
DEFINE_ACTION_FUNCTION(_Translation, GetID)
{
PARAM_PROLOGUE;
PARAM_NAME(t);
ACTION_RETURN_INT(R_FindCustomTranslation(t));
}
//----------------------------------------------------------------------------
//
//

View file

@ -218,7 +218,7 @@ class Actor : Thinker native
native Inventory Inv;
native uint8 smokecounter;
native uint8 FriendPlayer;
native uint Translation;
native TranslationID Translation;
native sound AttackSound;
native sound DeathSound;
native sound SeeSound;
@ -253,7 +253,7 @@ class Actor : Thinker native
native double StealthAlpha;
native int WoundHealth; // Health needed to enter wound state
native readonly color BloodColor;
native readonly int BloodTranslation;
native readonly TranslationID BloodTranslation;
native int RenderHidden;
native int RenderRequired;
native int FriendlySeeBlocks;

View file

@ -2667,7 +2667,7 @@ class PSprite : Object native play
native Bool firstTic;
native bool InterpolateTic;
native int Tics;
native uint Translation;
native TranslationID Translation;
native bool bAddWeapon;
native bool bAddBob;
native bool bPowDouble;

View file

@ -51,25 +51,21 @@ extend struct Console
extend struct Translation
{
Color colors[256];
native int AddTranslation();
native static bool SetPlayerTranslation(int group, int num, int plrnum, PlayerClass pclass);
native static int GetID(Name transname);
}
// This is needed because Actor contains a field named 'translation' which shadows the above.
struct Translate version("4.5")
{
static int MakeID(int group, int num)
static TranslationID MakeID(int group, int num)
{
return (group << 16) + num;
return Translation.MakeID(group, num);
}
static bool SetPlayerTranslation(int group, int num, int plrnum, PlayerClass pclass)
{
return Translation.SetPlayerTranslation(group, num, plrnum, pclass);
}
static int GetID(Name transname)
static TranslationID GetID(Name transname)
{
return Translation.GetID(transname);
}
@ -115,6 +111,7 @@ extend struct GameInfoStruct
extend class Object
{
private native static Object BuiltinNewDoom(Class<Object> cls, int outerclass, int compatibility);
private native static TranslationID BuiltinFindTranslation(Name nm);
private native static int BuiltinCallLineSpecial(int special, Actor activator, int arg1, int arg2, int arg3, int arg4, int arg5);
// These really should be global functions...
native static String G_SkillName();

View file

@ -919,10 +919,11 @@ struct StringStruct native
struct Translation version("2.4")
{
static int MakeID(int group, int num)
{
return (group << 16) + num;
}
Color colors[256];
native TranslationID AddTranslation();
native static TranslationID MakeID(int group, int num);
native static TranslationID GetID(Name transname);
}
// Convenient way to attach functions to Quat

View file

@ -141,12 +141,12 @@ class BlackScreen : ScreenJob
class ImageScreen : SkippableScreenJob
{
int tilenum;
int trans;
TranslationID trans;
int waittime; // in ms.
bool cleared;
TextureID texid;
ScreenJob Init(TextureID tile, int fade = fadein | fadeout, int wait = 3000, int translation = 0)
ScreenJob Init(TextureID tile, int fade = fadein | fadeout, int wait = 3000, TranslationID translation = 0)
{
Super.Init(fade);
waittime = wait;
@ -156,7 +156,7 @@ class ImageScreen : SkippableScreenJob
return self;
}
ScreenJob InitNamed(String tex, int fade = fadein | fadeout, int wait = 3000, int translation = 0)
ScreenJob InitNamed(String tex, int fade = fadein | fadeout, int wait = 3000, TranslationID translation = 0)
{
Super.Init(fade);
waittime = wait;
@ -166,12 +166,12 @@ class ImageScreen : SkippableScreenJob
return self;
}
static ScreenJob Create(TextureID tile, int fade = fadein | fadeout, int wait = 3000, int translation = 0)
static ScreenJob Create(TextureID tile, int fade = fadein | fadeout, int wait = 3000, TranslationID translation = 0)
{
return new("ImageScreen").Init(tile, fade, wait, translation);
}
static ScreenJob CreateNamed(String tex, int fade = fadein | fadeout, int wait = 3000, int translation = 0)
static ScreenJob CreateNamed(String tex, int fade = fadein | fadeout, int wait = 3000, TranslationID translation = 0)
{
return new("ImageScreen").InitNamed(tex, fade, wait, translation);
}

View file

@ -294,7 +294,7 @@ class ListMenuItemPlayerDisplay : ListMenuItem
if (sprite.IsValid())
{
int trans = mTranslate? Translation.MakeID(TRANSLATION_Players, MAXPLAYERS) : 0;
let trans = mTranslate? Translation.MakeID(TRANSLATION_Players, MAXPLAYERS) : 0;
let tscale = TexMan.GetScaledSize(sprite);
Scale.X *= sx * tscale.X;
Scale.Y *= sy * tscale.Y;
@ -357,7 +357,7 @@ class PlayerMenuPlayerDisplay : ListMenuItemPlayerDisplay
if (sprite.IsValid())
{
int trans = mTranslate? Translation.MakeID(TRANSLATION_Players, MAXPLAYERS) : 0;
let trans = mTranslate? Translation.MakeID(TRANSLATION_Players, MAXPLAYERS) : 0;
let tscale = TexMan.GetScaledSize(sprite);
Scale.X *= CleanXfac_1 * tscale.X * 2;
Scale.Y *= CleanYfac_1 * tscale.Y * 2;

View file

@ -755,7 +755,7 @@ class BaseStatusBar : StatusBarCore native
//
//============================================================================
int GetTranslation() const
TranslationID GetTranslation() const
{
if(gameinfo.gametype & GAME_Raven)
return Translation.MakeID(TRANSLATION_PlayersExtra, CPlayer.mo.PlayerNumber());