diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index 88c31d0078..62a6a2b2d2 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -3545,6 +3545,16 @@ const PClass *PClass::NativeClass() const return cls; } +VMFunction *PClass::FindFunction(FName clsname, FName funcname) +{ + auto cls = PClass::FindActor(clsname); + if (!cls) return nullptr; + auto func = dyn_cast(cls->Symbols.FindSymbol(funcname, true)); + if (!func) return nullptr; + return func->Variants[0].Implementation; +} + + /* FTypeTable **************************************************************/ //========================================================================== diff --git a/src/dobjtype.h b/src/dobjtype.h index b952a3c232..89d3091564 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -859,6 +859,7 @@ public: static PClassActor *FindActor(const FString &name) { return FindActor(FName(name, true)); } static PClassActor *FindActor(ENamedName name) { return FindActor(FName(name)); } static PClassActor *FindActor(FName name); + static VMFunction *FindFunction(FName cls, FName func); PClass *FindClassTentative(FName name); static TArray AllClasses; diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index 2172d2a0cc..cb5f4dd121 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -589,6 +589,28 @@ bool AInventory::SpecialDropAction (AActor *dropper) return false; } +DEFINE_ACTION_FUNCTION(AInventory, SpecialDropAction) +{ + PARAM_SELF_PROLOGUE(AInventory); + PARAM_OBJECT(dropper, AActor); + ACTION_RETURN_BOOL(self->SpecialDropAction(dropper)); +} + +bool AInventory::CallSpecialDropAction(AActor *dropper) +{ + IFVIRTUAL(AInventory, SpecialDropAction) + { + VMValue params[2] = { (DObject*)this, (DObject*)dropper }; + VMReturn ret; + VMFrameStack stack; + int retval; + ret.IntAt(&retval); + stack.Call(func, params, 2, &ret, 1, nullptr); + return !!retval; + } + return SpecialDropAction(dropper); +} + //=========================================================================== // // AInventory :: ShouldRespawn diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h index 429b2ab905..e15f0724c9 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -168,6 +168,7 @@ public: bool CallTryPickup (AActor *toucher, AActor **toucher_return = NULL); virtual void DoPickupSpecial (AActor *toucher); virtual bool SpecialDropAction (AActor *dropper); + bool CallSpecialDropAction(AActor *dropper); virtual bool DrawPowerup (int x, int y); virtual void DoEffect (); virtual bool Grind(bool items); diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp index b3aecb0b54..f78c610404 100644 --- a/src/g_shared/sbarinfo_commands.cpp +++ b/src/g_shared/sbarinfo_commands.cpp @@ -268,7 +268,7 @@ class CommandDrawImage : public SBarInfoCommandFlowControl } else if(type == SIGIL) { - AInventory *item = statusBar->CPlayer->mo->FindInventory(); + AInventory *item = statusBar->CPlayer->mo->FindInventory(PClass::FindActor(NAME_Sigil)); if (item != NULL) texture = TexMan(item->Icon); } diff --git a/src/g_strife/a_strifeglobal.h b/src/g_strife/a_strifeglobal.h index 9e7f9148e0..c07bce63c3 100644 --- a/src/g_strife/a_strifeglobal.h +++ b/src/g_strife/a_strifeglobal.h @@ -49,20 +49,6 @@ public: bool TryPickup (AActor *&toucher); }; -class ASigil : public AWeapon -{ - DECLARE_CLASS (ASigil, AWeapon) -public: - bool HandlePickup (AInventory *item); - AInventory *CreateCopy (AActor *other); - - void Serialize(FSerializer &arc); - bool SpecialDropAction (AActor *dropper); - static int GiveSigilPiece (AActor *daPlayer); - void BeginPlay(); - - int NumPieces, DownPieces; -}; extern PClassActor *QuestItemClasses[31]; diff --git a/src/g_strife/a_strifeweapons.cpp b/src/g_strife/a_strifeweapons.cpp index 0630089d69..34862f943d 100644 --- a/src/g_strife/a_strifeweapons.cpp +++ b/src/g_strife/a_strifeweapons.cpp @@ -698,435 +698,3 @@ DEFINE_ACTION_FUNCTION(AActor, A_FireGrenade) return 0; } -// The Almighty Sigil! ------------------------------------------------------ - -IMPLEMENT_CLASS(ASigil, false, false) - -//============================================================================ -// -// ASigil :: Serialize -// -//============================================================================ - -void ASigil::BeginPlay() -{ - NumPieces = health; -} - -//============================================================================ -// -// ASigil :: Serialize -// -//============================================================================ - -void ASigil::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - arc("numpieces", NumPieces) - ("downpieces", DownPieces); -} - -//============================================================================ -// -// ASigil :: HandlePickup -// -//============================================================================ - -bool ASigil::HandlePickup (AInventory *item) -{ - if (item->IsKindOf (RUNTIME_CLASS(ASigil))) - { - int otherPieces = static_cast(item)->NumPieces; - if (otherPieces > NumPieces) - { - item->ItemFlags |= IF_PICKUPGOOD; - Icon = item->Icon; - // If the player is holding the Sigil right now, drop it and bring - // it back with the new piece(s) in view. - if (Owner->player != NULL && Owner->player->ReadyWeapon == this) - { - DownPieces = NumPieces; - Owner->player->PendingWeapon = this; - } - NumPieces = otherPieces; - } - return true; - } - return false; -} - -//============================================================================ -// -// ASigil :: CreateCopy -// -//============================================================================ - -AInventory *ASigil::CreateCopy (AActor *other) -{ - ASigil *copy = Spawn (); - copy->Amount = Amount; - copy->MaxAmount = MaxAmount; - copy->NumPieces = NumPieces; - copy->Icon = Icon; - GoAwayAndDie (); - return copy; -} - -//============================================================================ -// -// A_SelectPiece -// -// Decide which sprite frame this Sigil should use as an item, based on how -// many pieces it represents. -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_SelectPiece) -{ - PARAM_ACTION_PROLOGUE(AActor); - - int pieces = MIN (static_cast(self)->NumPieces, 5); - - if (pieces > 1) - { - self->SetState (self->FindState("Spawn")+pieces); - } - return 0; -} - -//============================================================================ -// -// A_SelectSigilView -// -// Decide which first-person frame this Sigil should show, based on how many -// pieces it represents. Strife did this by selecting a flash that looked like -// the Sigil whenever you switched to it and at the end of an attack. I have -// chosen to make the weapon sprite choose the correct frame and let the flash -// be a regular flash. It means I need to use more states, but I think it's -// worth it. -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_SelectSigilView) -{ - PARAM_ACTION_PROLOGUE(AActor); - - DPSprite *pspr; - int pieces; - - if (self->player == nullptr) - { - return 0; - } - pieces = static_cast(self->player->ReadyWeapon)->NumPieces; - pspr = self->player->GetPSprite(PSP_WEAPON); - pspr->SetState(pspr->GetState() + pieces); - return 0; -} - -//============================================================================ -// -// A_SelectSigilDown -// -// Same as A_SelectSigilView, except it uses DownPieces. This is so that when -// you pick up a Sigil, the old one will drop and *then* change to the new -// one. -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_SelectSigilDown) -{ - PARAM_ACTION_PROLOGUE(AActor); - - DPSprite *pspr; - int pieces; - - if (self->player == nullptr) - { - return 0; - } - pieces = static_cast(self->player->ReadyWeapon)->DownPieces; - static_cast(self->player->ReadyWeapon)->DownPieces = 0; - if (pieces == 0) - { - pieces = static_cast(self->player->ReadyWeapon)->NumPieces; - } - pspr = self->player->GetPSprite(PSP_WEAPON); - pspr->SetState(pspr->GetState() + pieces); - return 0; -} - -//============================================================================ -// -// A_SelectSigilAttack -// -// Same as A_SelectSigilView, but used just before attacking. -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_SelectSigilAttack) -{ - PARAM_ACTION_PROLOGUE(AActor); - - DPSprite *pspr; - int pieces; - - if (self->player == nullptr) - { - return 0; - } - pieces = static_cast(self->player->ReadyWeapon)->NumPieces; - pspr = self->player->GetPSprite(PSP_WEAPON); - pspr->SetState(pspr->GetState() + 4*pieces - 3); - return 0; -} - -//============================================================================ -// -// A_SigilCharge -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_SigilCharge) -{ - PARAM_ACTION_PROLOGUE(AActor); - - S_Sound (self, CHAN_WEAPON, "weapons/sigilcharge", 1, ATTN_NORM); - if (self->player != NULL) - { - self->player->extralight = 2; - } - return 0; -} - -//============================================================================ -// -// A_FireSigil1 -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_FireSigil1) -{ - PARAM_ACTION_PROLOGUE(AActor); - - AActor *spot; - player_t *player = self->player; - FTranslatedLineTarget t; - - if (player == NULL || player->ReadyWeapon == NULL) - return 0; - - P_DamageMobj (self, self, NULL, 1*4, 0, DMG_NO_ARMOR); - S_Sound (self, CHAN_WEAPON, "weapons/sigilcharge", 1, ATTN_NORM); - - P_BulletSlope (self, &t, ALF_PORTALRESTRICT); - if (t.linetarget != NULL) - { - spot = Spawn("SpectralLightningSpot", t.linetarget->PosAtZ(t.linetarget->floorz), ALLOW_REPLACE); - if (spot != NULL) - { - spot->tracer = t.linetarget; - } - } - else - { - spot = Spawn("SpectralLightningSpot", self->Pos(), ALLOW_REPLACE); - if (spot != NULL) - { - spot->VelFromAngle(28., self->Angles.Yaw); - } - } - if (spot != NULL) - { - spot->SetFriendPlayer(player); - spot->target = self; - } - return 0; -} - -//============================================================================ -// -// A_FireSigil2 -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_FireSigil2) -{ - PARAM_ACTION_PROLOGUE(AActor); - - player_t *player = self->player; - - if (player == NULL || player->ReadyWeapon == NULL) - return 0; - - P_DamageMobj (self, self, NULL, 2*4, 0, DMG_NO_ARMOR); - S_Sound (self, CHAN_WEAPON, "weapons/sigilcharge", 1, ATTN_NORM); - - P_SpawnPlayerMissile (self, PClass::FindActor("SpectralLightningH1")); - return 0; -} - -//============================================================================ -// -// A_FireSigil3 -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_FireSigil3) -{ - PARAM_ACTION_PROLOGUE(AActor); - - AActor *spot; - player_t *player = self->player; - int i; - - if (player == NULL || player->ReadyWeapon == NULL) - return 0; - - P_DamageMobj (self, self, NULL, 3*4, 0, DMG_NO_ARMOR); - S_Sound (self, CHAN_WEAPON, "weapons/sigilcharge", 1, ATTN_NORM); - - self->Angles.Yaw -= 90.; - for (i = 0; i < 20; ++i) - { - self->Angles.Yaw += 9.; - spot = P_SpawnSubMissile (self, PClass::FindActor("SpectralLightningBall1"), self); - if (spot != NULL) - { - spot->SetZ(self->Z() + 32); - } - } - self->Angles.Yaw -= 90.; - return 0; -} - -//============================================================================ -// -// A_FireSigil4 -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_FireSigil4) -{ - PARAM_ACTION_PROLOGUE(AActor); - - AActor *spot; - player_t *player = self->player; - FTranslatedLineTarget t; - - if (player == NULL || player->ReadyWeapon == NULL) - return 0; - - P_DamageMobj (self, self, NULL, 4*4, 0, DMG_NO_ARMOR); - S_Sound (self, CHAN_WEAPON, "weapons/sigilcharge", 1, ATTN_NORM); - - P_BulletSlope (self, &t, ALF_PORTALRESTRICT); - if (t.linetarget != NULL) - { - spot = P_SpawnPlayerMissile (self, 0,0,0, PClass::FindActor("SpectralLightningBigV1"), self->Angles.Yaw, &t, NULL, false, false, ALF_PORTALRESTRICT); - if (spot != NULL) - { - spot->tracer = t.linetarget; - } - } - else - { - spot = P_SpawnPlayerMissile (self, PClass::FindActor("SpectralLightningBigV1")); - if (spot != NULL) - { - spot->VelFromAngle(spot->Speed, self->Angles.Yaw); - } - } - return 0; -} - -//============================================================================ -// -// A_FireSigil5 -// -//============================================================================ - -DEFINE_ACTION_FUNCTION(AActor, A_FireSigil5) -{ - PARAM_ACTION_PROLOGUE(AActor); - - player_t *player = self->player; - - if (player == NULL || player->ReadyWeapon == NULL) - return 0; - - P_DamageMobj (self, self, NULL, 5*4, 0, DMG_NO_ARMOR); - S_Sound (self, CHAN_WEAPON, "weapons/sigilcharge", 1, ATTN_NORM); - - P_SpawnPlayerMissile (self, PClass::FindActor("SpectralLightningBigBall1")); - return 0; -} - -//============================================================================ -// -// ASigil :: SpecialDropAction -// -// Monsters don't drop Sigil pieces. The Sigil pieces grab hold of the person -// who killed the dropper and automatically enter their inventory. That's the -// way it works if you believe Macil, anyway... -// -//============================================================================ - -bool ASigil::SpecialDropAction (AActor *dropper) -{ - // Give a Sigil piece to every player in the game - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (playeringame[i] && players[i].mo != NULL) - { - GiveSigilPiece (players[i].mo); - Destroy (); - } - } - return true; -} - -//============================================================================ -// -// ASigil :: GiveSigilPiece -// -// Gives the actor another Sigil piece, up to 5. Returns the number of Sigil -// pieces the actor previously held. -// -//============================================================================ - -int ASigil::GiveSigilPiece (AActor *receiver) -{ - ASigil *sigil; - - sigil = receiver->FindInventory (); - if (sigil == NULL) - { - sigil = static_cast(Spawn("Sigil1")); - if (!sigil->CallTryPickup (receiver)) - { - sigil->Destroy (); - } - return 0; - } - else if (sigil->NumPieces < 5) - { - ++sigil->NumPieces; - static const char* sigils[5] = - { - "Sigil1", "Sigil2", "Sigil3", "Sigil4", "Sigil5" - }; - sigil->Icon = ((AInventory*)GetDefaultByName (sigils[MAX(0,sigil->NumPieces-1)]))->Icon; - // If the player has the Sigil out, drop it and bring it back up. - if (sigil->Owner->player != NULL && sigil->Owner->player->ReadyWeapon == sigil) - { - sigil->Owner->player->PendingWeapon = sigil; - sigil->DownPieces = sigil->NumPieces - 1; - } - return sigil->NumPieces - 1; - } - else - { - return 5; - } -} diff --git a/src/g_strife/strife_sbar.cpp b/src/g_strife/strife_sbar.cpp index 39f92d0762..79a363d1c7 100644 --- a/src/g_strife/strife_sbar.cpp +++ b/src/g_strife/strife_sbar.cpp @@ -439,7 +439,7 @@ private: } // Sigil - item = CPlayer->mo->FindInventory(); + item = CPlayer->mo->FindInventory(PClass::FindActor(NAME_Sigil)); if (item != NULL) { DrawImage (TexMan(item->Icon), 253, 7); diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 7ba40f957e..33a90c85f5 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -483,18 +483,28 @@ void cht_DoCheat (player_t *player, int cheat) case CHT_LEGO: if (player->mo != NULL && player->health >= 0) { - int oldpieces = ASigil::GiveSigilPiece (player->mo); - item = player->mo->FindInventory (RUNTIME_CLASS(ASigil)); - - if (item != NULL) + static VMFunction *gsp = nullptr; + if (gsp == nullptr) gsp = PClass::FindFunction(NAME_Sigil, NAME_GiveSigilPiece); + if (gsp) { - if (oldpieces == 5) + VMValue params[1] = { player->mo }; + VMFrameStack stack; + VMReturn ret; + int oldpieces = 1; + ret.IntAt(&oldpieces); + stack.Call(gsp, params, 1, &ret, 1, nullptr); + item = player->mo->FindInventory(PClass::FindActor(NAME_Sigil)); + + if (item != NULL) { - item->Destroy (); - } - else - { - player->PendingWeapon = static_cast (item); + if (oldpieces == 5) + { + item->Destroy(); + } + else + { + player->PendingWeapon = static_cast (item); + } } } } diff --git a/src/namedef.h b/src/namedef.h index 3a4a1c1800..9cb4e8d231 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -120,6 +120,11 @@ xx(ArtiPoisonBag3) // Strife quests xx(QuestItem) +xx(Sigil) +xx(ScriptedMarine) +xx(GiveSigilPiece) +xx(SetWeapon) +xx(SetSprite) // Armor xx(BasicArmor) diff --git a/src/p_acs.cpp b/src/p_acs.cpp index a5b8b977d4..97f6602464 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -6112,29 +6112,25 @@ static bool CharArrayParms(int &capacity, int &offset, int &a, int *Stack, int & static void SetMarineWeapon(AActor *marine, int weapon) { static VMFunction *smw = nullptr; - if (smw == nullptr) + if (smw == nullptr) smw = PClass::FindFunction(NAME_ScriptedMarine, NAME_SetWeapon); + if (smw) { - auto cls = PClass::FindActor("ScriptedMarine"); - auto func = dyn_cast(cls->Symbols.FindSymbol("SetWeapon", true)); - smw = func->Variants[0].Implementation; + VMValue params[2] = { marine, weapon }; + VMFrameStack stack; + stack.Call(smw, params, 2, nullptr, 0, nullptr); } - VMValue params[2] = { marine, weapon }; - VMFrameStack stack; - stack.Call(smw, params, 2, nullptr, 0, nullptr); } static void SetMarineSprite(AActor *marine, PClassActor *source) { static VMFunction *sms = nullptr; - if (sms == nullptr) + if (sms == nullptr) sms = PClass::FindFunction(NAME_ScriptedMarine, NAME_SetSprite); + if (sms) { - auto cls = PClass::FindActor("ScriptedMarine"); - auto func = dyn_cast(cls->Symbols.FindSymbol("SetSprite", true)); - sms = func->Variants[0].Implementation; + VMValue params[2] = { marine, source }; + VMFrameStack stack; + stack.Call(sms, params, 2, nullptr, 0, nullptr); } - VMValue params[2] = { marine, source }; - VMFrameStack stack; - stack.Call(sms, params, 2, nullptr, 0, nullptr); } int DLevelScript::RunScript () @@ -8577,15 +8573,15 @@ scriptwait: case PCD_GETSIGILPIECES: { - ASigil *sigil; + AInventory *sigil; - if (activator == NULL || (sigil = activator->FindInventory()) == NULL) + if (activator == NULL || (sigil = activator->FindInventory(PClass::FindActor(NAME_Sigil))) == NULL) { PushToStack (0); } else { - PushToStack (sigil->NumPieces); + PushToStack (sigil->health); } } break; diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 404b2f2bc0..95aabe44b8 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -646,7 +646,7 @@ static void TakeStrifeItem (player_t *player, PClassActor *itemtype, int amount) return; // Don't take the sigil. - if (itemtype == RUNTIME_CLASS(ASigil)) + if (itemtype->GetClass()->TypeName == NAME_Sigil) return; player->mo->TakeInventory(itemtype, amount); diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index fcd8f9ceee..3712ac86c9 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -3209,7 +3209,7 @@ AInventory *P_DropItem (AActor *source, PClassActor *type, int dropamount, int c AInventory *inv = static_cast(mo); ModifyDropAmount(inv, dropamount); inv->ItemFlags |= IF_TOSSED; - if (inv->SpecialDropAction (source)) + if (inv->CallSpecialDropAction (source)) { // The special action indicates that the item should not spawn inv->Destroy(); diff --git a/src/p_pspr.cpp b/src/p_pspr.cpp index e2ea3084db..610ade35a2 100644 --- a/src/p_pspr.cpp +++ b/src/p_pspr.cpp @@ -439,6 +439,15 @@ void DPSprite::SetState(FState *newstate, bool pending) return; } +DEFINE_ACTION_FUNCTION(DPSprite, SetState) +{ + PARAM_SELF_PROLOGUE(DPSprite); + PARAM_POINTER(state, FState); + PARAM_BOOL_DEF(pending); + self->SetState(state, pending); + return 0; +} + //--------------------------------------------------------------------------- // // PROC P_BringUpWeapon diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 56f991287e..c454ced835 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -2408,7 +2408,7 @@ static bool CheckRandom(ZCC_Expression *duration) // Sets up the action function call // //========================================================================== -FxExpression *ZCCCompiler::SetupActionFunction(PClass *cls, ZCC_TreeNode *af) +FxExpression *ZCCCompiler::SetupActionFunction(PClass *cls, ZCC_TreeNode *af, int StateFlags) { // We have 3 cases to consider here: // 1. A function without parameters. This can be called directly @@ -2436,7 +2436,9 @@ FxExpression *ZCCCompiler::SetupActionFunction(PClass *cls, ZCC_TreeNode *af) // We can use this function directly without wrapping it in a caller. auto selfclass = dyn_cast(afd->Variants[0].SelfClass); assert(selfclass != nullptr); // non classes are not supposed to get here. - if ((afd->Variants[0].Flags & VARF_Action) || !cls->IsDescendantOf(RUNTIME_CLASS(AStateProvider)) || !selfclass->IsDescendantOf(RUNTIME_CLASS(AStateProvider))) + + int comboflags = afd->Variants[0].UseFlags & StateFlags; + if (comboflags == StateFlags) // the function must satisfy all the flags the state requires { return new FxVMFunctionCall(new FxSelf(*af), afd, argumentlist, *af, false); } @@ -2627,7 +2629,7 @@ void ZCCCompiler::CompileStates() if (sl->Action != nullptr) { - auto code = SetupActionFunction(static_cast(c->Type()), sl->Action); + auto code = SetupActionFunction(static_cast(c->Type()), sl->Action, state.UseFlags); if (code != nullptr) { auto funcsym = CreateAnonymousFunction(c->Type(), nullptr, state.UseFlags); diff --git a/src/scripting/zscript/zcc_compile.h b/src/scripting/zscript/zcc_compile.h index 9599b4b968..8b3a9f5f12 100644 --- a/src/scripting/zscript/zcc_compile.h +++ b/src/scripting/zscript/zcc_compile.h @@ -108,7 +108,7 @@ private: void InitFunctions(); void CompileStates(); - FxExpression *SetupActionFunction(PClass *cls, ZCC_TreeNode *sl); + FxExpression *SetupActionFunction(PClass *cls, ZCC_TreeNode *sl, int stateflags); bool SimplifyingConstant; TArray Constants; diff --git a/wadsrc/static/zscript/shared/inventory.txt b/wadsrc/static/zscript/shared/inventory.txt index 25c94cb87a..1bc695339d 100644 --- a/wadsrc/static/zscript/shared/inventory.txt +++ b/wadsrc/static/zscript/shared/inventory.txt @@ -29,6 +29,7 @@ class Inventory : Actor native virtual native color GetBlend (); virtual native bool HandlePickup(Inventory item); virtual native Inventory CreateCopy(Actor other); + virtual native bool SpecialDropAction (Actor dropper); native void GoAwayAndDie(); diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt index bcbc7db613..6d1b8abfcb 100644 --- a/wadsrc/static/zscript/shared/player.txt +++ b/wadsrc/static/zscript/shared/player.txt @@ -133,6 +133,9 @@ class PSprite : Object native native bool bPowDouble; native bool bCVarFast; native bool bFlip; + + native void SetState(State newstate, bool pending = false); + } struct PlayerInfo native // this is what internally is known as player_t diff --git a/wadsrc/static/zscript/strife/sigil.txt b/wadsrc/static/zscript/strife/sigil.txt index d8b1f80f8a..a9f8020094 100644 --- a/wadsrc/static/zscript/strife/sigil.txt +++ b/wadsrc/static/zscript/strife/sigil.txt @@ -1,8 +1,11 @@ // The Almighty Sigil! ------------------------------------------------------ -class Sigil : Weapon native +class Sigil : Weapon { + // NUmPieces gets stored in 'health', so that it can be quickly accessed by ACS's GetSigilPieces function. + int downpieces; + Default { Weapon.Kickback 100; @@ -16,18 +19,7 @@ class Sigil : Weapon native Inventory.PickupMessage "$TXT_SIGIL"; } - action native void A_SelectPiece (); - action native void A_SelectSigilView (); - action native void A_SelectSigilDown (); - action native void A_SelectSigilAttack (); - action native void A_SigilCharge (); - action native void A_FireSigil1 (); - action native void A_FireSigil2 (); - action native void A_FireSigil3 (); - action native void A_FireSigil4 (); - action native void A_FireSigil5 (); - - States + States(Actor) { Spawn: SIGL A 1; @@ -41,6 +33,9 @@ class Sigil : Weapon native Stop; SIGL E -1; Stop; + } + States(Weapon) + { Ready: SIGH A 0 Bright A_SelectSigilView; Wait; @@ -120,6 +115,353 @@ class Sigil : Weapon native SIGF C 0 Bright A_Light0; Stop; } + + + //============================================================================ + // + // ASigil :: HandlePickup + // + //============================================================================ + + override bool HandlePickup (Inventory item) + { + if (item is "Sigil") + { + int otherPieces = item.health; + if (otherPieces > health) + { + item.bPickupGood = true; + Icon = item.Icon; + // If the player is holding the Sigil right now, drop it and bring + // it back with the new piece(s) in view. + if (Owner.player != null && Owner.player.ReadyWeapon == self) + { + DownPieces = health; + Owner.player.PendingWeapon = self; + } + health = otherPieces; + } + return true; + } + return false; + } + + //============================================================================ + // + // ASigil :: CreateCopy + // + //============================================================================ + + override Inventory CreateCopy (Actor other) + { + Sigil copy = Sigil(Spawn("Sigil")); + copy.Amount = Amount; + copy.MaxAmount = MaxAmount; + copy.health = health; + copy.Icon = Icon; + GoAwayAndDie (); + return copy; + } + + //============================================================================ + // + // A_SelectPiece + // + // Decide which sprite frame self Sigil should use as an item, based on how + // many pieces it represents. + // + //============================================================================ + + void A_SelectPiece () + { + int pieces = min (health, 5); + + if (pieces > 1) + { + SetState (FindState("Spawn") + pieces); + } + } + + //============================================================================ + // + // A_SelectSigilView + // + // Decide which first-person frame self Sigil should show, based on how many + // pieces it represents. Strife did self by selecting a flash that looked like + // the Sigil whenever you switched to it and at the end of an attack. I have + // chosen to make the weapon sprite choose the correct frame and let the flash + // be a regular flash. It means I need to use more states, but I think it's + // worth it. + // + //============================================================================ + + action void A_SelectSigilView () + { + if (player == null) + { + return; + } + PSprite pspr = player.GetPSprite(PSP_WEAPON); + pspr.SetState(pspr.CurState + invoker.health); + } + + //============================================================================ + // + // A_SelectSigilDown + // + // Same as A_SelectSigilView, except it uses DownPieces. self is so that when + // you pick up a Sigil, the old one will drop and *then* change to the new + // one. + // + //============================================================================ + + action void A_SelectSigilDown () + { + if (player == null) + { + return; + } + PSprite pspr = player.GetPSprite(PSP_WEAPON); + int pieces = invoker.downpieces; + if (pieces < 1 || pieces > 5) pieces = invoker.health; + pspr.SetState(pspr.CurState + pieces); + } + + //============================================================================ + // + // A_SelectSigilAttack + // + // Same as A_SelectSigilView, but used just before attacking. + // + //============================================================================ + + action void A_SelectSigilAttack () + { + if (player == null) + { + return; + } + PSprite pspr = player.GetPSprite(PSP_WEAPON); + pspr.SetState(pspr.CurState + (4 * invoker.health - 3)); + } + + //============================================================================ + // + // A_SigilCharge + // + //============================================================================ + + action void A_SigilCharge () + { + A_PlaySound ("weapons/sigilcharge", CHAN_WEAPON); + if (player != null) + { + player.extralight = 2; + } + } + + //============================================================================ + // + // A_FireSigil1 + // + //============================================================================ + + action void A_FireSigil1 () + { + Actor spot; + FTranslatedLineTarget t; + + if (player == null || player.ReadyWeapon == null) + return; + + DamageMobj (self, null, 1*4, 'Sigil', DMG_NO_ARMOR); + A_PlaySound ("weapons/sigilcharge", CHAN_WEAPON); + + BulletSlope (t, ALF_PORTALRESTRICT); + if (t.linetarget != null) + { + spot = Spawn("SpectralLightningSpot", (t.linetarget.pos.xy, t.linetarget.floorz), ALLOW_REPLACE); + if (spot != null) + { + spot.tracer = t.linetarget; + } + } + else + { + spot = Spawn("SpectralLightningSpot", Pos, ALLOW_REPLACE); + if (spot != null) + { + spot.VelFromAngle(28., angle); + } + } + if (spot != null) + { + spot.SetFriendPlayer(player); + spot.target = self; + } + } + + //============================================================================ + // + // A_FireSigil2 + // + //============================================================================ + + action void A_FireSigil2 () + { + if (player == null || player.ReadyWeapon == null) + return; + + DamageMobj (self, null, 2*4, 'Sigil', DMG_NO_ARMOR); + A_PlaySound ("weapons/sigilcharge", CHAN_WEAPON); + SpawnPlayerMissile ("SpectralLightningH1"); + } + + //============================================================================ + // + // A_FireSigil3 + // + //============================================================================ + + action void A_FireSigil3 () + { + if (player == null || player.ReadyWeapon == null) + return; + + DamageMobj (self, null, 3*4, 'Sigil', DMG_NO_ARMOR); + A_PlaySound ("weapons/sigilcharge", CHAN_WEAPON); + + angle -= 90.; + for (int i = 0; i < 20; ++i) + { + angle += 9.; + Actor spot = SpawnSubMissile ("SpectralLightningBall1", self); + if (spot != null) + { + spot.SetZ(pos.z + 32); + } + } + angle -= 90.; + } + + //============================================================================ + // + // A_FireSigil4 + // + //============================================================================ + + action void A_FireSigil4 () + { + FTranslatedLineTarget t; + + if (player == null || player.ReadyWeapon == null) + return; + + DamageMobj (self, null, 4*4, 'Sigil', DMG_NO_ARMOR); + A_PlaySound ("weapons/sigilcharge", CHAN_WEAPON); + + BulletSlope (t, ALF_PORTALRESTRICT); + if (t.linetarget != null) + { + Actor spot = SpawnPlayerMissile ("SpectralLightningBigV1", angle, pLineTarget: t, aimFlags: ALF_PORTALRESTRICT); + if (spot != null) + { + spot.tracer = t.linetarget; + } + } + else + { + Actor spot = SpawnPlayerMissile ("SpectralLightningBigV1"); + if (spot != null) + { + spot.VelFromAngle(spot.Speed, angle); + } + } + } + + //============================================================================ + // + // A_FireSigil5 + // + //============================================================================ + + action void A_FireSigil5 () + { + if (player == null || player.ReadyWeapon == null) + return; + + DamageMobj (self, null, 5*4, 'Sigil', DMG_NO_ARMOR); + A_PlaySound ("weapons/sigilcharge", CHAN_WEAPON); + + SpawnPlayerMissile ("SpectralLightningBigBall1"); + } + + //============================================================================ + // + // ASigil :: SpecialDropAction + // + // Monsters don't drop Sigil pieces. The Sigil pieces grab hold of the person + // who killed the dropper and automatically enter their inventory. That's the + // way it works if you believe Macil, anyway... + // + //============================================================================ + + override bool SpecialDropAction (Actor dropper) + { + // Give a Sigil piece to every player in the game + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i] && players[i].mo != null) + { + GiveSigilPiece (players[i].mo); + Destroy (); + } + } + return true; + } + + //============================================================================ + // + // ASigil :: GiveSigilPiece + // + // Gives the actor another Sigil piece, up to 5. Returns the number of Sigil + // pieces the actor previously held. + // + //============================================================================ + + static int GiveSigilPiece (Actor receiver) + { + Sigil sigl = Sigil(receiver.FindInventory("Sigil")); + if (sigl == null) + { + sigl = Sigil(Spawn("Sigil1")); + if (!sigl.CallTryPickup (receiver)) + { + sigl.Destroy (); + } + return 0; + } + else if (sigl.health < 5) + { + ++sigl.health; + static const class sigils[] = + { + "Sigil1", "Sigil2", "Sigil3", "Sigil4", "Sigil5" + }; + sigl.Icon = GetDefaultByType(sigils[clamp(sigl.health, 1, 5)]).Icon; + // If the player has the Sigil out, drop it and bring it back up. + if (sigl.Owner.player != null && sigl.Owner.player.ReadyWeapon == sigl) + { + sigl.Owner.player.PendingWeapon = sigl; + sigl.DownPieces = sigl.health - 1; + } + return sigl.health - 1; + } + else + { + return 5; + } + } } // Sigil 1 ------------------------------------------------------------------