- scriptified parts of a_bossbrain.cpp. Some things cannot be done yet, the script code is there but commented out.

- exported thinker iterator and drop item chain to scripting. Unlike its native counterpart the script-side iterator is wrapped into a DObject to allow proper handling for memory management.
- fixed: The VMFunctionBuilder only distinguished between member and action functions but failed on static ones.
- fixed: FxAssign did not add all needed type casts. Except for purely numeric types it will now wrap the expression in an FxTypeCast. Numeric handling remains unchanged for both performance reasons and not altering semantics for DECORATE.
- exported all internal flags as variables to scripting. They still cannot be used in an actor definition.
- make ATAG_STATE the same as ATAG_GENERIC. Since state pointers exist as actual variables they can take both values which on occasion can trigger some asserts.
- gave PClass a bExported flag, so that scripts cannot see purely internal classes. Especially the types like PInt can cause problems.

Todo: we need readonly references to safely expose the actor defaults. Right now some badly behaving code could overwrite them.
This commit is contained in:
Christoph Oelckers 2016-10-31 17:02:47 +01:00
parent 6ff973a06b
commit e620c9bd7d
17 changed files with 668 additions and 161 deletions

View file

@ -808,8 +808,8 @@ void SetDehParams(FState *state, int codepointer)
}
else
{
VMFunctionBuilder buildit(true);
int numargs = sym->GetImplicitArgs();
VMFunctionBuilder buildit(numargs);
// Allocate registers used to pass parameters in.
// self, stateowner, state (all are pointers)
buildit.Registers[REGT_POINTER].Get(numargs);

View file

@ -2737,6 +2737,7 @@ PClass::PClass()
HashNext = NULL;
Defaults = NULL;
bRuntimeClass = false;
bExported = false;
ConstructNative = NULL;
mDescriptiveName = "Class";

View file

@ -769,6 +769,7 @@ public:
const size_t *FlatPointers; // object pointers defined by this class and all its superclasses; not initialized by default
BYTE *Defaults;
bool bRuntimeClass; // class was defined at run-time, not compile-time
bool bExported; // This type has been declared in a script
void (*ConstructNative)(void *);

View file

@ -521,6 +521,44 @@ DThinker *FThinkerIterator::Next ()
return NULL;
}
// This is for scripting, which needs the iterator wrapped into an object with the needed functions exported.
// Unfortunately we cannot have templated type conversions in scripts.
class DThinkerIterator : public DObject, public FThinkerIterator
{
DECLARE_CLASS(DThinkerIterator, DObject)
public:
DThinkerIterator(PClass *cls = nullptr, int statnum = MAX_STATNUM + 1)
: FThinkerIterator(cls, statnum)
{
}
};
IMPLEMENT_CLASS(DThinkerIterator);
DEFINE_ACTION_FUNCTION(DThinkerIterator, Create)
{
PARAM_PROLOGUE;
PARAM_CLASS_DEF(type, DThinker);
PARAM_INT_DEF(statnum);
ACTION_RETURN_OBJECT(new DThinkerIterator(type, statnum));
}
DEFINE_ACTION_FUNCTION(DThinkerIterator, Next)
{
PARAM_SELF_PROLOGUE(DThinkerIterator);
ACTION_RETURN_OBJECT(self->Next());
}
DEFINE_ACTION_FUNCTION(DThinkerIterator, Reinit)
{
PARAM_SELF_PROLOGUE(DThinkerIterator);
self->Reinit();
return 0;
}
ADD_STAT (think)
{
FString out;

View file

@ -12,103 +12,8 @@
#include "g_level.h"
*/
static FRandom pr_brainscream ("BrainScream");
static FRandom pr_brainexplode ("BrainExplode");
static FRandom pr_spawnfly ("SpawnFly");
DEFINE_ACTION_FUNCTION(AActor, A_BrainAwake)
{
PARAM_SELF_PROLOGUE(AActor);
// killough 3/26/98: only generates sound now
S_Sound (self, CHAN_VOICE, "brain/sight", 1, ATTN_NONE);
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_BrainPain)
{
PARAM_SELF_PROLOGUE(AActor);
S_Sound (self, CHAN_VOICE, "brain/pain", 1, ATTN_NONE);
return 0;
}
static void BrainishExplosion (const DVector3 &pos)
{
AActor *boom = Spawn("Rocket", pos, NO_REPLACE);
if (boom != NULL)
{
boom->DeathSound = "misc/brainexplode";
boom->Vel.Z = pr_brainscream() /128.;
PClassActor *cls = PClass::FindActor("BossBrain");
if (cls != NULL)
{
FState *state = cls->FindState(NAME_Brainexplode);
if (state != NULL)
boom->SetState (state);
}
boom->effects = 0;
boom->SetDamage(0); // disables collision detection which is not wanted here
boom->tics -= pr_brainscream() & 7;
if (boom->tics < 1)
boom->tics = 1;
}
}
DEFINE_ACTION_FUNCTION(AActor, A_BrainScream)
{
PARAM_SELF_PROLOGUE(AActor);
for (double x = -196; x < +320; x += 8)
{
// (1 / 512.) is actually what the original value of 128 did, even though it probably meant 128 map units.
BrainishExplosion(self->Vec2OffsetZ(x, -320, (1 / 512.) + pr_brainexplode() * 2));
}
S_Sound (self, CHAN_VOICE, "brain/death", 1, ATTN_NONE);
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_BrainExplode)
{
PARAM_SELF_PROLOGUE(AActor);
double x = pr_brainexplode.Random2() / 32.;
DVector3 pos = self->Vec2OffsetZ(x, 0, 1 / 512. + pr_brainexplode() * 2);
BrainishExplosion(pos);
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, A_BrainDie)
{
PARAM_SELF_PROLOGUE(AActor);
// [RH] If noexit, then don't end the level.
if ((deathmatch || alwaysapplydmflags) && (dmflags & DF_NO_EXIT))
return 0;
// New dmflag: Kill all boss spawned monsters before ending the level.
if (dmflags2 & DF2_KILLBOSSMONST)
{
int count; // Repeat until we have no more boss-spawned monsters.
do // (e.g. Pain Elementals can spawn more to kill upon death.)
{
TThinkerIterator<AActor> it;
AActor *mo;
count = 0;
while ((mo = it.Next()))
{
if (mo->health > 0 && mo->flags4 & MF4_BOSSSPAWNED)
{
P_DamageMobj(mo, self, self, mo->health, NAME_None,
DMG_NO_ARMOR|DMG_FORCED|DMG_THRUSTLESS|DMG_NO_FACTOR);
count++;
}
}
} while (count != 0);
}
G_ExitLevel (0, false);
return 0;
}
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BrainSpit)
{
PARAM_SELF_PROLOGUE(AActor);

View file

@ -6612,6 +6612,20 @@ void AActor::SetTranslation(const char *trname)
// silently ignore if the name does not exist, this would create some insane message spam otherwise.
}
DEFINE_ACTION_FUNCTION(AActor, SetDamage)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(dmg);
self->SetDamage(dmg);
return 0;
}
DEFINE_ACTION_FUNCTION(AActor, GetDefaultByType)
{
PARAM_PROLOGUE;
PARAM_CLASS(cls, AActor);
ACTION_RETURN_OBJECT(GetDefaultByType(cls));
}
// This combines all 3 variations of the internal function
DEFINE_ACTION_FUNCTION(AActor, VelFromAngle)
@ -6657,6 +6671,15 @@ DEFINE_ACTION_FUNCTION(AActor, Vec3Angle)
ACTION_RETURN_VEC3(self->Vec3Angle(length, angle, z, absolute));
}
DEFINE_ACTION_FUNCTION(AActor, Vec2OffsetZ)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_FLOAT(x);
PARAM_FLOAT(y);
PARAM_FLOAT(z);
PARAM_BOOL_DEF(absolute);
ACTION_RETURN_VEC3(self->Vec2OffsetZ(x, y, z, absolute));
}
//----------------------------------------------------------------------------
//
// DropItem handling

