diff --git a/src/actor.h b/src/actor.h index c21270af6..d1acd9ea7 100644 --- a/src/actor.h +++ b/src/actor.h @@ -382,6 +382,7 @@ enum ActorFlag7 MF7_ALLOWTHRUFLAGS = 0x00400000, // [MC] Allow THRUACTORS and the likes on puffs to prevent mod breakage. MF7_USEKILLSCRIPTS = 0x00800000, // [JM] Use "KILL" Script on death if not forced by GameInfo. MF7_NOKILLSCRIPTS = 0x01000000, // [JM] No "KILL" Script on death whatsoever, even if forced by GameInfo. + MF7_SPRITEANGLE = 0x02000000, // [MC] Utilize the SpriteAngle property and lock the rotation to the degrees specified. }; // --- mobj.renderflags --- @@ -978,6 +979,8 @@ public: DVector3 __Pos; // double underscores so that it won't get used by accident. Access to this should be exclusively through the designated access functions. DVector3 OldRenderPos; + DAngle SpriteAngle; + DAngle SpriteRotation; DRotator Angles; DVector3 Vel; double Speed; diff --git a/src/namedef.h b/src/namedef.h index 17fa9e291..cd0d5615b 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -214,6 +214,7 @@ xx(Brainexplode) // Weapon animator names. xx(Select) xx(Deselect) +xx(DeadLowered) xx(Ready) xx(Fire) xx(Hold) diff --git a/src/p_local.h b/src/p_local.h index 4b8b40a85..ebbaeace5 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -161,7 +161,7 @@ PClassActor *P_GetSpawnableType(int spawnnum); void InitSpawnablesFromMapinfo(); int P_Thing_CheckInputNum(player_t *p, int inputnum); int P_Thing_Warp(AActor *caller, AActor *reference, double xofs, double yofs, double zofs, DAngle angle, int flags, double heightoffset, double radiusoffset, DAngle pitch); -bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, int count, int flags, int ptr); +int P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, int count, int flags, int ptr, bool counting = false); enum { diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index be875b951..94df4c3bf 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -383,6 +383,11 @@ void AActor::Serialize(FArchive &arc) << RipLevelMin << RipLevelMax; arc << DefThreshold; + if (SaveVersion >= 4549) + { + arc << SpriteAngle; + arc << SpriteRotation; + } { FString tagstr; diff --git a/src/p_pspr.cpp b/src/p_pspr.cpp index 350acc3fd..48ead3699 100644 --- a/src/p_pspr.cpp +++ b/src/p_pspr.cpp @@ -1087,7 +1087,7 @@ DEFINE_ACTION_FUNCTION(AInventory, A_Lower) { // Player is dead, so don't bring up a pending weapon // Player is dead, so keep the weapon off screen P_SetPsprite(player, PSP_FLASH, nullptr); - psp->SetState(nullptr); + psp->SetState(player->ReadyWeapon->FindState(NAME_DeadLowered)); return 0; } // [RH] Clear the flash state. Only needed for Strife. diff --git a/src/p_things.cpp b/src/p_things.cpp index 6f01715ad..d94ba6a01 100644 --- a/src/p_things.cpp +++ b/src/p_things.cpp @@ -696,16 +696,16 @@ int P_Thing_CheckInputNum(player_t *p, int inputnum) } return renum; } -bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, int count, int flags, int ptr) +int P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, int count, int flags, int ptr, bool counting) { AActor *ref = COPY_AAPTR(self, ptr); // We need these to check out. if (!ref || !classname || distance <= 0) - return false; + return 0; int counter = 0; - bool result = false; + int result = 0; double closer = distance, farther = 0, current = distance; const bool ptrWillChange = !!(flags & (CPXF_SETTARGET | CPXF_SETMASTER | CPXF_SETTRACER)); const bool ptrDistPref = !!(flags & (CPXF_CLOSEST | CPXF_FARTHEST)); @@ -740,7 +740,7 @@ bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, in if ((ref->Distance2D(mo) < distance && ((flags & CPXF_NOZ) || ((ref->Z() > mo->Z() && ref->Z() - mo->Top() < distance) || - (ref->Z() <= mo->Z() && mo->Z() - ref->Top() < distance))))) + (ref->Z() <= mo->Z() && mo->Z() - ref->Top() < distance))))) { if ((flags & CPXF_CHECKSIGHT) && !(P_CheckSight(mo, ref, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY))) continue; @@ -766,19 +766,19 @@ bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, in { if (!(flags & (CPXF_COUNTDEAD | CPXF_DEADONLY))) continue; - counter++; } else { if (flags & CPXF_DEADONLY) continue; - counter++; } + counter++; // Abort if the number of matching classes nearby is greater, we have obviously succeeded in our goal. - if (counter > count) - { - result = (flags & (CPXF_LESSOREQUAL | CPXF_EXACT)) ? false : true; + // Don't abort if calling the counting version CheckProximity non-action function. + if (!counting && counter > count) + { + result = (flags & (CPXF_LESSOREQUAL | CPXF_EXACT)) ? 0 : 1; // However, if we have one SET* flag and either the closest or farthest flags, keep the function going. if (ptrWillChange && ptrDistPref) @@ -805,12 +805,14 @@ bool P_Thing_CheckProximity(AActor *self, PClass *classname, double distance, in } } - if (counter == count) - result = true; - else if (counter < count) - result = !!((flags & CPXF_LESSOREQUAL) && !(flags & CPXF_EXACT)); - - return result; + if (!counting) + { + if (counter == count) + result = 1; + else if (counter < count) + result = !!((flags & CPXF_LESSOREQUAL) && !(flags & CPXF_EXACT)) ? 1 : 0; + } + return counting ? counter : result; } int P_Thing_Warp(AActor *caller, AActor *reference, double xofs, double yofs, double zofs, DAngle angle, int flags, double heightoffset, double radiusoffset, DAngle pitch) diff --git a/src/r_things.cpp b/src/r_things.cpp index 3d2133398..78d72fbc8 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -809,11 +809,17 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor angle_t rot; if (sprframe->Texture[0] == sprframe->Texture[1]) { - rot = (ang - thing->Angles.Yaw + 45.0/2*9).BAMs() >> 28; + if (thing->flags7 & MF7_SPRITEANGLE) + rot = (thing->SpriteAngle + 45.0 / 2 * 9).BAMs() >> 28; + else + rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + 45.0 / 2 * 9).BAMs() >> 28; } else { - rot = (ang - thing->Angles.Yaw + (45.0/2*9-180.0/16)).BAMs() >> 28; + if (thing->flags7 & MF7_SPRITEANGLE) + rot = (thing->SpriteAngle + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; + else + rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; } picnum = sprframe->Texture[rot]; if (sprframe->Flip & (1 << rot)) @@ -848,11 +854,17 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor angle_t rot; if (sprframe->Texture[0] == sprframe->Texture[1]) { - rot = (ang - thing->Angles.Yaw + 45.0 / 2 * 9).BAMs() >> 28; + if (thing->flags7 & MF7_SPRITEANGLE) + rot = (thing->SpriteAngle + 45.0 / 2 * 9).BAMs() >> 28; + else + rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + 45.0 / 2 * 9).BAMs() >> 28; } else { - rot = (ang - thing->Angles.Yaw + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; + if (thing->flags7 & MF7_SPRITEANGLE) + rot = (thing->SpriteAngle + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; + else + rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; } picnum = sprframe->Texture[rot]; if (sprframe->Flip & (1 << rot)) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index d1933e142..16f9e2482 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -395,6 +395,64 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetGibHealth) return 0; } +//========================================================================== +// +// GetSpriteAngle +// +// NON-ACTION function returns the sprite angle of a pointer. +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetSpriteAngle) +{ + if (numret > 0) + { + assert(ret != NULL); + PARAM_SELF_PROLOGUE(AActor); + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *target = COPY_AAPTR(self, ptr); + if (target == nullptr) + { + ret->SetFloat(0.0); + } + else + { + const double ang = target->SpriteAngle.Degrees; + ret->SetFloat(ang); + } + return 1; + } + return 0; +} + +//========================================================================== +// +// GetSpriteRotation +// +// NON-ACTION function returns the sprite rotation of a pointer. +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetSpriteRotation) +{ + if (numret > 0) + { + assert(ret != NULL); + PARAM_SELF_PROLOGUE(AActor); + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *target = COPY_AAPTR(self, ptr); + if (target == nullptr) + { + ret->SetFloat(0.0); + } + else + { + const double ang = target->SpriteRotation.Degrees; + ret->SetFloat(ang); + } + return 1; + } + return 0; +} + //========================================================================== // // GetZAt @@ -585,6 +643,38 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, GetPlayerInput) return 0; } +//========================================================================== +// +// CountProximity +// +// NON-ACTION function of A_CheckProximity that returns how much it counts. +// Takes a pointer as anyone may or may not be a player. +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, CountProximity) +{ + if (numret > 0) + { + PARAM_SELF_PROLOGUE(AActor); + PARAM_CLASS(classname, AActor); + PARAM_FLOAT(distance); + PARAM_INT_OPT(flags) { flags = 0; } + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *mobj = COPY_AAPTR(self, ptr); + if (mobj == nullptr) + { + ret->SetInt(0); + } + else + { + ret->SetInt(P_Thing_CheckProximity(self, classname, distance, 0, flags, ptr, true)); + } + return 1; + } + return 0; +} + //=========================================================================== // // __decorate_internal_state__ @@ -7202,7 +7292,7 @@ enum CPSFFlags DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopySpriteFrame) { - PARAM_SELF_PROLOGUE(AActor); + PARAM_ACTION_PROLOGUE; PARAM_INT(from); PARAM_INT(to); PARAM_INT_OPT(flags) { flags = 0; } @@ -7219,3 +7309,49 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopySpriteFrame) if (!(flags & CPSF_NOFRAME)) copyto->frame = copyfrom->frame; ACTION_RETURN_BOOL(true); } + +//========================================================================== +// +// A_SetSpriteAngle(angle, ptr) +// +// Specifies which angle the actor must always draw its sprite from. +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpriteAngle) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FLOAT_OPT(angle) { angle = 0.; } + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (mobj == nullptr) + { + ACTION_RETURN_BOOL(false); + } + mobj->SpriteAngle = angle; + ACTION_RETURN_BOOL(true); +} + +//========================================================================== +// +// A_SetSpriteRotation(angle, ptr) +// +// Specifies how much to fake a sprite rotation. +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpriteRotation) +{ + PARAM_ACTION_PROLOGUE; + PARAM_ANGLE_OPT(angle) { angle = 0.; } + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *mobj = COPY_AAPTR(self, ptr); + + if (mobj == nullptr) + { + ACTION_RETURN_BOOL(false); + } + mobj->SpriteRotation = angle; + ACTION_RETURN_BOOL(true); +} diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp index 0768f80c8..3b114fbee 100644 --- a/src/thingdef/thingdef_data.cpp +++ b/src/thingdef/thingdef_data.cpp @@ -259,6 +259,7 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(MF7, ALLOWTHRUFLAGS, AActor, flags7), DEFINE_FLAG(MF7, USEKILLSCRIPTS, AActor, flags7), DEFINE_FLAG(MF7, NOKILLSCRIPTS, AActor, flags7), + DEFINE_FLAG(MF7, SPRITEANGLE, AActor, flags7), // Effect flags DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects), diff --git a/src/thingdef/thingdef_exp.h b/src/thingdef/thingdef_exp.h index 58820ea15..b878e50f1 100644 --- a/src/thingdef/thingdef_exp.h +++ b/src/thingdef/thingdef_exp.h @@ -48,8 +48,10 @@ #define RESOLVE(p,c) if (p!=NULL) p = p->Resolve(c) #define ABORT(p) if (!(p)) { delete this; return NULL; } #define SAFE_RESOLVE(p,c) RESOLVE(p,c); ABORT(p) +#define SAFE_RESOLVE_OPT(p,c) if (p!=NULL) { SAFE_RESOLVE(p,c) } class VMFunctionBuilder; +class FxJumpStatement; //========================================================================== // @@ -59,21 +61,15 @@ class VMFunctionBuilder; struct FCompileContext { - PClassActor *cls; + TArray Jumps; + PClassActor *Class; - FCompileContext(PClassActor *_cls = NULL) - { - cls = _cls; - } + FCompileContext(PClassActor *cls = nullptr); - PSymbol *FindInClass(FName identifier) - { - return cls ? cls->Symbols.FindSymbol(identifier, true) : NULL; - } - PSymbol *FindGlobal(FName identifier) - { - return GlobalSymbols.FindSymbol(identifier, true); - } + PSymbol *FindInClass(FName identifier); + PSymbol *FindGlobal(FName identifier); + + void HandleJumps(int token, FxExpression *handler); }; //========================================================================== @@ -212,6 +208,8 @@ public: virtual ExpEmit Emit(VMFunctionBuilder *build); + TArray JumpAddresses; + FScriptPosition ScriptPosition; PType *ValueType; @@ -930,6 +928,80 @@ public: ExpEmit Emit(VMFunctionBuilder *build); }; +//========================================================================== +// +// FxWhileLoop +// +//========================================================================== + +class FxWhileLoop : public FxExpression +{ + FxExpression *Condition; + FxExpression *Code; + +public: + FxWhileLoop(FxExpression *condition, FxExpression *code, const FScriptPosition &pos); + ~FxWhileLoop(); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// FxDoWhileLoop +// +//========================================================================== + +class FxDoWhileLoop : public FxExpression +{ + FxExpression *Condition; + FxExpression *Code; + +public: + FxDoWhileLoop(FxExpression *condition, FxExpression *code, const FScriptPosition &pos); + ~FxDoWhileLoop(); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// FxForLoop +// +//========================================================================== + +class FxForLoop : public FxExpression +{ + FxExpression *Init; + FxExpression *Condition; + FxExpression *Iteration; + FxExpression *Code; + +public: + FxForLoop(FxExpression *init, FxExpression *condition, FxExpression *iteration, FxExpression *code, const FScriptPosition &pos); + ~FxForLoop(); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// FxJumpStatement +// +//========================================================================== + +class FxJumpStatement : public FxExpression +{ +public: + FxJumpStatement(int token, const FScriptPosition &pos); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); + + int Token; + size_t Address; + FxExpression *AddressResolver; +}; + //========================================================================== // // FxReturnStatement diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index b4a0b3fda..5017ae421 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -85,6 +85,45 @@ static const FLOP FxFlops[] = { NAME_TanH, FLOP_TANH, [](double v) { return g_tanh(v); } }, }; +//========================================================================== +// +// FCompileContext +// +//========================================================================== + +FCompileContext::FCompileContext(PClassActor *cls) : Class(cls) +{ +} + +PSymbol *FCompileContext::FindInClass(FName identifier) +{ + return Class ? Class->Symbols.FindSymbol(identifier, true) : nullptr; +} +PSymbol *FCompileContext::FindGlobal(FName identifier) +{ + return GlobalSymbols.FindSymbol(identifier, true); +} + +void FCompileContext::HandleJumps(int token, FxExpression *handler) +{ + for (unsigned int i = 0; i < Jumps.Size(); i++) + { + if (Jumps[i]->Token == token) + { + Jumps[i]->AddressResolver = handler; + handler->JumpAddresses.Push(Jumps[i]); + Jumps.Delete(i); + i--; + } + } +} + +//========================================================================== +// +// ExpEmit +// +//========================================================================== + ExpEmit::ExpEmit(VMFunctionBuilder *build, int type) : RegNum(build->Registers[type].Get(1)), RegType(type), Konst(false), Fixed(false) { @@ -2838,14 +2877,14 @@ FxSelf::FxSelf(const FScriptPosition &pos) FxExpression *FxSelf::Resolve(FCompileContext& ctx) { CHECKRESOLVED(); - if (!ctx.cls) + if (!ctx.Class) { // can't really happen with DECORATE's expression evaluator. ScriptPosition.Message(MSG_ERROR, "self used outside of a member function"); delete this; return NULL; } - ValueType = ctx.cls; + ValueType = ctx.Class; ValueType = NewPointer(RUNTIME_CLASS(DObject)); return this; } @@ -3818,6 +3857,369 @@ ExpEmit FxIfStatement::Emit(VMFunctionBuilder *build) return ExpEmit(); } +//========================================================================== +// +// FxWhileLoop +// +//========================================================================== + +FxWhileLoop::FxWhileLoop(FxExpression *condition, FxExpression *code, const FScriptPosition &pos) +: FxExpression(pos), Condition(condition), Code(code) +{ + ValueType = TypeVoid; +} + +FxWhileLoop::~FxWhileLoop() +{ + SAFE_DELETE(Condition); + SAFE_DELETE(Code); +} + +FxExpression *FxWhileLoop::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE(Condition, ctx); + SAFE_RESOLVE_OPT(Code, ctx); + + ctx.HandleJumps(TK_Break, this); + ctx.HandleJumps(TK_Continue, this); + + if (Condition->ValueType != TypeBool) + { + Condition = new FxBoolCast(Condition); + SAFE_RESOLVE(Condition, ctx); + } + + if (Condition->isConstant()) + { + if (static_cast(Condition)->GetValue().GetBool() == false) + { // Nothing happens + FxExpression *nop = new FxNop(ScriptPosition); + delete this; + return nop; + } + else if (Code == nullptr) + { // "while (true) { }" + // Someone could be using this for testing. + ScriptPosition.Message(MSG_WARNING, "Infinite empty loop"); + } + } + + return this; +} + +ExpEmit FxWhileLoop::Emit(VMFunctionBuilder *build) +{ + assert(Condition->ValueType == TypeBool); + + size_t loopstart, loopend; + size_t jumpspot; + + // Evaluate the condition and execute/break out of the loop. + loopstart = build->GetAddress(); + if (!Condition->isConstant()) + { + ExpEmit cond = Condition->Emit(build); + build->Emit(OP_TEST, cond.RegNum, 0); + jumpspot = build->Emit(OP_JMP, 0); + cond.Free(build); + } + else assert(static_cast(Condition)->GetValue().GetBool() == true); + + // Execute the loop's content. + if (Code != nullptr) + { + ExpEmit code = Code->Emit(build); + code.Free(build); + } + + // Loop back. + build->Backpatch(build->Emit(OP_JMP, 0), loopstart); + loopend = build->GetAddress(); + + if (!Condition->isConstant()) + { + build->Backpatch(jumpspot, loopend); + } + + // Give a proper address to any break/continue statement within this loop. + for (unsigned int i = 0; i < JumpAddresses.Size(); i++) + { + if (JumpAddresses[i]->Token == TK_Break) + { + build->Backpatch(JumpAddresses[i]->Address, loopend); + } + else + { // Continue statement. + build->Backpatch(JumpAddresses[i]->Address, loopstart); + } + } + + return ExpEmit(); +} + +//========================================================================== +// +// FxDoWhileLoop +// +//========================================================================== + +FxDoWhileLoop::FxDoWhileLoop(FxExpression *condition, FxExpression *code, const FScriptPosition &pos) +: FxExpression(pos), Condition(condition), Code(code) +{ + ValueType = TypeVoid; +} + +FxDoWhileLoop::~FxDoWhileLoop() +{ + SAFE_DELETE(Condition); + SAFE_DELETE(Code); +} + +FxExpression *FxDoWhileLoop::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE(Condition, ctx); + SAFE_RESOLVE_OPT(Code, ctx); + + ctx.HandleJumps(TK_Break, this); + ctx.HandleJumps(TK_Continue, this); + + if (Condition->ValueType != TypeBool) + { + Condition = new FxBoolCast(Condition); + SAFE_RESOLVE(Condition, ctx); + } + + if (Condition->isConstant()) + { + if (static_cast(Condition)->GetValue().GetBool() == false) + { // The code executes once, if any. + if (JumpAddresses.Size() == 0) + { // We would still have to handle the jumps however. + FxExpression *e = Code; + if (e == nullptr) e = new FxNop(ScriptPosition); + Code = nullptr; + delete this; + return e; + } + } + else if (Code == nullptr) + { // "do { } while (true);" + // Someone could be using this for testing. + ScriptPosition.Message(MSG_WARNING, "Infinite empty loop"); + } + } + + return this; +} + +ExpEmit FxDoWhileLoop::Emit(VMFunctionBuilder *build) +{ + assert(Condition->ValueType == TypeBool); + + size_t loopstart, loopend; + size_t codestart; + + // Execute the loop's content. + codestart = build->GetAddress(); + if (Code != nullptr) + { + ExpEmit code = Code->Emit(build); + code.Free(build); + } + + // Evaluate the condition and execute/break out of the loop. + loopstart = build->GetAddress(); + if (!Condition->isConstant()) + { + ExpEmit cond = Condition->Emit(build); + build->Emit(OP_TEST, cond.RegNum, 1); + cond.Free(build); + build->Backpatch(build->Emit(OP_JMP, 0), codestart); + } + else if (static_cast(Condition)->GetValue().GetBool() == true) + { // Always looping + build->Backpatch(build->Emit(OP_JMP, 0), codestart); + } + loopend = build->GetAddress(); + + // Give a proper address to any break/continue statement within this loop. + for (unsigned int i = 0; i < JumpAddresses.Size(); i++) + { + if (JumpAddresses[i]->Token == TK_Break) + { + build->Backpatch(JumpAddresses[i]->Address, loopend); + } + else + { // Continue statement. + build->Backpatch(JumpAddresses[i]->Address, loopstart); + } + } + + return ExpEmit(); +} + +//========================================================================== +// +// FxForLoop +// +//========================================================================== + +FxForLoop::FxForLoop(FxExpression *init, FxExpression *condition, FxExpression *iteration, FxExpression *code, const FScriptPosition &pos) +: FxExpression(pos), Init(init), Condition(condition), Iteration(iteration), Code(code) +{ + ValueType = TypeVoid; +} + +FxForLoop::~FxForLoop() +{ + SAFE_DELETE(Init); + SAFE_DELETE(Condition); + SAFE_DELETE(Iteration); + SAFE_DELETE(Code); +} + +FxExpression *FxForLoop::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE_OPT(Init, ctx); + SAFE_RESOLVE_OPT(Condition, ctx); + SAFE_RESOLVE_OPT(Iteration, ctx); + SAFE_RESOLVE_OPT(Code, ctx); + + ctx.HandleJumps(TK_Break, this); + ctx.HandleJumps(TK_Continue, this); + + if (Condition != nullptr) + { + if (Condition->ValueType != TypeBool) + { + Condition = new FxBoolCast(Condition); + SAFE_RESOLVE(Condition, ctx); + } + + if (Condition->isConstant()) + { + if (static_cast(Condition)->GetValue().GetBool() == false) + { // Nothing happens + FxExpression *nop = new FxNop(ScriptPosition); + delete this; + return nop; + } + else + { // "for (..; true; ..)" + delete Condition; + Condition = nullptr; + } + } + } + if (Condition == nullptr && Code == nullptr) + { // "for (..; ; ..) { }" + // Someone could be using this for testing. + ScriptPosition.Message(MSG_WARNING, "Infinite empty loop"); + } + + return this; +} + +ExpEmit FxForLoop::Emit(VMFunctionBuilder *build) +{ + assert((Condition && Condition->ValueType == TypeBool && !Condition->isConstant()) || Condition == nullptr); + + size_t loopstart, loopend; + size_t codestart; + size_t jumpspot; + + // Init statement. + if (Init != nullptr) + { + ExpEmit init = Init->Emit(build); + init.Free(build); + } + + // Evaluate the condition and execute/break out of the loop. + codestart = build->GetAddress(); + if (Condition != nullptr) + { + ExpEmit cond = Condition->Emit(build); + build->Emit(OP_TEST, cond.RegNum, 0); + cond.Free(build); + jumpspot = build->Emit(OP_JMP, 0); + } + + // Execute the loop's content. + if (Code != nullptr) + { + ExpEmit code = Code->Emit(build); + code.Free(build); + } + + // Iteration statement. + loopstart = build->GetAddress(); + if (Iteration != nullptr) + { + ExpEmit iter = Iteration->Emit(build); + iter.Free(build); + } + build->Backpatch(build->Emit(OP_JMP, 0), codestart); + + // End of loop. + loopend = build->GetAddress(); + if (Condition != nullptr) + { + build->Backpatch(jumpspot, loopend); + } + + // Give a proper address to any break/continue statement within this loop. + for (unsigned int i = 0; i < JumpAddresses.Size(); i++) + { + if (JumpAddresses[i]->Token == TK_Break) + { + build->Backpatch(JumpAddresses[i]->Address, loopend); + } + else + { // Continue statement. + build->Backpatch(JumpAddresses[i]->Address, loopstart); + } + } + + return ExpEmit(); +} + +//========================================================================== +// +// FxJumpStatement +// +//========================================================================== + +FxJumpStatement::FxJumpStatement(int token, const FScriptPosition &pos) +: FxExpression(pos), Token(token), AddressResolver(nullptr) +{ + ValueType = TypeVoid; +} + +FxExpression *FxJumpStatement::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + + ctx.Jumps.Push(this); + + return this; +} + +ExpEmit FxJumpStatement::Emit(VMFunctionBuilder *build) +{ + if (AddressResolver == nullptr) + { + ScriptPosition.Message(MSG_ERROR, "Jump statement %s has nowhere to go!", FScanner::TokenName(Token)); + } + + Address = build->Emit(OP_JMP, 0); + + return ExpEmit(); +} + //========================================================================== // //========================================================================== @@ -4008,19 +4410,19 @@ ExpEmit FxClassTypeCast::Emit(VMFunctionBuilder *build) FxExpression *FxStateByIndex::Resolve(FCompileContext &ctx) { CHECKRESOLVED(); - if (ctx.cls->NumOwnedStates == 0) + if (ctx.Class->NumOwnedStates == 0) { // This can't really happen assert(false); } - if (ctx.cls->NumOwnedStates <= index) + if (ctx.Class->NumOwnedStates <= index) { ScriptPosition.Message(MSG_ERROR, "%s: Attempt to jump to non existing state index %d", - ctx.cls->TypeName.GetChars(), index); + ctx.Class->TypeName.GetChars(), index); delete this; return NULL; } - FxExpression *x = new FxConstant(ctx.cls->OwnedStates + index, ScriptPosition); + FxExpression *x = new FxConstant(ctx.Class->OwnedStates + index, ScriptPosition); delete this; return x; } @@ -4068,7 +4470,7 @@ FxExpression *FxMultiNameState::Resolve(FCompileContext &ctx) } else if (names[0] == NAME_Super) { - scope = dyn_cast(ctx.cls->ParentClass); + scope = dyn_cast(ctx.Class->ParentClass); } else { @@ -4079,9 +4481,9 @@ FxExpression *FxMultiNameState::Resolve(FCompileContext &ctx) delete this; return NULL; } - else if (!scope->IsDescendantOf(ctx.cls)) + else if (!scope->IsDescendantOf(ctx.Class)) { - ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(),ctx.cls->TypeName.GetChars()); + ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(),ctx.Class->TypeName.GetChars()); delete this; return NULL; } diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index db6ef14a0..372f444cd 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -1345,6 +1345,24 @@ DEFINE_PROPERTY(gravity, F, Actor) defaults->Gravity = i; } +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(spriteangle, F, Actor) +{ + PROP_DOUBLE_PARM(i, 0); + defaults->SpriteAngle = i; +} + +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(spriterotation, F, Actor) +{ + PROP_DOUBLE_PARM(i, 0); + defaults->SpriteRotation = i; +} + //========================================================================== // //========================================================================== diff --git a/src/thingdef/thingdef_states.cpp b/src/thingdef/thingdef_states.cpp index ae1805119..ecc62bf29 100644 --- a/src/thingdef/thingdef_states.cpp +++ b/src/thingdef/thingdef_states.cpp @@ -480,6 +480,93 @@ static FxExpression *ParseIf(FScanner &sc, FState state, FString statestring, Ba return add; } +static FxExpression *ParseWhile(FScanner &sc, FState state, FString statestring, Baggage &bag, + PPrototype *&retproto, bool &lastwasret) +{ + FxExpression *cond, *code; + PPrototype *proto; + bool ret; + + sc.MustGetStringName("("); + cond = ParseExpression(sc, bag.Info); + sc.MustGetStringName(")"); + sc.MustGetStringName("{"); // Enforce braces like for if statements. + + code = ParseActions(sc, state, statestring, bag, proto, ret); + sc.MustGetString(); + + retproto = ReturnCheck(retproto, proto, sc); + lastwasret = false; // A while loop always jumps back. + + return new FxWhileLoop(cond, code, sc); +} + +static FxExpression *ParseDoWhile(FScanner &sc, FState state, FString statestring, Baggage &bag, + PPrototype *&retproto, bool &lastwasret) +{ + FxExpression *cond, *code; + PPrototype *proto; + bool ret; + + sc.MustGetStringName("{"); // Enforce braces like for if statements. + code = ParseActions(sc, state, statestring, bag, proto, ret); + + sc.MustGetStringName("while"); + sc.MustGetStringName("("); + cond = ParseExpression(sc, bag.Info); + sc.MustGetStringName(")"); + sc.MustGetStringName(";"); + sc.MustGetString(); + + retproto = ReturnCheck(retproto, proto, sc); + lastwasret = false; + + return new FxDoWhileLoop(cond, code, sc); +} + +static FxExpression *ParseFor(FScanner &sc, FState state, FString statestring, Baggage &bag, + PPrototype *&retproto, bool &lastwasret) +{ + FxExpression *init = nullptr; + FxExpression *cond = nullptr; + FxExpression *iter = nullptr; + FxExpression *code = nullptr; + PPrototype *proto; + bool ret; + + // Parse the statements. + sc.MustGetStringName("("); + sc.MustGetString(); + if (!sc.Compare(";")) + { + init = ParseAction(sc, state, statestring, bag); // That's all DECORATE can handle for now. + sc.MustGetStringName(";"); + } + sc.MustGetString(); + if (!sc.Compare(";")) + { + sc.UnGet(); + cond = ParseExpression(sc, bag.Info); + sc.MustGetStringName(";"); + } + sc.MustGetString(); + if (!sc.Compare(")")) + { + iter = ParseAction(sc, state, statestring, bag); + sc.MustGetStringName(")"); + } + + // Now parse the loop's content. + sc.MustGetStringName("{"); // Enforce braces like for if statements. + code = ParseActions(sc, state, statestring, bag, proto, ret); + sc.MustGetString(); + + retproto = ReturnCheck(retproto, proto, sc); + lastwasret = false; + + return new FxForLoop(init, cond, iter, code, sc); +} + FxExpression *ParseActions(FScanner &sc, FState state, FString statestring, Baggage &bag, PPrototype *&retproto, bool &endswithret) { @@ -505,9 +592,21 @@ FxExpression *ParseActions(FScanner &sc, FState state, FString statestring, Bagg FxExpression *add; lastwasret = false; if (sc.Compare("if")) - { // Hangle an if statement + { // Handle an if statement add = ParseIf(sc, state, statestring, bag, proto, lastwasret); } + else if (sc.Compare("while")) + { // Handle a while loop + add = ParseWhile(sc, state, statestring, bag, proto, lastwasret); + } + else if (sc.Compare("do")) + { // Handle a do-while loop + add = ParseDoWhile(sc, state, statestring, bag, proto, lastwasret); + } + else if (sc.Compare("for")) + { // Handle a for loop + add = ParseFor(sc, state, statestring, bag, proto, lastwasret); + } else if (sc.Compare("return")) { // Handle a return statement lastwasret = true; @@ -529,6 +628,18 @@ FxExpression *ParseActions(FScanner &sc, FState state, FString statestring, Bagg sc.MustGetString(); add = new FxReturnStatement(retexp, sc); } + else if (sc.Compare("break")) + { + add = new FxJumpStatement(TK_Break, sc); + sc.MustGetStringName(";"); + sc.MustGetString(); + } + else if (sc.Compare("continue")) + { + add = new FxJumpStatement(TK_Continue, sc); + sc.MustGetStringName(";"); + sc.MustGetString(); + } else { // Handle a regular action function call add = ParseAction(sc, state, statestring, bag); diff --git a/src/version.h b/src/version.h index 8404ebeec..8859cbe7d 100644 --- a/src/version.h +++ b/src/version.h @@ -76,7 +76,7 @@ const char *GetVersionString(); // Use 4500 as the base git save version, since it's higher than the // SVN revision ever got. -#define SAVEVER 4548 +#define SAVEVER 4549 #define SAVEVERSTRINGIFY2(x) #x #define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x) diff --git a/src/zscript/vmbuilder.cpp b/src/zscript/vmbuilder.cpp index 2a4f6b1a8..eb49ffb14 100644 --- a/src/zscript/vmbuilder.cpp +++ b/src/zscript/vmbuilder.cpp @@ -436,6 +436,17 @@ bool VMFunctionBuilder::RegAvailability::Reuse(int reg) return true; } +//========================================================================== +// +// VMFunctionBuilder :: GetAddress +// +//========================================================================== + +size_t VMFunctionBuilder::GetAddress() +{ + return Code.Size(); +} + //========================================================================== // // VMFunctionBuilder :: Emit diff --git a/src/zscript/vmbuilder.h b/src/zscript/vmbuilder.h index 89b626c18..2d8721acc 100644 --- a/src/zscript/vmbuilder.h +++ b/src/zscript/vmbuilder.h @@ -34,6 +34,9 @@ public: int GetConstantAddress(void *ptr, VM_ATAG tag); int GetConstantString(FString str); + // Returns the address of the next instruction to be emitted. + size_t GetAddress(); + // Returns the address of the newly-emitted instruction. size_t Emit(int opcode, int opa, int opb, int opc); size_t Emit(int opcode, int opa, VM_SHALF opbc); diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index 77250b3df..868925c3d 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -35,6 +35,8 @@ ACTOR Actor native //: Thinker BloodType "Blood", "BloodSplatter", "AxeBlood" ExplosionDamage 128 MissileHeight 32 + SpriteAngle 0 + SpriteRotation 0 // Functions native bool CheckClass(class checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false); @@ -48,6 +50,9 @@ ACTOR Actor native //: Thinker native float GetCrouchFactor(int ptr = AAPTR_PLAYER1); native float GetCVar(string cvar); native int GetPlayerInput(int inputnum, int ptr = AAPTR_DEFAULT); + native int CountProximity(class classname, float distance, int flags = 0, int ptr = AAPTR_DEFAULT); + native float GetSpriteAngle(int ptr = AAPTR_DEFAULT); + native float GetSpriteRotation(int ptr = AAPTR_DEFAULT); // Action functions // Meh, MBF redundant functions. Only for DeHackEd support. @@ -331,6 +336,8 @@ ACTOR Actor native //: Thinker action native bool A_FaceMovementDirection(float offset = 0, float anglelimit = 0, float pitchlimit = 0, int flags = 0, int ptr = AAPTR_DEFAULT); action native int A_ClearOverlays(int sstart = 0, int sstop = 0, bool safety = true); action native bool A_CopySpriteFrame(int from, int to, int flags = 0); + action native bool A_SetSpriteAngle(float angle = 0, int ptr = AAPTR_DEFAULT); + action native bool A_SetSpriteRotation(float angle = 0, int ptr = AAPTR_DEFAULT); native void A_RearrangePointers(int newtarget, int newmaster = AAPTR_DEFAULT, int newtracer = AAPTR_DEFAULT, int flags=0); native void A_TransferPointer(int ptr_source, int ptr_recepient, int sourcefield, int recepientfield=AAPTR_DEFAULT, int flags=0);