From af34d82888ba4a362902d5e03635c5d1a6ae436f Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 20 Nov 2016 00:25:38 +0100 Subject: [PATCH] - scriptified A_Saw. - implemented multiple-return-value assignment. Due to some grammar conflicts the originally intended Lua-inspired syntax of 'a, b = Function()' could not be done, so it's '[a, b] = Function()' --- src/g_doom/a_doomweaps.cpp | 155 ---------------- src/g_shared/a_armor.cpp | 16 +- src/g_shared/a_pickups.cpp | 35 +++- src/g_shared/a_pickups.h | 1 + src/p_mobj.cpp | 6 + src/scripting/codegeneration/codegen.cpp | 150 ++++++++++++++-- src/scripting/codegeneration/codegen.h | 38 +++- src/scripting/zscript/zcc-parse.lemon | 9 +- src/scripting/zscript/zcc_compile.cpp | 31 +++- wadsrc/static/zscript.txt | 1 + wadsrc/static/zscript/actor.txt | 4 + wadsrc/static/zscript/doom/doomweapons.txt | 41 ----- wadsrc/static/zscript/doom/weaponchainsaw.txt | 166 ++++++++++++++++++ wadsrc/static/zscript/shared/inventory.txt | 4 +- 14 files changed, 426 insertions(+), 231 deletions(-) create mode 100644 wadsrc/static/zscript/doom/weaponchainsaw.txt diff --git a/src/g_doom/a_doomweaps.cpp b/src/g_doom/a_doomweaps.cpp index f7a73a4ce..e802a6103 100644 --- a/src/g_doom/a_doomweaps.cpp +++ b/src/g_doom/a_doomweaps.cpp @@ -16,167 +16,12 @@ */ void P_SetSafeFlash(AWeapon *weapon, player_t *player, FState *flashstate, int index); -static FRandom pr_saw ("Saw"); static FRandom pr_fireshotgun2 ("FireSG2"); static FRandom pr_fireplasma ("FirePlasma"); static FRandom pr_firerail ("FireRail"); static FRandom pr_bfgspray ("BFGSpray"); static FRandom pr_oldbfg ("OldBFG"); -// -// A_Saw -// -enum SAW_Flags -{ - SF_NORANDOM = 1, - SF_RANDOMLIGHTMISS = 2, - SF_RANDOMLIGHTHIT = 4, - SF_NOUSEAMMOMISS = 8, - SF_NOUSEAMMO = 16, - SF_NOPULLIN = 32, - SF_NOTURN = 64, - SF_STEALARMOR = 128, -}; - - -DEFINE_ACTION_FUNCTION(AActor, A_Saw) -{ - PARAM_ACTION_PROLOGUE(AActor); - PARAM_SOUND_DEF (fullsound) - PARAM_SOUND_DEF (hitsound) - PARAM_INT_DEF (damage) - PARAM_CLASS_DEF (pufftype, AActor) - PARAM_INT_DEF (flags) - PARAM_FLOAT_DEF (range) - PARAM_ANGLE_DEF (spread_xy) - PARAM_ANGLE_DEF (spread_z) - PARAM_FLOAT_DEF (lifesteal) - PARAM_INT_DEF (lifestealmax) - PARAM_CLASS_DEF (armorbonustype, ABasicArmorBonus) - - DAngle angle; - DAngle slope; - player_t *player; - FTranslatedLineTarget t; - int actualdamage; - - if (NULL == (player = self->player)) - { - return 0; - } - - if (pufftype == NULL) - { - pufftype = PClass::FindActor(NAME_BulletPuff); - } - if (damage == 0) - { - damage = 2; - } - if (!(flags & SF_NORANDOM)) - { - damage *= (pr_saw()%10+1); - } - if (range == 0) - { - range = SAWRANGE; - } - - angle = self->Angles.Yaw + spread_xy * (pr_saw.Random2() / 255.); - slope = P_AimLineAttack (self, angle, range, &t) + spread_z * (pr_saw.Random2() / 255.); - - AWeapon *weapon = self->player->ReadyWeapon; - if ((weapon != NULL) && !(flags & SF_NOUSEAMMO) && !(!t.linetarget && (flags & SF_NOUSEAMMOMISS)) && !(weapon->WeaponFlags & WIF_DEHAMMO) && ACTION_CALL_FROM_PSPRITE()) - { - if (!weapon->DepleteAmmo (weapon->bAltFire)) - return 0; - } - - P_LineAttack (self, angle, range, slope, damage, NAME_Melee, pufftype, false, &t, &actualdamage); - - if (!t.linetarget) - { - if ((flags & SF_RANDOMLIGHTMISS) && (pr_saw() > 64)) - { - player->extralight = !player->extralight; - } - S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM); - return 0; - } - - if (flags & SF_RANDOMLIGHTHIT) - { - int randVal = pr_saw(); - if (randVal < 64) - { - player->extralight = 0; - } - else if (randVal < 160) - { - player->extralight = 1; - } - else - { - player->extralight = 2; - } - } - - if (lifesteal && !(t.linetarget->flags5 & MF5_DONTDRAIN)) - { - if (flags & SF_STEALARMOR) - { - if (armorbonustype == NULL) - { - armorbonustype = dyn_cast(PClass::FindClass("ArmorBonus")); - } - if (armorbonustype != NULL) - { - assert(armorbonustype->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus))); - ABasicArmorBonus *armorbonus = static_cast(Spawn(armorbonustype)); - armorbonus->SaveAmount = int(armorbonus->SaveAmount * actualdamage * lifesteal); - armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax; - armorbonus->flags |= MF_DROPPED; - armorbonus->ClearCounters(); - - if (!armorbonus->CallTryPickup (self)) - { - armorbonus->Destroy (); - } - } - } - - else - { - P_GiveBody (self, int(actualdamage * lifesteal), lifestealmax); - } - } - - S_Sound (self, CHAN_WEAPON, hitsound, 1, ATTN_NORM); - - // turn to face target - if (!(flags & SF_NOTURN)) - { - DAngle anglediff = deltaangle(self->Angles.Yaw, t.angleFromSource); - - if (anglediff < 0.0) - { - if (anglediff < -4.5) - self->Angles.Yaw = angle + 90.0 / 21; - else - self->Angles.Yaw -= 4.5; - } - else - { - if (anglediff > 4.5) - self->Angles.Yaw = angle - 90.0 / 21; - else - self->Angles.Yaw += 4.5; - } - } - if (!(flags & SF_NOPULLIN)) - self->flags |= MF_JUSTATTACKED; - return 0; -} // // A_FireShotgun2 diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp index 636441a9d..c05cc2c6a 100644 --- a/src/g_shared/a_armor.cpp +++ b/src/g_shared/a_armor.cpp @@ -7,11 +7,12 @@ #include "g_level.h" #include "d_player.h" #include "serializer.h" +#include "cmdlib.h" IMPLEMENT_CLASS(AArmor, false, false, false, false) IMPLEMENT_CLASS(ABasicArmor, false, false, false, false) IMPLEMENT_CLASS(ABasicArmorPickup, false, false, false, false) -IMPLEMENT_CLASS(ABasicArmorBonus, false, false, false, false) +IMPLEMENT_CLASS(ABasicArmorBonus, false, false, true, false) IMPLEMENT_CLASS(AHexenArmor, false, false, false, false) //=========================================================================== @@ -278,6 +279,19 @@ bool ABasicArmorPickup::Use (bool pickup) return true; } +//=========================================================================== +// +// ABasicArmorBonus :: InitNativeFields +// +//=========================================================================== + +void ABasicArmorBonus::InitNativeFields() +{ + auto meta = RUNTIME_CLASS(ABasicArmorBonus); + meta->AddNativeField("SaveAmount", TypeSInt32, myoffsetof(ABasicArmorBonus, SaveAmount)); + meta->AddNativeField("MaxSaveAmount", TypeSInt32, myoffsetof(ABasicArmorBonus, MaxSaveAmount)); +} + //=========================================================================== // // ABasicArmorBonus :: Serialize diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index fbc5ea509..a3abe7cf4 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -344,6 +344,14 @@ bool P_GiveBody (AActor *actor, int num, int max) return false; } +DEFINE_ACTION_FUNCTION(AActor, GiveBody) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_INT(num); + PARAM_INT_DEF(max); + ACTION_RETURN_BOOL(P_GiveBody(self, num, max)); +} + //--------------------------------------------------------------------------- // // PROC A_RestoreSpecialThing1 @@ -352,12 +360,12 @@ bool P_GiveBody (AActor *actor, int num, int max) // //--------------------------------------------------------------------------- -DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialThing1) +DEFINE_ACTION_FUNCTION(AInventory, A_RestoreSpecialThing1) { - PARAM_SELF_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AInventory); self->renderflags &= ~RF_INVISIBLE; - if (static_cast(self)->DoRespawn ()) + if (self->DoRespawn ()) { S_Sound (self, CHAN_VOICE, "misc/spawn", 1, ATTN_IDLE); } @@ -370,9 +378,9 @@ DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialThing1) // //--------------------------------------------------------------------------- -DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialThing2) +DEFINE_ACTION_FUNCTION(AInventory, A_RestoreSpecialThing2) { - PARAM_SELF_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AInventory); self->flags |= MF_SPECIAL; if (!(self->GetDefault()->flags & MF_NOGRAVITY)) @@ -390,9 +398,9 @@ DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialThing2) // //--------------------------------------------------------------------------- -DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialDoomThing) +DEFINE_ACTION_FUNCTION(AInventory, A_RestoreSpecialDoomThing) { - PARAM_SELF_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AInventory); self->renderflags &= ~RF_INVISIBLE; self->flags |= MF_SPECIAL; @@ -400,7 +408,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialDoomThing) { self->flags &= ~MF_NOGRAVITY; } - if (static_cast(self)->DoRespawn ()) + if (self->DoRespawn ()) { self->SetState (self->SpawnState); S_Sound (self, CHAN_VOICE, "misc/spawn", 1, ATTN_IDLE); @@ -1547,6 +1555,17 @@ bool AInventory::CallTryPickup (AActor *toucher, AActor **toucher_return) return res; } +DEFINE_ACTION_FUNCTION(AInventory, CallTryPickup) +{ + PARAM_SELF_PROLOGUE(AInventory); + PARAM_OBJECT(toucher, AActor); + AActor *t_ret; + bool res = self->CallTryPickup(toucher, &t_ret); + if (numret > 0) ret[0].SetInt(res); + if (numret > 1) ret[1].SetPointer(t_ret, ATAG_OBJECT), numret = 2; + return numret; +} + //=========================================================================== // diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h index e50c7f3d8..9e1caaf5b 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -500,6 +500,7 @@ public: class ABasicArmorBonus : public AArmor { DECLARE_CLASS (ABasicArmorBonus, AArmor) + HAS_FIELDS public: virtual void Serialize(FSerializer &arc); diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 09d850af5..f28ea4b5f 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -6884,6 +6884,12 @@ void AActor::ClearCounters() } } +DEFINE_ACTION_FUNCTION(AActor, ClearCounters) +{ + PARAM_SELF_PROLOGUE(AActor); + self->ClearCounters(); + return 0; +} int AActor::ApplyDamageFactor(FName damagetype, int damage) const { diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index e38b6bd6b..12acc438b 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -2331,6 +2331,95 @@ ExpEmit FxAssignSelf::Emit(VMFunctionBuilder *build) } } + +//========================================================================== +// +// +// +//========================================================================== + +FxMultiAssign::FxMultiAssign(FArgumentList &base, FxExpression *right, const FScriptPosition &pos) + :FxExpression(EFX_MultiAssign, pos) +{ + Base = std::move(base); + Right = right; + LocalVarContainer = new FxCompoundStatement(ScriptPosition); +} + +//========================================================================== +// +// +// +//========================================================================== + +FxMultiAssign::~FxMultiAssign() +{ + SAFE_DELETE(Right); + SAFE_DELETE(LocalVarContainer); +} + +//========================================================================== +// +// +// +//========================================================================== + +FxExpression *FxMultiAssign::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE(Right, ctx); + if (Right->ExprType != EFX_VMFunctionCall) + { + Right->ScriptPosition.Message(MSG_ERROR, "Function call expected on right side of multi-assigment"); + delete this; + return nullptr; + } + auto VMRight = static_cast(Right); + auto rets = VMRight->GetReturnTypes(); + if (rets.Size() < Base.Size()) + { + Right->ScriptPosition.Message(MSG_ERROR, "Insufficient returns in function %s", VMRight->Function->SymbolName.GetChars()); + delete this; + return nullptr; + } + // Pack the generated data (temp local variables for the results and necessary type casts and single assignments) into a compound statement for easier management. + for (unsigned i = 0; i < Base.Size(); i++) + { + auto singlevar = new FxLocalVariableDeclaration(rets[i], NAME_None, nullptr, 0, ScriptPosition); + LocalVarContainer->Add(singlevar); + Base[i] = Base[i]->Resolve(ctx); + ABORT(Base[i]); + auto varaccess = new FxLocalVariable(singlevar, ScriptPosition); + auto assignee = new FxTypeCast(varaccess, Base[i]->ValueType, false); + LocalVarContainer->Add(new FxAssign(Base[i], assignee, false)); + } + auto x = LocalVarContainer->Resolve(ctx); + LocalVarContainer = nullptr; + ABORT(x); + LocalVarContainer = static_cast(x); + static_cast(Right)->AssignCount = Base.Size(); + ValueType = TypeVoid; + return this; +} + +//========================================================================== +// +// +// +//========================================================================== + +ExpEmit FxMultiAssign::Emit(VMFunctionBuilder *build) +{ + Right->Emit(build); + for (unsigned i = 0; i < Base.Size(); i++) + { + LocalVarContainer->LocalVars[i]->SetReg(static_cast(Right)->ReturnRegs[i]); + } + static_cast(Right)->ReturnRegs.Clear(); + static_cast(Right)->ReturnRegs.ShrinkToFit(); + return LocalVarContainer->Emit(build); +} + //========================================================================== // // @@ -5398,6 +5487,13 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx) } } + if (noglobal) + { + // This is needed to properly resolve class names on the left side of the member access operator + ValueType = TypeError; + return this; + } + // now check the global identifiers. if (newex == nullptr && (sym = ctx.FindGlobal(Identifier)) != nullptr) { @@ -6710,11 +6806,20 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) bool staticonly = false; bool novirtual = false; + PClass *ccls = nullptr; + if (Self->ExprType == EFX_Identifier) { + ccls = PClass::FindClass(static_cast(Self)->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. - PClass *ccls = PClass::FindClass(static_cast(Self)->Identifier); + if (ccls != nullptr)static_cast(Self)->noglobal = true; + } + + SAFE_RESOLVE(Self, ctx); + + if (Self->ValueType == TypeError) + { if (ccls != nullptr) { if (ccls->bExported) @@ -6729,7 +6834,6 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) // Todo: static struct members need to work as well. } } - SAFE_RESOLVE(Self, ctx); if (Self->ExprType == EFX_Super) { @@ -7282,10 +7386,8 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) } else if (vmfunc->Proto->ReturnTypes.Size() > 0) { // Call, expecting one result - ExpEmit reg(build, vmfunc->Proto->ReturnTypes[0]->GetRegType(), vmfunc->Proto->ReturnTypes[0]->GetRegCount()); - build->Emit(OP_CALL_K, funcaddr, count, 1); - build->Emit(OP_RESULT, 0, EncodeRegType(reg), reg.RegNum); - return reg; + build->Emit(OP_CALL_K, funcaddr, count, MAX(1, AssignCount)); + goto handlereturns; } else { // Call, expecting no results @@ -7307,18 +7409,34 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) } else if (vmfunc->Proto->ReturnTypes.Size() > 0) { // Call, expecting one result - ExpEmit reg(build, vmfunc->Proto->ReturnTypes[0]->GetRegType(), vmfunc->Proto->ReturnTypes[0]->GetRegCount()); - build->Emit(OP_CALL, funcreg.RegNum, count, 1); - build->Emit(OP_RESULT, 0, EncodeRegType(reg), reg.RegNum); - return reg; + build->Emit(OP_CALL, funcreg.RegNum, count, MAX(1, AssignCount)); + goto handlereturns; } else { // Call, expecting no results build->Emit(OP_CALL, funcreg.RegNum, count, 0); return ExpEmit(); } - - + } +handlereturns: + if (AssignCount == 0) + { + // Regular call, will not write to ReturnRegs + ExpEmit reg(build, vmfunc->Proto->ReturnTypes[0]->GetRegType(), vmfunc->Proto->ReturnTypes[0]->GetRegCount()); + build->Emit(OP_RESULT, 0, EncodeRegType(reg), reg.RegNum); + return reg; + } + else + { + // Multi-Assignment call, this must fill in the ReturnRegs array so that the multi-assignment operator can dispatch the return values. + assert((unsigned)AssignCount <= vmfunc->Proto->ReturnTypes.Size()); + for (int i = 0; i < AssignCount; i++) + { + ExpEmit reg(build, vmfunc->Proto->ReturnTypes[i]->GetRegType(), vmfunc->Proto->ReturnTypes[i]->GetRegCount()); + build->Emit(OP_RESULT, 0, EncodeRegType(reg), reg.RegNum); + ReturnRegs.Push(reg); + } + return ExpEmit(); } } @@ -9018,13 +9136,19 @@ FxExpression *FxLocalVariableDeclaration::Resolve(FCompileContext &ctx) return this; } +void FxLocalVariableDeclaration::SetReg(ExpEmit emit) +{ + assert(ValueType->GetRegType() == emit.RegType && ValueType->GetRegCount() == emit.RegCount); + RegNum = emit.RegNum; +} + ExpEmit FxLocalVariableDeclaration::Emit(VMFunctionBuilder *build) { if (ValueType->RegType != REGT_NIL) { if (Init == nullptr) { - RegNum = build->Registers[ValueType->GetRegType()].Get(RegCount); + if (RegNum == -1) RegNum = build->Registers[ValueType->GetRegType()].Get(RegCount); } else { diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index 5ad351d6b..03b256970 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -68,6 +68,7 @@ struct FScriptPosition; class FxLoopStatement; class FxCompoundStatement; class FxLocalVariableDeclaration; +typedef TDeletingArray FArgumentList; struct FCompileContext { @@ -280,6 +281,7 @@ enum EFxType EFX_GlobalVariable, EFX_Super, EFX_StackVariable, + EFX_MultiAssign, EFX_COUNT }; @@ -343,6 +345,7 @@ class FxIdentifier : public FxExpression { public: FName Identifier; + bool noglobal = false; FxIdentifier(FName i, const FScriptPosition &p); FxExpression *Resolve(FCompileContext&); @@ -782,6 +785,28 @@ public: ExpEmit Address; }; +//========================================================================== +// +// FxAssign +// +//========================================================================== +class FxCompoundStatement; + +class FxMultiAssign : public FxExpression +{ + FxCompoundStatement *LocalVarContainer; // for handling the temporary variables of the results, which may need type casts. + FArgumentList Base; + FxExpression *Right; + bool AddressRequested = false; + bool AddressWritable = false; + +public: + FxMultiAssign(FArgumentList &base, FxExpression *right, const FScriptPosition &pos); + ~FxMultiAssign(); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); +}; + //========================================================================== // // FxAssignSelf @@ -1344,8 +1369,6 @@ public: // //========================================================================== -typedef TDeletingArray FArgumentList; - class FxFunctionCall : public FxExpression { FName MethodName; @@ -1448,11 +1471,16 @@ public: class FxVMFunctionCall : public FxExpression { + friend class FxMultiAssign; + bool EmitTail; bool NoVirtual; FxExpression *Self; PFunction *Function; FArgumentList ArgList; + // for multi assignment + int AssignCount = 0; + TArray ReturnRegs; public: FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual); @@ -1462,6 +1490,10 @@ public: VMFunction *GetDirectFunction(); ExpEmit Emit(VMFunctionBuilder *build); bool CheckEmitCast(VMFunctionBuilder *build, bool returnit, ExpEmit ®); + TArray &GetReturnTypes() const + { + return Function->Variants[0].Proto->ReturnTypes; + } }; //========================================================================== @@ -1497,6 +1529,7 @@ class FxCompoundStatement : public FxSequence FxCompoundStatement *Outer = nullptr; friend class FxLocalVariableDeclaration; + friend class FxMultiAssign; public: FxCompoundStatement(const FScriptPosition &pos) : FxSequence(pos) {} @@ -1822,6 +1855,7 @@ public: FxExpression *Resolve(FCompileContext&); ExpEmit Emit(VMFunctionBuilder *build); void Release(VMFunctionBuilder *build); + void SetReg(ExpEmit reginfo); }; diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index 2d3605532..8bd9686e3 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -1556,7 +1556,7 @@ statement(X) ::= expression_statement(A) SEMICOLON. { X = A; /*X-overwrites-A*/ statement(X) ::= selection_statement(X). statement(X) ::= iteration_statement(X). statement(X) ::= jump_statement(X). -//statement(X) ::= assign_statement(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } +statement(X) ::= assign_statement(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } statement(X) ::= local_var(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } statement(X) ::= error SEMICOLON. { X = NULL; } @@ -1767,11 +1767,11 @@ labeled_statement(X) ::= DEFAULT(T) COLON. /*----- Assignment Statements -----*/ -/* This is no longer being used, in favor of handling assignments as expressions, just like C and C++ do. - Keep this here in case some other parts require assignment syntax or Lua-style multi-assignments become a thing. %type assign_statement{ZCC_AssignStmt *} -assign_statement(X) ::= expr_list(A) EQ expr_list(B). [EQ] +// The grammar won't let this pass without some syntactic help. +// Parentheses and braces aren't accepted either so brackets are the only way to get this through the parser without a conflict. +assign_statement(X) ::= LBRACKET expr_list(A) RBRACKET EQ expr(B). [EQ] { NEW_AST_NODE(AssignStmt,stmt,A); stmt->AssignOp = ZCC_EQ; @@ -1779,7 +1779,6 @@ assign_statement(X) ::= expr_list(A) EQ expr_list(B). [EQ] stmt->Sources = B; X = stmt; } -*/ /*----- Local Variable Definition "Statements" -----*/ diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index e50e8b822..7c78b3c1e 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2262,10 +2262,18 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool if (!(f->Flags & ZCC_Native)) { - auto code = ConvertAST(c->Type(), f->Body); - if (code != nullptr) + if (f->Body == nullptr) { - sym->Variants[0].Implementation = FunctionBuildList.AddFunction(sym, code, FStringf("%s.%s", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()), false, -1, 0, Lump); + Error(f, "Empty function %s", FName(f->Name).GetChars()); + return; + } + else + { + auto code = ConvertAST(c->Type(), f->Body); + if (code != nullptr) + { + sym->Variants[0].Implementation = FunctionBuildList.AddFunction(sym, code, FStringf("%s.%s", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()), false, -1, 0, Lump); + } } } if (sym->Variants[0].Implementation != nullptr && hasdefault) // do not copy empty default lists, they only waste space and processing time. @@ -3149,7 +3157,7 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast) case AST_CaseStmt: { auto cases = static_cast(ast); - return new FxCaseStatement(ConvertNode(cases->Condition), *ast); + return new FxCaseStatement(ConvertNode(cases->Condition), *ast); } case AST_CompoundStmt: @@ -3164,6 +3172,21 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast) } while (node != compound->Content); return x; } + + case AST_AssignStmt: + { + auto ass = static_cast(ast); + FArgumentList args; + ConvertNodeList(args, ass->Dests); + assert(ass->Sources->SiblingNext == ass->Sources); // right side should be a single function call - nothing else + if (ass->Sources->NodeType != AST_ExprFuncCall) + { + // don't let this through to the code generator. This node is only used to assign multiple returns of a function to more than one variable. + Error(ass, "Right side of multi-assignment must be a function call"); + return new FxNop(*ast); // allow compiler to continue looking for errors. + } + return new FxMultiAssign(args, ConvertNode(ass->Sources), *ast); + } } // only for development. I_Error is more convenient here than a normal error. I_Error("ConvertNode encountered unsupported node of type %d", ast->NodeType); diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 9244b9bdb..6832c70f0 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -52,6 +52,7 @@ zscript/doom/weaponfist.txt zscript/doom/weaponpistol.txt zscript/doom/weaponshotgun.txt zscript/doom/weaponchaingun.txt +zscript/doom/weaponchainsaw.txt zscript/doom/deadthings.txt zscript/doom/doomammo.txt diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 0fd6d2282..7f845472a 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -71,6 +71,9 @@ class Actor : Thinker native native void RemoveFromHash(); native string GetTag(string defstr = ""); native float GetBobOffset(float frac = 0); + native void ClearCounters(); + native bool GiveBody (int num, int max=0); + native void SetDamage(int dmg); native static bool isDehState(state st); native double Distance2D(Actor other); @@ -469,6 +472,7 @@ class Actor : Thinker native native int A_Explode(int damage = -1, int distance = -1, int flags = XF_HURTSOURCE, bool alert = false, int fulldamagedistance = 0, int nails = 0, int naildamage = 10, class pufftype = "BulletPuff", name damagetype = "none"); native void A_Stop(); native void A_Respawn(int flags = 1); + native void A_RestoreSpecialPosition(); native void A_QueueCorpse(); native void A_DeQueueCorpse(); native void A_ClearLastHeard(); diff --git a/wadsrc/static/zscript/doom/doomweapons.txt b/wadsrc/static/zscript/doom/doomweapons.txt index ee7a821ea..d7eb835ce 100644 --- a/wadsrc/static/zscript/doom/doomweapons.txt +++ b/wadsrc/static/zscript/doom/doomweapons.txt @@ -13,47 +13,6 @@ class DoomWeapon : Weapon } -// -------------------------------------------------------------------------- -// -// Chainsaw -// -// -------------------------------------------------------------------------- - -class Chainsaw : Weapon -{ - Default - { - Weapon.Kickback 0; - Weapon.SelectionOrder 2200; - Weapon.UpSound "weapons/sawup"; - Weapon.ReadySound "weapons/sawidle"; - Inventory.PickupMessage "$GOTCHAINSAW"; - Obituary "$OB_MPCHAINSAW"; - Tag "$TAG_CHAINSAW"; - +WEAPON.MELEEWEAPON - } - States - { - Ready: - SAWG CD 4 A_WeaponReady; - Loop; - Deselect: - SAWG C 1 A_Lower; - Loop; - Select: - SAWG C 1 A_Raise; - Loop; - Fire: - SAWG AB 4 A_Saw; - SAWG B 0 A_ReFire; - Goto Ready; - Spawn: - CSAW A -1; - Stop; - } -} - - // -------------------------------------------------------------------------- // // Shotgun diff --git a/wadsrc/static/zscript/doom/weaponchainsaw.txt b/wadsrc/static/zscript/doom/weaponchainsaw.txt new file mode 100644 index 000000000..b2583efa5 --- /dev/null +++ b/wadsrc/static/zscript/doom/weaponchainsaw.txt @@ -0,0 +1,166 @@ +// -------------------------------------------------------------------------- +// +// Chainsaw +// +// -------------------------------------------------------------------------- + +class Chainsaw : Weapon +{ + Default + { + Weapon.Kickback 0; + Weapon.SelectionOrder 2200; + Weapon.UpSound "weapons/sawup"; + Weapon.ReadySound "weapons/sawidle"; + Inventory.PickupMessage "$GOTCHAINSAW"; + Obituary "$OB_MPCHAINSAW"; + Tag "$TAG_CHAINSAW"; + +WEAPON.MELEEWEAPON + } + States + { + Ready: + SAWG CD 4 A_WeaponReady; + Loop; + Deselect: + SAWG C 1 A_Lower; + Loop; + Select: + SAWG C 1 A_Raise; + Loop; + Fire: + SAWG AB 4 A_Saw; + SAWG B 0 A_ReFire; + Goto Ready; + Spawn: + CSAW A -1; + Stop; + } +} + + +extend class StateProvider +{ + action void A_Saw(sound fullsound = "weapons/sawfull", sound hitsound = "weapons/sawhit", int damage = 2, class pufftype = "BulletPuff", int flags = 0, float range = 0, float spread_xy = 2.8125, float spread_z = 0, float lifesteal = 0, int lifestealmax = 0, class armorbonustype = "ArmorBonus") + { + FTranslatedLineTarget t; + + if (player == null) + { + return; + } + + if (pufftype == null) + { + pufftype = 'BulletPuff'; + } + if (damage == 0) + { + damage = 2; + } + if (!(flags & SF_NORANDOM)) + { + damage *= random[Saw](1, 10); + } + if (range == 0) + { + range = SAWRANGE; + } + + double ang = angle + spread_xy * (Random2[Saw]() / 255.); + double slope = AimLineAttack (ang, range, t) + spread_z * (Random2[Saw]() / 255.); + + Weapon weap = player.ReadyWeapon; + if (weap != null && !(flags & SF_NOUSEAMMO) && !(!t.linetarget && (flags & SF_NOUSEAMMOMISS)) && !weap.bDehAmmo && + invoker == weap && stateinfo != null && stateinfo.mStateType == STATE_Psprite) + { + if (!weap.DepleteAmmo (weap.bAltFire)) + return; + } + + Actor puff; + int actualdamage; + [puff, actualdamage] = LineAttack (ang, range, slope, damage, 'Melee', pufftype, false, t); + + if (!t.linetarget) + { + if ((flags & SF_RANDOMLIGHTMISS) && (Random[Saw]() > 64)) + { + player.extralight = !player.extralight; + } + A_PlaySound (fullsound, CHAN_WEAPON); + return; + } + + if (flags & SF_RANDOMLIGHTHIT) + { + int randVal = Random[Saw](); + if (randVal < 64) + { + player.extralight = 0; + } + else if (randVal < 160) + { + player.extralight = 1; + } + else + { + player.extralight = 2; + } + } + + if (lifesteal && !t.linetarget.bDontDrain) + { + if (flags & SF_STEALARMOR) + { + if (armorbonustype == null) + { + armorbonustype = "ArmorBonus"; + } + if (armorbonustype != null) + { + BasicArmorBonus armorbonus = BasicArmorBonus(Spawn(armorbonustype)); + armorbonus.SaveAmount = int(armorbonus.SaveAmount * actualdamage * lifesteal); + armorbonus.MaxSaveAmount = lifestealmax <= 0 ? armorbonus.MaxSaveAmount : lifestealmax; + armorbonus.bDropped = true; + armorbonus.ClearCounters(); + + if (!armorbonus.CallTryPickup (self)) + { + armorbonus.Destroy (); + } + } + } + + else + { + GiveBody (int(actualdamage * lifesteal), lifestealmax); + } + } + + A_PlaySound (hitsound, CHAN_WEAPON); + + // turn to face target + if (!(flags & SF_NOTURN)) + { + double anglediff = deltaangle(angle, t.angleFromSource); + + if (anglediff < 0.0) + { + if (anglediff < -4.5) + angle = ang + 90.0 / 21; + else + angle -= 4.5; + } + else + { + if (anglediff > 4.5) + angle = ang - 90.0 / 21; + else + angle += 4.5; + } + } + if (!(flags & SF_NOPULLIN)) + bJustAttacked = true; + } +} diff --git a/wadsrc/static/zscript/shared/inventory.txt b/wadsrc/static/zscript/shared/inventory.txt index 64c649ad6..67bb840dd 100644 --- a/wadsrc/static/zscript/shared/inventory.txt +++ b/wadsrc/static/zscript/shared/inventory.txt @@ -11,10 +11,11 @@ class Inventory : Actor native } // These are regular functions for the item itself. - private native void A_RestoreSpecialPosition(); private native void A_RestoreSpecialDoomThing(); private native void A_RestoreSpecialThing1(); private native void A_RestoreSpecialThing2(); + + native bool, Actor CallTryPickup(Actor toucher); States(Actor, Overlay, Weapon, Item) { @@ -70,7 +71,6 @@ class StateProvider : Inventory native action native void A_CheckReload(); action native void A_GunFlash(statelabel flash = null, int flags = 0); action native void A_FireAssaultGun(); - action native void A_Saw(sound fullsound = "weapons/sawfull", sound hitsound = "weapons/sawhit", int damage = 2, class pufftype = "BulletPuff", int flags = 0, float range = 0, float spread_xy = 2.8125, float spread_z = 0, float lifesteal = 0, int lifestealmax = 0, class armorbonustype = "ArmorBonus"); action native state A_CheckForReload(int counter, statelabel label, bool dontincrement = false); action native void A_ResetReloadCounter(); }