View file

@ -2078,6 +2078,8 @@ FxExpression *FxAssign::Resolve(FCompileContext &ctx)
return nullptr;
}
// keep the redundant handling for numeric types here to avoid problems with DECORATE.
// for non-numerics FxTypeCast can be used without issues.
if (Base->IsNumeric() && Right->IsNumeric())
{
if (Right->ValueType != ValueType)
@ -2113,32 +2115,13 @@ FxExpression *FxAssign::Resolve(FCompileContext &ctx)
}
// Both types are the same so this is ok.
}
else if ((Base->ValueType == TypeState || Base->ValueType->IsKindOf(RUNTIME_CLASS(PPointer))) && Right->ValueType == TypeNullPtr)
{
// null pointers can be assigned to any other pointer
}
else if (Base->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)))
{
// class pointers may be assignable so add a cast which performs a check.
Right = new FxClassTypeCast(static_cast<PClassPointer *>(ValueType), Right);
SAFE_RESOLVE(Right, ctx);
}
else if (Base->ValueType == TypeString && (Right->ValueType == TypeName || Right->ValueType == TypeSound))
{
Right = new FxStringCast(Right);
SAFE_RESOLVE(Right, ctx);
}
else if (Base->ValueType == TypeName && Right->ValueType == TypeString)
{
Right = new FxNameCast(Right);
SAFE_RESOLVE(Right, ctx);
}
else
{
ScriptPosition.Message(MSG_ERROR, "Assignment between incompatible types.");
delete this;
return nullptr;
// pass it to FxTypeCast for complete handling.
Right = new FxTypeCast(Right, Base->ValueType, false);
SAFE_RESOLVE(Right, ctx);
}
if (!Base->RequestAddress(&AddressWritable) || !AddressWritable)
{
ScriptPosition.Message(MSG_ERROR, "Expression must be a modifiable value");
@ -3100,6 +3083,69 @@ FxExpression *FxCompareEq::Resolve(FCompileContext& ctx)
delete this;
return e;
}
else
{
// also simplify comparisons against zero. For these a bool cast on the other value will do just as well and create better code.
if (left->isConstant())
{
bool leftisnull;
switch (left->ValueType->GetRegType())
{
case REGT_INT:
leftisnull = static_cast<FxConstant *>(left)->GetValue().GetInt() == 0;
break;
case REGT_FLOAT:
assert(left->ValueType->GetRegCount() == 1); // vectors should not be able to get here.
leftisnull = static_cast<FxConstant *>(left)->GetValue().GetFloat() == 0;
break;
case REGT_POINTER:
leftisnull = static_cast<FxConstant *>(left)->GetValue().GetPointer() == nullptr;
break;
default:
leftisnull = false;
}
if (leftisnull)
{
FxExpression *x = new FxBoolCast(right);
right = nullptr;
delete this;
return x->Resolve(ctx);
}
}
if (right->isConstant())
{
bool rightisnull;
switch (right->ValueType->GetRegType())
{
case REGT_INT:
rightisnull = static_cast<FxConstant *>(right)->GetValue().GetInt() == 0;
break;
case REGT_FLOAT:
assert(right->ValueType->GetRegCount() == 1); // vectors should not be able to get here.
rightisnull = static_cast<FxConstant *>(right)->GetValue().GetFloat() == 0;
break;
case REGT_POINTER:
rightisnull = static_cast<FxConstant *>(right)->GetValue().GetPointer() == nullptr;
break;
default:
rightisnull = false;
}
if (rightisnull)
{
FxExpression *x = new FxBoolCast(left);
left = nullptr;
delete this;
return x->Resolve(ctx);
}
}
}
Promote(ctx);
ValueType = TypeBool;
return this;
@ -3720,7 +3766,7 @@ int BuiltinTypeCheck(VMFrameStack *stack, VMValue *param, TArray<VMValue> &defau
assert(numparam == 2);
PARAM_POINTER_AT(0, obj, DObject);
PARAM_CLASS_AT(1, cls, DObject);
ACTION_RETURN_BOOL(obj->IsKindOf(cls));
ACTION_RETURN_BOOL(obj && obj->IsKindOf(cls));
}
//==========================================================================
@ -3762,6 +3808,72 @@ ExpEmit FxTypeCheck::Emit(VMFunctionBuilder *build)
//
//==========================================================================
FxDynamicCast::FxDynamicCast(PClass * cls, FxExpression *r)
: FxExpression(EFX_DynamicCast, r->ScriptPosition)
{
expr = new FxTypeCast(r, NewPointer(RUNTIME_CLASS(DObject)), true, true);
ValueType = NewPointer(cls);
CastType = cls;
}
//==========================================================================
//
//
//
//==========================================================================
FxDynamicCast::~FxDynamicCast()
{
SAFE_DELETE(expr);
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression *FxDynamicCast::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(expr, ctx);
return this;
}
//==========================================================================
//
//
//
//==========================================================================
ExpEmit FxDynamicCast::Emit(VMFunctionBuilder *build)
{
ExpEmit out = expr->Emit(build);
ExpEmit check(build, REGT_INT);
assert(out.RegType == REGT_POINTER);
build->Emit(OP_PARAM, 0, REGT_POINTER, out.RegNum);
build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, build->GetConstantAddress(CastType, ATAG_OBJECT));
PSymbol *sym = FindBuiltinFunction(NAME_BuiltinTypeCheck, BuiltinTypeCheck);
assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolVMFunction)));
assert(((PSymbolVMFunction *)sym)->Function != nullptr);
auto callfunc = ((PSymbolVMFunction *)sym)->Function;
build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc, ATAG_OBJECT), 2, 1);
build->Emit(OP_RESULT, 0, REGT_INT, check.RegNum);
auto patch = build->Emit(OP_EQ_K, 0, check.RegNum, build->GetConstantInt(0));
build->Emit(OP_LKP, out.RegNum, build->GetConstantAddress(nullptr, ATAG_OBJECT));
build->BackpatchToHere(patch);
return out;
}
//==========================================================================
//
//
//
//==========================================================================
FxConditional::FxConditional(FxExpression *c, FxExpression *t, FxExpression *f)
: FxExpression(EFX_Conditional, c->ScriptPosition)
{
@ -5655,6 +5767,24 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
return x->Resolve(ctx);
}
PClass *cls = PClass::FindClass(MethodName);
if (cls != nullptr && cls->bExported)
{
if (CheckArgSize(MethodName, ArgList, 1, 1, ScriptPosition))
{
FxExpression *x = new FxDynamicCast(cls, (*ArgList)[0]);
(*ArgList)[0] = nullptr;
delete this;
return x->Resolve(ctx);
}
else
{
delete this;
return nullptr;
}
}
// Last but not least: Check builtins and type casts. The random functions can take a named RNG if specified.
// Note that for all builtins the used arguments have to be nulled in the ArgList so that they won't get deleted before they get used.
FxExpression *func = nullptr;
@ -5681,7 +5811,12 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
break;
case NAME_Random:
if (CheckArgSize(NAME_Random, ArgList, 2, 2, ScriptPosition))
// allow calling Random without arguments to default to (0, 255)
if (ArgList->Size() == 0)
{
func = new FxRandom(RNG, new FxConstant(0, ScriptPosition), new FxConstant(255, ScriptPosition), ScriptPosition, ctx.FromDecorate);
}
else if (CheckArgSize(NAME_Random, ArgList, 2, 2, ScriptPosition))
{
func = new FxRandom(RNG, (*ArgList)[0], (*ArgList)[1], ScriptPosition, ctx.FromDecorate);
(*ArgList)[0] = (*ArgList)[1] = nullptr;
@ -5754,7 +5889,7 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
break;
default:
// todo: Check for class type casts
ScriptPosition.Message(MSG_ERROR, "Call to unknown function '%s'", MethodName.GetChars());
break;
}
if (func != nullptr)
@ -5762,7 +5897,6 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
delete this;
return func->Resolve(ctx);
}
ScriptPosition.Message(MSG_ERROR, "Call to unknown function '%s'", MethodName.GetChars());
delete this;
return nullptr;
}
@ -5803,10 +5937,22 @@ FxMemberFunctionCall::~FxMemberFunctionCall()
FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{
ABORT(ctx.Class);
SAFE_RESOLVE(Self, ctx);
PClass *cls;
bool staticonly = false;
if (Self->ExprType == EFX_Identifier)
{
// If the left side is a class name for a static member function call it needs to be resolved manually
// because the resulting value type would cause problems in nearly every other place where identifiers are being used.
cls = PClass::FindClass(static_cast<FxIdentifier *>(Self)->Identifier);
if (cls != nullptr && cls->bExported)
{
staticonly = true;
goto isresolved;
}
}
SAFE_RESOLVE(Self, ctx);
if (Self->IsVector())
{
// handle builtins: Vectors got 2: Length and Unit.
@ -5819,12 +5965,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
}
}
if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)))
{
cls = static_cast<PClassPointer *>(Self->ValueType)->ClassRestriction;
staticonly = true;
}
else if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)))
if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)))
{
auto ptype = static_cast<PPointer *>(Self->ValueType)->PointedType;
if (ptype->IsKindOf(RUNTIME_CLASS(PClass)))
@ -5845,6 +5986,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
return nullptr;
}
isresolved:
bool error = false;
PFunction *afd = FindClassMemberFunction(cls, cls, MethodName, ScriptPosition, &error);
if (error)
@ -5859,7 +6001,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
delete this;
return nullptr;
}
if (staticonly && !(afd->Variants[0].Flags & VARF_Static))
if (staticonly && (afd->Variants[0].Flags & VARF_Method))
{
if (!ctx.Class->IsDescendantOf(cls))
{
@ -5867,11 +6009,17 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
delete this;
return nullptr;
}
// If this is a qualified call to a parent class function, let it through (but this needs to disable virtual calls later.)
else
{
// Todo: If this is a qualified call to a parent class function, let it through (but this needs to disable virtual calls later.)
ScriptPosition.Message(MSG_ERROR, "Qualified member call to parent class not yet implemented\n", cls->TypeName.GetChars(), MethodName.GetChars());
delete this;
return nullptr;
}
}
// do not pass the self pointer to static functions.
auto self = !(afd->Variants[0].Flags & VARF_Static) ? Self : nullptr;
auto self = (afd->Variants[0].Flags & VARF_Method) ? Self : nullptr;
auto x = new FxVMFunctionCall(self, afd, ArgList, ScriptPosition, staticonly);
ArgList = nullptr;
if (Self == self) Self = nullptr;
@ -6128,7 +6276,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
int implicit = Function->GetImplicitArgs();
// This should never happen.
if (Self == nullptr && !(Function->Variants[0].Flags & VARF_Static))
if (Self == nullptr && (Function->Variants[0].Flags & VARF_Method))
{
ScriptPosition.Message(MSG_ERROR, "Call to non-static function without a self pointer");
delete this;
@ -6200,8 +6348,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
{
assert((build->IsActionFunc && build->Registers[REGT_POINTER].GetMostUsed() >= NAP) ||
(!build->IsActionFunc && build->Registers[REGT_POINTER].GetMostUsed() >= 1));
assert(build->Registers[REGT_POINTER].GetMostUsed() >= build->NumImplicits);
int count = 0; (ArgList ? ArgList->Size() : 0);
if (count == 1)
@ -6225,7 +6372,7 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
if (Function->Variants[0].Flags & VARF_Action)
{
static_assert(NAP == 3, "This code needs to be updated if NAP changes");
if (build->IsActionFunc && selfemit.RegNum == 0) // only pass this function's stateowner and stateinfo if the subfunction is run in self's context.
if (build->NumImplicits == NAP && selfemit.RegNum == 0) // only pass this function's stateowner and stateinfo if the subfunction is run in self's context.
{
build->Emit(OP_PARAM, 0, REGT_POINTER, 1);
build->Emit(OP_PARAM, 0, REGT_POINTER, 2);
@ -6238,6 +6385,7 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
}
count += 2;
}
selfemit.Free(build);
}
// Emit code to pass explicit parameters
if (ArgList != nullptr)
@ -7654,7 +7802,9 @@ static int BuiltinHandleRuntimeState(VMFrameStack *stack, VMValue *param, TArray
ExpEmit FxRuntimeStateIndex::Emit(VMFunctionBuilder *build)
{
assert(build->IsActionFunc && build->Registers[REGT_POINTER].GetMostUsed() >= 3 &&
// This code can only be called from DECORATE, not ZSCRIPT so any function going through here
// is an anoynmous one which are always marked as 'action'.
assert(build->NumImplicits >= NAP && build->Registers[REGT_POINTER].GetMostUsed() >= build->NumImplicits &&
"FxRuntimeStateIndex is only valid inside action functions");
ExpEmit out(build, REGT_POINTER);
@ -7741,7 +7891,7 @@ FxExpression *FxMultiNameState::Resolve(FCompileContext &ctx)
delete this;
return nullptr;
}
else if (!scope->IsAncestorOf(ctx.Class))
else if (!scope->IsAncestorOf(ctx.Class) && ctx.Class != RUNTIME_CLASS(AActor)) // AActor needs access to subclasses in a few places. TBD: Relax this for non-action functions?
{
ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(), ctx.Class->TypeName.GetChars());
delete this;
@ -7827,7 +7977,7 @@ int BuiltinFindSingleNameState(VMFrameStack *stack, VMValue *param, TArray<VMVal
ExpEmit FxMultiNameState::Emit(VMFunctionBuilder *build)
{
ExpEmit dest(build, REGT_POINTER);
if (build->IsActionFunc)
if (build->NumImplicits == NAP)
{
build->Emit(OP_PARAM, 0, REGT_POINTER, 1); // pass stateowner
}

View file

@ -268,6 +268,7 @@ enum EFxType
EFX_VectorValue,
EFX_VectorBuiltin,
EFX_TypeCheck,
EFX_DynamicCast,
EFX_COUNT
};
@ -320,10 +321,9 @@ public:
class FxIdentifier : public FxExpression
{
protected:
public:
FName Identifier;
public:
FxIdentifier(FName i, const FScriptPosition &p);
FxExpression *Resolve(FCompileContext&);
};
@ -940,7 +940,7 @@ public:
//==========================================================================
//
// FxBinaryLogical
//
//
//==========================================================================
@ -959,6 +959,25 @@ public:
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
//
//
//==========================================================================
class FxDynamicCast : public FxExpression
{
PClass *CastType;
public:
FxExpression *expr;
FxDynamicCast(PClass*, FxExpression*);
~FxDynamicCast();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxConditional

View file

@ -61,6 +61,39 @@ static TArray<AFuncDesc> AFTable;
#define DEFINE_DEPRECATED_FLAG(name) { DEPF_##name, #name, -1, 0, true }
#define DEFINE_DUMMY_FLAG(name, deprec) { DEPF_UNUSED, #name, -1, 0, deprec? VARF_Deprecated:0 }
// internal flags. These do not get exposed to actor definitions but scripts need to be able to access them as variables.
static FFlagDef InternalActorFlagDefs[]=
{
DEFINE_FLAG(MF, INCHASE, AActor, flags),
DEFINE_FLAG(MF, UNMORPHED, AActor, flags),
DEFINE_FLAG(MF2, FLY, AActor, flags2),
DEFINE_FLAG(MF2, ONMOBJ, AActor, flags2),
DEFINE_FLAG(MF2, DONTTRANSLATE, AActor, flags2),
DEFINE_FLAG(MF2, ARGSDEFINED, AActor, flags2),
DEFINE_FLAG(MF3, NOSIGHTCHECK, AActor, flags3),
DEFINE_FLAG(MF3, CRASHED, AActor, flags3),
DEFINE_FLAG(MF3, WARNBOT, AActor, flags3),
DEFINE_FLAG(MF3, HUNTPLAYERS, AActor, flags3),
DEFINE_FLAG(MF4, NOHATEPLAYERS, AActor, flags4),
DEFINE_FLAG(MF4, NOSKIN, AActor, flags4),
DEFINE_FLAG(MF4, SCROLLMOVE, AActor, flags4),
DEFINE_FLAG(MF4, VFRICTION, AActor, flags4),
DEFINE_FLAG(MF4, BOSSSPAWNED, AActor, flags4),
DEFINE_FLAG(MF5, AVOIDINGDROPOFF, AActor, flags5),
DEFINE_FLAG(MF5, CHASEGOAL, AActor, flags5),
DEFINE_FLAG(MF5, INCONVERSATION, AActor, flags5),
DEFINE_FLAG(MF6, ARMED, AActor, flags6),
DEFINE_FLAG(MF6, FALLING, AActor, flags6),
DEFINE_FLAG(MF6, LINEDONE, AActor, flags6),
DEFINE_FLAG(MF6, SHATTERING, AActor, flags6),
DEFINE_FLAG(MF6, KILLED, AActor, flags6),
DEFINE_FLAG(MF6, BOSSCUBE, AActor, flags6),
DEFINE_FLAG(MF6, INTRYMOVE, AActor, flags6),
DEFINE_FLAG(MF7, HANDLENODELAY, AActor, flags7),
DEFINE_FLAG(MF7, FLYCHEAT, AActor, flags7),
};
static FFlagDef ActorFlagDefs[]=
{
DEFINE_FLAG(MF, PICKUP, APlayerPawn, flags),
@ -687,6 +720,7 @@ void InitThingdef()
symt.AddSymbol(new PField("Gravity", TypeFloat64, VARF_Native, myoffsetof(AActor, Gravity)));
symt.AddSymbol(new PField("DamageType", TypeName, VARF_Native, myoffsetof(AActor, DamageType)));
symt.AddSymbol(new PField("FloatBobPhase", TypeUInt8, VARF_Native, myoffsetof(AActor, FloatBobPhase)));
symt.AddSymbol(new PField("tics", TypeSInt32, VARF_Native, myoffsetof(AActor, tics)));
symt.AddSymbol(new PField("RipperLevel", TypeSInt32, VARF_Native, myoffsetof(AActor, RipperLevel)));
symt.AddSymbol(new PField("RipLevelMin", TypeSInt32, VARF_Native, myoffsetof(AActor, RipLevelMin)));
symt.AddSymbol(new PField("RipLevelMax", TypeSInt32, VARF_Native, myoffsetof(AActor, RipLevelMax)));
@ -695,6 +729,7 @@ void InitThingdef()
symt.AddSymbol(new PField(NAME_VisibleEndAngle, TypeFloat64, VARF_Native, myoffsetof(AActor, VisibleEndAngle)));
symt.AddSymbol(new PField(NAME_VisibleEndPitch, TypeFloat64, VARF_Native, myoffsetof(AActor, VisibleEndPitch)));
symt.AddSymbol(new PField("AttackSound", TypeSound, VARF_Native, myoffsetof(AActor, AttackSound)));
symt.AddSymbol(new PField("DeathSound", TypeSound, VARF_Native, myoffsetof(AActor, DeathSound)));
symt.AddSymbol(new PField("Pos", TypeVector3, VARF_Native|VARF_ReadOnly, myoffsetof(AActor, __Pos)));
symt.AddSymbol(new PField("Vel", TypeVector3, VARF_Native, myoffsetof(AActor, Vel)));
symt.AddSymbol(new PField("Scale", TypeVector2, VARF_Native, myoffsetof(AActor, Scale)));
@ -706,7 +741,7 @@ void InitThingdef()
symt.AddSymbol(new PField("LastHeard", TypeActor, VARF_Native, myoffsetof(AActor, LastHeard)));
symt.AddSymbol(new PField("LastEnemy", TypeActor, VARF_Native, myoffsetof(AActor, lastenemy)));
// synthesize a symbol for each flag. The bounce flags are excluded on purpose.
// synthesize a symbol for each flag.
for (size_t i = 0; i < countof(ActorFlagDefs); i++)
{
int bit = 0;
@ -714,5 +749,19 @@ void InitThingdef()
while ((val >>= 1)) bit++;
symt.AddSymbol(new PField(FStringf("b%s", ActorFlagDefs[i].name), (ActorFlagDefs[i].fieldsize == 4? TypeSInt32 : TypeSInt16), ActorFlagDefs[i].varflags, ActorFlagDefs[i].structoffset, bit));
}
for (size_t i = 0; i < countof(InternalActorFlagDefs); i++)
{
int bit = 0;
unsigned val = InternalActorFlagDefs[i].flagbit;
while ((val >>= 1)) bit++;
symt.AddSymbol(new PField(FStringf("b%s", InternalActorFlagDefs[i].name), (InternalActorFlagDefs[i].fieldsize == 4 ? TypeSInt32 : TypeSInt16), InternalActorFlagDefs[i].varflags, InternalActorFlagDefs[i].structoffset, bit));
}
PSymbolTable &symt2 = RUNTIME_CLASS(DDropItem)->Symbols;
PType *TypeDropItem = NewPointer(RUNTIME_CLASS(DDropItem));
symt2.AddSymbol(new PField("Next", TypeDropItem, VARF_Native | VARF_ReadOnly, myoffsetof(DDropItem, Next)));
symt2.AddSymbol(new PField("ItemName", TypeName, VARF_Native | VARF_ReadOnly, myoffsetof(DDropItem, Name)));
symt2.AddSymbol(new PField("Probability", TypeSInt32, VARF_Native | VARF_ReadOnly, myoffsetof(DDropItem, Probability)));
symt2.AddSymbol(new PField("Amount", TypeSInt32, VARF_Native | VARF_ReadOnly, myoffsetof(DDropItem, Amount)));
}

View file

@ -161,8 +161,8 @@ enum
ATAG_SREGISTER, // pointer to a string register
ATAG_AREGISTER, // pointer to an address register
ATAG_STATE, // pointer to FState
ATAG_RNG, // pointer to FRandom
ATAG_STATE = ATAG_GENERIC, // pointer to FState (cannot have its own type because there's no means to track inside the VM.)
};
enum EVMAbortException

View file

@ -43,7 +43,7 @@
//
//==========================================================================
VMFunctionBuilder::VMFunctionBuilder(bool selfcheck)
VMFunctionBuilder::VMFunctionBuilder(int numimplicits)
{
NumIntConstants = 0;
NumFloatConstants = 0;
@ -51,7 +51,7 @@ VMFunctionBuilder::VMFunctionBuilder(bool selfcheck)
NumStringConstants = 0;
MaxParam = 0;
ActiveParam = 0;
IsActionFunc = selfcheck;
NumImplicits = numimplicits;
}
//==========================================================================

View file

@ -23,7 +23,7 @@ public:
friend class VMFunctionBuilder;
};
VMFunctionBuilder(bool checkself = false);
VMFunctionBuilder(int numimplicits);
~VMFunctionBuilder();
void MakeFunction(VMScriptFunction *func);
@ -60,8 +60,8 @@ public:
// Track available registers.
RegAvailability Registers[4];
// For use by DECORATE's self/stateowner sanitizer.
bool IsActionFunc;
// amount of implicit parameters so that proper code can be emitted for method calls
int NumImplicits;
private:
struct AddrKonst

View file

@ -175,7 +175,7 @@ begin:
OP(LO):
ASSERTA(a); ASSERTA(B); ASSERTKD(C);
GETADDR(PB,KC,X_READ_NIL);
reg.a[a] = GC::ReadBarrier(*(DObject **)ptr);
reg.a[a] = *(DObject **)ptr;
reg.atag[a] = ATAG_OBJECT;
NEXTOP;
OP(LO_R):

View file

@ -508,6 +508,7 @@ void ZCCCompiler::CreateClassTypes()
// We will never get here if the name is a duplicate, so we can just do the assignment.
c->cls->Type = parent->FindClassTentative(c->NodeName());
}
c->Type()->bExported = true; // this class is accessible to script side type casts. (The reason for this flag is that types like PInt need to be skipped.)
c->cls->Symbol = new PSymbolType(c->NodeName(), c->Type());
GlobalSymbols.AddSymbol(c->cls->Symbol);
c->Type()->Symbols.SetName(c->NodeName());

View file

@ -53,6 +53,8 @@ class Actor : Thinker native
return GetPointer(ptr_select1) == GetPointer(ptr_select2);
}
native static /*readonly*/ Actor GetDefaultByType(class<Actor> cls);
native void SetDamage(int dmg);
native static bool isDehState(state st);
native void SetOrigin(vector3 newpos, bool moving);
native void SetXYZ(vector3 newpos);
@ -71,6 +73,7 @@ class Actor : Thinker native
native void LinkToWorld();
native void UnlinkFromWorld();
native vector3 Vec3Angle(float length, float angle, float z = 0, bool absolute = false);
native vector3 Vec2OffsetZ(float x, float y, float atz, bool absolute = false);
native void VelFromAngle(float speed = 0, float angle = 0);
native bool isFriend(Actor other);
@ -218,14 +221,6 @@ class Actor : Thinker native
native void A_SkullAttack(float speed = 20);
native void A_BetaSkullAttack();
native void A_KeenDie(int doortag = 666);
native void A_BrainPain();
native void A_BrainScream();
native void A_BrainDie();
native void A_BrainAwake();
native void A_BrainSpit(class<Actor> spawntype = null); // needs special treatment for default
native void A_SpawnSound();
native void A_SpawnFly(class<Actor> spawntype = null); // needs special treatment for default
native void A_BrainExplode();
native void A_Detonate();
native bool A_CallSpecial(int special, int arg1=0, int arg2=0, int arg3=0, int arg4=0, int arg5=0);

View file

@ -5,4 +5,27 @@ class Object native
class Thinker : Object native
{
}
}
class ThinkerIterator : Object native
{
enum EStatnums
{
MAX_STATNUM = 127
}
native static ThinkerIterator Create(class<Actor> type = "Actor", int statnum=MAX_STATNUM+1);
native Thinker Next();
native void Reinit();
}
class DropItem : Object native
{
/* native fields listed for reference only for now
native readonly DropItem Next;
native readonly name ItemName; // internally called 'name' which clashes with the type.
native readonly int Probability;
native readonly int Amount;
*/
}

