diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp index cc699ac47..896e174d6 100644 --- a/src/d_dehacked.cpp +++ b/src/d_dehacked.cpp @@ -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); diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index 7eb152ebd..57a67384a 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -2737,6 +2737,7 @@ PClass::PClass() HashNext = NULL; Defaults = NULL; bRuntimeClass = false; + bExported = false; ConstructNative = NULL; mDescriptiveName = "Class"; diff --git a/src/dobjtype.h b/src/dobjtype.h index 302352b21..5aeefac50 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -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 *); diff --git a/src/dthinker.cpp b/src/dthinker.cpp index 114d260df..022e147da 100644 --- a/src/dthinker.cpp +++ b/src/dthinker.cpp @@ -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; diff --git a/src/g_doom/a_bossbrain.cpp b/src/g_doom/a_bossbrain.cpp index 1b50cb3cc..f58e75375 100644 --- a/src/g_doom/a_bossbrain.cpp +++ b/src/g_doom/a_bossbrain.cpp @@ -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 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); diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 26987e622..f597b89c2 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -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 diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index 66a7d8b41..fa5405c60 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -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(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(left)->GetValue().GetInt() == 0; + break; + + case REGT_FLOAT: + assert(left->ValueType->GetRegCount() == 1); // vectors should not be able to get here. + leftisnull = static_cast(left)->GetValue().GetFloat() == 0; + break; + + case REGT_POINTER: + leftisnull = static_cast(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(right)->GetValue().GetInt() == 0; + break; + + case REGT_FLOAT: + assert(right->ValueType->GetRegCount() == 1); // vectors should not be able to get here. + rightisnull = static_cast(right)->GetValue().GetFloat() == 0; + break; + + case REGT_POINTER: + rightisnull = static_cast(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 &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(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(Self->ValueType)->ClassRestriction; - staticonly = true; - } - else if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PPointer))) + if (Self->ValueType->IsKindOf(RUNTIME_CLASS(PPointer))) { auto ptype = static_cast(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, TArrayIsActionFunc) + if (build->NumImplicits == NAP) { build->Emit(OP_PARAM, 0, REGT_POINTER, 1); // pass stateowner } diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index b3a5e4297..cfe261b39 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -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 diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 6c47fabe0..c623290da 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -61,6 +61,39 @@ static TArray 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))); } diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index ca7aa6d54..811fb82e0 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -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 diff --git a/src/scripting/vm/vmbuilder.cpp b/src/scripting/vm/vmbuilder.cpp index afadaaa1e..d2d9e7140 100644 --- a/src/scripting/vm/vmbuilder.cpp +++ b/src/scripting/vm/vmbuilder.cpp @@ -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; } //========================================================================== diff --git a/src/scripting/vm/vmbuilder.h b/src/scripting/vm/vmbuilder.h index dcaae7251..9a97c216d 100644 --- a/src/scripting/vm/vmbuilder.h +++ b/src/scripting/vm/vmbuilder.h @@ -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 diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 10b85ef86..500c30c66 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -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): diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 9e8b84163..98d847ebe 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -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()); diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 83ea76cb7..213ca8b25 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -53,6 +53,8 @@ class Actor : Thinker native return GetPointer(ptr_select1) == GetPointer(ptr_select2); } + native static /*readonly*/ Actor GetDefaultByType(class 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 spawntype = null); // needs special treatment for default - native void A_SpawnSound(); - native void A_SpawnFly(class 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); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index bda63f059..cca39f1e2 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -5,4 +5,27 @@ class Object native class Thinker : Object native { -} \ No newline at end of file +} + +class ThinkerIterator : Object native +{ + enum EStatnums + { + MAX_STATNUM = 127 + } + + native static ThinkerIterator Create(class 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; + */ +} + diff --git a/wadsrc/static/zscript/doom/bossbrain.txt b/wadsrc/static/zscript/doom/bossbrain.txt index 533d0fa01..2193be954 100644 --- a/wadsrc/static/zscript/doom/bossbrain.txt +++ b/wadsrc/static/zscript/doom/bossbrain.txt @@ -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 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 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 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 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"); + } + */ +}