View file

@ -133,3 +133,305 @@ class SpawnFire : Actor
Stop;
}
}
//===========================================================================
//
// Code (must be attached to Actor)
//
//===========================================================================
extend class Actor
{
void A_BrainAwake()
{
A_PlaySound("brain/sight", CHAN_VOICE, 1, false, ATTN_NONE);
}
void A_BrainPain()
{
A_PlaySound("brain/pain", CHAN_VOICE, 1, false, ATTN_NONE);
}
private static void BrainishExplosion(vector3 pos)
{
Actor boom = Actor.Spawn("Rocket", pos, NO_REPLACE);
if (boom)
{
boom.DeathSound = "misc/brainexplode";
boom.Vel.z = random[BrainScream](0, 255)/128.;
State stt = "BossBrain::Brainexplode";
if (stt) boom.SetState (stt);
}
boom.bRocketTrail = false;
boom.SetDamage(0); // disables collision detection which is not wanted here
boom.tics -= random[BrainScream](0, 7);
if (boom.tics < 1) boom.tics = 1;
}
void A_BrainScream()
{
for (double x = -196; x < +320; x += 8)
{
// (1 / 512.) is actually what the original value of 128 did, even though it probably meant 128 map units.
BrainishExplosion(Vec2OffsetZ(x, -320, (1 / 512.) + random[BrainExplode](0, 255) * 2));
}
A_PlaySound("brain/death", CHAN_VOICE, 1, false, ATTN_NONE);
}
void A_BrainExplode()
{
double x = random2[BrainExplode]() / 32.;
Vector3 pos = Vec2OffsetZ(x, 0, 1 / 512. + random[BrainExplode]() * 2);
BrainishExplosion(pos);
}
void A_BrainDie()
{
A_Log("A_BrainDie");
// [RH] If noexit, then don't end the level.
if ((GetCVar("deathmatch") || GetCVar("alwaysapplydmflags")) && GetCVar("sv_noexit"))
return;
// New dmflag: Kill all boss spawned monsters before ending the level.
if (GetCVar("sv_killbossmonst"))
{
int count; // Repeat until we have no more boss-spawned monsters.
ThinkerIterator it = ThinkerIterator.Create();
do // (e.g. Pain Elementals can spawn more to kill upon death.)
{
Actor mo;
it.Reinit();
count = 0;
while (mo = Actor(it.Next()))
{
if (mo.health > 0 && mo.bBossSpawned)
{
mo.DamageMobj(self, self, mo.health, "None", DMG_NO_ARMOR|DMG_FORCED|DMG_THRUSTLESS|DMG_NO_FACTOR);
count++;
}
}
} while (count != 0);
}
Exit_Normal(0);
}
native
void A_BrainSpit(class<Actor> spawntype = null) // needs special treatment for default
;/*
{
SpotState spstate = SpotState.GetSpotState();
Actor targ;
Actor spit;
bool isdefault = false;
// shoot a cube at current target
targ = spstate.GetNextInList("BossTarget", G_SkillProperty(SKILLP_EasyBossBrain));
if (targ)
{
if (spawntype == null)
{
spawntype = "SpawnShot";
isdefault = true;
}
// spawn brain missile
spit = SpawnMissile (targ, spawntype);
if (spit)
{
// Boss cubes should move freely to their destination so it's
// probably best to disable all collision detection for them.
spit.bNoInteraction = spit.bNoClip;
spit.target = targ;
spit.master = self;
// [RH] Do this correctly for any trajectory. Doom would divide by 0
// if the target had the same y coordinate as the spitter.
if (spit.Vel.xy == (0, 0))
{
spit.special2 = 0;
}
else if (abs(spit.Vel.y) > fabs(spit.Vel.x))
{
spit.special2 = int((targ.pos.y - pos.y) / spit.Vel.y);
}
else
{
spit.special2 = int((targ.pos.x - pos.x) / spit.Vel.y);
}
// [GZ] Calculates when the projectile will have reached destination
spit.special2 += level.maptime;
spit.bBossCube = true;
}
if (!isdefault)
{
A_PlaySound(self.AttackSound, CHAN_WEAPON);
}
else
{
// compatibility fallback
A_PlaySound("brain/spit", CHAN_WEAPON);
}
}
}
*/
/*
private void SpawnFly(class<Actor> spawntype, sound snd)
{
AActor newmobj;
AActor fog;
AActor eye = master; // The eye is the spawnshot's master, not the target!
AActor targ = target; // Unlike other projectiles, the target is the intended destination.
int r;
// [GZ] Should be more viable than a countdown...
if (special2 != 0)
{
if (special2 > level.maptime)
return; // still flying
}
else
{
if (reactiontime == 0 || --reactiontime != 0)
return; // still flying
}
if (spawntype)
{
fog = Spawn (spawntype, targ.pos, ALLOW_REPLACE);
if (fog) A_PlaySound(snd, CHAN_BODY);
}
class<Actor> SpawnName;
DropItem di; // di will be our drop item list iterator
DropItem drop; // while drop stays as the reference point.
int n = 0;
// First see if this cube has its own actor list
drop = GetDropItems();
// If not, then default back to its master's list
if (drop == null && eye != null)
drop = eye.GetDropItems();
if (drop != null)
{
for (di = drop; di != null; di = di.Next)
{
if (di.Name != 'None')
{
if (di.Amount < 0)
{
di.Amount = 1; // default value is -1, we need a positive value.
}
n += di.Amount; // this is how we can weight the list.
}
}
di = drop;
n = randon[pr_spawnfly](0, n);
while (n >= 0)
{
if (di.Name != 'none')
{
n -= di.Amount; // logically, none of the -1 values have survived by now.
}
if ((di.Next != null) && (n >= 0))
{
di = di.Next;
}
else
{
n = -1;
}
}
SpawnName = di.Name;
}
if (SpawnName == null)
{
// Randomly select monster to spawn.
r = random[pr_spawnfly](0, 255);
// Probability distribution (kind of :),
// decreasing likelihood.
if (r < 50) SpawnName = "DoomImp";
else if (r < 90) SpawnName = "Demon";
else if (r < 120) SpawnName = "Spectre";
else if (r < 130) SpawnName = "PainElemental";
else if (r < 160) SpawnName = "Cacodemon";
else if (r < 162) SpawnName = "Archvile";
else if (r < 172) SpawnName = "Revenant";
else if (r < 192) SpawnName = "Arachnotron";
else if (r < 222) SpawnName = "Fatso";
else if (r < 246) SpawnName = "HellKnight";
else SpawnName = "BaronOfHell";
}
if (spawnname != null)
{
newmobj = Spawn (spawnname, targ.pos, ALLOW_REPLACE);
if (newmobj != null)
{
// Make the new monster hate what the boss eye hates
if (eye != null)
{
newmobj.CopyFriendliness (eye, false);
}
// Make it act as if it was around when the player first made noise
// (if the player has made noise).
newmobj.LastHeard = newmobj.Sector.SoundTarget;
if (newmobj.SeeState != null && newmobj.LookForPlayers (true, null))
{
newmobj.SetState (newmobj.SeeState);
}
if (!(newmobj.ObjectFlags & OF_EuthanizeMe))
{
// telefrag anything in this spot
newmobj.TeleportMove (newmobj.pos, true);
}
newmobj.bBossSpawned = true;
}
}
// remove self (i.e., cube).
Destroy ();
}
*/
native
void A_SpawnFly(class<Actor> spawntype = null) // needs special treatment for default
;
/*
{
sound snd;
if (spawntype != null)
{
snd = GetDefaultByType(spawntype).SeeSound;
}
else
{
spawntype = "SpawnFire";
snd = "brain/spawn";
}
SpawnFly(self, spawntype, snd);
}
*/
native
void A_SpawnSound()
;
/*
{
// travelling cube sound
A_PlaySound("brain/cube", CHAN_BODY);
SpawnFly("SpawnFire", "brain/spawn");
}
*/
}