diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c0f4756d4..a6fa721b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -998,6 +998,7 @@ set (PCH_SOURCES s_sound.cpp serializer.cpp sc_man.cpp + scriptutil.cpp st_stuff.cpp statistics.cpp stats.cpp diff --git a/src/actor.h b/src/actor.h index 8f0a8e9f3..1c3d54bc1 100644 --- a/src/actor.h +++ b/src/actor.h @@ -640,7 +640,6 @@ public: AActor &operator= (const AActor &other); ~AActor (); - virtual void Finalize(FStateDefinitions &statedef); virtual void OnDestroy() override; virtual void Serialize(FSerializer &arc) override; virtual void PostSerialize() override; @@ -685,8 +684,6 @@ public: void LevelSpawned(); // Called after BeginPlay if this actor was spawned by the world void HandleSpawnFlags(); // Translates SpawnFlags into in-game flags. - virtual void MarkPrecacheSounds() const; // Marks sounds used by this actor for precaching. - virtual void Activate (AActor *activator); void CallActivate(AActor *activator); diff --git a/src/b_bot.cpp b/src/b_bot.cpp index 24a1bfe03..25418acca 100644 --- a/src/b_bot.cpp +++ b/src/b_bot.cpp @@ -47,6 +47,7 @@ #include "d_net.h" #include "serializer.h" #include "d_player.h" +#include "w_wad.h" #include "vm.h" IMPLEMENT_CLASS(DBot, false, true) @@ -246,69 +247,61 @@ CCMD (listbots) // set the bot specific weapon information // This is intentionally not in the weapon definition anymore. + +BotInfoMap BotInfo; + void InitBotStuff() { - static struct BotInit + int lump; + int lastlump = 0; + while (-1 != (lump = Wads.FindLump("BOTSUPP", &lastlump))) { - const char *type; - int movecombatdist; - int weaponflags; - const char *projectile; - } botinits[] = { - - { "Pistol", 25000000, 0, NULL }, - { "Shotgun", 24000000, 0, NULL }, - { "SuperShotgun", 15000000, 0, NULL }, - { "Chaingun", 27000000, 0, NULL }, - { "RocketLauncher", 18350080, WIF_BOT_REACTION_SKILL_THING|WIF_BOT_EXPLOSIVE, "Rocket" }, - { "PlasmaRifle", 27000000, 0, "PlasmaBall" }, - { "BFG9000", 10000000, WIF_BOT_REACTION_SKILL_THING|WIF_BOT_BFG, "BFGBall" }, - { "GoldWand", 25000000, 0, NULL }, - { "GoldWandPowered", 25000000, 0, NULL }, - { "Crossbow", 24000000, 0, "CrossbowFX1" }, - { "CrossbowPowered", 24000000, 0, "CrossbowFX2" }, - { "Blaster", 27000000, 0, NULL }, - { "BlasterPowered", 27000000, 0, "BlasterFX1" }, - { "SkullRod", 27000000, 0, "HornRodFX1" }, - { "SkullRodPowered", 27000000, 0, "HornRodFX2" }, - { "PhoenixRod", 18350080, WIF_BOT_REACTION_SKILL_THING|WIF_BOT_EXPLOSIVE, "PhoenixFX1" }, - { "Mace", 27000000, WIF_BOT_REACTION_SKILL_THING, "MaceFX2" }, - { "MacePowered", 27000000, WIF_BOT_REACTION_SKILL_THING|WIF_BOT_EXPLOSIVE, "MaceFX4" }, - { "FWeapHammer", 22000000, 0, "HammerMissile" }, - { "FWeapQuietus", 20000000, 0, "FSwordMissile" }, - { "CWeapStaff", 25000000, 0, "CStaffMissile" }, - { "CWeapFlane", 27000000, 0, "CFlameMissile" }, - { "MWeapWand", 25000000, 0, "MageWandMissile" }, - { "CWeapWraithverge", 22000000, 0, "HolyMissile" }, - { "MWeapFrost", 19000000, 0, "FrostMissile" }, - { "MWeapLightning", 23000000, 0, "LightningFloor" }, - { "MWeapBloodscourge", 20000000, 0, "MageStaffFX2" }, - { "StrifeCrossbow", 24000000, 0, "ElectricBolt" }, - { "StrifeCrossbow2", 24000000, 0, "PoisonBolt" }, - { "AssaultGun", 27000000, 0, NULL }, - { "MiniMissileLauncher", 18350080, WIF_BOT_REACTION_SKILL_THING|WIF_BOT_EXPLOSIVE, "MiniMissile" }, - { "FlameThrower", 24000000, 0, "FlameMissile" }, - { "Mauler", 15000000, 0, NULL }, - { "Mauler2", 10000000, 0, "MaulerTorpedo" }, - { "StrifeGrenadeLauncher", 18350080, WIF_BOT_REACTION_SKILL_THING|WIF_BOT_EXPLOSIVE, "HEGrenade" }, - { "StrifeGrenadeLauncher2", 18350080, WIF_BOT_REACTION_SKILL_THING|WIF_BOT_EXPLOSIVE, "PhosphorousGrenade" }, - }; - - for(unsigned i=0;iIsDescendantOf(NAME_Weapon)) + FScanner sc(lump); + sc.SetCMode(true); + while (sc.GetString()) { - AWeapon *w = (AWeapon*)GetDefaultByType(cls); - if (w != NULL) + PClassActor *wcls = PClass::FindActor(sc.String); + if (wcls != NULL && wcls->IsDescendantOf(NAME_Weapon)) { - w->MoveCombatDist = botinits[i].movecombatdist/65536.; - w->WeaponFlags |= botinits[i].weaponflags; - w->ProjectileType = PClass::FindActor(botinits[i].projectile); + BotInfoData bi = {}; + sc.MustGetStringName(","); + sc.MustGetNumber(); + bi.MoveCombatDist = sc.Number; + while (sc.CheckString(",")) + { + sc.MustGetString(); + if (sc.Compare("BOT_REACTION_SKILL_THING")) + { + bi.flags |= BIF_BOT_REACTION_SKILL_THING; + } + else if (sc.Compare("BOT_EXPLOSIVE")) + { + bi.flags |= BIF_BOT_EXPLOSIVE; + } + else if (sc.Compare("BOT_BFG")) + { + bi.flags |= BIF_BOT_BFG; + } + else + { + PClassActor *cls = PClass::FindActor(sc.String); + bi.projectileType = cls; + if (cls == nullptr) + { + sc.ScriptError("Unknown token %s", sc.String); + } + } + } + BotInfo[wcls->TypeName] = bi; + } + else + { + sc.ScriptError("%s is not a weapon type", sc.String); } } } + // Fixme: Export these, too. static const char *warnbotmissiles[] = { "PlasmaBall", "Ripper", "HornRodFX1" }; for(unsigned i=0;i; + +extern BotInfoMap BotInfo; + +inline BotInfoData GetBotInfo(AInventory *weap) +{ + if (weap == nullptr) return BotInfoData(); + auto k = BotInfo.CheckKey(weap->GetClass()->TypeName); + if (k) return *k; + return BotInfoData(); +} + //Used to keep all the globally needed variables in nice order. class FCajunMaster { diff --git a/src/b_func.cpp b/src/b_func.cpp index 3c6425116..34a5bd9fd 100644 --- a/src/b_func.cpp +++ b/src/b_func.cpp @@ -188,7 +188,7 @@ void DBot::Dofire (ticcmd_t *cmd) //Reaction skill thing. if (first_shot && - !(player->ReadyWeapon->WeaponFlags & WIF_BOT_REACTION_SKILL_THING)) + !(GetBotInfo(player->ReadyWeapon).flags & BIF_BOT_REACTION_SKILL_THING)) { t_react = (100-skill.reaction+1)/((pr_botdofire()%3)+3); } @@ -203,38 +203,21 @@ void DBot::Dofire (ticcmd_t *cmd) Dist = player->mo->Distance2D(enemy, player->mo->Vel.X - enemy->Vel.X, player->mo->Vel.Y - enemy->Vel.Y); //FIRE EACH TYPE OF WEAPON DIFFERENT: Here should all the different weapons go. - if (player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON) + if (GetBotInfo(player->ReadyWeapon).MoveCombatDist == 0) { - if ((player->ReadyWeapon->ProjectileType != NULL)) - { - if (player->ReadyWeapon->CheckAmmo (AWeapon::PrimaryFire, false, true)) - { - // This weapon can fire a projectile and has enough ammo to do so - goto shootmissile; - } - else if (!(player->ReadyWeapon->WeaponFlags & WIF_AMMO_OPTIONAL)) - { - // Ammo is required, so don't shoot. This is for weapons that shoot - // missiles that die at close range, such as the powered-up Phoneix Rod. - return; - } - } - else - { - //*4 is for atmosphere, the chainsaws sounding and all.. - no_fire = (Dist > DEFMELEERANGE*4); - } + //*4 is for atmosphere, the chainsaws sounding and all.. + no_fire = (Dist > DEFMELEERANGE*4); } - else if (player->ReadyWeapon->WeaponFlags & WIF_BOT_BFG) + else if (GetBotInfo(player->ReadyWeapon).flags & BIF_BOT_BFG) { //MAKEME: This should be smarter. if ((pr_botdofire()%200)<=skill.reaction) if(Check_LOS(enemy, SHOOTFOV)) no_fire = false; } - else if (player->ReadyWeapon->ProjectileType != NULL) + else if (GetBotInfo(player->ReadyWeapon).projectileType != NULL) { - if (player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) + if (GetBotInfo(player->ReadyWeapon).flags & BIF_BOT_EXPLOSIVE) { //Special rules for RL an = FireRox (enemy, cmd); @@ -250,9 +233,8 @@ void DBot::Dofire (ticcmd_t *cmd) } } // prediction aiming -shootmissile: Dist = player->mo->Distance2D(enemy); - fm = Dist / GetDefaultByType (player->ReadyWeapon->ProjectileType)->Speed; + fm = Dist / GetDefaultByType (GetBotInfo(player->ReadyWeapon).projectileType)->Speed; bglobal.SetBodyAt(enemy->Pos() + enemy->Vel.XY() * fm * 2, 1); Angle = player->mo->AngleTo(bglobal.body1); if (Check_LOS (enemy, SHOOTFOV)) diff --git a/src/b_move.cpp b/src/b_move.cpp index 3d3e67ed2..52ae724dc 100644 --- a/src/b_move.cpp +++ b/src/b_move.cpp @@ -345,7 +345,7 @@ void DBot::TurnToAng () if (player->ReadyWeapon != NULL) { - if (player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) + if (GetBotInfo(player->ReadyWeapon).flags & BIF_BOT_EXPLOSIVE) { if (t_roam && !missile) { //Keep angle that where when shot where decided. @@ -356,7 +356,7 @@ void DBot::TurnToAng () if(enemy) if(!dest) //happens when running after item in combat situations, or normal, prevents weak turns - if(player->ReadyWeapon->ProjectileType == NULL && !(player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON)) + if(GetBotInfo(player->ReadyWeapon).projectileType == NULL && GetBotInfo(player->ReadyWeapon).MoveCombatDist > 0) if(Check_LOS(enemy, SHOOTFOV+5)) maxturn = 3; } diff --git a/src/b_think.cpp b/src/b_think.cpp index 2c41bcda3..c335a3402 100644 --- a/src/b_think.cpp +++ b/src/b_think.cpp @@ -173,9 +173,9 @@ void DBot::ThinkForMove (ticcmd_t *cmd) is (Megasphere) ) || dist < (GETINCOMBAT/4) || - (player->ReadyWeapon == NULL || player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON) + (GetBotInfo(player->ReadyWeapon).MoveCombatDist == 0) ) - && (dist < GETINCOMBAT || (player->ReadyWeapon == NULL || player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)) + && (dist < GETINCOMBAT || (GetBotInfo(player->ReadyWeapon).MoveCombatDist == 0)) && Reachable (dest)) #undef is { @@ -185,7 +185,7 @@ void DBot::ThinkForMove (ticcmd_t *cmd) dest = NULL; //To let bot turn right - if (player->ReadyWeapon != NULL && !(player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)) + if (GetBotInfo(player->ReadyWeapon).MoveCombatDist == 0) player->mo->flags &= ~MF_DROPOFF; //Don't jump off any ledges when fighting. if (!(enemy->flags3 & MF3_ISMONSTER)) @@ -205,7 +205,7 @@ void DBot::ThinkForMove (ticcmd_t *cmd) if (player->ReadyWeapon == NULL || player->mo->Distance2D(enemy) > - player->ReadyWeapon->MoveCombatDist) + GetBotInfo(player->ReadyWeapon).MoveCombatDist) { // If a monster, use lower speed (just for cooler apperance while strafing down doomed monster) cmd->ucmd.forwardmove = (enemy->flags3 & MF3_ISMONSTER) ? FORWARDWALK : FORWARDRUN; @@ -273,8 +273,8 @@ void DBot::ThinkForMove (ticcmd_t *cmd) { if (enemy->player) { - if (((enemy->player->ReadyWeapon != NULL && enemy->player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) || - (pr_botmove()%100)>skill.isp) && player->ReadyWeapon != NULL && !(player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)) + if (((enemy->player->ReadyWeapon != NULL && GetBotInfo(enemy->player->ReadyWeapon).flags & BIF_BOT_EXPLOSIVE) || + (pr_botmove()%100)>skill.isp) && (GetBotInfo(player->ReadyWeapon).MoveCombatDist != 0)) dest = enemy;//Dont let enemy kill the bot by supressive fire. So charge enemy. else //hide while t_fight, but keep view at enemy. Angle = player->mo->AngleTo(enemy); @@ -362,15 +362,17 @@ void DBot::WhatToGet (AActor *item) if (item->IsKindOf(NAME_Weapon)) { // FIXME - AWeapon *heldWeapon; + AInventory *heldWeapon; - heldWeapon = dyn_cast(player->mo->FindInventory(item->GetClass())); + heldWeapon = player->mo->FindInventory(item->GetClass()); if (heldWeapon != NULL) { if (!weapgiveammo) return; - if ((heldWeapon->Ammo1 == NULL || heldWeapon->Ammo1->Amount >= heldWeapon->Ammo1->MaxAmount) && - (heldWeapon->Ammo2 == NULL || heldWeapon->Ammo2->Amount >= heldWeapon->Ammo2->MaxAmount)) + auto ammo1 = heldWeapon->PointerVar(NAME_Ammo1); + auto ammo2 = heldWeapon->PointerVar(NAME_Ammo2); + if ((ammo1 == NULL || ammo1->Amount >= ammo1->MaxAmount) && + (ammo2 == NULL || ammo2->Amount >= ammo2->MaxAmount)) { return; } diff --git a/src/ct_chat.cpp b/src/ct_chat.cpp index 85e8f0c23..5f8a8e126 100644 --- a/src/ct_chat.cpp +++ b/src/ct_chat.cpp @@ -374,7 +374,9 @@ static void ShoveChatStr (const char *str, uint8_t who) static bool DoSubstitution (FString &out, const char *in) { player_t *player = &players[consoleplayer]; - AWeapon *weapon = player->ReadyWeapon; + auto weapon = player->ReadyWeapon; + auto ammo1 = weapon ? weapon->PointerVar(NAME_Ammo1) : nullptr; + auto ammo2 = weapon ? weapon->PointerVar(NAME_Ammo2) : nullptr; const char *a, *b; a = in; @@ -427,10 +429,10 @@ static bool DoSubstitution (FString &out, const char *in) } else { - out.AppendFormat("%d", weapon->Ammo1 != NULL ? weapon->Ammo1->Amount : 0); - if (weapon->Ammo2 != NULL) + out.AppendFormat("%d", ammo1 != NULL ? ammo1->Amount : 0); + if (ammo2 != NULL) { - out.AppendFormat("/%d", weapon->Ammo2->Amount); + out.AppendFormat("/%d", ammo2->Amount); } } } @@ -439,16 +441,16 @@ static bool DoSubstitution (FString &out, const char *in) { if (strnicmp(a, "ammo", 4) == 0) { - if (weapon == NULL || weapon->Ammo1 == NULL) + if (ammo1 == NULL) { out += "no ammo"; } else { - out.AppendFormat("%s", weapon->Ammo1->GetClass()->TypeName.GetChars()); - if (weapon->Ammo2 != NULL) + out.AppendFormat("%s", ammo1->GetClass()->TypeName.GetChars()); + if (ammo2 != NULL) { - out.AppendFormat("/%s", weapon->Ammo2->GetClass()->TypeName.GetChars()); + out.AppendFormat("/%s", ammo2->GetClass()->TypeName.GetChars()); } } } diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp index a79e8d6fe..de6e37815 100644 --- a/src/d_dehacked.cpp +++ b/src/d_dehacked.cpp @@ -761,8 +761,8 @@ void SetDehParams(FState *state, int codepointer) // Let's identify the codepointer we're dealing with. PFunction *sym; - sym = dyn_cast(RUNTIME_CLASS(AWeapon)->FindSymbol(FName(MBFCodePointers[codepointer].name), true)); - if (sym == NULL) return; + sym = dyn_cast(PClass::FindActor(NAME_Weapon)->FindSymbol(FName(MBFCodePointers[codepointer].name), true)); + if (sym == NULL ) return; if (codepointer < 0 || (unsigned)codepointer >= countof(MBFCodePointerFactories)) { @@ -1580,16 +1580,18 @@ static int PatchAmmo (int ammoNum) defaultAmmo->MaxAmount = *max; defaultAmmo->Amount = Scale (defaultAmmo->Amount, *per, oldclip); } - else if (type->IsDescendantOf (RUNTIME_CLASS(AWeapon))) + else if (type->IsDescendantOf (NAME_Weapon)) { - AWeapon *defWeap = (AWeapon *)GetDefaultByType (type); - if (defWeap->AmmoType1 == ammoType) + auto defWeap = GetDefaultByType (type); + if (defWeap->PointerVar(NAME_AmmoType1) == ammoType) { - defWeap->AmmoGive1 = Scale (defWeap->AmmoGive1, *per, oldclip); + auto &AmmoGive1 = defWeap->IntVar(NAME_AmmoGive1); + AmmoGive1 = Scale (AmmoGive1, *per, oldclip); } - if (defWeap->AmmoType2 == ammoType) + if (defWeap->PointerVar(NAME_AmmoType2) == ammoType) { - defWeap->AmmoGive2 = Scale (defWeap->AmmoGive2, *per, oldclip); + auto &AmmoGive2 = defWeap->IntVar(NAME_AmmoGive2); + AmmoGive2 = Scale (AmmoGive2, *per, oldclip); } } } @@ -1601,9 +1603,8 @@ static int PatchAmmo (int ammoNum) static int PatchWeapon (int weapNum) { int result; - PClassActor *type = NULL; - uint8_t dummy[sizeof(AWeapon)]; - AWeapon *info = (AWeapon *)&dummy; + PClassActor *type = nullptr; + AInventory *info = nullptr; bool patchedStates = false; FStateDefinitions statedef; @@ -1612,7 +1613,7 @@ static int PatchWeapon (int weapNum) type = WeaponNames[weapNum]; if (type != NULL) { - info = (AWeapon *)GetDefaultByType (type); + info = (AInventory*)GetDefaultByType (type); DPrintf (DMSG_SPAMMY, "Weapon %d\n", weapNum); } } @@ -1655,13 +1656,18 @@ static int PatchWeapon (int weapNum) { val = 5; } - info->AmmoType1 = AmmoNames[val]; - if (info->AmmoType1 != NULL) + if (info) { - info->AmmoGive1 = ((AInventory*)GetDefaultByType (info->AmmoType1))->Amount * 2; - if (info->AmmoUse1 == 0) + auto &AmmoType = info->PointerVar(NAME_AmmoType1); + AmmoType = AmmoNames[val]; + if (AmmoType != nullptr) { - info->AmmoUse1 = 1; + info->IntVar(NAME_AmmoGive1) = ((AInventory*)GetDefaultByType(AmmoType))->Amount * 2; + auto &AmmoUse = info->IntVar(NAME_AmmoUse1); + if (AmmoUse == 0) + { + AmmoUse = 1; + } } } } @@ -1676,7 +1682,7 @@ static int PatchWeapon (int weapNum) const FDecalTemplate *decal = DecalLibrary.GetDecalByName (Line2); if (decal != NULL) { - info->DecalGenerator = const_cast (decal); + if (info) info->DecalGenerator = const_cast (decal); } else { @@ -1685,12 +1691,15 @@ static int PatchWeapon (int weapNum) } else if (stricmp (Line1, "Ammo use") == 0 || stricmp (Line1, "Ammo per shot") == 0) { - info->AmmoUse1 = val; - info->flags6 |= MF6_INTRYMOVE; // flag the weapon for postprocessing (reuse a flag that can't be set by external means) + if (info) + { + info->IntVar(NAME_AmmoUse1) = val; + info->flags6 |= MF6_INTRYMOVE; // flag the weapon for postprocessing (reuse a flag that can't be set by external means) + } } else if (stricmp (Line1, "Min ammo") == 0) { - info->MinAmmo1 = val; + if (info) info->IntVar(NAME_MinSelAmmo1) = val; } else { @@ -1698,14 +1707,17 @@ static int PatchWeapon (int weapNum) } } - if (info->AmmoType1 == NULL) + if (info) { - info->AmmoUse1 = 0; - } + if (info->PointerVar(NAME_AmmoType1) == nullptr) + { + info->IntVar(NAME_AmmoUse1) = 0; + } - if (patchedStates) - { - statedef.InstallStates(type, info); + if (patchedStates) + { + statedef.InstallStates(type, info); + } } return result; @@ -2117,7 +2129,7 @@ static int PatchCodePtrs (int dummy) // This skips the action table and goes directly to the internal symbol table // DEH compatible functions are easy to recognize. - PFunction *sym = dyn_cast(RUNTIME_CLASS(AWeapon)->FindSymbol(symname, true)); + PFunction *sym = dyn_cast(PClass::FindActor(NAME_Weapon)->FindSymbol(symname, true)); if (sym == NULL) { Printf(TEXTCOLOR_RED "Frame %d: Unknown code pointer '%s'\n", frame, Line2); @@ -2724,6 +2736,7 @@ static bool LoadDehSupp () sc.OpenLumpNum(lump); sc.SetCMode(true); + auto wcls = PClass::FindActor(NAME_Weapon); while (sc.GetString()) { if (sc.Compare("ActionList")) @@ -2738,11 +2751,11 @@ static bool LoadDehSupp () } else { - // all relevant code pointers are either defined in AWeapon - // or AActor so this will find all of them. + // all relevant code pointers are either defined in Weapon + // or Actor so this will find all of them. FString name = "A_"; name << sc.String; - PFunction *sym = dyn_cast(RUNTIME_CLASS(AWeapon)->FindSymbol(name, true)); + PFunction *sym = dyn_cast(wcls->FindSymbol(name, true)); if (sym == NULL) { sc.ScriptError("Unknown code pointer '%s'", sc.String); @@ -3084,9 +3097,10 @@ void FinishDehPatch () // Now it gets nasty: We have to fiddle around with the weapons' ammo use info to make Doom's original // ammo consumption work as intended. + auto wcls = PClass::FindActor(NAME_Weapon); for(unsigned i = 0; i < WeaponNames.Size(); i++) { - AWeapon *weap = (AWeapon*)GetDefaultByType(WeaponNames[i]); + AInventory *weap = (AInventory*)GetDefaultByType(WeaponNames[i]); bool found = false; if (weap->flags6 & MF6_INTRYMOVE) { @@ -3095,8 +3109,8 @@ void FinishDehPatch () } else { - weap->WeaponFlags |= WIF_DEHAMMO; - weap->AmmoUse1 = 0; + weap->BoolVar(NAME_bDehAmmo) = true; + weap->IntVar(NAME_AmmoUse1) = 0; // to allow proper checks in CheckAmmo we have to find the first attack pointer in the Fire sequence // and set its default ammo use as the weapon's AmmoUse1. @@ -3115,7 +3129,7 @@ void FinishDehPatch () { if (AmmoPerAttacks[j].ptr == nullptr) { - auto p = dyn_cast(RUNTIME_CLASS(AWeapon)->FindSymbol(AmmoPerAttacks[j].func, true)); + auto p = dyn_cast(wcls->FindSymbol(AmmoPerAttacks[j].func, true)); if (p != nullptr) AmmoPerAttacks[j].ptr = p->Variants[0].Implementation; } if (state->ActionFunc == AmmoPerAttacks[j].ptr) @@ -3123,7 +3137,7 @@ void FinishDehPatch () found = true; int use = AmmoPerAttacks[j].ammocount; if (use < 0) use = deh.BFGCells; - weap->AmmoUse1 = use; + weap->IntVar(NAME_AmmoUse1) = use; break; } } diff --git a/src/d_net.cpp b/src/d_net.cpp index 85a093cb8..4c9f09fb3 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -2335,7 +2335,7 @@ void Net_DoCommand (int type, uint8_t **stream, int player) { if (GetDefaultByType (typeinfo)->flags & MF_MISSILE) { - P_SpawnPlayerMissile (source, typeinfo); + P_SpawnPlayerMissile (source, 0, 0, 0, typeinfo, source->Angles.Yaw); } else { @@ -2536,10 +2536,10 @@ void Net_DoCommand (int type, uint8_t **stream, int player) case DEM_MORPHEX: { s = ReadString (stream); - const char *msg = cht_Morph (players + player, PClass::FindActor (s), false); + FString msg = cht_Morph (players + player, PClass::FindActor (s), false); if (player == consoleplayer) { - Printf ("%s\n", *msg != '\0' ? msg : "Morph failed."); + Printf ("%s\n", msg[0] != '\0' ? msg.GetChars() : "Morph failed."); } } break; @@ -2631,7 +2631,7 @@ void Net_DoCommand (int type, uint8_t **stream, int player) int count = ReadByte(stream); if (slot < NUM_WEAPON_SLOTS) { - players[pnum].weapons.Slots[slot].Clear(); + players[pnum].weapons.ClearSlot(slot); } for(i = 0; i < count; ++i) { diff --git a/src/d_player.h b/src/d_player.h index ecb3a71ea..67c409ba3 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -92,20 +92,15 @@ public: virtual void AddInventory (AInventory *item) override; virtual void RemoveInventory (AInventory *item) override; virtual bool UseInventory (AInventory *item) override; - virtual void MarkPrecacheSounds () const override; virtual void BeginPlay () override; - virtual void Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath) override; virtual bool UpdateWaterLevel (bool splash) override; bool ResetAirSupply (bool playgasp = true); int GetMaxHealth(bool withupgrades = false) const; - void ActivateMorphWeapon (); - AWeapon *PickNewWeapon (PClassActor *ammotype); - AWeapon *BestWeapon (PClassActor *ammotype); + AInventory *PickNewWeapon (PClassActor *ammotype); + AInventory *BestWeapon (PClassActor *ammotype); void GiveDeathmatchInventory (); - void FilterCoopRespawnInventory (APlayerPawn *oldplayer); - - void SetupWeaponSlots (); + void GiveDefaultInventory (); // These are virtual on the script side only. @@ -155,6 +150,7 @@ public: // [SP] ViewBob Multiplier double ViewBob; + double curBob; // Former class properties that were moved into the object to get rid of the meta class. FName SoundClass; // Sound class @@ -235,7 +231,7 @@ enum // The VM cannot deal with this as an invalid pointer because it performs a read barrier on every object pointer read. // This doesn't have to point to a valid weapon, though, because WP_NOCHANGE is never dereferenced, but it must point to a valid object // and the class descriptor just works fine for that. -extern AWeapon *WP_NOCHANGE; +extern AInventory *WP_NOCHANGE; #define MAXPLAYERNAME 15 @@ -421,8 +417,8 @@ public: uint8_t spreecount = 0; // [RH] Keep track of killing sprees uint16_t WeaponState = 0; - AWeapon *ReadyWeapon = nullptr; - AWeapon *PendingWeapon = nullptr; // WP_NOCHANGE if not changing + AInventory *ReadyWeapon = nullptr; + AInventory *PendingWeapon = nullptr; // WP_NOCHANGE if not changing TObjPtr psprites = nullptr; // view sprites (gun, etc) int cheats = 0; // bit flags @@ -446,8 +442,8 @@ public: int morphTics = 0; // player is a chicken/pig if > 0 PClassActor *MorphedPlayerClass = nullptr; // [MH] (for SBARINFO) class # for this player instance when morphed int MorphStyle = 0; // which effects to apply for this player instance when morphed - PClassActor *MorphExitFlash = nullptr; // flash to apply when demorphing (cache of value given to P_MorphPlayer) - TObjPtr PremorphWeapon = nullptr; // ready weapon before morphing + PClassActor *MorphExitFlash = nullptr; // flash to apply when demorphing (cache of value given to MorphPlayer) + TObjPtr PremorphWeapon = nullptr; // ready weapon before morphing int chickenPeck = 0; // chicken peck countdown int jumpTics = 0; // delay the next jump for a moment bool onground = 0; // Identifies if this player is on the ground or other object diff --git a/src/dobject.h b/src/dobject.h index fb561e2c6..ff885d4ca 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -426,6 +426,8 @@ template T *dyn_cast(DObject *p) return NULL; } + + template const T *dyn_cast(const DObject *p) { return dyn_cast(const_cast(p)); diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index 9d2d6f438..d96695cee 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -75,7 +75,7 @@ bool PClass::bVMOperational; // that does not work anymore. WP_NOCHANGE needs to point to a vaild object to work as intended. // This Object does not need to be garbage collected, though, but it needs to provide the proper structure so that the // GC can process it. -AWeapon *WP_NOCHANGE; +AInventory *WP_NOCHANGE; DEFINE_GLOBAL(WP_NOCHANGE); @@ -231,7 +231,7 @@ void PClass::StaticInit () // WP_NOCHANGE must point to a valid object, although it does not need to be a weapon. // A simple DObject is enough to give the GC the ability to deal with it, if subjected to it. - WP_NOCHANGE = (AWeapon*)Create(); + WP_NOCHANGE = (AInventory*)Create(); WP_NOCHANGE->Release(); } diff --git a/src/fragglescript/t_func.cpp b/src/fragglescript/t_func.cpp index a872029d8..b8799505e 100644 --- a/src/fragglescript/t_func.cpp +++ b/src/fragglescript/t_func.cpp @@ -48,6 +48,7 @@ #include "r_utility.h" #include "g_levellocals.h" #include "actorinlines.h" +#include "scriptutil.h" static FRandom pr_script("FScript"); @@ -283,6 +284,17 @@ static int T_GetPlayerNum(const svalue_t &arg) return playernum; } +APlayerPawn *T_GetPlayerActor(const svalue_t &arg) +{ + int num = T_GetPlayerNum(arg); + return num == -1 ? nullptr : players[num].mo; +} + +PClassActor *T_ClassType(const svalue_t &arg) +{ + return PClass::FindActor(stringvalue(arg)); +} + //========================================================================== // // Finds a sector from a tag. This has been extended to allow looking for @@ -2576,50 +2588,11 @@ void FParser::SF_PlayerAmmo(void) void FParser::SF_MaxPlayerAmmo() { - int playernum, amount; - PClassActor * ammotype; - if (CheckArgs(2)) { - playernum=T_GetPlayerNum(t_argv[0]); - if (playernum==-1) return; - - ammotype=T_GetAmmo(t_argv[1]); - if (!ammotype) return; - - if(t_argc == 2) - { - } - else if(t_argc >= 3) - { - auto iammo = players[playernum].mo->FindInventory(ammotype); - amount = intvalue(t_argv[2]); - if(amount < 0) amount = 0; - if (!iammo) - { - players[playernum].mo->GiveAmmo(ammotype, 1); - iammo = players[playernum].mo->FindInventory(ammotype); - iammo->Amount = 0; - } - iammo->MaxAmount = amount; - - - for (AInventory *item = players[playernum].mo->Inventory; item != NULL; item = item->Inventory) - { - if (item->IsKindOf(NAME_BackpackItem)) - { - if (t_argc>=4) amount = intvalue(t_argv[3]); - else amount*=2; - break; - } - } - iammo->IntVar("BackpackMaxAmount") = amount; - } - t_return.type = svt_int; - AInventory * iammo = players[playernum].mo->FindInventory(ammotype); - if (iammo) t_return.value.i = iammo->MaxAmount; - else t_return.value.i = ((AInventory*)GetDefaultByType(ammotype))->MaxAmount; + t_return.value.i = ScriptUtil::Exec("MaxPlayerAmmo", ScriptUtil::Pointer, T_GetPlayerActor(t_argv[0]), ScriptUtil::Class, T_ClassType(t_argv[1]), + ScriptUtil::Int, t_argc >= 3? intvalue(t_argv[2]) : INT_MIN, ScriptUtil::Int, t_argc >= 4 ? intvalue(t_argv[3]) : INT_MIN, ScriptUtil::End); } } @@ -2740,7 +2713,7 @@ void FParser::SF_PlayerSelectedWeapon() return; } - players[playernum].PendingWeapon = (AWeapon*)players[playernum].mo->FindInventory(ti); + players[playernum].PendingWeapon = (AInventory*)players[playernum].mo->FindInventory(ti); } t_return.type = svt_int; @@ -2834,34 +2807,8 @@ void FParser::SF_SetWeapon() { if (CheckArgs(2)) { - int playernum=T_GetPlayerNum(t_argv[0]); - if (playernum!=-1) - { - AInventory *item = players[playernum].mo->FindInventory (PClass::FindActor (stringvalue(t_argv[1]))); - - if (item == NULL || !item->IsKindOf(NAME_Weapon)) - { - } - else if (players[playernum].ReadyWeapon == item) - { - // The weapon is already selected, so setweapon succeeds by default, - // but make sure the player isn't switching away from it. - players[playernum].PendingWeapon = WP_NOCHANGE; - t_return.value.i = 1; - } - else - { - auto weap = static_cast (item); - - if (weap->CheckAmmo (AWeapon::EitherFire, false)) - { - // There's enough ammo, so switch to it. - t_return.value.i = 1; - players[playernum].PendingWeapon = weap; - } - } - } - t_return.value.i = 0; + t_return.type = svt_int; + t_return.value.i = ScriptUtil::Exec(NAME_SetWeapon, ScriptUtil::Pointer, T_GetPlayerActor(t_argv[0]), ScriptUtil::Class, T_ClassType(t_argv[1]), ScriptUtil::End); } } diff --git a/src/g_game.cpp b/src/g_game.cpp index 9be9b5455..1c4304f51 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -281,10 +281,16 @@ CCMD (slot) { int slot = atoi (argv[1]); - if (slot < NUM_WEAPON_SLOTS) + auto mo = players[consoleplayer].mo; + if (slot < NUM_WEAPON_SLOTS && mo) { - SendItemUse = players[consoleplayer].weapons.Slots[slot].PickWeapon (&players[consoleplayer], - !(dmflags2 & DF2_DONTCHECKAMMO)); + // Needs to be redone + IFVIRTUALPTR(mo, APlayerPawn, PickWeapon) + { + VMValue param[] = { mo, slot, !(dmflags2 & DF2_DONTCHECKAMMO) }; + VMReturn ret((void**)&SendItemUse); + VMCall(func, param, 3, &ret, 1); + } } } } @@ -316,7 +322,18 @@ CCMD (turn180) CCMD (weapnext) { - SendItemUse = players[consoleplayer].weapons.PickNextWeapon (&players[consoleplayer]); + auto mo = players[consoleplayer].mo; + if (mo) + { + // Needs to be redone + IFVIRTUALPTR(mo, APlayerPawn, PickNextWeapon) + { + VMValue param[] = { mo }; + VMReturn ret((void**)&SendItemUse); + VMCall(func, param, 1, &ret, 1); + } + } + // [BC] Option to display the name of the weapon being cycled to. if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse) { @@ -331,7 +348,18 @@ CCMD (weapnext) CCMD (weapprev) { - SendItemUse = players[consoleplayer].weapons.PickPrevWeapon (&players[consoleplayer]); + auto mo = players[consoleplayer].mo; + if (mo) + { + // Needs to be redone + IFVIRTUALPTR(mo, APlayerPawn, PickPrevWeapon) + { + VMValue param[] = { mo }; + VMReturn ret((void**)&SendItemUse); + VMCall(func, param, 1, &ret, 1); + } + } + // [BC] Option to display the name of the weapon being cycled to. if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse) { @@ -765,19 +793,28 @@ void G_BuildTiccmd (ticcmd_t *cmd) //[Graf Zahl] This really helps if the mouse update rate can't be increased! CVAR (Bool, smooth_mouse, false, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) +static int LookAdjust(int look) +{ + look <<= 16; + if (players[consoleplayer].playerstate != PST_DEAD && // No adjustment while dead. + players[consoleplayer].ReadyWeapon != NULL) // No adjustment if no weapon. + { + auto scale = players[consoleplayer].ReadyWeapon->FloatVar(NAME_FOVScale); + if (scale > 0) // No adjustment if it is non-positive. + { + look = int(look * scale); + } + } + return look; +} + void G_AddViewPitch (int look, bool mouse) { if (gamestate == GS_TITLELEVEL) { return; } - look <<= 16; - if (players[consoleplayer].playerstate != PST_DEAD && // No adjustment while dead. - players[consoleplayer].ReadyWeapon != NULL && // No adjustment if no weapon. - players[consoleplayer].ReadyWeapon->FOVScale > 0) // No adjustment if it is non-positive. - { - look = int(look * players[consoleplayer].ReadyWeapon->FOVScale); - } + look = LookAdjust(look); if (!level.IsFreelookAllowed()) { LocalViewPitch = 0; @@ -817,14 +854,9 @@ void G_AddViewAngle (int yaw, bool mouse) if (gamestate == GS_TITLELEVEL) { return; + } - yaw <<= 16; - if (players[consoleplayer].playerstate != PST_DEAD && // No adjustment while dead. - players[consoleplayer].ReadyWeapon != NULL && // No adjustment if no weapon. - players[consoleplayer].ReadyWeapon->FOVScale > 0) // No adjustment if it is non-positive. - { - yaw = int(yaw * players[consoleplayer].ReadyWeapon->FOVScale); - } + yaw = LookAdjust(yaw); LocalViewAngle -= yaw; if (yaw != 0) { @@ -1249,7 +1281,7 @@ void G_PlayerFinishLevel (int player, EFinishLevelType mode, int flags) if (p->morphTics != 0) { // Undo morph - P_UndoPlayerMorph (p, p, 0, true); + P_UnmorphActor(p->mo, p->mo, 0, true); } // Strip all current powers, unless moving in a hub and the power is okay to keep. @@ -1269,8 +1301,8 @@ void G_PlayerFinishLevel (int player, EFinishLevelType mode, int flags) item = next; } if (p->ReadyWeapon != NULL && - p->ReadyWeapon->WeaponFlags&WIF_POWERED_UP && - p->PendingWeapon == p->ReadyWeapon->SisterWeapon) + p->ReadyWeapon->IntVar(NAME_WeaponFlags) & WIF_POWERED_UP && + p->PendingWeapon == p->ReadyWeapon->PointerVar(NAME_SisterWeapon)) { // Unselect powered up weapons if the unpowered counterpart is pending p->ReadyWeapon=p->PendingWeapon; diff --git a/src/g_inventory/a_pickups.cpp b/src/g_inventory/a_pickups.cpp index 6ec1a4e84..17a112a98 100644 --- a/src/g_inventory/a_pickups.cpp +++ b/src/g_inventory/a_pickups.cpp @@ -54,12 +54,6 @@ EXTERN_CVAR(Bool, sv_unlimited_pickup) -void AInventory::Finalize(FStateDefinitions &statedef) -{ - Super::Finalize(statedef); - flags |= MF_SPECIAL; -} - IMPLEMENT_CLASS(AInventory, false, true) IMPLEMENT_POINTERS_START(AInventory) @@ -161,152 +155,6 @@ bool AInventory::Massacre() return false; } -//=========================================================================== -// -// AInventory :: MarkPrecacheSounds -// -//=========================================================================== - -void AInventory::MarkPrecacheSounds() const -{ - Super::MarkPrecacheSounds(); - PickupSound.MarkUsed(); -} - -//=========================================================================== -// -// AInventory :: BecomeItem -// -// Lets this actor know that it's about to be placed in an inventory. -// -//=========================================================================== - -void AInventory::BecomeItem () -{ - if (!(flags & (MF_NOBLOCKMAP|MF_NOSECTOR))) - { - UnlinkFromWorld (nullptr); - flags |= MF_NOBLOCKMAP|MF_NOSECTOR; - LinkToWorld (nullptr); - } - RemoveFromHash (); - flags &= ~MF_SPECIAL; - ChangeStatNum(STAT_INVENTORY); - // stop all sounds this item is playing. - for(int i = 1;i<=7;i++) S_StopSound(this, i); - SetState (FindState("Held")); -} - -DEFINE_ACTION_FUNCTION(AInventory, BecomeItem) -{ - PARAM_SELF_PROLOGUE(AInventory); - self->BecomeItem(); - return 0; -} - -//=========================================================================== -// -// AInventory :: BecomePickup -// -// Lets this actor know it should wait to be picked up. -// -//=========================================================================== - -void AInventory::BecomePickup () -{ - if (Owner != NULL) - { - Owner->RemoveInventory (this); - } - if (flags & (MF_NOBLOCKMAP|MF_NOSECTOR)) - { - UnlinkFromWorld (nullptr); - flags &= ~(MF_NOBLOCKMAP|MF_NOSECTOR); - LinkToWorld (nullptr); - P_FindFloorCeiling (this); - } - flags = (GetDefault()->flags | MF_DROPPED) & ~MF_COUNTITEM; - renderflags &= ~RF_INVISIBLE; - ChangeStatNum(STAT_DEFAULT); - SetState (SpawnState); -} - -DEFINE_ACTION_FUNCTION(AInventory, BecomePickup) -{ - PARAM_SELF_PROLOGUE(AInventory); - self->BecomePickup(); - return 0; -} - -//=========================================================================== -// -// AInventory :: GetSpeedFactor -// -//=========================================================================== - -double AInventory::GetSpeedFactor() -{ - double factor = 1.; - auto self = this; - while (self != nullptr) - { - IFVIRTUALPTR(self, AInventory, GetSpeedFactor) - { - VMValue params[1] = { (DObject*)self }; - double retval; - VMReturn ret(&retval); - VMCall(func, params, 1, &ret, 1); - factor *= retval; - } - self = self->Inventory; - } - return factor; -} - -//=========================================================================== -// -// AInventory :: GetNoTeleportFreeze -// -//=========================================================================== - -bool AInventory::GetNoTeleportFreeze () -{ - auto self = this; - while (self != nullptr) - { - IFVIRTUALPTR(self, AInventory, GetNoTeleportFreeze) - { - VMValue params[1] = { (DObject*)self }; - int retval; - VMReturn ret(&retval); - VMCall(func, params, 1, &ret, 1); - if (retval) return true; - } - self = self->Inventory; - } - return false; -} - -//=========================================================================== -// -// AInventory :: Use -// -//=========================================================================== - -bool AInventory::CallUse(bool pickup) -{ - IFVIRTUAL(AInventory, Use) - { - VMValue params[2] = { (DObject*)this, pickup }; - int retval; - VMReturn ret(&retval); - VMCall(func, params, 2, &ret, 1); - return !!retval; - } - return false; -} - - //=========================================================================== // // @@ -391,25 +239,6 @@ PalEntry AInventory::CallGetBlend() else return 0; } -//=========================================================================== -// -// AInventory :: PrevItem -// -// Returns the previous item. -// -//=========================================================================== - -AInventory *AInventory::PrevItem () -{ - AInventory *item = Owner->Inventory; - - while (item != NULL && item->Inventory != this) - { - item = item->Inventory; - } - return item; -} - //=========================================================================== // // AInventory :: PrevInv @@ -545,10 +374,3 @@ CCMD (targetinv) "the NOBLOCKMAP flag or have height/radius of 0.\n"); } -//=========================================================================== -//=========================================================================== - - - -IMPLEMENT_CLASS(AStateProvider, false, false) - diff --git a/src/g_inventory/a_pickups.h b/src/g_inventory/a_pickups.h index 33b7a40a2..82843f056 100644 --- a/src/g_inventory/a_pickups.h +++ b/src/g_inventory/a_pickups.h @@ -70,9 +70,7 @@ class AInventory : public AActor HAS_OBJECT_POINTERS public: - virtual void Finalize(FStateDefinitions &statedef) override; virtual void Serialize(FSerializer &arc) override; - virtual void MarkPrecacheSounds() const override; virtual void OnDestroy() override; virtual void Tick() override; virtual bool Massacre() override; @@ -80,17 +78,11 @@ public: bool CallTryPickup(AActor *toucher, AActor **toucher_return = NULL); // Wrapper for script function. void DepleteOrDestroy (); // virtual on the script side. - bool CallUse(bool pickup); // virtual on the script side. PalEntry CallGetBlend(); // virtual on the script side. - double GetSpeedFactor(); // virtual on the script side. bool GetNoTeleportFreeze(); // virtual on the script side. - void BecomeItem (); - void BecomePickup (); - bool DoRespawn(); - AInventory *PrevItem(); // Returns the item preceding this one in the list. AInventory *PrevInv(); // Returns the previous item with IF_INVBAR set. AInventory *NextInv(); // Returns the next item with IF_INVBAR set. @@ -110,11 +102,4 @@ public: FSoundIDNoInit PickupSound; }; -class AStateProvider : public AInventory -{ - DECLARE_CLASS (AStateProvider, AInventory) -public: - bool CallStateChain(AActor *actor, FState *state); -}; - #endif //__A_PICKUPS_H__ diff --git a/src/g_inventory/a_weapons.cpp b/src/g_inventory/a_weapons.cpp index a72a10d7e..0e2f1b6ab 100644 --- a/src/g_inventory/a_weapons.cpp +++ b/src/g_inventory/a_weapons.cpp @@ -47,49 +47,6 @@ #include "serializer.h" #include "vm.h" -IMPLEMENT_CLASS(AWeapon, false, true) - -IMPLEMENT_POINTERS_START(AWeapon) - IMPLEMENT_POINTER(Ammo1) - IMPLEMENT_POINTER(Ammo2) - IMPLEMENT_POINTER(SisterWeapon) -IMPLEMENT_POINTERS_END - -DEFINE_FIELD(AWeapon, AmmoType1) -DEFINE_FIELD(AWeapon, AmmoType2) -DEFINE_FIELD(AWeapon, AmmoGive1) -DEFINE_FIELD(AWeapon, AmmoGive2) -DEFINE_FIELD(AWeapon, MinAmmo1) -DEFINE_FIELD(AWeapon, MinAmmo2) -DEFINE_FIELD(AWeapon, AmmoUse1) -DEFINE_FIELD(AWeapon, AmmoUse2) -DEFINE_FIELD(AWeapon, Kickback) -DEFINE_FIELD(AWeapon, YAdjust) -DEFINE_FIELD(AWeapon, UpSound) -DEFINE_FIELD(AWeapon, ReadySound) -DEFINE_FIELD(AWeapon, SisterWeaponType) -DEFINE_FIELD(AWeapon, ProjectileType) -DEFINE_FIELD(AWeapon, AltProjectileType) -DEFINE_FIELD(AWeapon, SelectionOrder) -DEFINE_FIELD(AWeapon, MinSelAmmo1) -DEFINE_FIELD(AWeapon, MinSelAmmo2) -DEFINE_FIELD(AWeapon, MoveCombatDist) -DEFINE_FIELD(AWeapon, ReloadCounter) -DEFINE_FIELD(AWeapon, BobStyle) -DEFINE_FIELD(AWeapon, BobSpeed) -DEFINE_FIELD(AWeapon, BobRangeX) -DEFINE_FIELD(AWeapon, BobRangeY) -DEFINE_FIELD(AWeapon, Ammo1) -DEFINE_FIELD(AWeapon, Ammo2) -DEFINE_FIELD(AWeapon, SisterWeapon) -DEFINE_FIELD(AWeapon, FOVScale) -DEFINE_FIELD(AWeapon, Crosshair) -DEFINE_FIELD(AWeapon, GivenAsMorphWeapon) -DEFINE_FIELD(AWeapon, bAltFire) -DEFINE_FIELD(AWeapon, SlotNumber) -DEFINE_FIELD(AWeapon, WeaponFlags) -DEFINE_FIELD_BIT(AWeapon, WeaponFlags, bDehAmmo, WIF_DEHAMMO) - //=========================================================================== // // @@ -105,368 +62,6 @@ TMap Weapons_hton; static int ntoh_cmp(const void *a, const void *b); -//=========================================================================== -// -// -// -//=========================================================================== - -void AWeapon::Finalize(FStateDefinitions &statedef) -{ - Super::Finalize(statedef); - FState *ready = FindState(NAME_Ready); - FState *select = FindState(NAME_Select); - FState *deselect = FindState(NAME_Deselect); - FState *fire = FindState(NAME_Fire); - auto TypeName = GetClass()->TypeName; - - // Consider any weapon without any valid state abstract and don't output a warning - // This is for creating base classes for weapon groups that only set up some properties. - if (ready || select || deselect || fire) - { - if (!ready) - { - I_Error("Weapon %s doesn't define a ready state.", TypeName.GetChars()); - } - if (!select) - { - I_Error("Weapon %s doesn't define a select state.", TypeName.GetChars()); - } - if (!deselect) - { - I_Error("Weapon %s doesn't define a deselect state.", TypeName.GetChars()); - } - if (!fire) - { - I_Error("Weapon %s doesn't define a fire state.", TypeName.GetChars()); - } - } -} - - -//=========================================================================== -// -// AWeapon :: Serialize -// -//=========================================================================== - -void AWeapon::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - auto def = (AWeapon*)GetDefault(); - arc("weaponflags", WeaponFlags, def->WeaponFlags) - ("ammogive1", AmmoGive1, def->AmmoGive1) - ("ammogive2", AmmoGive2, def->AmmoGive2) - ("minammo1", MinAmmo1, def->MinAmmo1) - ("minammo2", MinAmmo2, def->MinAmmo2) - ("ammouse1", AmmoUse1, def->AmmoUse1) - ("ammouse2", AmmoUse2, def->AmmoUse2) - ("kickback", Kickback, Kickback) - ("yadjust", YAdjust, def->YAdjust) - ("upsound", UpSound, def->UpSound) - ("readysound", ReadySound, def->ReadySound) - ("selectionorder", SelectionOrder, def->SelectionOrder) - ("ammo1", Ammo1) - ("ammo2", Ammo2) - ("sisterweapon", SisterWeapon) - ("givenasmorphweapon", GivenAsMorphWeapon, def->GivenAsMorphWeapon) - ("altfire", bAltFire, def->bAltFire) - ("reloadcounter", ReloadCounter, def->ReloadCounter) - ("bobstyle", BobStyle, def->BobStyle) - ("bobspeed", BobSpeed, def->BobSpeed) - ("bobrangex", BobRangeX, def->BobRangeX) - ("bobrangey", BobRangeY, def->BobRangeY) - ("fovscale", FOVScale, def->FOVScale) - ("crosshair", Crosshair, def->Crosshair) - ("minselammo1", MinSelAmmo1, def->MinSelAmmo1) - ("minselammo2", MinSelAmmo2, def->MinSelAmmo2); - /* these can never change - ("ammotype1", AmmoType1, def->AmmoType1) - ("ammotype2", AmmoType2, def->AmmoType2) - ("sisterweapontype", SisterWeaponType, def->SisterWeaponType) - ("projectiletype", ProjectileType, def->ProjectileType) - ("altprojectiletype", AltProjectileType, def->AltProjectileType) - ("movecombatdist", MoveCombatDist, def->MoveCombatDist) - */ - -} - -//=========================================================================== -// -// AWeapon :: MarkPrecacheSounds -// -//=========================================================================== - -void AWeapon::MarkPrecacheSounds() const -{ - Super::MarkPrecacheSounds(); - UpSound.MarkUsed(); - ReadySound.MarkUsed(); -} - -//=========================================================================== -// -// AWeapon :: CheckAmmo -// -// Returns true if there is enough ammo to shoot. If not, selects the -// next weapon to use. -// -//=========================================================================== - -bool AWeapon::CheckAmmo(int fireMode, bool autoSwitch, bool requireAmmo, int ammocount) -{ - IFVIRTUAL(AWeapon, CheckAmmo) - { - VMValue params[] = { (DObject*)this, fireMode, autoSwitch, requireAmmo, ammocount }; - VMReturn ret; - int retval; - ret.IntAt(&retval); - VMCall(func, params, 5, &ret, 1); - return !!retval; - } - return DoCheckAmmo(fireMode, autoSwitch, requireAmmo, ammocount); -} - -bool AWeapon::DoCheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo, int ammocount) -{ - int altFire; - int count1, count2; - int enough, enoughmask; - int lAmmoUse1; - - if ((dmflags & DF_INFINITE_AMMO) || (Owner->FindInventory (PClass::FindActor(NAME_PowerInfiniteAmmo), true) != nullptr)) - { - return true; - } - if (fireMode == EitherFire) - { - bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false); - if (!gotSome && autoSwitch) - { - barrier_cast(Owner)->PickNewWeapon (nullptr); - } - return gotSome; - } - altFire = (fireMode == AltFire); - if (!requireAmmo && (WeaponFlags & (WIF_AMMO_OPTIONAL << altFire))) - { - return true; - } - count1 = (Ammo1 != nullptr) ? Ammo1->Amount : 0; - count2 = (Ammo2 != nullptr) ? Ammo2->Amount : 0; - - if ((WeaponFlags & WIF_DEHAMMO) && (Ammo1 == nullptr)) - { - lAmmoUse1 = 0; - } - else if (ammocount >= 0 && (WeaponFlags & WIF_DEHAMMO)) - { - lAmmoUse1 = ammocount; - } - else - { - lAmmoUse1 = AmmoUse1; - } - - enough = (count1 >= lAmmoUse1) | ((count2 >= AmmoUse2) << 1); - if (WeaponFlags & (WIF_PRIMARY_USES_BOTH << altFire)) - { - enoughmask = 3; - } - else - { - enoughmask = 1 << altFire; - } - if (altFire && FindState(NAME_AltFire) == nullptr) - { // If this weapon has no alternate fire, then there is never enough ammo for it - enough &= 1; - } - if (((enough & enoughmask) == enoughmask) || (enough && (WeaponFlags & WIF_AMMO_CHECKBOTH))) - { - return true; - } - // out of ammo, pick a weapon to change to - if (autoSwitch) - { - barrier_cast(Owner)->PickNewWeapon (nullptr); - } - return false; -} - -DEFINE_ACTION_FUNCTION(AWeapon, CheckAmmo) -{ - PARAM_SELF_PROLOGUE(AWeapon); - PARAM_INT(mode); - PARAM_BOOL(autoswitch); - PARAM_BOOL(require); - PARAM_INT(ammocnt); - ACTION_RETURN_BOOL(self->DoCheckAmmo(mode, autoswitch, require, ammocnt)); -} - -//=========================================================================== -// -// AWeapon :: DepleteAmmo -// -// Use up some of the weapon's ammo. Returns true if the ammo was successfully -// depleted. If checkEnough is false, then the ammo will always be depleted, -// even if it drops below zero. -// -//=========================================================================== - -bool AWeapon::DepleteAmmo(bool altFire, bool checkEnough, int ammouse) -{ - IFVIRTUAL(AWeapon, DepleteAmmo) - { - VMValue params[] = { (DObject*)this, altFire, checkEnough, ammouse }; - VMReturn ret; - int retval; - ret.IntAt(&retval); - VMCall(func, params, 4, &ret, 1); - return !!retval; - } - return DoDepleteAmmo(altFire, checkEnough, ammouse); -} - -bool AWeapon::DoDepleteAmmo (bool altFire, bool checkEnough, int ammouse) -{ - if (!((dmflags & DF_INFINITE_AMMO) || (Owner->FindInventory (PClass::FindActor(NAME_PowerInfiniteAmmo), true) != nullptr))) - { - if (checkEnough && !CheckAmmo (altFire ? AltFire : PrimaryFire, false, false, ammouse)) - { - return false; - } - if (!altFire) - { - if (Ammo1 != nullptr) - { - if (ammouse >= 0 && (WeaponFlags & WIF_DEHAMMO)) - { - Ammo1->Amount -= ammouse; - } - else - { - Ammo1->Amount -= AmmoUse1; - } - } - if ((WeaponFlags & WIF_PRIMARY_USES_BOTH) && Ammo2 != nullptr) - { - Ammo2->Amount -= AmmoUse2; - } - } - else - { - if (Ammo2 != nullptr) - { - Ammo2->Amount -= AmmoUse2; - } - if ((WeaponFlags & WIF_ALT_USES_BOTH) && Ammo1 != nullptr) - { - Ammo1->Amount -= AmmoUse1; - } - } - if (Ammo1 != nullptr && Ammo1->Amount < 0) - Ammo1->Amount = 0; - if (Ammo2 != nullptr && Ammo2->Amount < 0) - Ammo2->Amount = 0; - } - return true; -} - -DEFINE_ACTION_FUNCTION(AWeapon, DepleteAmmo) -{ - PARAM_SELF_PROLOGUE(AWeapon); - PARAM_BOOL(altfire); - PARAM_BOOL(checkenough); - PARAM_INT(ammouse); - ACTION_RETURN_BOOL(self->DoDepleteAmmo(altfire, checkenough, ammouse)); -} - - -//=========================================================================== -// -// AWeapon :: PostMorphWeapon -// -// Bring this weapon up after a player unmorphs. -// -//=========================================================================== - -void AWeapon::PostMorphWeapon () -{ - DPSprite *pspr; - if (Owner == nullptr) - { - return; - } - Owner->player->PendingWeapon = WP_NOCHANGE; - Owner->player->ReadyWeapon = this; - Owner->player->refire = 0; - - pspr = Owner->player->GetPSprite(PSP_WEAPON); - pspr->y = WEAPONBOTTOM; - pspr->ResetInterpolation(); - pspr->SetState(GetUpState()); -} - -//=========================================================================== -// -// AWeapon :: GetUpState -// -//=========================================================================== - -FState *AWeapon::GetUpState () -{ - IFVIRTUAL(AWeapon, GetUpState) - { - VMValue params[1] = { (DObject*)this }; - VMReturn ret; - FState *retval; - ret.PointerAt((void**)&retval); - VMCall(func, params, 1, &ret, 1); - return retval; - } - return nullptr; -} - -//=========================================================================== -// -// AWeapon :: GetDownState -// -//=========================================================================== - -FState *AWeapon::GetDownState () -{ - IFVIRTUAL(AWeapon, GetDownState) - { - VMValue params[1] = { (DObject*)this }; - VMReturn ret; - FState *retval; - ret.PointerAt((void**)&retval); - VMCall(func, params, 1, &ret, 1); - return retval; - } - return nullptr; -} - -//=========================================================================== -// -// AWeapon :: GetReadyState -// -//=========================================================================== - -FState *AWeapon::GetReadyState () -{ - IFVIRTUAL(AWeapon, GetReadyState) - { - VMValue params[1] = { (DObject*)this }; - VMReturn ret; - FState *retval; - ret.PointerAt((void**)&retval); - VMCall(func, params, 1, &ret, 1); - return retval; - } - return nullptr; -} - /* Weapon slots ***********************************************************/ @@ -558,73 +153,6 @@ int FWeaponSlot::LocateWeapon(PClassActor *type) return -1; } -//=========================================================================== -// -// FWeaponSlot :: PickWeapon -// -// Picks a weapon from this slot. If no weapon is selected in this slot, -// or the first weapon in this slot is selected, returns the last weapon. -// Otherwise, returns the previous weapon in this slot. This means -// precedence is given to the last weapon in the slot, which by convention -// is probably the strongest. Does not return weapons you have no ammo -// for or which you do not possess. -// -//=========================================================================== - -AWeapon *FWeaponSlot::PickWeapon(player_t *player, bool checkammo) -{ - int i, j; - - if (player->mo == nullptr) - { - return nullptr; - } - // Does this slot even have any weapons? - if (Weapons.Size() == 0) - { - return player->ReadyWeapon; - } - if (player->ReadyWeapon != nullptr) - { - for (i = 0; (unsigned)i < Weapons.Size(); i++) - { - if (Weapons[i].Type == player->ReadyWeapon->GetClass() || - (player->ReadyWeapon->WeaponFlags & WIF_POWERED_UP && - player->ReadyWeapon->SisterWeapon != nullptr && - player->ReadyWeapon->SisterWeapon->GetClass() == Weapons[i].Type)) - { - for (j = (i == 0 ? Weapons.Size() - 1 : i - 1); - j != i; - j = (j == 0 ? Weapons.Size() - 1 : j - 1)) - { - AWeapon *weap = static_cast (player->mo->FindInventory(Weapons[j].Type)); - - if (weap != nullptr && weap->IsKindOf(NAME_Weapon)) - { - if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false)) - { - return weap; - } - } - } - } - } - } - for (i = Weapons.Size() - 1; i >= 0; i--) - { - AWeapon *weap = static_cast (player->mo->FindInventory(Weapons[i].Type)); - - if (weap != nullptr && weap->IsKindOf(NAME_Weapon)) - { - if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false)) - { - return weap; - } - } - } - return player->ReadyWeapon; -} - //=========================================================================== // // FWeaponSlot :: SetInitialPositions @@ -765,7 +293,7 @@ bool FWeaponSlots::LocateWeapon (PClassActor *type, int *const slot, int *const DEFINE_ACTION_FUNCTION(FWeaponSlots, LocateWeapon) { PARAM_SELF_STRUCT_PROLOGUE(FWeaponSlots); - PARAM_CLASS(weap, AWeapon); + PARAM_CLASS(weap, AInventory); int slot = 0, index = 0; bool retv = self->LocateWeapon(weap, &slot, &index); if (numret >= 1) ret[0].SetInt(retv); @@ -774,152 +302,21 @@ DEFINE_ACTION_FUNCTION(FWeaponSlots, LocateWeapon) return MIN(numret, 3); } -//=========================================================================== -// -// FindMostRecentWeapon -// -// Locates the slot and index for the most recently selected weapon. If the -// player is in the process of switching to a new weapon, that is the most -// recently selected weapon. Otherwise, the current weapon is the most recent -// weapon. -// -//=========================================================================== - -static bool FindMostRecentWeapon(player_t *player, int *slot, int *index) +DEFINE_ACTION_FUNCTION(FWeaponSlots, GetWeapon) { - if (player->PendingWeapon != WP_NOCHANGE) - { - return player->weapons.LocateWeapon(player->PendingWeapon->GetClass(), slot, index); - } - else if (player->ReadyWeapon != nullptr) - { - AWeapon *weap = player->ReadyWeapon; - if (!player->weapons.LocateWeapon(weap->GetClass(), slot, index)) - { - // If the current weapon wasn't found and is powered up, - // look for its non-powered up version. - if (weap->WeaponFlags & WIF_POWERED_UP && weap->SisterWeaponType != nullptr) - { - return player->weapons.LocateWeapon(weap->SisterWeaponType, slot, index); - } - return false; - } - return true; - } - else - { - return false; - } + PARAM_SELF_STRUCT_PROLOGUE(FWeaponSlots); + PARAM_INT(slot); + PARAM_INT(index); + ACTION_RETURN_POINTER(self->GetWeapon(slot, index)); + return 1; } -//=========================================================================== -// -// FWeaponSlots :: PickNextWeapon -// -// Returns the "next" weapon for this player. If the current weapon is not -// in a slot, then it just returns that weapon, since there's nothing to -// consider it relative to. -// -//=========================================================================== - -AWeapon *FWeaponSlots::PickNextWeapon(player_t *player) +DEFINE_ACTION_FUNCTION(FWeaponSlots, SlotSize) { - int startslot, startindex; - int slotschecked = 0; - - if (player->mo == nullptr) - { - return nullptr; - } - if (player->ReadyWeapon == nullptr || FindMostRecentWeapon(player, &startslot, &startindex)) - { - int slot; - int index; - - if (player->ReadyWeapon == nullptr) - { - startslot = NUM_WEAPON_SLOTS - 1; - startindex = Slots[startslot].Size() - 1; - } - - slot = startslot; - index = startindex; - do - { - if (++index >= Slots[slot].Size()) - { - index = 0; - slotschecked++; - if (++slot >= NUM_WEAPON_SLOTS) - { - slot = 0; - } - } - PClassActor *type = Slots[slot].GetWeapon(index); - AWeapon *weap = static_cast(player->mo->FindInventory(type)); - if (weap != nullptr && weap->CheckAmmo(AWeapon::EitherFire, false)) - { - return weap; - } - } - while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS); - } - return player->ReadyWeapon; -} - -//=========================================================================== -// -// FWeaponSlots :: PickPrevWeapon -// -// Returns the "previous" weapon for this player. If the current weapon is -// not in a slot, then it just returns that weapon, since there's nothing to -// consider it relative to. -// -//=========================================================================== - -AWeapon *FWeaponSlots::PickPrevWeapon (player_t *player) -{ - int startslot, startindex; - int slotschecked = 0; - - if (player->mo == nullptr) - { - return nullptr; - } - if (player->ReadyWeapon == nullptr || FindMostRecentWeapon (player, &startslot, &startindex)) - { - int slot; - int index; - - if (player->ReadyWeapon == nullptr) - { - startslot = 0; - startindex = 0; - } - - slot = startslot; - index = startindex; - do - { - if (--index < 0) - { - slotschecked++; - if (--slot < 0) - { - slot = NUM_WEAPON_SLOTS - 1; - } - index = Slots[slot].Size() - 1; - } - PClassActor *type = Slots[slot].GetWeapon(index); - AWeapon *weap = static_cast(player->mo->FindInventory(type)); - if (weap != nullptr && weap->CheckAmmo(AWeapon::EitherFire, false)) - { - return weap; - } - } - while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS); - } - return player->ReadyWeapon; + PARAM_SELF_STRUCT_PROLOGUE(FWeaponSlots); + PARAM_INT(slot); + ACTION_RETURN_INT(self->SlotSize(slot)); + return 1; } //=========================================================================== @@ -950,18 +347,23 @@ void FWeaponSlots::AddExtraWeapons() { continue; } - auto weapdef = ((AWeapon*)GetDefaultByType(cls)); - auto gf = cls->ActorInfo()->GameFilter; - if ((gf == GAME_Any || (gf & gameinfo.gametype)) && - cls->ActorInfo()->Replacement == nullptr && // Replaced weapons don't get slotted. - !(weapdef->WeaponFlags & WIF_POWERED_UP) && - !LocateWeapon(cls, nullptr, nullptr) // Don't duplicate it if it's already present. - ) + if (LocateWeapon(cls, nullptr, nullptr)) // Do we already have it? Don't add it again. { - int slot = weapdef->SlotNumber; - if ((unsigned)slot < NUM_WEAPON_SLOTS) + continue; + } + auto weapdef = ((AInventory*)GetDefaultByType(cls)); + + // Let the weapon decide for itself if it wants to get added to a slot. + IFVIRTUALPTRNAME(weapdef, NAME_Weapon, CheckAddToSlots) + { + VMValue param = weapdef; + int slot = -1, slotpriority; + VMReturn rets[]{ &slot, &slotpriority }; + VMCall(func, ¶m, 1, rets, 2); + + if (slot >= 0 && slot < NUM_WEAPON_SLOTS) { - FWeaponSlot::WeaponInfo info = { cls, weapdef->SlotPriority }; + FWeaponSlot::WeaponInfo info = { cls, slotpriority }; Slots[slot].Weapons.Push(info); } } @@ -1217,10 +619,10 @@ CCMD (setslot) } else if (PlayingKeyConf != nullptr) { - PlayingKeyConf->Slots[slot].Clear(); + PlayingKeyConf->ClearSlot(slot); for (int i = 2; i < argv.argc(); ++i) { - PlayingKeyConf->Slots[slot].AddWeapon(argv[i]); + PlayingKeyConf->AddWeapon(slot, argv[i]); } } else @@ -1382,6 +784,49 @@ void P_PlaybackKeyConfWeapons(FWeaponSlots *slots) PlayingKeyConf = nullptr; } +//=========================================================================== +// +// APlayerPawn :: SetupWeaponSlots +// +// Sets up the default weapon slots for this player. If this is also the +// local player, determines local modifications and sends those across the +// network. Ignores voodoo dolls. +// +//=========================================================================== + +void FWeaponSlots::SetupWeaponSlots(APlayerPawn *pp) +{ + auto player = pp->player; + if (player != nullptr && player->mo == pp) + { + player->weapons.StandardSetup(pp->GetClass()); + // If we're the local player, then there's a bit more work to do. + // This also applies if we're a bot and this is the net arbitrator. + if (player - players == consoleplayer || + (player->Bot != nullptr && consoleplayer == Net_Arbitrator)) + { + FWeaponSlots local_slots(player->weapons); + if (player->Bot != nullptr) + { // Bots only need weapons from KEYCONF, not INI modifications. + P_PlaybackKeyConfWeapons(&local_slots); + } + else + { + local_slots.LocalSetup(pp->GetClass()); + } + local_slots.SendDifferences(int(player - players), player->weapons); + } + } +} + +DEFINE_ACTION_FUNCTION(FWeaponSlots, SetupWeaponSlots) +{ + PARAM_PROLOGUE; + PARAM_OBJECT(pawn, APlayerPawn); + FWeaponSlots::SetupWeaponSlots(pawn); + return 0; +} + //=========================================================================== // // P_SetupWeapons_ntohton diff --git a/src/g_inventory/a_weapons.h b/src/g_inventory/a_weapons.h index 1e0634ddd..ac2c9f14d 100644 --- a/src/g_inventory/a_weapons.h +++ b/src/g_inventory/a_weapons.h @@ -2,7 +2,7 @@ #include "a_pickups.h" class PClassActor; -class AWeapon; +class APlayerPawn; class FWeaponSlot { @@ -14,7 +14,6 @@ public: bool AddWeapon (const char *type); bool AddWeapon (PClassActor *type); void AddWeaponList (const char *list, bool clear); - AWeapon *PickWeapon (player_t *player, bool checkammo = false); int Size () const { return (int)Weapons.Size(); } int LocateWeapon (PClassActor *type); @@ -55,10 +54,9 @@ struct FWeaponSlots FWeaponSlots() { Clear(); } FWeaponSlots(const FWeaponSlots &other); +private: FWeaponSlot Slots[NUM_WEAPON_SLOTS]; - - AWeapon *PickNextWeapon (player_t *player); - AWeapon *PickPrevWeapon (player_t *player); +public: void Clear (); bool LocateWeapon (PClassActor *type, int *const slot, int *const index); @@ -71,10 +69,53 @@ struct FWeaponSlots void SendDifferences(int playernum, const FWeaponSlots &other); int RestoreSlots (FConfigFile *config, const char *section); void PrintSettings(); + static void SetupWeaponSlots(APlayerPawn *pp); void AddSlot(int slot, PClassActor *type, bool feedback); void AddSlotDefault(int slot, PClassActor *type, bool feedback); + // Abstract access interface to the slots + void AddWeapon(int slot, PClassActor *type) + { + if (slot >= 0 && slot < NUM_WEAPON_SLOTS) + { + Slots[slot].AddWeapon(type); + } + } + + void AddWeapon(int slot, const char *type) + { + if (slot >= 0 && slot < NUM_WEAPON_SLOTS) + { + Slots[slot].AddWeapon(type); + } + } + + void ClearSlot(int slot) + { + if (slot >= 0 && slot < NUM_WEAPON_SLOTS) + { + Slots[slot].Weapons.Clear(); + } + } + + int SlotSize(int slot) const + { + if (slot >= 0 && slot < NUM_WEAPON_SLOTS) + { + return Slots[slot].Weapons.Size(); + } + return 0; + } + + PClassActor *GetWeapon(int slot, int index) const + { + if (slot >= 0 && slot < NUM_WEAPON_SLOTS && (unsigned)index < Slots[slot].Weapons.Size()) + { + return Slots[slot].GetWeapon(index); + } + return nullptr; + } }; void P_PlaybackKeyConfWeapons(FWeaponSlots *slots); @@ -86,74 +127,14 @@ void P_WriteDemoWeaponsChunk(uint8_t **demo); void P_ReadDemoWeaponsChunk(uint8_t **demo); -class AWeapon : public AStateProvider +enum class EBobStyle { - DECLARE_CLASS(AWeapon, AStateProvider) - HAS_OBJECT_POINTERS -public: - uint32_t WeaponFlags; - PClassActor *AmmoType1, *AmmoType2; // Types of ammo used by this weapon - int AmmoGive1, AmmoGive2; // Amount of each ammo to get when picking up weapon - int MinAmmo1, MinAmmo2; // Minimum ammo needed to switch to this weapon - int AmmoUse1, AmmoUse2; // How much ammo to use with each shot - int Kickback; - float YAdjust; // For viewing the weapon fullscreen (visual only so no need to be a double) - FSoundIDNoInit UpSound, ReadySound; // Sounds when coming up and idle - PClassActor *SisterWeaponType; // Another weapon to pick up with this one - PClassActor *ProjectileType; // Projectile used by primary attack - PClassActor *AltProjectileType; // Projectile used by alternate attack - int SelectionOrder; // Lower-numbered weapons get picked first - int MinSelAmmo1, MinSelAmmo2; // Ignore in BestWeapon() if inadequate ammo - double MoveCombatDist; // Used by bots, but do they *really* need it? - int ReloadCounter; // For A_CheckForReload - int BobStyle; // [XA] Bobbing style. Defines type of bobbing (e.g. Normal, Alpha) (visual only so no need to be a double) - float BobSpeed; // [XA] Bobbing speed. Defines how quickly a weapon bobs. - float BobRangeX, BobRangeY; // [XA] Bobbing range. Defines how far a weapon bobs in either direction. - int SlotNumber; - int SlotPriority; - - // In-inventory instance variables - TObjPtr Ammo1, Ammo2; - TObjPtr SisterWeapon; - float FOVScale; - int Crosshair; // 0 to use player's crosshair - bool GivenAsMorphWeapon; - - bool bAltFire; // Set when this weapon's alternate fire is used. - - virtual void MarkPrecacheSounds() const; - - void Finalize(FStateDefinitions &statedef) override; - void Serialize(FSerializer &arc) override; - - void PostMorphWeapon(); - - // scripted virtuals. - FState *GetUpState (); - FState *GetDownState (); - FState *GetReadyState (); - - enum - { - PrimaryFire, - AltFire, - EitherFire - }; - bool CheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo=false, int ammocount = -1); - bool DoCheckAmmo(int fireMode, bool autoSwitch, bool requireAmmo, int ammocount); - bool DepleteAmmo (bool altFire, bool checkEnough=true, int ammouse = -1); - bool DoDepleteAmmo(bool altFire, bool checkEnough, int ammouse); - - enum - { - BobNormal, - BobInverse, - BobAlpha, - BobInverseAlpha, - BobSmooth, - BobInverseSmooth - }; - + BobNormal, + BobInverse, + BobAlpha, + BobInverseAlpha, + BobSmooth, + BobInverseSmooth }; enum @@ -174,16 +155,9 @@ enum WIF_STAFF2_KICKBACK = 0x00002000, // the powered-up Heretic staff has special kickback WIF_NOAUTOAIM = 0x00004000, // this weapon never uses autoaim (useful for ballistic projectiles) WIF_MELEEWEAPON = 0x00008000, // melee weapon. Used by bots and monster AI. - WIF_DEHAMMO = 0x00010000, // Uses Doom's original amount of ammo for the respective attack functions so that old DEHACKED patches work as intended. - // AmmoUse1 will be set to the first attack's ammo use so that checking for empty weapons still works + //WIF_DEHAMMO = 0x00010000, WIF_NODEATHDESELECT = 0x00020000, // Don't jump to the Deselect state when the player dies WIF_NODEATHINPUT = 0x00040000, // The weapon cannot be fired/reloaded/whatever when the player is dead WIF_CHEATNOTWEAPON = 0x08000000, // Give cheat considers this not a weapon (used by Sigil) - - // Flags used only by bot AI: - - WIF_BOT_REACTION_SKILL_THING = 1<<31, // I don't understand this - WIF_BOT_EXPLOSIVE = 1<<30, // weapon fires an explosive - WIF_BOT_BFG = 1<<28, // this is a BFG }; diff --git a/src/g_level.cpp b/src/g_level.cpp index 775d226fa..392e1e63e 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1513,6 +1513,7 @@ void G_InitLevelLocals () level.fogdensity = info->fogdensity; level.outsidefogdensity = info->outsidefogdensity; level.skyfog = info->skyfog; + level.deathsequence = info->deathsequence; level.pixelstretch = info->pixelstretch; @@ -2238,6 +2239,7 @@ DEFINE_FIELD(FLevelLocals, fogdensity) DEFINE_FIELD(FLevelLocals, outsidefogdensity) DEFINE_FIELD(FLevelLocals, skyfog) DEFINE_FIELD(FLevelLocals, pixelstretch) +DEFINE_FIELD(FLevelLocals, deathsequence) DEFINE_FIELD_BIT(FLevelLocals, flags, noinventorybar, LEVEL_NOINVENTORYBAR) DEFINE_FIELD_BIT(FLevelLocals, flags, monsterstelefrag, LEVEL_MONSTERSTELEFRAG) DEFINE_FIELD_BIT(FLevelLocals, flags, actownspecial, LEVEL_ACTOWNSPECIAL) @@ -2304,3 +2306,12 @@ DEFINE_ACTION_FUNCTION(FLevelLocals, ChangeSky) R_InitSkyMap(); return 0; } + +DEFINE_ACTION_FUNCTION(FLevelLocals, StartIntermission) +{ + PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals); + PARAM_NAME(seq); + PARAM_INT(state); + F_StartIntermission(seq, (uint8_t)state); + return 0; +} diff --git a/src/g_levellocals.h b/src/g_levellocals.h index 86050b68c..ffb4aad61 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -175,6 +175,7 @@ struct FLevelLocals int outsidefogdensity; int skyfog; + FName deathsequence; float pixelstretch; float MusicVolume; diff --git a/src/g_shared/a_morph.cpp b/src/g_shared/a_morph.cpp index 95b85349c..d917df3d4 100644 --- a/src/g_shared/a_morph.cpp +++ b/src/g_shared/a_morph.cpp @@ -1,9 +1,6 @@ //----------------------------------------------------------------------------- // -// Copyright 1994-1996 Raven Software -// Copyright 1999-2016 Randy Heit -// Copyright 2002-2016 Christoph Oelckers -// Copyright 2005-2008 Martin Howe +// Copyright 2018 Christoph Oelckers // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -38,711 +35,31 @@ static FRandom pr_morphmonst ("MorphMonster"); -void EndAllPowerupEffects(AInventory *item); -void InitAllPowerupEffects(AInventory *item); -//--------------------------------------------------------------------------- -// -// FUNC P_MorphPlayer -// -// Returns true if the player gets turned into a chicken/pig. -// -// TODO: Allow morphed players to receive weapon sets (not just one weapon), -// since they have their own weapon slots now. -// -//--------------------------------------------------------------------------- - -bool P_MorphPlayer (player_t *activator, player_t *p, PClassActor *spawntype, int duration, int style, PClassActor *enter_flash, PClassActor *exit_flash) +bool P_MorphActor(AActor *activator, AActor *victim, PClassActor *ptype, PClassActor *mtype, int duration, int style, PClassActor *enter_flash, PClassActor *exit_flash) { - AInventory *item; - APlayerPawn *morphed; - APlayerPawn *actor; - - actor = p->mo; - if (actor == nullptr) + IFVIRTUALPTR(victim, AActor, Morph) { - return false; + VMValue params[] = { victim, activator, ptype, mtype, duration, style, enter_flash, exit_flash }; + int retval; + VMReturn ret(&retval); + VMCall(func, params, countof(params), &ret, 1); + return !!retval; } - if (actor->flags3 & MF3_DONTMORPH) - { - return false; - } - if ((p->mo->flags2 & MF2_INVULNERABLE) && ((p != activator) || (!(style & MORPH_WHENINVULNERABLE)))) - { // Immune when invulnerable unless this is a power we activated - return false; - } - if (p->morphTics) - { // Player is already a beast - if ((p->mo->GetClass() == spawntype) - && (p->mo->PlayerFlags & PPF_CANSUPERMORPH) - && (p->morphTics < (((duration) ? duration : MORPHTICS) - TICRATE)) - && (p->mo->FindInventory (PClass::FindActor(NAME_PowerWeaponLevel2), true) == nullptr)) - { // Make a super chicken - p->mo->GiveInventoryType (PClass::FindActor(NAME_PowerWeaponLevel2)); - } - return false; - } - if (p->health <= 0) - { // Dead players cannot morph - return false; - } - if (spawntype == nullptr) - { - return false; - } - if (!spawntype->IsDescendantOf (RUNTIME_CLASS(APlayerPawn))) - { - return false; - } - if (spawntype == p->mo->GetClass()) - { - return false; - } - - morphed = static_cast(Spawn (spawntype, actor->Pos(), NO_REPLACE)); - EndAllPowerupEffects(actor->Inventory); - DObject::StaticPointerSubstitution (actor, morphed); - if ((style & MORPH_TRANSFERTRANSLATION) && !(morphed->flags2 & MF2_DONTTRANSLATE)) - { - morphed->Translation = actor->Translation; - } - if ((actor->tid != 0) && (style & MORPH_NEWTIDBEHAVIOUR)) - { - morphed->tid = actor->tid; - morphed->AddToHash (); - actor->RemoveFromHash (); - actor->tid = 0; - } - morphed->Angles.Yaw = actor->Angles.Yaw; - morphed->target = actor->target; - morphed->tracer = actor->tracer; - morphed->alternative = actor; - morphed->FriendPlayer = actor->FriendPlayer; - morphed->DesignatedTeam = actor->DesignatedTeam; - morphed->Score = actor->Score; - p->PremorphWeapon = p->ReadyWeapon; - morphed->special2 = actor->flags & ~MF_JUSTHIT; - morphed->player = p; - if (actor->renderflags & RF_INVISIBLE) - { - morphed->special2 |= MF_JUSTHIT; - } - if (morphed->ViewHeight > p->viewheight && p->deltaviewheight == 0) - { // If the new view height is higher than the old one, start moving toward it. - p->deltaviewheight = p->GetDeltaViewHeight(); - } - morphed->flags |= actor->flags & (MF_SHADOW|MF_NOGRAVITY); - morphed->flags2 |= actor->flags2 & MF2_FLY; - morphed->flags3 |= actor->flags3 & MF3_GHOST; - AActor *eflash = Spawn(((enter_flash) ? enter_flash : PClass::FindActor("TeleportFog")), actor->PosPlusZ(TELEFOGHEIGHT), ALLOW_REPLACE); - actor->player = nullptr; - actor->alternative = morphed; - actor->flags &= ~(MF_SOLID|MF_SHOOTABLE); - actor->flags |= MF_UNMORPHED; - actor->renderflags |= RF_INVISIBLE; - p->morphTics = (duration) ? duration : MORPHTICS; - - // [MH] Used by SBARINFO to speed up face drawing - p->MorphedPlayerClass = spawntype; - - p->MorphStyle = style; - p->MorphExitFlash = (exit_flash) ? exit_flash : PClass::FindActor("TeleportFog"); - p->health = morphed->health; - p->mo = morphed; - p->Vel.X = p->Vel.Y = 0; - morphed->ObtainInventory (actor); - // Remove all armor - for (item = morphed->Inventory; item != nullptr; ) - { - AInventory *next = item->Inventory; - if (item->IsKindOf (PClass::FindActor(NAME_Armor))) - { - item->DepleteOrDestroy(); - } - item = next; - } - InitAllPowerupEffects(morphed->Inventory); - morphed->ActivateMorphWeapon (); - if (p->camera == actor) - { - p->camera = morphed; - } - morphed->ScoreIcon = actor->ScoreIcon; // [GRB] - if (eflash) - eflash->target = p->mo; - return true; -} - -DEFINE_ACTION_FUNCTION(_PlayerInfo, MorphPlayer) -{ - PARAM_SELF_STRUCT_PROLOGUE(player_t); - PARAM_POINTER(activator, player_t); - PARAM_CLASS(spawntype, APlayerPawn); - PARAM_INT(duration); - PARAM_INT(style); - PARAM_CLASS(enter_flash, AActor); - PARAM_CLASS(exit_flash, AActor); - ACTION_RETURN_BOOL(P_MorphPlayer(activator, self, spawntype, duration, style, enter_flash, exit_flash)); -} - -//---------------------------------------------------------------------------- -// -// FUNC P_UndoPlayerMorph -// -//---------------------------------------------------------------------------- - -bool P_UndoPlayerMorph (player_t *activator, player_t *player, int unmorphflag, bool force) -{ - AWeapon *beastweap; - APlayerPawn *mo; - APlayerPawn *pmo; - - pmo = player->mo; - // [MH] - // Checks pmo as well; the PowerMorph destroyer will - // try to unmorph the player; if the destroyer runs - // because the level or game is ended while morphed, - // by the time it gets executed the morphed player - // pawn instance may have already been destroyed. - if (pmo == nullptr || pmo->alternative == nullptr) - { - return false; - } - - bool DeliberateUnmorphIsOkay = !!(MORPH_STANDARDUNDOING & unmorphflag); - - if ((pmo->flags2 & MF2_INVULNERABLE) // If the player is invulnerable - && ((player != activator) // and either did not decide to unmorph, - || (!((player->MorphStyle & MORPH_WHENINVULNERABLE) // or the morph style does not allow it - || (DeliberateUnmorphIsOkay))))) // (but standard morph styles always allow it), - { // Then the player is immune to the unmorph. - return false; - } - - mo = barrier_cast(pmo->alternative); - mo->SetOrigin (pmo->Pos(), false); - mo->flags |= MF_SOLID; - pmo->flags &= ~MF_SOLID; - if (!force && !P_TestMobjLocation (mo)) - { // Didn't fit - mo->flags &= ~MF_SOLID; - pmo->flags |= MF_SOLID; - player->morphTics = 2*TICRATE; - return false; - } - // No longer using tracer as morph storage. That is what 'alternative' is for. - // If the tracer has changed on the morph, change the original too. - mo->target = pmo->target; - mo->tracer = pmo->tracer; - pmo->player = nullptr; - mo->alternative = pmo->alternative = nullptr; - - // Remove the morph power if the morph is being undone prematurely. - auto pmtype = PClass::FindActor("PowerMorph"); - for (AInventory *item = pmo->Inventory, *next = nullptr; item != nullptr; item = next) - { - next = item->Inventory; - if (item->IsKindOf(pmtype)) - { - item->Destroy(); - } - } - EndAllPowerupEffects(pmo->Inventory); - mo->ObtainInventory (pmo); - DObject::StaticPointerSubstitution (pmo, mo); - if ((pmo->tid != 0) && (player->MorphStyle & MORPH_NEWTIDBEHAVIOUR)) - { - mo->tid = pmo->tid; - mo->AddToHash (); - } - mo->Angles.Yaw = pmo->Angles.Yaw; - mo->player = player; - mo->reactiontime = 18; - mo->flags = ActorFlags::FromInt (pmo->special2) & ~MF_JUSTHIT; - mo->Vel.X = mo->Vel.Y = 0; - player->Vel.Zero(); - mo->Vel.Z = pmo->Vel.Z; - mo->floorz = pmo->floorz; - if (!(pmo->special2 & MF_JUSTHIT)) - { - mo->renderflags &= ~RF_INVISIBLE; - } - mo->flags = (mo->flags & ~(MF_SHADOW|MF_NOGRAVITY)) | (pmo->flags & (MF_SHADOW|MF_NOGRAVITY)); - mo->flags2 = (mo->flags2 & ~MF2_FLY) | (pmo->flags2 & MF2_FLY); - mo->flags3 = (mo->flags3 & ~MF3_GHOST) | (pmo->flags3 & MF3_GHOST); - mo->Score = pmo->Score; - InitAllPowerupEffects(mo->Inventory); - - PClassActor *exit_flash = player->MorphExitFlash; - bool correctweapon = !!(player->MorphStyle & MORPH_LOSEACTUALWEAPON); - bool undobydeathsaves = !!(player->MorphStyle & MORPH_UNDOBYDEATHSAVES); - - player->morphTics = 0; - player->MorphedPlayerClass = 0; - player->MorphStyle = 0; - player->MorphExitFlash = nullptr; - player->viewheight = mo->ViewHeight; - AInventory *level2 = mo->FindInventory (PClass::FindActor(NAME_PowerWeaponLevel2), true); - if (level2 != nullptr) - { - level2->Destroy (); - } - - if ((player->health > 0) || undobydeathsaves) - { - player->health = mo->health = mo->SpawnHealth(); - } - else // killed when morphed so stay dead - { - mo->health = player->health; - } - - player->mo = mo; - if (player->camera == pmo) - { - player->camera = mo; - } - - // [MH] - // If the player that was morphed is the one - // taking events, reset up the face, if any; - // this is only needed for old-skool skins - // and for the original DOOM status bar. - if (player == &players[consoleplayer]) - { - FName face = pmo->Face; - if (face != NAME_None) - { - // Assume root-level base skin to begin with - size_t skinindex = 0; - // If a custom skin was in use, then reload it - // or else the base skin for the player class. - if ((unsigned int)player->userinfo.GetSkin() >= PlayerClasses.Size () && - (unsigned)player->userinfo.GetSkin() < Skins.Size()) - { - - skinindex = player->userinfo.GetSkin(); - } - else if (PlayerClasses.Size () > 1) - { - const PClass *whatami = player->mo->GetClass(); - for (unsigned int i = 0; i < PlayerClasses.Size (); ++i) - { - if (PlayerClasses[i].Type == whatami) - { - skinindex = i; - break; - } - } - } - } - } - - AActor *eflash = nullptr; - if (exit_flash != nullptr) - { - eflash = Spawn(exit_flash, pmo->Vec3Angle(20., mo->Angles.Yaw, TELEFOGHEIGHT), ALLOW_REPLACE); - if (eflash) eflash->target = mo; - } - mo->SetupWeaponSlots(); // Use original class's weapon slots. - beastweap = player->ReadyWeapon; - if (player->PremorphWeapon != nullptr) - { - player->PremorphWeapon->PostMorphWeapon (); - } - else - { - player->ReadyWeapon = player->PendingWeapon = nullptr; - } - if (correctweapon) - { // Better "lose morphed weapon" semantics - PClassActor *morphweapon = PClass::FindActor(pmo->MorphWeapon); - if (morphweapon != nullptr && morphweapon->IsDescendantOf(NAME_Weapon)) - { - AWeapon *OriginalMorphWeapon = static_cast(mo->FindInventory (morphweapon)); - if ((OriginalMorphWeapon != nullptr) && (OriginalMorphWeapon->GivenAsMorphWeapon)) - { // You don't get to keep your morphed weapon. - if (OriginalMorphWeapon->SisterWeapon != nullptr) - { - OriginalMorphWeapon->SisterWeapon->Destroy (); - } - OriginalMorphWeapon->Destroy (); - } - } - } - else // old behaviour (not really useful now) - { // Assumptions made here are no longer valid - if (beastweap != nullptr) - { // You don't get to keep your morphed weapon. - if (beastweap->SisterWeapon != nullptr) - { - beastweap->SisterWeapon->Destroy (); - } - beastweap->Destroy (); - } - } - pmo->Destroy (); - // Restore playerclass armor to its normal amount. - auto hxarmor = mo->FindInventory(NAME_HexenArmor); - if (hxarmor != nullptr) - { - double *Slots = (double*)hxarmor->ScriptVar(NAME_Slots, nullptr); - Slots[4] = mo->HexenArmor[0]; - } - return true; -} - -DEFINE_ACTION_FUNCTION(_PlayerInfo, UndoPlayerMorph) -{ - PARAM_SELF_STRUCT_PROLOGUE(player_t); - PARAM_POINTER_NOT_NULL(player, player_t); - PARAM_INT(unmorphflag); - PARAM_BOOL(force); - ACTION_RETURN_BOOL(P_UndoPlayerMorph(self, player, unmorphflag, force)); -} - -//--------------------------------------------------------------------------- -// -// FUNC P_MorphMonster -// -// Returns true if the monster gets turned into a chicken/pig. -// -//--------------------------------------------------------------------------- - -bool P_MorphMonster (AActor *actor, PClassActor *spawntype, int duration, int style, PClassActor *enter_flash, PClassActor *exit_flash) -{ - AMorphedMonster *morphed; - - if (actor == NULL || actor->player || spawntype == NULL || - actor->flags3 & MF3_DONTMORPH || - !(actor->flags3 & MF3_ISMONSTER) || - !spawntype->IsDescendantOf (PClass::FindActor(NAME_MorphedMonster))) - { - return false; - } - - morphed = static_cast(Spawn (spawntype, actor->Pos(), NO_REPLACE)); - DObject::StaticPointerSubstitution (actor, morphed); - if ((style & MORPH_TRANSFERTRANSLATION) && !(morphed->flags2 & MF2_DONTTRANSLATE)) - { - morphed->Translation = actor->Translation; - } - morphed->tid = actor->tid; - morphed->Angles.Yaw = actor->Angles.Yaw; - morphed->UnmorphedMe = actor; - morphed->Alpha = actor->Alpha; - morphed->RenderStyle = actor->RenderStyle; - morphed->Score = actor->Score; - - morphed->UnmorphTime = level.time + ((duration) ? duration : MORPHTICS) + pr_morphmonst(); - morphed->MorphStyle = style; - morphed->MorphExitFlash = (exit_flash) ? exit_flash : PClass::FindActor("TeleportFog"); - morphed->FlagsSave = actor->flags & ~MF_JUSTHIT; - morphed->special = actor->special; - memcpy (morphed->args, actor->args, sizeof(actor->args)); - morphed->CopyFriendliness (actor, true); - morphed->flags |= actor->flags & MF_SHADOW; - morphed->flags3 |= actor->flags3 & MF3_GHOST; - if (actor->renderflags & RF_INVISIBLE) - { - morphed->FlagsSave |= MF_JUSTHIT; - } - morphed->AddToHash (); - actor->RemoveFromHash (); - actor->special = 0; - actor->tid = 0; - actor->flags &= ~(MF_SOLID|MF_SHOOTABLE); - actor->flags |= MF_UNMORPHED; - actor->renderflags |= RF_INVISIBLE; - AActor *eflash = Spawn(((enter_flash) ? enter_flash : PClass::FindActor("TeleportFog")), actor->PosPlusZ(TELEFOGHEIGHT), ALLOW_REPLACE); - if (eflash) - eflash->target = morphed; - return true; -} - -DEFINE_ACTION_FUNCTION(AActor, MorphMonster) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_CLASS(spawntype, AActor); - PARAM_INT(duration); - PARAM_INT(style); - PARAM_CLASS(enter_flash, AActor); - PARAM_CLASS(exit_flash, AActor); - ACTION_RETURN_BOOL(P_MorphMonster(self, spawntype, duration, style, enter_flash, exit_flash)); -} - -//---------------------------------------------------------------------------- -// -// FUNC P_UndoMonsterMorph -// -// Returns true if the monster unmorphs. -// -//---------------------------------------------------------------------------- - -bool P_UndoMonsterMorph (AMorphedMonster *beast, bool force) -{ - AActor *actor; - - if (beast->UnmorphTime == 0 || - beast->UnmorphedMe == NULL || - beast->flags3 & MF3_STAYMORPHED || - beast->UnmorphedMe->flags3 & MF3_STAYMORPHED) - { - return false; - } - actor = beast->UnmorphedMe; - actor->SetOrigin (beast->Pos(), false); - actor->flags |= MF_SOLID; - beast->flags &= ~MF_SOLID; - ActorFlags6 beastflags6 = beast->flags6; - beast->flags6 &= ~MF6_TOUCHY; - if (!force && !P_TestMobjLocation (actor)) - { // Didn't fit - actor->flags &= ~MF_SOLID; - beast->flags |= MF_SOLID; - beast->flags6 = beastflags6; - beast->UnmorphTime = level.time + 5*TICRATE; // Next try in 5 seconds - return false; - } - actor->Angles.Yaw = beast->Angles.Yaw; - actor->target = beast->target; - actor->FriendPlayer = beast->FriendPlayer; - actor->flags = beast->FlagsSave & ~MF_JUSTHIT; - actor->flags = (actor->flags & ~(MF_FRIENDLY|MF_SHADOW)) | (beast->flags & (MF_FRIENDLY|MF_SHADOW)); - actor->flags3 = (actor->flags3 & ~(MF3_NOSIGHTCHECK|MF3_HUNTPLAYERS|MF3_GHOST)) - | (beast->flags3 & (MF3_NOSIGHTCHECK|MF3_HUNTPLAYERS|MF3_GHOST)); - actor->flags4 = (actor->flags4 & ~MF4_NOHATEPLAYERS) | (beast->flags4 & MF4_NOHATEPLAYERS); - if (!(beast->FlagsSave & MF_JUSTHIT)) - actor->renderflags &= ~RF_INVISIBLE; - actor->health = actor->SpawnHealth(); - actor->Vel = beast->Vel; - actor->tid = beast->tid; - actor->special = beast->special; - actor->Score = beast->Score; - memcpy (actor->args, beast->args, sizeof(actor->args)); - actor->AddToHash (); - beast->UnmorphedMe = NULL; - DObject::StaticPointerSubstitution (beast, actor); - PClassActor *exit_flash = beast->MorphExitFlash; - beast->Destroy (); - AActor *eflash = Spawn(exit_flash, beast->PosPlusZ(TELEFOGHEIGHT), ALLOW_REPLACE); - if (eflash) - eflash->target = actor; - return true; -} - -//---------------------------------------------------------------------------- -// -// FUNC P_UpdateMorphedMonster -// -// Returns true if the monster unmorphs. -// -//---------------------------------------------------------------------------- - -bool P_UpdateMorphedMonster (AMorphedMonster *beast) -{ - if (beast->UnmorphTime > level.time) - { - return false; - } - return P_UndoMonsterMorph (beast); -} - -//---------------------------------------------------------------------------- -// -// FUNC P_MorphedDeath -// -// Unmorphs the actor if possible. -// Returns the unmorphed actor, the style with which they were morphed and the -// health (of the AActor, not the player_t) they last had before unmorphing. -// -//---------------------------------------------------------------------------- - -bool P_MorphedDeath(AActor *actor, AActor **morphed, int *morphedstyle, int *morphedhealth) -{ - // May be a morphed player - if ((actor->player) && - (actor->player->morphTics) && - (actor->player->MorphStyle & MORPH_UNDOBYDEATH) && - (actor->player->mo) && - (actor->player->mo->alternative)) - { - AActor *realme = actor->player->mo->alternative; - int realstyle = actor->player->MorphStyle; - int realhealth = actor->health; - if (P_UndoPlayerMorph(actor->player, actor->player, 0, !!(actor->player->MorphStyle & MORPH_UNDOBYDEATHFORCED))) - { - *morphed = realme; - *morphedstyle = realstyle; - *morphedhealth = realhealth; - return true; - } - return false; - } - - // May be a morphed monster - if (actor->GetClass()->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster))) - { - AMorphedMonster *fakeme = static_cast(actor); - AActor *realme = fakeme->UnmorphedMe; - if (realme != NULL) - { - if ((fakeme->UnmorphTime) && - (fakeme->MorphStyle & MORPH_UNDOBYDEATH)) - { - int realstyle = fakeme->MorphStyle; - int realhealth = fakeme->health; - if (P_UndoMonsterMorph(fakeme, !!(fakeme->MorphStyle & MORPH_UNDOBYDEATHFORCED))) - { - *morphed = realme; - *morphedstyle = realstyle; - *morphedhealth = realhealth; - return true; - } - } - if (realme->flags4 & MF4_BOSSDEATH) - { - realme->health = 0; // make sure that A_BossDeath considers it dead. - A_BossDeath(realme); - } - } - fakeme->flags3 |= MF3_STAYMORPHED; // moved here from AMorphedMonster::Die() - return false; - } - - // Not a morphed player or monster return false; } -//=========================================================================== -// -// EndAllPowerupEffects -// -// Calls EndEffect() on every Powerup in the inventory list. -// -//=========================================================================== - -void EndAllPowerupEffects(AInventory *item) +bool P_UnmorphActor(AActor *activator, AActor *morphed, int flags, bool force) { - auto ptype = PClass::FindActor(NAME_Powerup); - while (item != NULL) + IFVIRTUALPTR(morphed, AActor, UnMorph) { - if (item->IsKindOf(ptype)) - { - IFVIRTUALPTRNAME(item, NAME_Powerup, EndEffect) - { - VMValue params[1] = { item }; - VMCall(func, params, 1, nullptr, 0); - } - } - item = item->Inventory; - } -} - -//=========================================================================== -// -// InitAllPowerupEffects -// -// Calls InitEffect() on every Powerup in the inventory list. -// -//=========================================================================== - -void InitAllPowerupEffects(AInventory *item) -{ - auto ptype = PClass::FindActor(NAME_Powerup); - while (item != NULL) - { - if (item->IsKindOf(ptype)) - { - IFVIRTUALPTRNAME(item, NAME_Powerup, InitEffect) - { - VMValue params[1] = { item }; - VMCall(func, params, 1, nullptr, 0); - } - } - item = item->Inventory; - } -} - -// Morphed Monster (you must subclass this to do something useful) --------- - -IMPLEMENT_CLASS(AMorphedMonster, false, true) - -IMPLEMENT_POINTERS_START(AMorphedMonster) - IMPLEMENT_POINTER(UnmorphedMe) -IMPLEMENT_POINTERS_END - -DEFINE_FIELD(AMorphedMonster, UnmorphedMe) -DEFINE_FIELD(AMorphedMonster, UnmorphTime) -DEFINE_FIELD(AMorphedMonster, MorphStyle) -DEFINE_FIELD(AMorphedMonster, MorphExitFlash) - -void AMorphedMonster::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - arc("unmorphedme", UnmorphedMe) - ("unmorphtime", UnmorphTime) - ("morphstyle", MorphStyle) - ("morphexitflash", MorphExitFlash) - ("flagsave", FlagsSave); -} - -void AMorphedMonster::OnDestroy () -{ - if (UnmorphedMe != NULL) - { - UnmorphedMe->Destroy (); - } - Super::OnDestroy(); -} - -void AMorphedMonster::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath) -{ - // Dead things don't unmorph -// flags3 |= MF3_STAYMORPHED; - // [MH] - // But they can now, so that line above has been - // moved into P_MorphedDeath() and is now set by - // that function if and only if it is needed. - Super::Die (source, inflictor, dmgflags, MeansOfDeath); - if (UnmorphedMe != NULL && (UnmorphedMe->flags & MF_UNMORPHED)) - { - UnmorphedMe->health = health; - UnmorphedMe->CallDie (source, inflictor, dmgflags, MeansOfDeath); - } -} - -void AMorphedMonster::Tick () -{ - if (!P_UpdateMorphedMonster (this)) - { - Super::Tick (); + VMValue params[] = { morphed, activator, flags, force }; + int retval; + VMReturn ret(&retval); + VMCall(func, params, countof(params), &ret, 1); + return !!retval; } + return false; } -DEFINE_ACTION_FUNCTION(AActor, A_Morph) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_CLASS(type, AActor); - PARAM_INT(duration); - PARAM_INT(flags); - PARAM_CLASS(enter_flash, AActor); - PARAM_CLASS(exit_flash, AActor); - bool res = false; - if (self->player) - { - if (type->IsDescendantOf(RUNTIME_CLASS(APlayerPawn))) - { - res = P_MorphPlayer(self->player, self->player, type, duration, flags, enter_flash, exit_flash); - } - } - else - { - if (type->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster))) - { - res = P_MorphMonster(self, type, duration, flags, enter_flash, exit_flash); - } - } - ACTION_RETURN_BOOL(res); -} diff --git a/src/g_shared/a_morph.h b/src/g_shared/a_morph.h index 1c0047c69..00303533c 100644 --- a/src/g_shared/a_morph.h +++ b/src/g_shared/a_morph.h @@ -33,15 +33,8 @@ enum class PClass; class AActor; class player_t; -class AMorphedMonster; -bool P_MorphPlayer (player_t *activator, player_t *player, PClassActor *morphclass, int duration = 0, int style = 0, - PClassActor *enter_flash = NULL, PClassActor *exit_flash = NULL); -bool P_UndoPlayerMorph (player_t *activator, player_t *player, int unmorphflag = 0, bool force = false); -bool P_MorphMonster (AActor *actor, PClassActor *morphclass, int duration = 0, int style = 0, - PClassActor *enter_flash = NULL, PClassActor *exit_flash = NULL); -bool P_UndoMonsterMorph (AMorphedMonster *beast, bool force = false); -bool P_UpdateMorphedMonster (AActor *actor); -bool P_MorphedDeath(AActor *actor, AActor **morphed, int *morphedstyle, int *morphedhealth); +bool P_MorphActor(AActor *activator, AActor *victim, PClassActor *ptype, PClassActor *mtype, int duration, int style, PClassActor *enter_flash, PClassActor *exit_flash); +bool P_UnmorphActor(AActor *activator, AActor *morphed, int flags = 0, bool force = false); #endif //__A_MORPH__ diff --git a/src/g_shared/a_sharedglobal.h b/src/g_shared/a_sharedglobal.h index e2a033b07..a63056914 100644 --- a/src/g_shared/a_sharedglobal.h +++ b/src/g_shared/a_sharedglobal.h @@ -154,21 +154,4 @@ private: DEarthquake (); }; -class AMorphedMonster : public AActor -{ - DECLARE_CLASS (AMorphedMonster, AActor) - HAS_OBJECT_POINTERS -public: - void Tick (); - - void Serialize(FSerializer &arc); - void Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath) override; - void OnDestroy() override; - - TObjPtr UnmorphedMe; - int UnmorphTime, MorphStyle; - PClassActor *MorphExitFlash; - ActorFlags FlagsSave; -}; - #endif //__A_SHAREDGLOBAL_H__ diff --git a/src/g_shared/shared_hud.cpp b/src/g_shared/shared_hud.cpp index a3b7c2a6d..6f6ddf53d 100644 --- a/src/g_shared/shared_hud.cpp +++ b/src/g_shared/shared_hud.cpp @@ -507,12 +507,12 @@ static int DrawKeys(player_t * CPlayer, int x, int y) //--------------------------------------------------------------------------- static TArray orderedammos; -static void AddAmmoToList(AWeapon * weapdef) +static void AddAmmoToList(AInventory * weapdef) { for (int i = 0; i < 2; i++) { - auto ti = i == 0 ? weapdef->AmmoType1 : weapdef->AmmoType2; + auto ti = weapdef->PointerVar(i == 0 ? NAME_AmmoType1 : NAME_AmmoType2); if (ti) { auto ammodef = (AInventory*)GetDefaultByType(ti); @@ -570,7 +570,7 @@ static int DrawAmmo(player_t *CPlayer, int x, int y) char buf[256]; AInventory *inv; - AWeapon *wi=CPlayer->ReadyWeapon; + auto wi=CPlayer->ReadyWeapon; orderedammos.Clear(); @@ -582,9 +582,9 @@ static int DrawAmmo(player_t *CPlayer, int x, int y) else { // Order ammo by use of weapons in the weapon slots - for (k = 0; k < NUM_WEAPON_SLOTS; k++) for(j = 0; j < CPlayer->weapons.Slots[k].Size(); j++) + for (k = 0; k < NUM_WEAPON_SLOTS; k++) for(j = 0; j < CPlayer->weapons.SlotSize(k); j++) { - PClassActor *weap = CPlayer->weapons.Slots[k].GetWeapon(j); + PClassActor *weap = CPlayer->weapons.GetWeapon(k, j); if (weap) { @@ -593,7 +593,7 @@ static int DrawAmmo(player_t *CPlayer, int x, int y) if (hud_showammo > 1 || CPlayer->mo->FindInventory(weap)) { - AddAmmoToList((AWeapon*)GetDefaultByType(weap)); + AddAmmoToList((AInventory*)GetDefaultByType(weap)); } } } @@ -603,7 +603,7 @@ static int DrawAmmo(player_t *CPlayer, int x, int y) { if (inv->IsKindOf(NAME_Weapon)) { - AddAmmoToList((AWeapon*)inv); + AddAmmoToList(inv); } } } @@ -643,7 +643,7 @@ static int DrawAmmo(player_t *CPlayer, int x, int y) FTextureID icon = !AltIcon.isNull()? AltIcon : inv->Icon; if (!icon.isValid()) continue; - double trans= (wi && (type==wi->AmmoType1 || type==wi->AmmoType2)) ? 0.75 : 0.375; + double trans= (wi && (type==wi->PointerVar(NAME_AmmoType1) || type==wi->PointerVar(NAME_AmmoType2))) ? 0.75 : 0.375; int maxammo = inv->MaxAmount; int ammo = ammoitem? ammoitem->Amount : 0; @@ -736,18 +736,19 @@ DEFINE_ACTION_FUNCTION(DBaseStatusBar, GetInventoryIcon) return MIN(numret, 2); } -static void DrawOneWeapon(player_t * CPlayer, int x, int & y, AWeapon * weapon) +static void DrawOneWeapon(player_t * CPlayer, int x, int & y, AInventory * weapon) { double trans; // Powered up weapons and inherited sister weapons are not displayed. - if (weapon->WeaponFlags & WIF_POWERED_UP) return; - if (weapon->SisterWeapon && weapon->IsKindOf(weapon->SisterWeapon->GetClass())) return; + if (weapon->IntVar(NAME_WeaponFlags) & WIF_POWERED_UP) return; + auto SisterWeapon = weapon->PointerVar(NAME_SisterWeapon); + if (SisterWeapon && weapon->IsKindOf(SisterWeapon->GetClass())) return; trans=0.4; if (CPlayer->ReadyWeapon) { - if (weapon==CPlayer->ReadyWeapon || weapon==CPlayer->ReadyWeapon->SisterWeapon) trans = 0.85; + if (weapon==CPlayer->ReadyWeapon || SisterWeapon == CPlayer->ReadyWeapon) trans = 0.85; } FTextureID picnum = GetInventoryIcon(weapon, DI_ALTICONFIRST); @@ -775,22 +776,22 @@ static void DrawWeapons(player_t *CPlayer, int x, int y) for(inv = CPlayer->mo->Inventory; inv; inv = inv->Inventory) { if (inv->IsKindOf(NAME_Weapon) && - !CPlayer->weapons.LocateWeapon(static_cast(inv)->GetClass(), NULL, NULL)) + !CPlayer->weapons.LocateWeapon(inv->GetClass(), NULL, NULL)) { - DrawOneWeapon(CPlayer, x, y, static_cast(inv)); + DrawOneWeapon(CPlayer, x, y, inv); } } // And now everything in the weapon slots back to front - for (k = NUM_WEAPON_SLOTS - 1; k >= 0; k--) for(j = CPlayer->weapons.Slots[k].Size() - 1; j >= 0; j--) + for (k = NUM_WEAPON_SLOTS - 1; k >= 0; k--) for(j = CPlayer->weapons.SlotSize(k) - 1; j >= 0; j--) { - PClassActor *weap = CPlayer->weapons.Slots[k].GetWeapon(j); + PClassActor *weap = CPlayer->weapons.GetWeapon(k, j); if (weap) { inv=CPlayer->mo->FindInventory(weap); if (inv) { - DrawOneWeapon(CPlayer, x, y, static_cast(inv)); + DrawOneWeapon(CPlayer, x, y, inv); } } } diff --git a/src/g_statusbar/sbar.h b/src/g_statusbar/sbar.h index 9f7bbb4cc..f38e06d6b 100644 --- a/src/g_statusbar/sbar.h +++ b/src/g_statusbar/sbar.h @@ -52,8 +52,6 @@ enum EHudState HUD_AltHud // Used for passing through popups to the alt hud }; -class AWeapon; - bool ST_IsTimeVisible(); bool ST_IsLatencyVisible(); diff --git a/src/g_statusbar/sbarinfo.cpp b/src/g_statusbar/sbarinfo.cpp index 0b34c098d..f3b8de32b 100644 --- a/src/g_statusbar/sbarinfo.cpp +++ b/src/g_statusbar/sbarinfo.cpp @@ -1039,22 +1039,22 @@ public: } wrapper->ForceHUDScale(script->huds[hud]->ForceScaled()); - if (CPlayer->ReadyWeapon != NULL) + if (CPlayer->ReadyWeapon != nullptr) { - ammo1 = CPlayer->ReadyWeapon->Ammo1; - ammo2 = CPlayer->ReadyWeapon->Ammo2; - if (ammo1 == NULL) + ammo1 = CPlayer->ReadyWeapon->PointerVar(NAME_Ammo1); + ammo2 = CPlayer->ReadyWeapon->PointerVar(NAME_Ammo2); + if (ammo1 == nullptr) { ammo1 = ammo2; - ammo2 = NULL; + ammo2 = nullptr; } } else { - ammo1 = ammo2 = NULL; + ammo1 = ammo2 = nullptr; } - ammocount1 = ammo1 != NULL ? ammo1->Amount : 0; - ammocount2 = ammo2 != NULL ? ammo2->Amount : 0; + ammocount1 = ammo1 != nullptr ? ammo1->Amount : 0; + ammocount2 = ammo2 != nullptr ? ammo2->Amount : 0; //prepare ammo counts armor = CPlayer->mo->FindInventory(NAME_BasicArmor); @@ -1467,6 +1467,12 @@ public: return TRANSLATION(TRANSLATION_Players, int(CPlayer - players)); } + PClassActor *AmmoType(int no) const + { + auto w = StatusBar->CPlayer->ReadyWeapon; + return w == nullptr ? nullptr : (w->PointerVar(no == 1 ? NAME_AmmoType1 : NAME_AmmoType2)); + } + AInventory *ammo1, *ammo2; int ammocount1, ammocount2; AInventory *armor; diff --git a/src/g_statusbar/sbarinfo_commands.cpp b/src/g_statusbar/sbarinfo_commands.cpp index 0eba791b3..38373f328 100644 --- a/src/g_statusbar/sbarinfo_commands.cpp +++ b/src/g_statusbar/sbarinfo_commands.cpp @@ -245,7 +245,7 @@ class CommandDrawImage : public SBarInfoCommandFlowControl else if(type == AMMO1) { auto ammo = statusBar->ammo1; - if(ammo != NULL) + if(ammo != NULL) GetIcon(ammo); } else if(type == AMMO2) @@ -527,9 +527,9 @@ class CommandDrawSwitchableImage : public CommandDrawImage if(condition == WEAPONSLOT) //weaponslots { drawAlt = 1; //draw off state until we know we have something. - for (int i = 0; i < statusBar->CPlayer->weapons.Slots[conditionalValue[0]].Size(); i++) + for (int i = 0; i < statusBar->CPlayer->weapons.SlotSize(conditionalValue[0]); i++) { - PClassActor *weap = statusBar->CPlayer->weapons.Slots[conditionalValue[0]].GetWeapon(i); + PClassActor *weap = statusBar->CPlayer->weapons.GetWeapon(conditionalValue[0], i); if(weap == NULL) { continue; @@ -597,8 +597,9 @@ class CommandDrawSwitchableImage : public CommandDrawImage auto armor = statusBar->CPlayer->mo->FindInventory(NAME_BasicArmor); if(armor != NULL) { - bool matches1 = armor->NameVar(NAME_ArmorType).GetIndex() == armorType[0] && EvaluateOperation(conditionalOperator[0], conditionalValue[0], armor->Amount); - bool matches2 = armor->NameVar(NAME_ArmorType).GetIndex() == armorType[1] && EvaluateOperation(conditionalOperator[1], conditionalValue[1], armor->Amount); + auto n = armor->NameVar(NAME_ArmorType).GetIndex(); + bool matches1 = n == armorType[0] && EvaluateOperation(conditionalOperator[0], conditionalValue[0], armor->Amount); + bool matches2 = n == armorType[1] && EvaluateOperation(conditionalOperator[1], conditionalValue[1], armor->Amount); drawAlt = 1; if(conditionAnd) @@ -1873,7 +1874,7 @@ class CommandUsesAmmo : public SBarInfoNegatableFlowControl { SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged); - SetTruth(statusBar->CPlayer->ReadyWeapon != NULL && (statusBar->CPlayer->ReadyWeapon->AmmoType1 != NULL || statusBar->CPlayer->ReadyWeapon->AmmoType2 != NULL), block, statusBar); + SetTruth(statusBar->AmmoType(1) || statusBar->AmmoType(2), block, statusBar); } }; @@ -1890,7 +1891,7 @@ class CommandUsesSecondaryAmmo : public CommandUsesAmmo { SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); - SetTruth(statusBar->CPlayer->ReadyWeapon != NULL && statusBar->CPlayer->ReadyWeapon->AmmoType2 != NULL && statusBar->CPlayer->ReadyWeapon->AmmoType1 != statusBar->CPlayer->ReadyWeapon->AmmoType2, block, statusBar); + SetTruth(statusBar->AmmoType(2) && statusBar->AmmoType(2) != statusBar->AmmoType(1), block, statusBar); } }; @@ -2881,7 +2882,7 @@ class CommandIsSelected : public SBarInfoNegatableFlowControl if(weapon[i] == NULL || !weapon[i]->IsDescendantOf(NAME_Weapon)) { sc.ScriptMessage("'%s' is not a type of weapon.", sc.String); - weapon[i] = RUNTIME_CLASS(AWeapon); + weapon[i] = PClass::FindClass(NAME_Weapon); } if(sc.CheckToken(',')) @@ -3034,7 +3035,7 @@ class CommandHasWeaponPiece : public SBarInfoCommandFlowControl if (weapon == NULL || !weapon->IsDescendantOf(NAME_Weapon)) //must be a weapon { sc.ScriptMessage("%s is not a kind of weapon.", sc.String); - weapon = RUNTIME_CLASS(AWeapon); + weapon = PClass::FindClass(NAME_Weapon); } sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); @@ -3246,8 +3247,8 @@ class CommandWeaponAmmo : public SBarInfoNegatableFlowControl if(statusBar->CPlayer->ReadyWeapon != NULL) { - const PClass *AmmoType1 = statusBar->CPlayer->ReadyWeapon->AmmoType1; - const PClass *AmmoType2 = statusBar->CPlayer->ReadyWeapon->AmmoType2; + const PClass *AmmoType1 = statusBar->AmmoType(1); + const PClass *AmmoType2 = statusBar->AmmoType(2); bool usesammo1 = (AmmoType1 != NULL); bool usesammo2 = (AmmoType2 != NULL); //if(!usesammo1 && !usesammo2) //if the weapon doesn't use ammo don't go though the trouble. diff --git a/src/g_statusbar/shared_sbar.cpp b/src/g_statusbar/shared_sbar.cpp index e3430f2cf..52e280d83 100644 --- a/src/g_statusbar/shared_sbar.cpp +++ b/src/g_statusbar/shared_sbar.cpp @@ -175,7 +175,7 @@ void ST_LoadCrosshair(bool alwaysload) players[consoleplayer].camera->player != NULL && players[consoleplayer].camera->player->ReadyWeapon != NULL) { - num = players[consoleplayer].camera->player->ReadyWeapon->Crosshair; + num = players[consoleplayer].camera->player->ReadyWeapon->IntVar(NAME_Crosshair); } if (num == 0) { diff --git a/src/gi.cpp b/src/gi.cpp index 4edb2420f..8587aa143 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -62,6 +62,8 @@ DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, statusscreen_coop) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, statusscreen_dm) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, mSliderColor) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, defaultbloodcolor) +DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, telefogheight) +DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, defKickback) const char *GameNames[17] = { diff --git a/src/hwrenderer/scene/hw_weapon.cpp b/src/hwrenderer/scene/hw_weapon.cpp index d0bb20e4d..171c28647 100644 --- a/src/hwrenderer/scene/hw_weapon.cpp +++ b/src/hwrenderer/scene/hw_weapon.cpp @@ -433,21 +433,7 @@ bool HUDSprite::GetWeaponRect(HWDrawInfo *di, DPSprite *psp, float sx, float sy, x2 += viewwindowx; // killough 12/98: fix psprite positioning problem - ftexturemid = 100.f - sy - r.top; - - AWeapon * wi = player->ReadyWeapon; - if (wi && wi->YAdjust != 0) - { - float fYAd = wi->YAdjust; - if (screenblocks >= 11) - { - ftexturemid -= fYAd; - } - else - { - ftexturemid -= float(StatusBar->GetDisplacement()) * fYAd; - } - } + ftexturemid = 100.f - sy - r.top - psp->GetYAdjust(screenblocks >= 11); scale = (SCREENHEIGHT*vw) / (SCREENWIDTH * 200.0f); y1 = viewwindowy + vh / 2 - (ftexturemid * scale); diff --git a/src/info.cpp b/src/info.cpp index cfa6b067b..00a66b45b 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -365,29 +365,6 @@ bool PClassActor::SetReplacement(FName replaceName) return true; } -//========================================================================== -// -// PClassActor :: Finalize -// -// Installs the parsed states and does some sanity checking -// -//========================================================================== - -void AActor::Finalize(FStateDefinitions &statedef) -{ - try - { - statedef.FinishStates(GetClass()); - } - catch (CRecoverableError &) - { - statedef.MakeStateDefines(nullptr); - throw; - } - statedef.InstallStates(GetClass(), this); - statedef.MakeStateDefines(nullptr); -} - //========================================================================== // // PClassActor :: RegisterIDs diff --git a/src/intermission/intermission.cpp b/src/intermission/intermission.cpp index 30ef3fa40..65f31910e 100644 --- a/src/intermission/intermission.cpp +++ b/src/intermission/intermission.cpp @@ -872,7 +872,6 @@ void F_StartIntermission(FName seq, uint8_t state) } } - //========================================================================== // // Called by main loop. diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 768802693..f9c0f6783 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -90,10 +90,17 @@ void cht_DoCheat (player_t *player, int cheat) }; PClassActor *type; AInventory *item; + FString smsg; const char *msg = ""; char msgbuild[32]; int i; + // No cheating when not having a pawn attached. + if (player->mo == nullptr) + { + return; + } + switch (cheat) { case CHT_IDDQD: @@ -188,7 +195,8 @@ void cht_DoCheat (player_t *player, int cheat) break; case CHT_MORPH: - msg = cht_Morph (player, PClass::FindActor (gameinfo.gametype == GAME_Heretic ? NAME_ChickenPlayer : NAME_PigPlayer), true); + smsg = cht_Morph (player, PClass::FindActor (gameinfo.gametype == GAME_Heretic ? NAME_ChickenPlayer : NAME_PigPlayer), true); + msg = smsg.GetChars(); break; case CHT_NOTARGET: @@ -408,29 +416,7 @@ void cht_DoCheat (player_t *player, int cheat) break; case CHT_TAKEWEAPS: - if (player->morphTics || player->mo == NULL || player->mo->health <= 0) - { - return; - } - { - // Take away all weapons that are either non-wimpy or use ammo. - AInventory **invp = &player->mo->Inventory, **lastinvp; - for (item = *invp; item != NULL; item = *invp) - { - lastinvp = invp; - invp = &(*invp)->Inventory; - if (item->IsKindOf(NAME_Weapon)) - { - AWeapon *weap = static_cast (item); - if (!(weap->WeaponFlags & WIF_WIMPY_WEAPON) || - weap->AmmoType1 != NULL) - { - item->Destroy (); - invp = lastinvp; - } - } - } - } + cht_Takeweaps(player); msg = GStrings("TXT_CHEATIDKFA"); break; @@ -494,7 +480,7 @@ void cht_DoCheat (player_t *player, int cheat) } else { - player->PendingWeapon = static_cast (item); + player->PendingWeapon = item; } } } @@ -546,32 +532,17 @@ void cht_DoCheat (player_t *player, int cheat) Printf ("%s cheats: %s\n", player->userinfo.GetName(), msg); } -const char *cht_Morph (player_t *player, PClassActor *morphclass, bool quickundo) +FString cht_Morph(player_t *player, PClassActor *morphclass, bool quickundo) { - if (player->mo == NULL) - { - return ""; - } - auto oldclass = player->mo->GetClass(); + if (player->mo == nullptr) return ""; - // Set the standard morph style for the current game - int style = MORPH_UNDOBYTOMEOFPOWER; - if (gameinfo.gametype == GAME_Hexen) style |= MORPH_UNDOBYCHAOSDEVICE; - - if (player->morphTics) + IFVIRTUALPTR(player->mo, APlayerPawn, CheatMorph) { - if (P_UndoPlayerMorph (player, player)) - { - if (!quickundo && oldclass != morphclass && P_MorphPlayer (player, player, morphclass, 0, style)) - { - return GStrings("TXT_STRANGER"); - } - return GStrings("TXT_NOTSTRANGE"); - } - } - else if (P_MorphPlayer (player, player, morphclass, 0, style)) - { - return GStrings("TXT_STRANGE"); + FString message; + VMReturn msgret(&message); + VMValue params[3] = { player->mo, morphclass, quickundo }; + VMCall(func, params, 3, &msgret, 1); + return message; } return ""; } @@ -602,8 +573,6 @@ void cht_SetInv(player_t *player, const char *string, int amount, bool beyond) void cht_Give (player_t *player, const char *name, int amount) { - if (player->mo == nullptr) return; - IFVIRTUALPTR(player->mo, APlayerPawn, CheatGive) { FString namestr = name; @@ -614,8 +583,6 @@ void cht_Give (player_t *player, const char *name, int amount) void cht_Take (player_t *player, const char *name, int amount) { - if (player->mo == nullptr) return; - IFVIRTUALPTR(player->mo, APlayerPawn, CheatTake) { FString namestr = name; @@ -624,6 +591,15 @@ void cht_Take (player_t *player, const char *name, int amount) } } +void cht_Takeweaps(player_t *player) +{ + IFVIRTUALPTR(player->mo, APlayerPawn, CheatTakeWeaps) + { + VMValue params[3] = { player->mo }; + VMCall(func, params, 1, nullptr, 0); + } +} + class DSuicider : public DThinker { DECLARE_CLASS(DSuicider, DThinker) diff --git a/src/m_cheat.h b/src/m_cheat.h index c6ebcb1a1..a6bb5ab68 100644 --- a/src/m_cheat.h +++ b/src/m_cheat.h @@ -15,6 +15,7 @@ void cht_Give (player_t *player, const char *item, int amount=1); void cht_Take (player_t *player, const char *item, int amount=1); void cht_SetInv(player_t *player, const char *item, int amount = 1, bool beyondMax = false); void cht_Suicide (player_t *player); -const char *cht_Morph (player_t *player, PClassActor *morphclass, bool quickundo); +FString cht_Morph (player_t *player, PClassActor *morphclass, bool quickundo); +void cht_Takeweaps(player_t *player); #endif diff --git a/src/namedef.h b/src/namedef.h index dbb063840..7c366610b 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -204,6 +204,7 @@ xx(PowerDrain) xx(Reflection) xx(CustomInventory) xx(Inventory) +xx(StateProvider) xx(CallTryPickup) xx(QuestItem25) xx(QuestItem28) @@ -986,3 +987,26 @@ xx(snd_output) xx(snd_output_format) xx(snd_speakermode) xx(snd_resampler) + +// ScriptUtil entry points +xx(ScriptUtil) +xx(SetMarineWeapon) +xx(SetMarineSprite) + +// Weapon member fields that need direct access +xx(Ammo1) +xx(Ammo2) +xx(AmmoType1) +xx(AmmoType2) +xx(AmmoGive1) +xx(AmmoGive2) +xx(AmmoUse1) +xx(SisterWeapon) +xx(BobStyle) +xx(Kickback) +xx(MinSelAmmo1) +xx(bDehAmmo) +xx(FOVScale) +xx(YAdjust) +xx(Crosshair) +xx(WeaponFlags) \ No newline at end of file diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 4f45952f2..7c47495b7 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -74,6 +74,7 @@ #include "g_levellocals.h" #include "actorinlines.h" #include "types.h" +#include "scriptutil.h" // P-codes for ACS scripts enum @@ -6963,28 +6964,6 @@ static bool CharArrayParms(int &capacity, int &offset, int &a, FACSStackMemory& return true; } -static void SetMarineWeapon(AActor *marine, int weapon) -{ - static VMFunction *smw = nullptr; - if (smw == nullptr) PClass::FindFunction(&smw, NAME_ScriptedMarine, NAME_SetWeapon); - if (smw) - { - VMValue params[2] = { marine, weapon }; - VMCall(smw, params, 2, nullptr, 0); - } -} - -static void SetMarineSprite(AActor *marine, PClassActor *source) -{ - static VMFunction *sms = nullptr; - if (sms == nullptr) PClass::FindFunction(&sms, NAME_ScriptedMarine, NAME_SetSprite); - if (sms) - { - VMValue params[2] = { marine, source }; - VMCall(sms, params, 2, nullptr, 0); - } -} - int DLevelScript::RunScript () { DACSThinker *controller = DACSThinker::ActiveThinker; @@ -9832,93 +9811,16 @@ scriptwait: break; case PCD_SETWEAPON: - if (activator == NULL || activator->player == NULL) - { - STACK(1) = 0; - } - else - { - AInventory *item = activator->FindInventory (PClass::FindActor (FBehavior::StaticLookupString (STACK(1)))); - - if (item == NULL || !item->IsKindOf(NAME_Weapon)) - { - STACK(1) = 0; - } - else if (activator->player->ReadyWeapon == item) - { - // The weapon is already selected, so setweapon succeeds by default, - // but make sure the player isn't switching away from it. - activator->player->PendingWeapon = WP_NOCHANGE; - STACK(1) = 1; - } - else - { - AWeapon *weap = static_cast (item); - - if (weap->CheckAmmo (AWeapon::EitherFire, false)) - { - // There's enough ammo, so switch to it. - STACK(1) = 1; - activator->player->PendingWeapon = weap; - } - else - { - STACK(1) = 0; - } - } - } + STACK(1) = ScriptUtil::Exec(NAME_SetWeapon, ScriptUtil::Pointer, activator, ScriptUtil::ACSClass, STACK(1), ScriptUtil::End); break; case PCD_SETMARINEWEAPON: - if (STACK(2) != 0) - { - AActor *marine; - NActorIterator iterator(NAME_ScriptedMarine, STACK(2)); - - while ((marine = iterator.Next()) != NULL) - { - SetMarineWeapon(marine, STACK(1)); - } - } - else - { - if (activator != nullptr && activator->IsKindOf (NAME_ScriptedMarine)) - { - SetMarineWeapon(activator, STACK(1)); - } - } + ScriptUtil::Exec(NAME_SetMarineWeapon, ScriptUtil::Pointer, activator, ScriptUtil::Int, STACK(2), ScriptUtil::Int, STACK(1), ScriptUtil::End); sp -= 2; break; case PCD_SETMARINESPRITE: - { - PClassActor *type = PClass::FindActor(FBehavior::StaticLookupString (STACK(1))); - - if (type != NULL) - { - if (STACK(2) != 0) - { - AActor *marine; - NActorIterator iterator(NAME_ScriptedMarine, STACK(2)); - - while ((marine = iterator.Next()) != NULL) - { - SetMarineSprite(marine, type); - } - } - else - { - if (activator != nullptr && activator->IsKindOf(NAME_ScriptedMarine)) - { - SetMarineSprite(activator, type); - } - } - } - else - { - Printf ("Unknown actor type: %s\n", FBehavior::StaticLookupString (STACK(1))); - } - } + ScriptUtil::Exec(NAME_SetMarineSprite, ScriptUtil::Pointer, activator, ScriptUtil::Int, STACK(2), ScriptUtil::ACSClass, STACK(1), ScriptUtil::End); sp -= 2; break; @@ -10305,14 +10207,7 @@ scriptwait: if (tag == 0) { - if (activator != NULL && activator->player) - { - changes += P_MorphPlayer(activator->player, activator->player, playerclass, duration, style, morphflash, unmorphflash); - } - else - { - changes += P_MorphMonster(activator, monsterclass, duration, style, morphflash, unmorphflash); - } + changes = P_MorphActor(activator, activator, playerclass, monsterclass, duration, style, morphflash, unmorphflash); } else { @@ -10321,15 +10216,7 @@ scriptwait: while ( (actor = iterator.Next ()) ) { - if (actor->player) - { - changes += P_MorphPlayer(activator == NULL ? NULL : activator->player, - actor->player, playerclass, duration, style, morphflash, unmorphflash); - } - else - { - changes += P_MorphMonster(actor, monsterclass, duration, style, morphflash, unmorphflash); - } + changes += P_MorphActor(activator, actor, playerclass, monsterclass, duration, style, morphflash, unmorphflash); } } @@ -10346,24 +10233,7 @@ scriptwait: if (tag == 0) { - if (activator->player) - { - if (P_UndoPlayerMorph(activator->player, activator->player, 0, force)) - { - changes++; - } - } - else - { - if (activator->GetClass()->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster))) - { - AMorphedMonster *morphed_actor = barrier_cast(activator); - if (P_UndoMonsterMorph(morphed_actor, force)) - { - changes++; - } - } - } + changes += P_UnmorphActor(activator, activator, 0, force); } else { @@ -10372,24 +10242,7 @@ scriptwait: while ( (actor = iterator.Next ()) ) { - if (actor->player) - { - if (P_UndoPlayerMorph(activator->player, actor->player, 0, force)) - { - changes++; - } - } - else - { - if (actor->GetClass()->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster))) - { - AMorphedMonster *morphed_actor = static_cast(actor); - if (P_UndoMonsterMorph(morphed_actor, force)) - { - changes++; - } - } - } + changes += P_UnmorphActor(activator, actor, 0, force); } } diff --git a/src/p_acs.h b/src/p_acs.h index fc361a230..9115a370b 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -43,6 +43,7 @@ class FFont; class FileReader; struct line_t; +class FSerializer; enum diff --git a/src/p_actionfunctions.cpp b/src/p_actionfunctions.cpp index 2a835ccb7..43c9e45fe 100644 --- a/src/p_actionfunctions.cpp +++ b/src/p_actionfunctions.cpp @@ -75,7 +75,6 @@ AActor *SingleActorFromTID(int tid, AActor *defactor); static FRandom pr_camissile ("CustomActorfire"); static FRandom pr_cabullet ("CustomBullet"); -static FRandom pr_cwbullet ("CustomWpBullet"); static FRandom pr_cwjump ("CustomWpJump"); static FRandom pr_cwpunch ("CustomWpPunch"); static FRandom pr_grenade ("ThrowGrenade"); @@ -99,7 +98,7 @@ FRandom pr_cajump("CustomJump"); extern TArray actionParams; // this can use the same storage as CallAction -bool AStateProvider::CallStateChain (AActor *actor, FState *state) +static bool CallStateChain (AActor *self, AActor *actor, FState *state) { INTBOOL result = false; int counter = 0; @@ -112,7 +111,7 @@ bool AStateProvider::CallStateChain (AActor *actor, FState *state) ret[0].PointerAt((void **)&nextstate); ret[1].IntAt(&retval); - FState *savedstate = this->state; + FState *savedstate = self->state; while (state != NULL) { @@ -122,7 +121,7 @@ bool AStateProvider::CallStateChain (AActor *actor, FState *state) return false; } - this->state = state; + self->state = state; nextstate = NULL; // assume no jump if (state->ActionFunc != NULL) @@ -171,7 +170,7 @@ bool AStateProvider::CallStateChain (AActor *actor, FState *state) try { - state->CheckCallerType(actor, this); + state->CheckCallerType(actor, self); if (state->ActionFunc->DefaultArgs.Size() > 0) { @@ -188,7 +187,7 @@ bool AStateProvider::CallStateChain (AActor *actor, FState *state) } if (state->ActionFunc->ImplicitArgs == 3) { - actionParams[index + 1] = this; + actionParams[index + 1] = self; actionParams[index + 2] = VMValue(&stp); } @@ -197,14 +196,14 @@ bool AStateProvider::CallStateChain (AActor *actor, FState *state) } else { - VMValue params[3] = { actor, this, VMValue(&stp) }; + VMValue params[3] = { actor, self, VMValue(&stp) }; VMCallAction(state->ActionFunc, params, state->ActionFunc->ImplicitArgs, wantret, numret); } } catch (CVMAbortException &err) { err.MaybePrintMessage(); - err.stacktrace.AppendFormat("Called from state %s in inventory state chain in %s\n", FState::StaticGetStateName(state).GetChars(), GetClass()->TypeName.GetChars()); + err.stacktrace.AppendFormat("Called from state %s in inventory state chain in %s\n", FState::StaticGetStateName(state).GetChars(), self->GetClass()->TypeName.GetChars()); throw; } @@ -232,16 +231,16 @@ bool AStateProvider::CallStateChain (AActor *actor, FState *state) } state = nextstate; } - this->state = savedstate; + self->state = savedstate; return !!result; } DEFINE_ACTION_FUNCTION(ACustomInventory, CallStateChain) { - PARAM_SELF_PROLOGUE(AStateProvider); + PARAM_SELF_PROLOGUE(AActor); PARAM_OBJECT(affectee, AActor); PARAM_POINTER(state, FState); - ACTION_RETURN_BOOL(self->CallStateChain(affectee, state)); + ACTION_RETURN_BOOL(CallStateChain(self, affectee, state)); } //========================================================================== @@ -1160,119 +1159,6 @@ DEFINE_ACTION_FUNCTION(AActor, CheckInventory) } -//========================================================================== -// -// Parameterized version of A_Explode -// -//========================================================================== - -enum -{ - XF_HURTSOURCE = 1, - XF_NOTMISSILE = 4, - XF_NOACTORTYPE = 1 << 3, - XF_NOSPLASH = 16, -}; - -DEFINE_ACTION_FUNCTION(AActor, A_Explode) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_INT (damage); - PARAM_INT (distance); - PARAM_INT (flags); - PARAM_BOOL (alert); - PARAM_INT (fulldmgdistance); - PARAM_INT (nails); - PARAM_INT (naildamage); - PARAM_CLASS (pufftype, AActor); - PARAM_NAME (damagetype); - - if (damage < 0) // get parameters from metadata - { - damage = self->IntVar(NAME_ExplosionDamage); - distance = self->IntVar(NAME_ExplosionRadius); - flags = !self->BoolVar(NAME_DontHurtShooter); - alert = false; - } - if (distance <= 0) distance = damage; - - // NailBomb effect, from SMMU but not from its source code: instead it was implemented and - // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html - - if (nails) - { - DAngle ang; - for (int i = 0; i < nails; i++) - { - ang = i*360./nails; - // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim - P_LineAttack(self, ang, MISSILERANGE, 0., - //P_AimLineAttack (self, ang, MISSILERANGE), - naildamage, NAME_Hitscan, pufftype, (self->flags & MF_MISSILE) ? LAF_TARGETISSOURCE : 0); - } - } - - if (!(flags & XF_NOACTORTYPE) && damagetype == NAME_None) - { - damagetype = self->DamageType; - } - - int pflags = 0; - if (flags & XF_HURTSOURCE) pflags |= RADF_HURTSOURCE; - if (flags & XF_NOTMISSILE) pflags |= RADF_SOURCEISSPOT; - - int count = P_RadiusAttack (self, self->target, damage, distance, damagetype, pflags, fulldmgdistance); - if (!(flags & XF_NOSPLASH)) P_CheckSplash(self, distance); - if (alert && self->target != NULL && self->target->player != NULL) - { - P_NoiseAlert(self->target, self); - } - ACTION_RETURN_INT(count); -} - -//========================================================================== -// -// A_RadiusThrust -// -//========================================================================== - -enum -{ - RTF_AFFECTSOURCE = 1, - RTF_NOIMPACTDAMAGE = 2, - RTF_NOTMISSILE = 4, -}; - -DEFINE_ACTION_FUNCTION(AActor, A_RadiusThrust) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_INT (force); - PARAM_INT (distance); - PARAM_INT (flags); - PARAM_INT (fullthrustdistance); - - bool sourcenothrust = false; - - if (force == 0) force = 128; - if (distance <= 0) distance = abs(force); - - // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. - if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST) - { - sourcenothrust = true; - self->target->flags2 &= ~MF2_NODMGTHRUST; - } - - P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance); - P_CheckSplash(self, distance); - - if (sourcenothrust) - { - self->target->flags2 |= MF2_NODMGTHRUST; - } - return 0; -} - //========================================================================== // // A_RadiusDamageSelf @@ -1489,112 +1375,6 @@ DEFINE_ACTION_FUNCTION(AActor, A_SpawnProjectile) ACTION_RETURN_OBJECT(missile); } -//========================================================================== -// -// An even more customizable hitscan attack -// -//========================================================================== -enum CBA_Flags -{ - CBAF_AIMFACING = 1, - CBAF_NORANDOM = 2, - CBAF_EXPLICITANGLE = 4, - CBAF_NOPITCH = 8, - CBAF_NORANDOMPUFFZ = 16, - CBAF_PUFFTARGET = 32, - CBAF_PUFFMASTER = 64, - CBAF_PUFFTRACER = 128, -}; - -static void AimBulletMissile(AActor *proj, AActor *puff, int flags, bool temp, bool cba); - -DEFINE_ACTION_FUNCTION(AActor, A_CustomBulletAttack) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_ANGLE (spread_xy); - PARAM_ANGLE (spread_z); - PARAM_INT (numbullets); - PARAM_INT (damageperbullet); - PARAM_CLASS (pufftype, AActor); - PARAM_FLOAT (range); - PARAM_INT (flags); - PARAM_INT (ptr); - PARAM_CLASS (missile, AActor); - PARAM_FLOAT (Spawnheight); - PARAM_FLOAT (Spawnofs_xy); - - AActor *ref = COPY_AAPTR(self, ptr); - - if (range == 0) - range = MISSILERANGE; - - int i; - DAngle bangle; - DAngle bslope = 0.; - int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; - - if (ref != NULL || (flags & CBAF_AIMFACING)) - { - if (!(flags & CBAF_AIMFACING)) - { - A_Face(self, ref); - } - bangle = self->Angles.Yaw; - - if (!(flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); - if (pufftype == nullptr) pufftype = PClass::FindActor(NAME_BulletPuff); - - S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); - for (i = 0; i < numbullets; i++) - { - DAngle angle = bangle; - DAngle slope = bslope; - - if (flags & CBAF_EXPLICITANGLE) - { - angle += spread_xy; - slope += spread_z; - } - else - { - angle += spread_xy * (pr_cwbullet.Random2() / 255.); - slope += spread_z * (pr_cwbullet.Random2() / 255.); - } - - int damage = damageperbullet; - - if (!(flags & CBAF_NORANDOM)) - damage *= ((pr_cabullet()%3)+1); - - AActor *puff = P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); - if (missile != nullptr && pufftype != nullptr) - { - double x = Spawnofs_xy * angle.Cos(); - double y = Spawnofs_xy * angle.Sin(); - - DVector3 pos = self->Pos(); - self->SetXYZ(self->Vec3Offset(x, y, 0.)); - AActor *proj = P_SpawnMissileAngleZSpeed(self, self->Z() + self->GetBobOffset() + Spawnheight, missile, self->Angles.Yaw, 0, GetDefaultByType(missile)->Speed, self, false); - self->SetXYZ(pos); - - if (proj) - { - bool temp = (puff == nullptr); - if (!puff) - { - puff = P_LineAttack(self, angle, range, slope, 0, NAME_Hitscan, pufftype, laflags | LAF_NOINTERACT); - } - if (puff) - { - AimBulletMissile(proj, puff, flags, temp, true); - } - } - } - } - } - return 0; -} - //========================================================================== // // A fully customizable melee attack @@ -1682,459 +1462,6 @@ DEFINE_ACTION_FUNCTION(AActor, A_CustomComboAttack) return 0; } -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AStateProvider, A_JumpIfNoAmmo) -{ - PARAM_ACTION_PROLOGUE(AStateProvider); - PARAM_STATE_ACTION(jump); - - if (!ACTION_CALL_FROM_PSPRITE() || self->player->ReadyWeapon == nullptr) - { - ACTION_RETURN_STATE(NULL); - } - - if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) - { - ACTION_RETURN_STATE(jump); - } - ACTION_RETURN_STATE(NULL); -} - - -//========================================================================== -// -// An even more customizable hitscan attack -// -//========================================================================== -enum FB_Flags -{ - FBF_USEAMMO = 1, - FBF_NORANDOM = 2, - FBF_EXPLICITANGLE = 4, - FBF_NOPITCH = 8, - FBF_NOFLASH = 16, - FBF_NORANDOMPUFFZ = 32, - FBF_PUFFTARGET = 64, - FBF_PUFFMASTER = 128, - FBF_PUFFTRACER = 256, -}; - -static void AimBulletMissile(AActor *proj, AActor *puff, int flags, bool temp, bool cba) -{ - if (proj && puff) - { - if (proj) - { - // FAF_BOTTOM = 1 - // Aim for the base of the puff as that's where blood puffs will spawn... roughly. - - A_Face(proj, puff, 0., 0., 0., 0., 1); - proj->Vel3DFromAngle(proj->Angles.Pitch, proj->Speed); - - if (!temp) - { - if (cba) - { - if (flags & CBAF_PUFFTARGET) proj->target = puff; - if (flags & CBAF_PUFFMASTER) proj->master = puff; - if (flags & CBAF_PUFFTRACER) proj->tracer = puff; - } - else - { - if (flags & FBF_PUFFTARGET) proj->target = puff; - if (flags & FBF_PUFFMASTER) proj->master = puff; - if (flags & FBF_PUFFTRACER) proj->tracer = puff; - } - } - } - } - if (puff && temp) - { - puff->Destroy(); - } -} - -DEFINE_ACTION_FUNCTION(AStateProvider, A_FireBullets) -{ - PARAM_ACTION_PROLOGUE(AStateProvider); - PARAM_ANGLE (spread_xy); - PARAM_ANGLE (spread_z); - PARAM_INT (numbullets); - PARAM_INT (damageperbullet); - PARAM_CLASS (pufftype, AActor); - PARAM_INT (flags); - PARAM_FLOAT (range); - PARAM_CLASS (missile, AActor); - PARAM_FLOAT (Spawnheight); - PARAM_FLOAT (Spawnofs_xy); - - if (!self->player) return 0; - - player_t *player = self->player; - AWeapon *weapon = player->ReadyWeapon; - - int i; - DAngle bangle; - DAngle bslope = 0.; - int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; - - if ((flags & FBF_USEAMMO) && weapon && ACTION_CALL_FROM_PSPRITE()) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) - return 0; // out of ammo - } - - if (range == 0) range = PLAYERMISSILERANGE; - - if (!(flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); - - if (!(flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); - bangle = self->Angles.Yaw; - - if (pufftype == NULL) pufftype = PClass::FindActor(NAME_BulletPuff); - - if (weapon != NULL) - { - S_Sound(self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); - } - - if ((numbullets == 1 && !player->refire) || numbullets == 0) - { - int damage = damageperbullet; - - if (!(flags & FBF_NORANDOM)) - damage *= ((pr_cwbullet()%3)+1); - - AActor *puff = P_LineAttack(self, bangle, range, bslope, damage, NAME_Hitscan, pufftype, laflags); - - if (missile != nullptr) - { - bool temp = false; - DAngle ang = self->Angles.Yaw - 90; - DVector2 ofs = ang.ToVector(Spawnofs_xy); - AActor *proj = P_SpawnPlayerMissile(self, ofs.X, ofs.Y, Spawnheight, missile, bangle, nullptr, nullptr, false, true); - if (proj) - { - if (!puff) - { - temp = true; - puff = P_LineAttack(self, bangle, range, bslope, 0, NAME_Hitscan, pufftype, laflags | LAF_NOINTERACT); - } - AimBulletMissile(proj, puff, flags, temp, false); - } - } - } - else - { - if (numbullets < 0) - numbullets = 1; - for (i = 0; i < numbullets; i++) - { - DAngle angle = bangle; - DAngle slope = bslope; - - if (flags & FBF_EXPLICITANGLE) - { - angle += spread_xy; - slope += spread_z; - } - else - { - angle += spread_xy * (pr_cwbullet.Random2() / 255.); - slope += spread_z * (pr_cwbullet.Random2() / 255.); - } - - int damage = damageperbullet; - - if (!(flags & FBF_NORANDOM)) - damage *= ((pr_cwbullet()%3)+1); - - AActor *puff = P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); - - if (missile != nullptr) - { - bool temp = false; - DAngle ang = self->Angles.Yaw - 90; - DVector2 ofs = ang.ToVector(Spawnofs_xy); - AActor *proj = P_SpawnPlayerMissile(self, ofs.X, ofs.Y, Spawnheight, missile, angle, nullptr, nullptr, false, true); - if (proj) - { - if (!puff) - { - temp = true; - puff = P_LineAttack(self, angle, range, slope, 0, NAME_Hitscan, pufftype, laflags | LAF_NOINTERACT); - } - AimBulletMissile(proj, puff, flags, temp, false); - } - } - } - } - return 0; -} - - -//========================================================================== -// -// A_FireProjectile -// -//========================================================================== -enum FP_Flags -{ - FPF_AIMATANGLE = 1, - FPF_TRANSFERTRANSLATION = 2, - FPF_NOAUTOAIM = 4, -}; -DEFINE_ACTION_FUNCTION(AStateProvider, A_FireProjectile) -{ - PARAM_ACTION_PROLOGUE(AStateProvider); - PARAM_CLASS (ti, AActor); - PARAM_ANGLE (angle); - PARAM_BOOL (useammo); - PARAM_FLOAT (spawnofs_xy); - PARAM_FLOAT (spawnheight); - PARAM_INT (flags); - PARAM_ANGLE (pitch); - - if (!self->player) - ACTION_RETURN_OBJECT(nullptr); - - player_t *player = self->player; - AWeapon *weapon = player->ReadyWeapon; - FTranslatedLineTarget t; - - // Only use ammo if called from a weapon - if (useammo && ACTION_CALL_FROM_PSPRITE() && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) - ACTION_RETURN_OBJECT(nullptr); // out of ammo - } - - if (ti) - { - DAngle ang = self->Angles.Yaw - 90; - DVector2 ofs = ang.ToVector(spawnofs_xy); - DAngle shootangle = self->Angles.Yaw; - - if (flags & FPF_AIMATANGLE) shootangle += angle; - - // Temporarily adjusts the pitch - DAngle saved_player_pitch = self->Angles.Pitch; - self->Angles.Pitch += pitch; - AActor * misl=P_SpawnPlayerMissile (self, ofs.X, ofs.Y, spawnheight, ti, shootangle, &t, NULL, false, (flags & FPF_NOAUTOAIM) != 0); - self->Angles.Pitch = saved_player_pitch; - - // automatic handling of seeker missiles - if (misl) - { - if (flags & FPF_TRANSFERTRANSLATION) - misl->Translation = self->Translation; - if (t.linetarget && !t.unlinked && (misl->flags2 & MF2_SEEKERMISSILE)) - misl->tracer = t.linetarget; - if (!(flags & FPF_AIMATANGLE)) - { - // This original implementation is to aim straight ahead and then offset - // the angle from the resulting direction. - misl->Angles.Yaw += angle; - misl->VelFromAngle(misl->VelXYToSpeed()); - } - } - ACTION_RETURN_OBJECT(misl); - } - ACTION_RETURN_OBJECT(nullptr); -} - - -//========================================================================== -// -// A_CustomPunch -// -// Berserk is not handled here. That can be done with A_CheckIfInventory -// -//========================================================================== - -enum -{ - CPF_USEAMMO = 1, - CPF_DAGGER = 2, - CPF_PULLIN = 4, - CPF_NORANDOMPUFFZ = 8, - CPF_NOTURN = 16, - CPF_STEALARMOR = 32, -}; - -DEFINE_ACTION_FUNCTION(AStateProvider, A_CustomPunch) -{ - PARAM_ACTION_PROLOGUE(AStateProvider); - PARAM_INT (damage); - PARAM_BOOL (norandom); - PARAM_INT (flags); - PARAM_CLASS (pufftype, AActor); - PARAM_FLOAT (range); - PARAM_FLOAT (lifesteal); - PARAM_INT (lifestealmax); - PARAM_CLASS (armorbonustype, AActor); - PARAM_SOUND (MeleeSound); - PARAM_SOUND (MissSound); - - if (!self->player) - return 0; - - player_t *player = self->player; - AWeapon *weapon = player->ReadyWeapon; - - - DAngle angle; - DAngle pitch; - FTranslatedLineTarget t; - int actualdamage; - - if (!norandom) - damage *= pr_cwpunch() % 8 + 1; - - angle = self->Angles.Yaw + pr_cwpunch.Random2() * (5.625 / 256); - if (range == 0) range = DEFMELEERANGE; - pitch = P_AimLineAttack (self, angle, range, &t, 0., ALF_CHECK3D); - - // only use ammo when actually hitting something! - if ((flags & CPF_USEAMMO) && t.linetarget && weapon && ACTION_CALL_FROM_PSPRITE()) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) - return 0; // out of ammo - } - - if (pufftype == NULL) - pufftype = PClass::FindActor(NAME_BulletPuff); - int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0); - - P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, puffFlags, &t, &actualdamage); - - if (!t.linetarget) - { - if (MissSound) S_Sound(self, CHAN_WEAPON, MissSound, 1, ATTN_NORM); - } - else - { - if (lifesteal > 0 && !(t.linetarget->flags5 & MF5_DONTDRAIN)) - { - if (flags & CPF_STEALARMOR) - { - if (armorbonustype == NULL) - { - armorbonustype = PClass::FindActor("ArmorBonus"); - } - if (armorbonustype != NULL) - { - auto armorbonus = Spawn(armorbonustype); - armorbonus->IntVar(NAME_SaveAmount) *= int(actualdamage * lifesteal); - if (lifestealmax > 0) armorbonus->IntVar("MaxSaveAmount") = lifestealmax; - armorbonus->flags |= MF_DROPPED; - armorbonus->ClearCounters(); - - if (!static_cast(armorbonus)->CallTryPickup(self)) - { - armorbonus->Destroy (); - } - } - } - else - { - P_GiveBody (self, int(actualdamage * lifesteal), lifestealmax); - } - } - if (weapon != NULL) - { - if (MeleeSound) S_Sound(self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); - else S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); - } - - if (!(flags & CPF_NOTURN)) - { - // turn to face target - self->Angles.Yaw = t.angleFromSource; - } - - if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED; - if (flags & CPF_DAGGER) P_DaggerAlert (self, t.linetarget); - } - return 0; -} - - -//========================================================================== -// -// customizable railgun attack function -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AStateProvider, A_RailAttack) -{ - PARAM_ACTION_PROLOGUE(AStateProvider); - PARAM_INT (damage); - PARAM_INT (spawnofs_xy); - PARAM_BOOL (useammo); - PARAM_COLOR (color1); - PARAM_COLOR (color2); - PARAM_INT (flags); - PARAM_FLOAT (maxdiff); - PARAM_CLASS (pufftype, AActor); - PARAM_ANGLE (spread_xy); - PARAM_ANGLE (spread_z); - PARAM_FLOAT (range) ; - PARAM_INT (duration); - PARAM_FLOAT (sparsity); - PARAM_FLOAT (driftspeed); - PARAM_CLASS (spawnclass, AActor); - PARAM_FLOAT (spawnofs_z); - PARAM_INT (SpiralOffset); - PARAM_INT (limit); - - if (range == 0) range = 8192; - if (sparsity == 0) sparsity=1.0; - - if (self->player == NULL) - return 0; - - AWeapon *weapon = self->player->ReadyWeapon; - - // only use ammo when actually hitting something! - if (useammo && weapon != NULL && ACTION_CALL_FROM_PSPRITE()) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) - return 0; // out of ammo - } - - if (!(flags & RAF_EXPLICITANGLE)) - { - spread_xy = spread_xy * pr_crailgun.Random2() / 255; - spread_z = spread_z * pr_crailgun.Random2() / 255; - } - - FRailParams p; - p.source = self; - p.damage = damage; - p.offset_xy = spawnofs_xy; - p.offset_z = spawnofs_z; - p.color1 = color1; - p.color2 = color2; - p.maxdiff = maxdiff; - p.flags = flags; - p.puff = pufftype; - p.angleoffset = spread_xy; - p.pitchoffset = spread_z; - p.distance = range; - p.duration = duration; - p.sparsity = sparsity; - p.drift = driftspeed; - p.spawnclass = spawnclass; - p.SpiralOffset = SpiralOffset; - p.limit = limit; - P_RailAttack(&p); - return 0; -} - //========================================================================== // // also for monsters @@ -2493,452 +1820,6 @@ DEFINE_ACTION_FUNCTION(AActor, A_TakeFromSiblings) ACTION_RETURN_INT(count); } -//=========================================================================== -// -// Common code for A_SpawnItem and A_SpawnItemEx -// -//=========================================================================== - -enum SIX_Flags -{ - SIXF_TRANSFERTRANSLATION = 0x00000001, - SIXF_ABSOLUTEPOSITION = 0x00000002, - SIXF_ABSOLUTEANGLE = 0x00000004, - SIXF_ABSOLUTEVELOCITY = 0x00000008, - SIXF_SETMASTER = 0x00000010, - SIXF_NOCHECKPOSITION = 0x00000020, - SIXF_TELEFRAG = 0x00000040, - SIXF_CLIENTSIDE = 0x00000080, // only used by Skulldronum - SIXF_TRANSFERAMBUSHFLAG = 0x00000100, - SIXF_TRANSFERPITCH = 0x00000200, - SIXF_TRANSFERPOINTERS = 0x00000400, - SIXF_USEBLOODCOLOR = 0x00000800, - SIXF_CLEARCALLERTID = 0x00001000, - SIXF_MULTIPLYSPEED = 0x00002000, - SIXF_TRANSFERSCALE = 0x00004000, - SIXF_TRANSFERSPECIAL = 0x00008000, - SIXF_CLEARCALLERSPECIAL = 0x00010000, - SIXF_TRANSFERSTENCILCOL = 0x00020000, - SIXF_TRANSFERALPHA = 0x00040000, - SIXF_TRANSFERRENDERSTYLE = 0x00080000, - SIXF_SETTARGET = 0x00100000, - SIXF_SETTRACER = 0x00200000, - SIXF_NOPOINTERS = 0x00400000, - SIXF_ORIGINATOR = 0x00800000, - SIXF_TRANSFERSPRITEFRAME = 0x01000000, - SIXF_TRANSFERROLL = 0x02000000, - SIXF_ISTARGET = 0x04000000, - SIXF_ISMASTER = 0x08000000, - SIXF_ISTRACER = 0x10000000, -}; - -static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) -{ - if (mo == NULL) - { - return false; - } - AActor *originator = self; - - if (!(mo->flags2 & MF2_DONTTRANSLATE)) - { - if (flags & SIXF_TRANSFERTRANSLATION) - { - mo->Translation = self->Translation; - } - else if (flags & SIXF_USEBLOODCOLOR) - { - // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. - mo->Translation = self->BloodTranslation; - } - } - if (flags & SIXF_TRANSFERPOINTERS) - { - mo->target = self->target; - mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set - mo->tracer = self->tracer; - } - - mo->Angles.Yaw = self->Angles.Yaw; - if (flags & SIXF_TRANSFERPITCH) - { - mo->Angles.Pitch = self->Angles.Pitch; - } - if (!(flags & SIXF_ORIGINATOR)) - { - while (originator && originator->isMissile()) - { - originator = originator->target; - } - } - if (flags & SIXF_TELEFRAG) - { - P_TeleportMove(mo, mo->Pos(), true); - // This is needed to ensure consistent behavior. - // Otherwise it will only spawn if nothing gets telefragged - flags |= SIXF_NOCHECKPOSITION; - } - if (mo->flags3 & MF3_ISMONSTER) - { - if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) - { - // The monster is blocked so don't spawn it at all! - mo->ClearCounters(); - mo->Destroy(); - return false; - } - else if (originator && !(flags & SIXF_NOPOINTERS)) - { - if (originator->flags3 & MF3_ISMONSTER) - { - // If this is a monster transfer all friendliness information - mo->CopyFriendliness(originator, true); - } - else if (originator->player) - { - // A player always spawns a monster friendly to him - mo->flags |= MF_FRIENDLY; - mo->SetFriendPlayer(originator->player); - - AActor * attacker=originator->player->attacker; - if (attacker) - { - if (!(attacker->flags&MF_FRIENDLY) || - (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) - { - // Target the monster which last attacked the player - mo->LastHeard = mo->target = attacker; - } - } - } - } - } - else if (!(flags & SIXF_TRANSFERPOINTERS)) - { - // If this is a missile or something else set the target to the originator - mo->target = originator ? originator : self; - } - if (flags & SIXF_NOPOINTERS) - { - //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. - mo->LastHeard = NULL; //Sanity check. - mo->target = NULL; - mo->master = NULL; - mo->tracer = NULL; - } - if (flags & SIXF_SETMASTER) - { // don't let it attack you (optional)! - mo->master = originator; - } - if (flags & SIXF_SETTARGET) - { - mo->target = originator; - } - if (flags & SIXF_SETTRACER) - { - mo->tracer = originator; - } - if (flags & SIXF_TRANSFERSCALE) - { - mo->Scale = self->Scale; - } - if (flags & SIXF_TRANSFERAMBUSHFLAG) - { - mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); - } - if (flags & SIXF_CLEARCALLERTID) - { - self->RemoveFromHash(); - self->tid = 0; - } - if (flags & SIXF_TRANSFERSPECIAL) - { - mo->special = self->special; - memcpy(mo->args, self->args, sizeof(self->args)); - } - if (flags & SIXF_CLEARCALLERSPECIAL) - { - self->special = 0; - memset(self->args, 0, sizeof(self->args)); - } - if (flags & SIXF_TRANSFERSTENCILCOL) - { - mo->fillcolor = self->fillcolor; - } - if (flags & SIXF_TRANSFERALPHA) - { - mo->Alpha = self->Alpha; - } - if (flags & SIXF_TRANSFERRENDERSTYLE) - { - mo->RenderStyle = self->RenderStyle; - } - - if (flags & SIXF_TRANSFERSPRITEFRAME) - { - mo->sprite = self->sprite; - mo->frame = self->frame; - } - - if (flags & SIXF_TRANSFERROLL) - { - mo->Angles.Roll = self->Angles.Roll; - } - - if (flags & SIXF_ISTARGET) - { - self->target = mo; - } - if (flags & SIXF_ISMASTER) - { - self->master = mo; - } - if (flags & SIXF_ISTRACER) - { - self->tracer = mo; - } - return true; -} - -//=========================================================================== -// -// A_SpawnItem -// -// Spawns an item in front of the caller like Heretic's time bomb -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_SpawnItem) -{ - PARAM_ACTION_PROLOGUE(AActor); - PARAM_CLASS (missile, AActor) - PARAM_FLOAT (distance) - PARAM_FLOAT (zheight) - PARAM_BOOL (useammo) - PARAM_BOOL (transfer_translation); - - if (numret > 1) ret[1].SetObject(nullptr); - - if (missile == NULL) - { - if (numret > 0) ret[0].SetInt(false); - return MIN(numret, 2); - } - - // Don't spawn monsters if this actor has been massacred - if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - - if (ACTION_CALL_FROM_PSPRITE()) - { - // Used from a weapon, so use some ammo - AWeapon *weapon = self->player->ReadyWeapon; - - if (weapon == NULL) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - } - - AActor *mo = Spawn( missile, self->Vec3Angle(distance, self->Angles.Yaw, -self->Floorclip + self->GetBobOffset() + zheight), ALLOW_REPLACE); - - int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0); - bool res = InitSpawnedItem(self, mo, flags); // for an inventory item's use state - if (numret > 0) ret[0].SetInt(res); - if (numret > 1) ret[1].SetObject(mo); - return MIN(numret, 2); - -} - -//=========================================================================== -// -// A_SpawnItemEx -// -// Enhanced spawning function -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_SpawnItemEx) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_CLASS (missile, AActor); - PARAM_FLOAT (xofs) - PARAM_FLOAT (yofs) - PARAM_FLOAT (zofs) - PARAM_FLOAT (xvel) - PARAM_FLOAT (yvel) - PARAM_FLOAT (zvel) - PARAM_ANGLE (angle) - PARAM_INT (flags) - PARAM_INT (chance) - PARAM_INT (tid) - - if (numret > 1) ret[1].SetObject(nullptr); - - if (missile == NULL) - { - if (numret > 0) ret[0].SetInt(false); - return MIN(numret, 2); - } - if (chance > 0 && pr_spawnitemex() < chance) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - // Don't spawn monsters if this actor has been massacred - if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - - DVector2 pos; - - if (!(flags & SIXF_ABSOLUTEANGLE)) - { - angle += self->Angles.Yaw; - } - double s = angle.Sin(); - double c = angle.Cos(); - - if (flags & SIXF_ABSOLUTEPOSITION) - { - pos = self->Vec2Offset(xofs, yofs); - } - else - { - // in relative mode negative y values mean 'left' and positive ones mean 'right' - // This is the inverse orientation of the absolute mode! - pos = self->Vec2Offset(xofs * c + yofs * s, xofs * s - yofs*c); - } - - if (!(flags & SIXF_ABSOLUTEVELOCITY)) - { - // Same orientation issue here! - double newxvel = xvel * c + yvel * s; - yvel = xvel * s - yvel * c; - xvel = newxvel; - } - - AActor *mo = Spawn(missile, DVector3(pos, self->Z() - self->Floorclip + self->GetBobOffset() + zofs), ALLOW_REPLACE); - bool res = InitSpawnedItem(self, mo, flags); - if (res) - { - if (tid != 0) - { - assert(mo->tid == 0); - mo->tid = tid; - mo->AddToHash(); - } - mo->Vel = {xvel, yvel, zvel}; - if (flags & SIXF_MULTIPLYSPEED) - { - mo->Vel *= mo->Speed; - } - mo->Angles.Yaw = angle; - } - if (numret > 0) ret[0].SetInt(res); - if (numret > 1) ret[1].SetObject(mo); - return MIN(numret, 2); -} - -//=========================================================================== -// -// A_ThrowGrenade -// -// Throws a grenade (like Hexen's fighter flechette) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_ThrowGrenade) -{ - PARAM_ACTION_PROLOGUE(AActor); - PARAM_CLASS (missile, AActor); - PARAM_FLOAT (zheight) - PARAM_FLOAT (xyvel) - PARAM_FLOAT (zvel) - PARAM_BOOL (useammo) - - if (numret > 1) ret[1].SetObject(nullptr); - - if (missile == NULL) - { - if (numret > 0) ret[0].SetInt(false); - return MIN(numret, 2); - } - if (ACTION_CALL_FROM_PSPRITE()) - { - // Used from a weapon, so use some ammo - AWeapon *weapon = self->player->ReadyWeapon; - - if (weapon == NULL) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - } - - AActor *bo; - - bo = Spawn(missile, - self->PosPlusZ(-self->Floorclip + self->GetBobOffset() + zheight + 35 + (self->player? self->player->crouchoffset : 0.)), - ALLOW_REPLACE); - if (bo) - { - P_PlaySpawnSound(bo, self); - if (xyvel != 0) - bo->Speed = xyvel; - bo->Angles.Yaw = self->Angles.Yaw + (((pr_grenade()&7) - 4) * (360./256.)); - - DAngle pitch = -self->Angles.Pitch; - DAngle angle = bo->Angles.Yaw; - - // There are two vectors we are concerned about here: xy and z. We rotate - // them separately according to the shooter's pitch and then sum them to - // get the final velocity vector to shoot with. - - double xy_xyscale = bo->Speed * pitch.Cos(); - double xy_velz = bo->Speed * pitch.Sin(); - double xy_velx = xy_xyscale * angle.Cos(); - double xy_vely = xy_xyscale * angle.Sin(); - - pitch = self->Angles.Pitch; - double z_xyscale = zvel * pitch.Sin(); - double z_velz = zvel * pitch.Cos(); - double z_velx = z_xyscale * angle.Cos(); - double z_vely = z_xyscale * angle.Sin(); - - bo->Vel.X = xy_velx + z_velx + self->Vel.X / 2; - bo->Vel.Y = xy_vely + z_vely + self->Vel.Y / 2; - bo->Vel.Z = xy_velz + z_velz; - - bo->target = self; - if (!P_CheckMissileSpawn(bo, self->radius)) bo = nullptr; - - if (numret > 0) ret[0].SetInt(true); - if (numret > 1) ret[1].SetObject(bo); - return MIN(numret, 2); - } - else - { - if (numret > 0) ret[0].SetInt(false); - return MIN(numret, 2); - } -} - - //=========================================================================== // // A_Recoil @@ -2966,7 +1847,7 @@ enum SW_Flags DEFINE_ACTION_FUNCTION(AActor, A_SelectWeapon) { PARAM_SELF_PROLOGUE(AActor); - PARAM_CLASS(cls, AWeapon); + PARAM_CLASS(cls, AInventory); PARAM_INT(flags); bool selectPriority = !!(flags & SWF_SELECTPRIORITY); @@ -2976,7 +1857,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_SelectWeapon) ACTION_RETURN_BOOL(false); } - AWeapon *weaponitem = static_cast(self->FindInventory(cls)); + auto weaponitem = self->FindInventory(cls); if (weaponitem != NULL && weaponitem->IsKindOf(NAME_Weapon)) { @@ -4339,77 +3220,6 @@ DEFINE_ACTION_FUNCTION(AActor, CheckIfInTargetLOS) ACTION_RETURN_BOOL(true); } -//=========================================================================== -// -// Modified code pointer from Skulltag -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AStateProvider, A_CheckForReload) -{ - PARAM_ACTION_PROLOGUE(AStateProvider); - - if ( self->player == NULL || self->player->ReadyWeapon == NULL ) - { - ACTION_RETURN_STATE(NULL); - } - PARAM_INT (count); - PARAM_STATE_ACTION (jump); - PARAM_BOOL (dontincrement); - - if (numret > 0) - { - ret->SetPointer(NULL); - numret = 1; - } - - AWeapon *weapon = self->player->ReadyWeapon; - - int ReloadCounter = weapon->ReloadCounter; - if (!dontincrement || ReloadCounter != 0) - ReloadCounter = (weapon->ReloadCounter+1) % count; - else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though. - ReloadCounter = 1; - - // If we have not made our last shot... - if (ReloadCounter != 0) - { - // Go back to the refire frames, instead of continuing on to the reload frames. - if (numret != 0) - { - ret->SetPointer(jump); - } - } - else - { - // We need to reload. However, don't reload if we're out of ammo. - weapon->CheckAmmo(false, false); - } - if (!dontincrement) - { - weapon->ReloadCounter = ReloadCounter; - } - return numret; -} - -//=========================================================================== -// -// Resets the counter for the above function -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AStateProvider, A_ResetReloadCounter) -{ - PARAM_ACTION_PROLOGUE(AStateProvider); - - if (self->player == NULL || self->player->ReadyWeapon == NULL) - return 0; - - AWeapon *weapon = self->player->ReadyWeapon; - weapon->ReloadCounter = 0; - return 0; -} - //=========================================================================== // // A_ChangeFlag @@ -5675,8 +4485,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_DropItem) PARAM_INT(amount); PARAM_INT(chance); - P_DropItem(self, spawntype, amount, chance); - return 0; + ACTION_RETURN_OBJECT(P_DropItem(self, spawntype, amount, chance)); } //=========================================================================== diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index c577a4c2c..d0e67ca7a 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -965,7 +965,7 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply if (item->GetClass()->TypeName == NAME_FlameThrower) { // The flame thrower gives less ammo when given in a dialog - static_cast(item)->AmmoGive1 = 40; + item->IntVar(NAME_AmmoGive1) = 40; } item->flags |= MF_DROPPED; if (!item->CallTryPickup(player->mo)) diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index 12f43e9fc..a54603d8f 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -268,71 +268,6 @@ DEFINE_ACTION_FUNCTION(AActor, SoundAlert) return 0; } -//============================================================================ -// -// P_DaggerAlert -// -//============================================================================ - -void P_DaggerAlert(AActor *target, AActor *emitter) -{ - AActor *looker; - sector_t *sec = emitter->Sector; - - if (emitter->LastHeard != NULL) - return; - if (emitter->health <= 0) - return; - if (!(emitter->flags3 & MF3_ISMONSTER)) - return; - if (emitter->flags4 & MF4_INCOMBAT) - return; - emitter->flags4 |= MF4_INCOMBAT; - - emitter->target = target; - FState *painstate = emitter->FindState(NAME_Pain, NAME_Dagger); - if (painstate != NULL) - { - emitter->SetState(painstate); - } - - for (looker = sec->thinglist; looker != NULL; looker = looker->snext) - { - if (looker == emitter || looker == target) - continue; - - if (looker->health <= 0) - continue; - - if (!(looker->flags4 & MF4_SEESDAGGERS)) - continue; - - if (!(looker->flags4 & MF4_INCOMBAT)) - { - if (!P_CheckSight(looker, target) && !P_CheckSight(looker, emitter)) - continue; - - looker->target = target; - if (looker->SeeSound) - { - S_Sound(looker, CHAN_VOICE, looker->SeeSound, 1, ATTN_NORM); - } - looker->SetState(looker->SeeState); - looker->flags4 |= MF4_INCOMBAT; - } - } -} - -DEFINE_ACTION_FUNCTION(AActor, DaggerAlert) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_OBJECT(target, AActor); - // Note that the emitter is self, not the target of the alert! Target can be NULL. - P_DaggerAlert(target, self); - return 0; -} - - //---------------------------------------------------------------------------- // // AActor :: CheckMeleeRange @@ -1094,7 +1029,7 @@ void P_NewChaseDir(AActor * actor) { // melee range of player weapon is a parameter of the action function and cannot be checked here. // Add a new weapon property? - ismeleeattacker = ((target->player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON) && dist < 192); + ismeleeattacker = ((target->player->ReadyWeapon->IntVar(NAME_WeaponFlags) & WIF_MELEEWEAPON) && dist < 192); } if (ismeleeattacker) { @@ -3222,51 +3157,6 @@ DEFINE_ACTION_FUNCTION(AActor, A_MonsterRail) return 0; } -DEFINE_ACTION_FUNCTION(AActor, A_Scream) -{ - PARAM_SELF_PROLOGUE(AActor); - if (self->DeathSound) - { - // Check for bosses. - if (self->flags2 & MF2_BOSS) - { - // full volume - S_Sound (self, CHAN_VOICE, self->DeathSound, 1, ATTN_NONE); - } - else - { - S_Sound (self, CHAN_VOICE, self->DeathSound, 1, ATTN_NORM); - } - } - return 0; -} - -DEFINE_ACTION_FUNCTION(AActor, A_XScream) -{ - PARAM_SELF_PROLOGUE(AActor); - if (self->player) - S_Sound (self, CHAN_VOICE, "*gibbed", 1, ATTN_NORM); - else - S_Sound (self, CHAN_VOICE, "misc/gibbed", 1, ATTN_NORM); - return 0; -} - -//=========================================================================== -// -// A_ActiveSound -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_ActiveSound) -{ - PARAM_SELF_PROLOGUE(AActor); - if (self->ActiveSound) - { - S_Sound(self, CHAN_VOICE, self->ActiveSound, 1, ATTN_NORM); - } - return 0; -} - //--------------------------------------------------------------------------- // // Modifies the drop amount of this item according to the current skill's @@ -3275,64 +3165,11 @@ DEFINE_ACTION_FUNCTION(AActor, A_ActiveSound) //--------------------------------------------------------------------------- void ModifyDropAmount(AInventory *inv, int dropamount) { - auto flagmask = IF_IGNORESKILL; - double dropammofactor = G_SkillProperty(SKILLP_DropAmmoFactor); - // Default drop amount is half of regular amount * regular ammo multiplication - if (dropammofactor == -1) + IFVIRTUALPTR(inv, AInventory, ModifyDropAmount) { - dropammofactor = 0.5; - flagmask = ItemFlag(0); + VMValue params[] = { inv, dropamount }; + VMCall(func, params, 2, nullptr, 0); } - - if (dropamount > 0) - { - if (flagmask != 0 && inv->IsKindOf(NAME_Ammo)) - { - inv->Amount = int(dropamount * dropammofactor); - inv->ItemFlags |= IF_IGNORESKILL; - } - else - { - inv->Amount = dropamount; - } - } - else if (inv->IsKindOf (PClass::FindActor(NAME_Ammo))) - { - // Half ammo when dropped by bad guys. - int amount = inv->IntVar("DropAmount"); - if (amount <= 0) - { - amount = MAX(1, int(inv->Amount * dropammofactor)); - } - inv->Amount = amount; - inv->ItemFlags |= flagmask; - } - else if (inv->IsKindOf (PClass::FindActor(NAME_WeaponGiver))) - { - inv->FloatVar("AmmoFactor") = dropammofactor; - inv->ItemFlags |= flagmask; - } - else if (inv->IsKindOf(NAME_Weapon)) - { - // The same goes for ammo from a weapon. - static_cast(inv)->AmmoGive1 = int(static_cast(inv)->AmmoGive1 * dropammofactor); - static_cast(inv)->AmmoGive2 = int(static_cast(inv)->AmmoGive2 * dropammofactor); - inv->ItemFlags |= flagmask; - } - else if (inv->IsKindOf (PClass::FindClass(NAME_DehackedPickup))) - { - // For weapons and ammo modified by Dehacked we need to flag the item. - inv->BoolVar("droppedbymonster") = true; - } -} - -// todo: make this a scripted virtual function so it can better deal with some of the classes involved. -DEFINE_ACTION_FUNCTION(AInventory, ModifyDropAmount) -{ - PARAM_SELF_PROLOGUE(AInventory); - PARAM_INT(dropamount); - ModifyDropAmount(self, dropamount); - return 0; } //--------------------------------------------------------------------------- @@ -3343,7 +3180,7 @@ DEFINE_ACTION_FUNCTION(AInventory, ModifyDropAmount) CVAR(Int, sv_dropstyle, 0, CVAR_SERVERINFO | CVAR_ARCHIVE); -AInventory *P_DropItem (AActor *source, PClassActor *type, int dropamount, int chance) +AActor *P_DropItem (AActor *source, PClassActor *type, int dropamount, int chance) { if (type != NULL && pr_dropitem() <= chance) { @@ -3394,9 +3231,8 @@ AInventory *P_DropItem (AActor *source, PClassActor *type, int dropamount, int c return NULL; } } - return inv; } - // we can't really return an AInventory pointer to a non-inventory item here, can we? + return mo; } } return NULL; @@ -3474,20 +3310,6 @@ DEFINE_ACTION_FUNCTION(AActor, A_Pain) return 0; } -// -// A_Detonate -// killough 8/9/98: same as A_Explode, except that the damage is variable -// - -DEFINE_ACTION_FUNCTION(AActor, A_Detonate) -{ - PARAM_SELF_PROLOGUE(AActor); - int damage = self->GetMissileDamage(0, 1); - P_RadiusAttack (self, self->target, damage, damage, self->DamageType, RADF_HURTSOURCE); - P_CheckSplash(self, damage); - return 0; -} - bool CheckBossDeath (AActor *actor) { int i; diff --git a/src/p_enemy.h b/src/p_enemy.h index bdc044c5c..4dff6f810 100644 --- a/src/p_enemy.h +++ b/src/p_enemy.h @@ -46,7 +46,6 @@ struct FLookExParams FState *seestate; }; -void P_DaggerAlert (AActor *target, AActor *emitter); bool P_HitFriend (AActor *self); void P_NoiseAlert (AActor *target, AActor *emmiter, bool splash=false, double maxdist=0); @@ -54,7 +53,7 @@ bool P_CheckMeleeRange2 (AActor *actor); bool P_Move (AActor *actor); bool P_TryWalk (AActor *actor); void P_NewChaseDir (AActor *actor); -AInventory *P_DropItem (AActor *source, PClassActor *type, int special, int chance); +AActor *P_DropItem (AActor *source, PClassActor *type, int special, int chance); void P_TossItem (AActor *item); bool P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params); void A_Weave(AActor *self, int xyspeed, int zspeed, double xydist, double zdist); diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index f40d01034..fd7f53fbe 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -68,7 +68,6 @@ FRandom pr_damagemobj ("ActorTakeDamage"); static FRandom pr_lightning ("LightningDamage"); static FRandom pr_poison ("PoisonDamage"); static FRandom pr_switcher ("SwitchTarget"); -static FRandom pr_kickbackdir ("KickbackDir"); CVAR (Bool, cl_showsprees, true, CVAR_ARCHIVE) CVAR (Bool, cl_showmultikills, true, CVAR_ARCHIVE) @@ -289,39 +288,41 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf { // Handle possible unmorph on death bool wasgibbed = (health < GetGibHealth()); - AActor *realthis = NULL; - int realstyle = 0; - int realhealth = 0; - if (P_MorphedDeath(this, &realthis, &realstyle, &realhealth)) + { - if (!(realstyle & MORPH_UNDOBYDEATHSAVES)) + IFVIRTUAL(AActor, MorphedDeath) { - if (wasgibbed) + AActor *realthis = NULL; + int realstyle = 0; + int realhealth = 0; + + VMValue params[] = { this }; + VMReturn returns[3]; + returns[0].PointerAt((void**)&realthis); + returns[1].IntAt(&realstyle); + returns[2].IntAt(&realhealth); + VMCall(func, params, 1, returns, 3); + + if (realthis && !(realstyle & MORPH_UNDOBYDEATHSAVES)) { - int realgibhealth = realthis->GetGibHealth(); - if (realthis->health >= realgibhealth) + if (wasgibbed) { - realthis->health = realgibhealth -1; // if morphed was gibbed, so must original be (where allowed)l + int realgibhealth = realthis->GetGibHealth(); + if (realthis->health >= realgibhealth) + { + realthis->health = realgibhealth - 1; // if morphed was gibbed, so must original be (where allowed)l + } } + realthis->CallDie(source, inflictor, dmgflags, MeansOfDeath); } - realthis->CallDie(source, inflictor, dmgflags, MeansOfDeath); + } - return; } // [SO] 9/2/02 -- It's rather funny to see an exploded player body with the invuln sparkle active :) effects &= ~FX_RESPAWNINVUL; //flags &= ~MF_INVINCIBLE; - if (debugfile && this->player) - { - static int dieticks[MAXPLAYERS]; // [ZzZombo] not used? Except if for peeking in debugger... - int pnum = int(this->player-players); - dieticks[pnum] = gametic; - fprintf(debugfile, "died (%d) on tic %d (%s)\n", pnum, gametic, - this->player->cheats&CF_PREDICTING ? "predicting" : "real"); - } - // [RH] Notify this actor's items. for (AInventory *item = Inventory; item != NULL; ) { @@ -597,7 +598,13 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf flags &= ~MF_SOLID; player->playerstate = PST_DEAD; - P_DropWeapon (player); + + IFVM(PlayerPawn, DropWeapon) + { + VMValue param = player->mo; + VMCall(func, ¶m, 1, nullptr, 0); + } + if (this == players[consoleplayer].camera && automapactive) { // don't die in auto map, switch view prior to dying @@ -915,9 +922,7 @@ static inline bool isFakePain(AActor *target, AActor *inflictor, int damage) // the damage was cancelled. static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags, DAngle angle, bool& needevent) { - DAngle ang; player_t *player = NULL; - double thrust; int temp; int painchance = 0; FState * woundstate = NULL; @@ -1180,73 +1185,10 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da && !(target->flags7 & MF7_DONTTHRUST) && (source == NULL || source->player == NULL || !(source->flags2 & MF2_NODMGTHRUST))) { - int kickback; - - if (inflictor && inflictor->projectileKickback) - kickback = inflictor->projectileKickback; - else if (!source || !source->player || !source->player->ReadyWeapon) - kickback = gameinfo.defKickback; - else - kickback = source->player->ReadyWeapon->Kickback; - - kickback = int(kickback * G_SkillProperty(SKILLP_KickbackFactor)); - if (kickback) + IFVIRTUALPTR(target, AActor, ApplyKickback) { - AActor *origin = (source && (flags & DMG_INFLICTOR_IS_PUFF))? source : inflictor; - - if (flags & DMG_USEANGLE) - { - ang = angle; - } - else if (origin->X() == target->X() && origin->Y() == target->Y()) - { - // If the origin and target are in exactly the same spot, choose a random direction. - // (Most likely cause is from telefragging somebody during spawning because they - // haven't moved from their spawn spot at all.) - ang = pr_kickbackdir.GenRand_Real2() * 360.; - } - else - { - ang = origin->AngleTo(target); - } - - thrust = mod == NAME_MDK ? 10 : 32; - if (target->Mass > 0) - { - thrust = clamp((damage * 0.125 * kickback) / target->Mass, 0., thrust); - } - - // Don't apply ultra-small damage thrust - if (thrust < 0.01) thrust = 0; - - // make fall forwards sometimes - if ((damage < 40) && (damage > target->health) - && (target->Z() - origin->Z() > 64) - && (pr_damagemobj()&1) - // [RH] But only if not too fast and not flying - && thrust < 10 - && !(target->flags & MF_NOGRAVITY) - && (inflictor == NULL || !(inflictor->flags5 & MF5_NOFORWARDFALL)) - ) - { - ang += 180.; - thrust *= 4; - } - if (source && source->player && (flags & DMG_INFLICTOR_IS_PUFF) - && source->player->ReadyWeapon != NULL && - (source->player->ReadyWeapon->WeaponFlags & WIF_STAFF2_KICKBACK)) - { - // Staff power level 2 - target->Thrust(ang, 10); - if (!(target->flags & MF_NOGRAVITY)) - { - target->Vel.Z += 5.; - } - } - else - { - target->Thrust(ang, thrust); - } + VMValue params[] = { target, inflictor, source, damage, angle.Degrees, mod.GetIndex(), flags }; + VMCall(func, params, countof(params), nullptr, 0); } } diff --git a/src/p_local.h b/src/p_local.h index 9b41253a9..8e4c415a2 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -80,7 +80,6 @@ extern int bmapnegy; // P_PSPR // void P_SetupPsprites (player_t* curplayer, bool startweaponup); -void P_DropWeapon (player_t* player); // @@ -132,9 +131,6 @@ AActor *P_SpawnMissileAngleZSpeed(AActor *source, double z, PClassActor *type, D AActor *P_SpawnMissileZAimed(AActor *source, double z, AActor *dest, PClassActor *type); -AActor *P_SpawnPlayerMissile (AActor* source, PClassActor *type); -AActor *P_SpawnPlayerMissile (AActor *source, PClassActor *type, DAngle angle); - AActor *P_SpawnPlayerMissile (AActor *source, double x, double y, double z, PClassActor *type, DAngle angle, FTranslatedLineTarget *pLineTarget = NULL, AActor **MissileActor = NULL, bool nofreeaim = false, bool noautoaim = false, int aimflags = 0); @@ -320,6 +316,7 @@ enum // P_AimLineAttack flags ALF_CHECKCONVERSATION = 8, ALF_NOFRIENDS = 16, ALF_PORTALRESTRICT = 32, // only work through portals with a global offset (to be used for stuff that cannot remember the calculated FTranslatedLineTarget info) + ALF_NOWEAPONCHECK = 64, // ignore NOAUTOAIM flag on a player's weapon. }; enum // P_LineAttack flags @@ -360,7 +357,6 @@ void P_TraceBleed(int damage, FTranslatedLineTarget *t, AActor *puff); // hitsc void P_TraceBleed (int damage, AActor *target); // random direction version bool P_HitFloor (AActor *thing); bool P_HitWater (AActor *thing, sector_t *sec, const DVector3 &pos, bool checkabove = false, bool alert = true, bool force = false); -void P_CheckSplash(AActor *self, double distance); struct FRailParams { @@ -411,6 +407,7 @@ enum RADF_SOURCEISSPOT = 4, RADF_NODAMAGE = 8, RADF_THRUSTZ = 16, + RADF_OLDRADIUSDAMAGE = 32 }; int P_RadiusAttack (AActor *spot, AActor *source, int damage, int distance, FName damageType, int flags, int fulldamagedistance=0); diff --git a/src/p_map.cpp b/src/p_map.cpp index dcf435994..c2b3c0e70 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -156,6 +156,25 @@ DEFINE_FIELD_X(FCheckPosition, FCheckPosition, portalstep); DEFINE_FIELD_X(FCheckPosition, FCheckPosition, portalgroup); DEFINE_FIELD_X(FCheckPosition, FCheckPosition, PushTime); +DEFINE_FIELD_X(FRailParams, FRailParams, source); +DEFINE_FIELD_X(FRailParams, FRailParams, damage); +DEFINE_FIELD_X(FRailParams, FRailParams, offset_xy); +DEFINE_FIELD_X(FRailParams, FRailParams, offset_z); +DEFINE_FIELD_X(FRailParams, FRailParams, color1); +DEFINE_FIELD_X(FRailParams, FRailParams, color2); +DEFINE_FIELD_X(FRailParams, FRailParams, maxdiff); +DEFINE_FIELD_X(FRailParams, FRailParams, flags); +DEFINE_FIELD_X(FRailParams, FRailParams, puff); +DEFINE_FIELD_X(FRailParams, FRailParams, angleoffset); +DEFINE_FIELD_X(FRailParams, FRailParams, pitchoffset); +DEFINE_FIELD_X(FRailParams, FRailParams, distance); +DEFINE_FIELD_X(FRailParams, FRailParams, duration); +DEFINE_FIELD_X(FRailParams, FRailParams, sparsity); +DEFINE_FIELD_X(FRailParams, FRailParams, drift); +DEFINE_FIELD_X(FRailParams, FRailParams, spawnclass); +DEFINE_FIELD_X(FRailParams, FRailParams, SpiralOffset); +DEFINE_FIELD_X(FRailParams, FRailParams, limit); + //========================================================================== // // CanCollideWith @@ -4439,8 +4458,8 @@ DAngle P_AimLineAttack(AActor *t1, DAngle angle, double distance, FTranslatedLin else { // [BB] Disable autoaim on weapons with WIF_NOAUTOAIM. - AWeapon *weapon = t1->player->ReadyWeapon; - if (weapon && (weapon->WeaponFlags & WIF_NOAUTOAIM)) + auto weapon = t1->player->ReadyWeapon; + if ((weapon && (weapon->IntVar(NAME_WeaponFlags) & WIF_NOAUTOAIM)) && !(flags & ALF_NOWEAPONCHECK)) { vrange = 0.5; } @@ -4840,38 +4859,13 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance, } if (!(puffDefaults != NULL && puffDefaults->flags3&MF3_BLOODLESSIMPACT)) { - bool bloodsplatter = (t1->flags5 & MF5_BLOODSPLATTER) || - (t1->player != nullptr && t1->player->ReadyWeapon != nullptr && - (t1->player->ReadyWeapon->WeaponFlags & WIF_AXEBLOOD)); - - bool axeBlood = (t1->player != nullptr && - t1->player->ReadyWeapon != nullptr && - (t1->player->ReadyWeapon->WeaponFlags & WIF_AXEBLOOD)); - - if (!bloodsplatter && !axeBlood && - !(trace.Actor->flags & MF_NOBLOOD) && - !(trace.Actor->flags2 & (MF2_INVULNERABLE | MF2_DORMANT))) + IFVIRTUALPTR(trace.Actor, AActor, SpawnLineAttackBlood) { - P_SpawnBlood(bleedpos, trace.SrcAngleFromTarget, newdam > 0 ? newdam : damage, trace.Actor); + VMValue params[] = { trace.Actor, t1, bleedpos.X, bleedpos.Y, bleedpos.Z, trace.SrcAngleFromTarget.Degrees, damage, newdam }; + VMCall(func, params, countof(params), nullptr, 0); } - if (damage) { - if (bloodsplatter || axeBlood) - { - if (!(trace.Actor->flags&MF_NOBLOOD) && - !(trace.Actor->flags2&(MF2_INVULNERABLE | MF2_DORMANT))) - { - if (axeBlood) - { - P_BloodSplatter2(bleedpos, trace.Actor, trace.SrcAngleFromTarget); - } - if (pr_lineattack() < 192) - { - P_BloodSplatter(bleedpos, trace.Actor, trace.SrcAngleFromTarget); - } - } - } // [RH] Stick blood to walls P_TraceBleed(newdam > 0 ? newdam : damage, trace.HitPos, trace.Actor, trace.SrcAngleFromTarget, pitch); } @@ -5603,6 +5597,15 @@ void P_RailAttack(FRailParams *p) P_DrawRailTrail(source, rail_data.PortalHits, p->color1, p->color2, p->maxdiff, p->flags, p->spawnclass, angle, p->duration, p->sparsity, p->drift, p->SpiralOffset, pitch); } +DEFINE_ACTION_FUNCTION(AActor, RailAttack) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_POINTER(p, FRailParams); + p->source = self; + P_RailAttack(p); + return 0; +} + //========================================================================== // // [RH] P_AimCamera @@ -6201,7 +6204,7 @@ int P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bom // them far too "active." BossBrains also use the old code // because some user levels require they have a height of 16, // which can make them near impossible to hit with the new code. - if ((flags & RADF_NODAMAGE) || !((bombspot->flags5 | thing->flags5) & MF5_OLDRADIUSDMG)) + if (((flags & RADF_NODAMAGE) || !((bombspot->flags5 | thing->flags5) & MF5_OLDRADIUSDMG)) && !(flags & RADF_OLDRADIUSDAMAGE)) { double points = P_GetRadiusDamage(false, bombspot, thing, bombdamage, bombdistance, fulldamagedistance, bombsource == thing); double check = int(points) * bombdamage; @@ -6278,6 +6281,18 @@ int P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bom return count; } +DEFINE_ACTION_FUNCTION(AActor, RadiusAttack) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OBJECT(bombsource, AActor); + PARAM_INT(bombdamage); + PARAM_INT(bombdistance); + PARAM_NAME(damagetype); + PARAM_INT(flags); + PARAM_INT(fulldamagedistance); + ACTION_RETURN_INT(P_RadiusAttack(self, bombsource, bombdamage, bombdistance, damagetype, flags, fulldamagedistance)); +} + //========================================================================== // // SECTOR HEIGHT CHANGING diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index a4d4d9393..733ea9c57 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -804,7 +804,7 @@ bool AActor::GiveInventory(PClassActor *type, int amount, bool givecheat) if (type == nullptr || !type->IsDescendantOf(RUNTIME_CLASS(AInventory))) return false; - AWeapon *savedPendingWeap = player != NULL ? player->PendingWeapon : NULL; + auto savedPendingWeap = player != NULL ? player->PendingWeapon : NULL; bool hadweap = player != NULL ? player->ReadyWeapon != NULL : true; AInventory *item; @@ -1071,6 +1071,12 @@ void AActor::DestroyAllInventory () } } +DEFINE_ACTION_FUNCTION(AActor, DestroyAllInventory) +{ + PARAM_SELF_PROLOGUE(AActor); + self->DestroyAllInventory(); + return 0; +} //============================================================================ // // AActor :: FirstInv @@ -1120,9 +1126,14 @@ bool AActor::UseInventory (AInventory *item) { return false; } - if (!item->CallUse (false)) + + IFVIRTUALPTR(item, AInventory, Use) { - return false; + VMValue params[2] = { item, false }; + int retval; + VMReturn ret(&retval); + VMCall(func, params, 2, &ret, 1); + if (!retval) return false; } if (dmflags2 & DF2_INFINITE_INVENTORY) @@ -5319,26 +5330,6 @@ void AActor::CallPostBeginPlay() E_WorldThingSpawned(this); } -void AActor::MarkPrecacheSounds() const -{ - SeeSound.MarkUsed(); - AttackSound.MarkUsed(); - PainSound.MarkUsed(); - DeathSound.MarkUsed(); - ActiveSound.MarkUsed(); - UseSound.MarkUsed(); - BounceSound.MarkUsed(); - WallBounceSound.MarkUsed(); - CrushPainSound.MarkUsed(); -} - -DEFINE_ACTION_FUNCTION(AActor, MarkPrecacheSounds) -{ - PARAM_SELF_PROLOGUE(AActor); - self->MarkPrecacheSounds(); - return 0; -} - bool AActor::isFast() { if (flags5&MF5_ALWAYSFAST) return true; @@ -5731,7 +5722,11 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags) else if ((multiplayer || (level.flags2 & LEVEL2_ALLOWRESPAWN) || sv_singleplayerrespawn || !!G_SkillProperty(SKILLP_PlayerRespawn)) && state == PST_REBORN && oldactor != NULL) { // Special inventory handling for respawning in coop - p->mo->FilterCoopRespawnInventory (oldactor); + IFVM(PlayerPawn, FilterCoopRespawnInventory) + { + VMValue params[] = { p->mo, oldactor }; + VMCall(func, params, 2, nullptr, 0); + } } if (oldactor != NULL) { // Remove any inventory left from the old actor. Coop handles @@ -6797,29 +6792,6 @@ DEFINE_ACTION_FUNCTION(AActor, HitFloor) ACTION_RETURN_BOOL(P_HitFloor(self)); } -//--------------------------------------------------------------------------- -// -// P_CheckSplash -// -// Checks for splashes caused by explosions -// -//--------------------------------------------------------------------------- - -void P_CheckSplash(AActor *self, double distance) -{ - sector_t *floorsec; - self->Sector->LowestFloorAt(self, &floorsec); - if (self->Z() <= self->floorz + distance && self->floorsector == floorsec && self->Sector->GetHeightSec() == NULL && floorsec->heightsec == NULL) - { - // Explosion splashes never alert monsters. This is because A_Explode has - // a separate parameter for that so this would get in the way of proper - // behavior. - DVector3 pos = self->PosRelative(floorsec); - pos.Z = self->floorz; - P_HitWater (self, floorsec, pos, false, false); - } -} - //--------------------------------------------------------------------------- // // FUNC P_CheckMissileSpawn @@ -7315,20 +7287,6 @@ DEFINE_ACTION_FUNCTION(AActor, SpawnSubMissile) ================ */ -AActor *P_SpawnPlayerMissile (AActor *source, PClassActor *type) -{ - if (source == NULL) - { - return NULL; - } - return P_SpawnPlayerMissile (source, 0, 0, 0, type, source->Angles.Yaw); -} - -AActor *P_SpawnPlayerMissile (AActor *source, PClassActor *type, DAngle angle) -{ - return P_SpawnPlayerMissile (source, 0, 0, 0, type, angle); -} - AActor *P_SpawnPlayerMissile (AActor *source, double x, double y, double z, PClassActor *type, DAngle angle, FTranslatedLineTarget *pLineTarget, AActor **pMissileActor, bool nofreeaim, bool noautoaim, int aimflags) @@ -7346,7 +7304,7 @@ AActor *P_SpawnPlayerMissile (AActor *source, double x, double y, double z, DAngle vrange = nofreeaim ? 35. : 0.; if (!pLineTarget) pLineTarget = &scratch; - if (source->player && source->player->ReadyWeapon && ((source->player->ReadyWeapon->WeaponFlags & WIF_NOAUTOAIM) || noautoaim)) + if (!(aimflags & ALF_NOWEAPONCHECK) && source->player && source->player->ReadyWeapon && ((source->player->ReadyWeapon->IntVar(NAME_WeaponFlags) & WIF_NOAUTOAIM) || noautoaim)) { // Keep exactly the same angle and pitch as the player's own aim an = angle; diff --git a/src/p_pspr.cpp b/src/p_pspr.cpp index 76587f6cb..222ab9523 100644 --- a/src/p_pspr.cpp +++ b/src/p_pspr.cpp @@ -44,6 +44,7 @@ #include "cmdlib.h" #include "g_levellocals.h" #include "vm.h" +#include "sbar.h" // MACROS ------------------------------------------------------------------ @@ -92,8 +93,6 @@ CVAR(Int, sv_fastweapons, false, CVAR_SERVERINFO); // PRIVATE DATA DEFINITIONS ------------------------------------------------ -static FRandom pr_wpnreadysnd ("WpnReadySnd"); - static const FGenericButtons ButtonChecks[] = { { WRF_AllowZoom, WF_WEAPONZOOMOK, BT_ZOOM, NAME_Zoom }, @@ -556,86 +555,13 @@ DEFINE_ACTION_FUNCTION(DPSprite, SetState) void P_BringUpWeapon (player_t *player) { - AWeapon *weapon; - - if (player->PendingWeapon == WP_NOCHANGE) + IFVM(PlayerPawn, BringUpWeapon) { - if (player->ReadyWeapon != nullptr) - { - player->GetPSprite(PSP_WEAPON)->y = WEAPONTOP; - P_SetPsprite(player, PSP_WEAPON, player->ReadyWeapon->GetReadyState()); - } - return; - } - - weapon = player->PendingWeapon; - - // If the player has a tome of power, use this weapon's powered up - // version, if one is available. - if (weapon != nullptr && - weapon->SisterWeapon && - weapon->SisterWeapon->WeaponFlags & WIF_POWERED_UP && - player->mo->FindInventory (PClass::FindActor(NAME_PowerWeaponLevel2), true)) - { - weapon = weapon->SisterWeapon; - } - - player->PendingWeapon = WP_NOCHANGE; - player->ReadyWeapon = weapon; - player->mo->weaponspecial = 0; - - if (weapon != nullptr) - { - if (weapon->UpSound) - { - S_Sound (player->mo, CHAN_WEAPON, weapon->UpSound, 1, ATTN_NORM); - } - player->refire = 0; - - player->GetPSprite(PSP_WEAPON)->y = player->cheats & CF_INSTANTWEAPSWITCH - ? WEAPONTOP : WEAPONBOTTOM; - // make sure that the previous weapon's flash state is terminated. - // When coming here from a weapon drop it may still be active. - P_SetPsprite(player, PSP_FLASH, nullptr); - P_SetPsprite(player, PSP_WEAPON, weapon->GetUpState()); + VMValue param = player->mo; + VMCall(func, ¶m, 1, nullptr, 0); } } -DEFINE_ACTION_FUNCTION(_PlayerInfo, BringUpWeapon) -{ - PARAM_SELF_STRUCT_PROLOGUE(player_t); - P_BringUpWeapon(self); - return 0; -} - -//--------------------------------------------------------------------------- -// -// PROC P_DropWeapon -// -// The player died, so put the weapon away. -// -//--------------------------------------------------------------------------- - -void P_DropWeapon (player_t *player) -{ - if (player == nullptr) - { - return; - } - // Since the weapon is dropping, stop blocking switching. - player->WeaponState &= ~WF_DISABLESWITCH; - if ((player->ReadyWeapon != nullptr) && (player->health > 0 || !(player->ReadyWeapon->WeaponFlags & WIF_NODEATHDESELECT))) - { - P_SetPsprite(player, PSP_WEAPON, player->ReadyWeapon->GetDownState()); - } -} - -DEFINE_ACTION_FUNCTION(_PlayerInfo, DropWeapon) -{ - PARAM_SELF_STRUCT_PROLOGUE(player_t); - P_DropWeapon(self); - return 0; -} //============================================================================ // // P_BobWeapon @@ -651,219 +577,17 @@ DEFINE_ACTION_FUNCTION(_PlayerInfo, DropWeapon) void P_BobWeapon (player_t *player, float *x, float *y, double ticfrac) { - static float curbob; - double xx[2], yy[2]; - - AWeapon *weapon; - float bobtarget; - - weapon = player->ReadyWeapon; - - if (weapon == nullptr || weapon->WeaponFlags & WIF_DONTBOB) + IFVIRTUALPTR(player->mo, APlayerPawn, BobWeapon) { - *x = *y = 0; + VMValue param[] = { player->mo, ticfrac }; + DVector2 result; + VMReturn ret(&result); + VMCall(func, param, 2, &ret, 1); + *x = (float)result.X; + *y = (float)result.Y; return; } - - // [XA] Get the current weapon's bob properties. - int bobstyle = weapon->BobStyle; - float BobSpeed = (weapon->BobSpeed * 128); - float Rangex = weapon->BobRangeX; - float Rangey = weapon->BobRangeY; - - for (int i = 0; i < 2; i++) - { - // Bob the weapon based on movement speed. ([SP] And user's bob speed setting) - FAngle angle = (BobSpeed * player->userinfo.GetWBobSpeed() * 35 / - TICRATE*(level.time - 1 + i)) * (360.f / 8192.f); - - // [RH] Smooth transitions between bobbing and not-bobbing frames. - // This also fixes the bug where you can "stick" a weapon off-center by - // shooting it when it's at the peak of its swing. - bobtarget = float((player->WeaponState & WF_WEAPONBOBBING) ? player->bob : 0.); - if (curbob != bobtarget) - { - if (fabsf(bobtarget - curbob) <= 1) - { - curbob = bobtarget; - } - else - { - float zoom = MAX(1.f, fabsf(curbob - bobtarget) / 40); - if (curbob > bobtarget) - { - curbob -= zoom; - } - else - { - curbob += zoom; - } - } - } - - if (curbob != 0) - { - //[SP] Added in decorate player.viewbob checks - float bobx = float(player->bob * Rangex * (float)player->mo->ViewBob); - float boby = float(player->bob * Rangey * (float)player->mo->ViewBob); - switch (bobstyle) - { - case AWeapon::BobNormal: - xx[i] = bobx * angle.Cos(); - yy[i] = boby * fabsf(angle.Sin()); - break; - - case AWeapon::BobInverse: - xx[i] = bobx*angle.Cos(); - yy[i] = boby * (1.f - fabsf(angle.Sin())); - break; - - case AWeapon::BobAlpha: - xx[i] = bobx * angle.Sin(); - yy[i] = boby * fabsf(angle.Sin()); - break; - - case AWeapon::BobInverseAlpha: - xx[i] = bobx * angle.Sin(); - yy[i] = boby * (1.f - fabsf(angle.Sin())); - break; - - case AWeapon::BobSmooth: - xx[i] = bobx*angle.Cos(); - yy[i] = 0.5f * (boby * (1.f - ((angle * 2).Cos()))); - break; - - case AWeapon::BobInverseSmooth: - xx[i] = bobx*angle.Cos(); - yy[i] = 0.5f * (boby * (1.f + ((angle * 2).Cos()))); - } - } - else - { - xx[i] = 0; - yy[i] = 0; - } - } - *x = (float)(xx[0] * (1. - ticfrac) + xx[1] * ticfrac); - *y = (float)(yy[0] * (1. - ticfrac) + yy[1] * ticfrac); -} - -//============================================================================ -// -// PROC A_WeaponReady -// -// Readies a weapon for firing or bobbing with its three ancillary functions, -// DoReadyWeaponToSwitch(), DoReadyWeaponToFire() and DoReadyWeaponToBob(). -// [XA] Added DoReadyWeaponToReload() and DoReadyWeaponToZoom() -// -//============================================================================ - -void DoReadyWeaponToSwitch (AActor *self, bool switchable) -{ - // Prepare for switching action. - player_t *player; - if (self && (player = self->player)) - { - if (switchable) - { - player->WeaponState |= WF_WEAPONSWITCHOK | WF_REFIRESWITCHOK; - } - else - { - // WF_WEAPONSWITCHOK is automatically cleared every tic by P_SetPsprite(). - player->WeaponState &= ~WF_REFIRESWITCHOK; - } - } -} - -void DoReadyWeaponDisableSwitch (AActor *self, INTBOOL disable) -{ - // Discard all switch attempts? - player_t *player; - if (self && (player = self->player)) - { - if (disable) - { - player->WeaponState |= WF_DISABLESWITCH; - player->WeaponState &= ~WF_REFIRESWITCHOK; - } - else - { - player->WeaponState &= ~WF_DISABLESWITCH; - } - } -} - -void DoReadyWeaponToFire (AActor *self, bool prim, bool alt) -{ - player_t *player; - AWeapon *weapon; - - if (!self || !(player = self->player) || !(weapon = player->ReadyWeapon)) - { - return; - } - - // Change player from attack state - if (self->InStateSequence(self->state, self->MissileState) || - self->InStateSequence(self->state, self->MeleeState)) - { - static_cast(self)->PlayIdle (); - } - - // Play ready sound, if any. - if (weapon->ReadySound && player->GetPSprite(PSP_WEAPON)->GetState() == weapon->FindState(NAME_Ready)) - { - if (!(weapon->WeaponFlags & WIF_READYSNDHALF) || pr_wpnreadysnd() < 128) - { - S_Sound (self, CHAN_WEAPON, weapon->ReadySound, 1, ATTN_NORM); - } - } - - // Prepare for firing action. - player->WeaponState |= ((prim ? WF_WEAPONREADY : 0) | (alt ? WF_WEAPONREADYALT : 0)); - return; -} - -void DoReadyWeaponToBob (AActor *self) -{ - if (self && self->player && self->player->ReadyWeapon) - { - // Prepare for bobbing action. - self->player->WeaponState |= WF_WEAPONBOBBING; - self->player->GetPSprite(PSP_WEAPON)->x = 0; - self->player->GetPSprite(PSP_WEAPON)->y = WEAPONTOP; - } -} - -void DoReadyWeaponToGeneric(AActor *self, int paramflags) -{ - int flags = 0; - - for (size_t i = 0; i < countof(ButtonChecks); ++i) - { - if (paramflags & ButtonChecks[i].ReadyFlag) - { - flags |= ButtonChecks[i].StateFlag; - } - } - if (self != NULL && self->player != NULL) - { - self->player->WeaponState |= flags; - } -} - -DEFINE_ACTION_FUNCTION(AStateProvider, A_WeaponReady) -{ - PARAM_ACTION_PROLOGUE(AStateProvider); - PARAM_INT(flags); - - DoReadyWeaponToSwitch(self, !(flags & WRF_NoSwitch)); - if ((flags & WRF_NoFire) != WRF_NoFire) DoReadyWeaponToFire(self, !(flags & WRF_NoPrimary), !(flags & WRF_NoSecondary)); - if (!(flags & WRF_NoBob)) DoReadyWeaponToBob(self); - DoReadyWeaponToGeneric(self, flags); - DoReadyWeaponDisableSwitch(self, flags & WRF_DisableSwitch); - return 0; + *x = *y = 0; } //--------------------------------------------------------------------------- @@ -880,7 +604,7 @@ static void P_CheckWeaponButtons (player_t *player) { return; } - AWeapon *weapon = player->ReadyWeapon; + auto weapon = player->ReadyWeapon; if (weapon == nullptr) { return; @@ -1333,12 +1057,13 @@ void player_t::DestroyPSprites() // //------------------------------------------------------------------------------------ -void P_SetSafeFlash(AWeapon *weapon, player_t *player, FState *flashstate, int index) +void P_SetSafeFlash(AInventory *weapon, player_t *player, FState *flashstate, int index) { + auto wcls = PClass::FindActor(NAME_Weapon); if (flashstate != nullptr) { PClassActor *cls = weapon->GetClass(); - while (cls != RUNTIME_CLASS(AWeapon)) + while (cls != wcls) { if (cls->OwnsState(flashstate)) { @@ -1376,7 +1101,7 @@ void P_SetSafeFlash(AWeapon *weapon, player_t *player, FState *flashstate, int i DEFINE_ACTION_FUNCTION(_PlayerInfo, SetSafeFlash) { PARAM_SELF_STRUCT_PROLOGUE(player_t); - PARAM_OBJECT_NOT_NULL(weapon, AWeapon); + PARAM_OBJECT_NOT_NULL(weapon, AInventory); PARAM_POINTER(state, FState); PARAM_INT(index); P_SetSafeFlash(weapon, self, state, index); @@ -1421,6 +1146,33 @@ void DPSprite::OnDestroy() // //------------------------------------------------------------------------ +float DPSprite::GetYAdjust(bool fullscreen) +{ + auto weapon = GetCaller(); + if (weapon != nullptr && weapon->IsKindOf(NAME_Weapon)) + { + auto fYAd = weapon->FloatVar(NAME_YAdjust); + if (fYAd != 0) + { + if (fullscreen) + { + return (float)fYAd; + } + else + { + return (float)(StatusBar->GetDisplacement() * fYAd); + } + } + } + return 0; +} + +//------------------------------------------------------------------------ +// +// +// +//------------------------------------------------------------------------ + ADD_STAT(psprites) { FString out; diff --git a/src/p_pspr.h b/src/p_pspr.h index c98918ded..33d46d29c 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -92,6 +92,7 @@ public: void ResetInterpolation() { oldx = x; oldy = y; } void OnDestroy() override; std::pair GetRenderStyle(FRenderStyle ownerstyle, double owneralpha); + float GetYAdjust(bool fullscreen); double x, y, alpha; double oldx, oldy; @@ -124,7 +125,6 @@ void P_CalcSwing (player_t *player); void P_SetPsprite(player_t *player, PSPLayers id, FState *state, bool pending = false); void P_BringUpWeapon (player_t *player); void P_FireWeapon (player_t *player); -void P_DropWeapon (player_t *player); void P_BobWeapon (player_t *player, float *x, float *y, double ticfrac); DAngle P_BulletSlope (AActor *mo, FTranslatedLineTarget *pLineTarget = NULL, int aimflags = 0); AActor *P_AimTarget(AActor *mo); diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index e5c111a8f..5436cfbe7 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -972,6 +972,7 @@ void G_SerializeLevel(FSerializer &arc, bool hubload) ("level.fogdensity", level.fogdensity) ("level.outsidefogdensity", level.outsidefogdensity) ("level.skyfog", level.skyfog) + ("level.deathsequence", level.deathsequence) ("level.bodyqueslot", level.bodyqueslot) .Array("level.bodyque", level.bodyque, level.BODYQUESIZE); @@ -1025,7 +1026,7 @@ void G_SerializeLevel(FSerializer &arc, bool hubload) { if (playeringame[i] && players[i].mo != NULL) { - players[i].mo->SetupWeaponSlots(); + FWeaponSlots::SetupWeaponSlots(players[i].mo); } } } diff --git a/src/p_teleport.cpp b/src/p_teleport.cpp index 78f21b741..7618b6165 100644 --- a/src/p_teleport.cpp +++ b/src/p_teleport.cpp @@ -203,9 +203,14 @@ bool P_Teleport (AActor *thing, DVector3 pos, DAngle angle, int flags) // [BC] && bHaltVelocity. if (thing->player && ((flags & TELF_DESTFOG) || !(flags & TELF_KEEPORIENTATION)) && !(flags & TELF_KEEPVELOCITY)) { - // Freeze player for about .5 sec - if (thing->Inventory == NULL || !thing->Inventory->GetNoTeleportFreeze()) - thing->reactiontime = 18; + int time = 18; + IFVIRTUALPTR(thing, APlayerPawn, GetTeleportFreezeTime) + { + VMValue param = thing; + VMReturn ret(&time); + VMCall(func, ¶m, 1, &ret, 1); + } + thing->reactiontime = time; } if (thing->flags & MF_MISSILE) { diff --git a/src/p_user.cpp b/src/p_user.cpp index 48ad0ee67..1a9ef4b1f 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -293,6 +293,13 @@ CCMD (playerclasses) } } +DEFINE_ACTION_FUNCTION(AActor, Substitute) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OBJECT(replace, AActor); + DObject::StaticPointerSubstitution(self, replace); + return 0; +} // // Movement. @@ -648,9 +655,9 @@ void player_t::SendPitchLimits() const bool player_t::HasWeaponsInSlot(int slot) const { - for (int i = 0; i < weapons.Slots[slot].Size(); i++) + for (int i = 0; i < weapons.SlotSize(slot); i++) { - PClassActor *weap = weapons.Slots[slot].GetWeapon(i); + PClassActor *weap = weapons.GetWeapon(slot, i); if (weap != NULL && mo->FindInventory(weap)) return true; } return false; @@ -683,12 +690,13 @@ bool player_t::Resurrect() } if (ReadyWeapon != nullptr) { - P_SetPsprite(this, PSP_WEAPON, ReadyWeapon->GetUpState()); + PendingWeapon = ReadyWeapon; + P_BringUpWeapon(this); } if (morphTics) { - P_UndoPlayerMorph(this, this); + P_UnmorphActor(mo, mo); } // player is now alive. @@ -772,6 +780,12 @@ DEFINE_ACTION_FUNCTION(_PlayerInfo, GetNoAutostartMap) ACTION_RETURN_INT(self->userinfo.GetNoAutostartMap()); } +DEFINE_ACTION_FUNCTION(_PlayerInfo, GetWBobSpeed) +{ + PARAM_SELF_STRUCT_PROLOGUE(player_t); + ACTION_RETURN_FLOAT(self->userinfo.GetWBobSpeed()); +} + //=========================================================================== // @@ -819,16 +833,16 @@ void APlayerPawn::Serialize(FSerializer &arc) //=========================================================================== // -// APlayerPawn :: MarkPrecacheSounds +// APlayerPawn :: MarkPlayerSounds // //=========================================================================== -void APlayerPawn::MarkPrecacheSounds() const +DEFINE_ACTION_FUNCTION(APlayerPawn, MarkPlayerSounds) { - Super::MarkPrecacheSounds(); - S_MarkPlayerSounds(GetSoundClass()); + PARAM_SELF_PROLOGUE(APlayerPawn); + S_MarkPlayerSounds(self->GetSoundClass()); + return 0; } - //=========================================================================== // // APlayerPawn :: BeginPlay @@ -909,7 +923,7 @@ void APlayerPawn::Tick() void APlayerPawn::PostBeginPlay() { Super::PostBeginPlay(); - SetupWeaponSlots(); + FWeaponSlots::SetupWeaponSlots(this); // Voodoo dolls: restore original floorz/ceilingz logic if (player == NULL || player->mo != this) @@ -924,40 +938,6 @@ void APlayerPawn::PostBeginPlay() } } -//=========================================================================== -// -// APlayerPawn :: SetupWeaponSlots -// -// Sets up the default weapon slots for this player. If this is also the -// local player, determines local modifications and sends those across the -// network. Ignores voodoo dolls. -// -//=========================================================================== - -void APlayerPawn::SetupWeaponSlots() -{ - if (player != NULL && player->mo == this) - { - player->weapons.StandardSetup(GetClass()); - // If we're the local player, then there's a bit more work to do. - // This also applies if we're a bot and this is the net arbitrator. - if (player - players == consoleplayer || - (player->Bot != NULL && consoleplayer == Net_Arbitrator)) - { - FWeaponSlots local_slots(player->weapons); - if (player->Bot != NULL) - { // Bots only need weapons from KEYCONF, not INI modifications. - P_PlaybackKeyConfWeapons(&local_slots); - } - else - { - local_slots.LocalSetup(GetClass()); - } - local_slots.SendDifferences(int(player - players), player->weapons); - } - } -} - //=========================================================================== // // APlayerPawn :: AddInventory @@ -1067,73 +1047,6 @@ bool APlayerPawn::UseInventory (AInventory *item) return true; } -//=========================================================================== -// -// APlayerPawn :: BestWeapon -// -// Returns the best weapon a player has, possibly restricted to a single -// type of ammo. -// -//=========================================================================== - -AWeapon *APlayerPawn::BestWeapon(PClassActor *ammotype) -{ - AWeapon *bestMatch = NULL; - int bestOrder = INT_MAX; - AInventory *item; - AWeapon *weap; - bool tomed = NULL != FindInventory (PClass::FindActor(NAME_PowerWeaponLevel2), true); - - // Find the best weapon the player has. - for (item = Inventory; item != NULL; item = item->Inventory) - { - if (!item->IsKindOf(NAME_Weapon)) - continue; - - weap = static_cast (item); - - // Don't select it if it's worse than what was already found. - if (weap->SelectionOrder > bestOrder) - continue; - - // Don't select it if its primary fire doesn't use the desired ammo. - if (ammotype != NULL && - (weap->Ammo1 == NULL || - weap->Ammo1->GetClass() != ammotype)) - continue; - - // Don't select it if the Tome is active and this isn't the powered-up version. - if (tomed && weap->SisterWeapon != NULL && weap->SisterWeapon->WeaponFlags & WIF_POWERED_UP) - continue; - - // Don't select it if it's powered-up and the Tome is not active. - if (!tomed && weap->WeaponFlags & WIF_POWERED_UP) - continue; - - // Don't select it if there isn't enough ammo to use its primary fire. - if (!(weap->WeaponFlags & WIF_AMMO_OPTIONAL) && - !weap->CheckAmmo (AWeapon::PrimaryFire, false)) - continue; - - // Don't select if if there isn't enough ammo as determined by the weapon's author. - if (weap->MinSelAmmo1 > 0 && (weap->Ammo1 == NULL || weap->Ammo1->Amount < weap->MinSelAmmo1)) - continue; - if (weap->MinSelAmmo2 > 0 && (weap->Ammo2 == NULL || weap->Ammo2->Amount < weap->MinSelAmmo2)) - continue; - - // This weapon is usable! - bestOrder = weap->SelectionOrder; - bestMatch = weap; - } - return bestMatch; -} - -DEFINE_ACTION_FUNCTION(APlayerPawn, BestWeapon) -{ - PARAM_SELF_PROLOGUE(APlayerPawn); - PARAM_CLASS(ammo, AActor); - ACTION_RETURN_POINTER(self->BestWeapon(ammo)); -} //=========================================================================== // // APlayerPawn :: PickNewWeapon @@ -1144,31 +1057,19 @@ DEFINE_ACTION_FUNCTION(APlayerPawn, BestWeapon) // //=========================================================================== -AWeapon *APlayerPawn::PickNewWeapon(PClassActor *ammotype) +AInventory *APlayerPawn::PickNewWeapon(PClassActor *ammotype) { - AWeapon *best = BestWeapon (ammotype); - - if (best != NULL) + AInventory *best = nullptr; + IFVM(PlayerPawn, DropWeapon) { - player->PendingWeapon = best; - if (player->ReadyWeapon != NULL) - { - P_DropWeapon(player); - } - else if (player->PendingWeapon != WP_NOCHANGE) - { - P_BringUpWeapon (player); - } + VMValue param = player->mo; + VMReturn ret((void**)&best); + VMCall(func, ¶m, 1, &ret, 1); } + return best; } -DEFINE_ACTION_FUNCTION(APlayerPawn, PickNewWeapon) -{ - PARAM_SELF_PROLOGUE(APlayerPawn); - PARAM_CLASS(ammo, AActor); - ACTION_RETURN_POINTER(self->PickNewWeapon(ammo)); -} //=========================================================================== // // APlayerPawn :: GiveDeathmatchInventory @@ -1197,125 +1098,6 @@ void APlayerPawn::GiveDeathmatchInventory() } } -//=========================================================================== -// -// APlayerPawn :: FilterCoopRespawnInventory -// -// When respawning in coop, this function is called to walk through the dead -// player's inventory and modify it according to the current game flags so -// that it can be transferred to the new live player. This player currently -// has the default inventory, and the oldplayer has the inventory at the time -// of death. -// -//=========================================================================== - -void APlayerPawn::FilterCoopRespawnInventory (APlayerPawn *oldplayer) -{ - AInventory *item, *next, *defitem; - - // If we're losing everything, this is really simple. - if (dmflags & DF_COOP_LOSE_INVENTORY) - { - oldplayer->DestroyAllInventory(); - return; - } - - if (dmflags & (DF_COOP_LOSE_KEYS | - DF_COOP_LOSE_WEAPONS | - DF_COOP_LOSE_AMMO | - DF_COOP_HALVE_AMMO | - DF_COOP_LOSE_ARMOR | - DF_COOP_LOSE_POWERUPS)) - { - // Walk through the old player's inventory and destroy or modify - // according to dmflags. - for (item = oldplayer->Inventory; item != NULL; item = next) - { - next = item->Inventory; - - // If this item is part of the default inventory, we never want - // to destroy it, although we might want to copy the default - // inventory amount. - defitem = FindInventory (item->GetClass()); - - if ((dmflags & DF_COOP_LOSE_KEYS) && - defitem == NULL && - item->IsKindOf(NAME_Key)) - { - item->Destroy(); - } - else if ((dmflags & DF_COOP_LOSE_WEAPONS) && - defitem == NULL && - item->IsKindOf(NAME_Weapon)) - { - item->Destroy(); - } - else if ((dmflags & DF_COOP_LOSE_ARMOR) && - item->IsKindOf(NAME_Armor)) - { - if (defitem == NULL) - { - item->Destroy(); - } - else if (item->IsKindOf(NAME_BasicArmor)) - { - item->IntVar(NAME_SavePercent) = defitem->IntVar(NAME_SavePercent); - item->Amount = defitem->Amount; - } - else if (item->IsKindOf(NAME_HexenArmor)) - { - double *SlotsTo = (double*)item->ScriptVar(NAME_Slots, nullptr); - double *SlotsFrom = (double*)defitem->ScriptVar(NAME_Slots, nullptr); - memcpy(SlotsTo, SlotsFrom, 4 * sizeof(double)); - } - } - else if ((dmflags & DF_COOP_LOSE_POWERUPS) && - defitem == NULL && - item->IsKindOf(NAME_PowerupGiver)) - { - item->Destroy(); - } - else if ((dmflags & (DF_COOP_LOSE_AMMO | DF_COOP_HALVE_AMMO)) && - item->IsKindOf(NAME_Ammo)) - { - if (defitem == NULL) - { - if (dmflags & DF_COOP_LOSE_AMMO) - { - // Do NOT destroy the ammo, because a weapon might reference it. - item->Amount = 0; - } - else if (item->Amount > 1) - { - item->Amount /= 2; - } - } - else - { - // When set to lose ammo, you get to keep all your starting ammo. - // When set to halve ammo, you won't be left with less than your starting amount. - if (dmflags & DF_COOP_LOSE_AMMO) - { - item->Amount = defitem->Amount; - } - else if (item->Amount > 1) - { - item->Amount = MAX(item->Amount / 2, defitem->Amount); - } - } - } - } - } - - // Now destroy the default inventory this player is holding and move - // over the old player's remaining inventory. - DestroyAllInventory(); - ObtainInventory (oldplayer); - - player->ReadyWeapon = NULL; - PickNewWeapon (NULL); -} - //=========================================================================== // // APlayerPawn :: GetSoundClass @@ -1449,208 +1231,13 @@ void APlayerPawn::PlayAttacking2 () void APlayerPawn::GiveDefaultInventory () { - if (player == NULL) return; - - // HexenArmor must always be the first item in the inventory because - // it provides player class based protection that should not affect - // any other protection item. - auto myclass = GetClass(); - GiveInventoryType(PClass::FindActor(NAME_HexenArmor)); - auto harmor = FindInventory(NAME_HexenArmor); - - double *Slots = (double*)harmor->ScriptVar(NAME_Slots, nullptr); - double *SlotsIncrement = (double*)harmor->ScriptVar(NAME_SlotsIncrement, nullptr); - Slots[4] = HexenArmor[0]; - for (int i = 0; i < 4; ++i) + IFVIRTUAL(APlayerPawn, GiveDefaultInventory) { - SlotsIncrement[i] = HexenArmor[i + 1]; - } - - // BasicArmor must come right after that. It should not affect any - // other protection item as well but needs to process the damage - // before the HexenArmor does. - auto barmor = (AInventory*)Spawn(NAME_BasicArmor); - barmor->BecomeItem (); - AddInventory (barmor); - - // Now add the items from the DECORATE definition - auto di = GetDropItems(); - - while (di) - { - PClassActor *ti = PClass::FindActor (di->Name); - if (ti) - { - if (!ti->IsDescendantOf(RUNTIME_CLASS(AInventory))) - { - Printf(TEXTCOLOR_ORANGE "%s is not an inventory item and cannot be given to a player as start item.\n", ti->TypeName.GetChars()); - } - else - { - AInventory *item = FindInventory(ti); - if (item != NULL) - { - item->Amount = clamp( - item->Amount + (di->Amount ? di->Amount : ((AInventory *)item->GetDefault())->Amount), - 0, item->MaxAmount); - } - else - { - item = static_cast(Spawn(ti)); - item->ItemFlags |= IF_IGNORESKILL; // no skill multiplicators here - item->Amount = di->Amount; - if (item->IsKindOf(NAME_Weapon)) - { - // To allow better control any weapon is emptied of - // ammo before being given to the player. - static_cast(item)->AmmoGive1 = - static_cast(item)->AmmoGive2 = 0; - } - AActor *check; - if (!item->CallTryPickup(this, &check)) - { - if (check != this) - { - // Player was morphed. This is illegal at game start. - // This problem is only detectable when it's too late to do something about it... - I_Error("Cannot give morph items when starting a game"); - } - item->Destroy(); - item = NULL; - } - } - if (item != NULL && item->IsKindOf(NAME_Weapon) && - static_cast(item)->CheckAmmo(AWeapon::EitherFire, false)) - { - player->ReadyWeapon = player->PendingWeapon = static_cast (item); - } - } - } - di = di->Next; + VMValue params[1] = { (DObject*)this }; + VMCall(func, params, 1, nullptr, 0); } } - -void APlayerPawn::ActivateMorphWeapon () -{ - PClassActor *morphweapon = PClass::FindActor (MorphWeapon); - player->PendingWeapon = WP_NOCHANGE; - - if (player->ReadyWeapon != nullptr) - { - player->GetPSprite(PSP_WEAPON)->y = WEAPONTOP; - } - - if (morphweapon == nullptr || !morphweapon->IsDescendantOf (RUNTIME_CLASS(AWeapon))) - { // No weapon at all while morphed! - player->ReadyWeapon = nullptr; - } - else - { - player->ReadyWeapon = static_cast(player->mo->FindInventory (morphweapon)); - if (player->ReadyWeapon == nullptr) - { - player->ReadyWeapon = static_cast(player->mo->GiveInventoryType (morphweapon)); - if (player->ReadyWeapon != nullptr) - { - player->ReadyWeapon->GivenAsMorphWeapon = true; // flag is used only by new beastweap semantics in P_UndoPlayerMorph - } - } - if (player->ReadyWeapon != nullptr) - { - P_SetPsprite(player, PSP_WEAPON, player->ReadyWeapon->GetReadyState()); - } - } - - if (player->ReadyWeapon != nullptr) - { - P_SetPsprite(player, PSP_FLASH, nullptr); - } - - player->PendingWeapon = WP_NOCHANGE; -} - -//=========================================================================== -// -// APlayerPawn :: Die -// -//=========================================================================== - -void APlayerPawn::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath) -{ - Super::Die (source, inflictor, dmgflags, MeansOfDeath); - - if (player != NULL && player->mo == this) player->bonuscount = 0; - - if (player != NULL && player->mo != this) - { // Make the real player die, too - player->mo->CallDie (source, inflictor, dmgflags, MeansOfDeath); - } - else - { - if (player != NULL && (dmflags2 & DF2_YES_WEAPONDROP)) - { // Voodoo dolls don't drop weapons - AWeapon *weap = player->ReadyWeapon; - if (weap != NULL) - { - AInventory *item; - - // kgDROP - start - modified copy from a_action.cpp - auto di = weap->GetDropItems(); - - if (di != NULL) - { - while (di != NULL) - { - if (di->Name != NAME_None) - { - PClassActor *ti = PClass::FindActor(di->Name); - if (ti) P_DropItem (player->mo, ti, di->Amount, di->Probability); - } - di = di->Next; - } - } else - // kgDROP - end - if (weap->SpawnState != NULL && - weap->SpawnState != ::GetDefault()->SpawnState) - { - item = P_DropItem (this, weap->GetClass(), -1, 256); - if (item != NULL && item->IsKindOf(NAME_Weapon)) - { - if (weap->AmmoGive1 && weap->Ammo1) - { - static_cast(item)->AmmoGive1 = weap->Ammo1->Amount; - } - if (weap->AmmoGive2 && weap->Ammo2) - { - static_cast(item)->AmmoGive2 = weap->Ammo2->Amount; - } - item->ItemFlags |= IF_IGNORESKILL; - } - } - else - { - item = P_DropItem (this, weap->AmmoType1, -1, 256); - if (item != NULL) - { - item->Amount = weap->Ammo1->Amount; - item->ItemFlags |= IF_IGNORESKILL; - } - item = P_DropItem (this, weap->AmmoType2, -1, 256); - if (item != NULL) - { - item->Amount = weap->Ammo2->Amount; - item->ItemFlags |= IF_IGNORESKILL; - } - } - } - } - if (!multiplayer && level.info->deathsequence != NAME_None) - { - F_StartIntermission(level.info->deathsequence, FSTATE_EndingGame); - } - } -} - + //=========================================================================== // // A_PlayerScream @@ -2697,6 +2284,7 @@ DEFINE_FIELD(APlayerPawn, AirCapacity) DEFINE_FIELD(APlayerPawn, FlechetteType) DEFINE_FIELD(APlayerPawn, DamageFade) DEFINE_FIELD(APlayerPawn, ViewBob) +DEFINE_FIELD(APlayerPawn, curBob) DEFINE_FIELD(APlayerPawn, FullHeight) DEFINE_FIELD(APlayerPawn, SoundClass) DEFINE_FIELD(APlayerPawn, Face) diff --git a/src/polyrenderer/scene/poly_playersprite.cpp b/src/polyrenderer/scene/poly_playersprite.cpp index fecbc77ac..ada83a747 100644 --- a/src/polyrenderer/scene/poly_playersprite.cpp +++ b/src/polyrenderer/scene/poly_playersprite.cpp @@ -296,18 +296,7 @@ void RenderPolyPlayerSprites::RenderSprite(PolyRenderThread *thread, DPSprite *p viewheight == renderTarget->GetHeight() || (renderTarget->GetWidth() > (BASEXCENTER * 2)))) { // Adjust PSprite for fullscreen views - AWeapon *weapon = dyn_cast(pspr->GetCaller()); - if (weapon != nullptr && weapon->YAdjust != 0) - { - if (renderToCanvas || viewheight == renderTarget->GetHeight()) - { - vis.texturemid -= weapon->YAdjust; - } - else - { - vis.texturemid -= StatusBar->GetDisplacement() * weapon->YAdjust; - } - } + vis.texturemid -= pspr->GetYAdjust(renderToCanvas || viewheight == renderTarget->GetHeight()); } if (pspr->GetID() < PSP_TARGETCENTER) { // Move the weapon down for 1280x1024. diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp index f0ec171c6..0be53d7e9 100644 --- a/src/s_advsound.cpp +++ b/src/s_advsound.cpp @@ -2232,18 +2232,20 @@ void AAmbientSound::Serialize(FSerializer &arc) //========================================================================== // -// AmbientSound :: MarkPrecacheSounds +// AmbientSound :: MarkAmbientSounds // //========================================================================== -void AAmbientSound::MarkPrecacheSounds() const +DEFINE_ACTION_FUNCTION(AAmbientSound, MarkAmbientSounds) { - Super::MarkPrecacheSounds(); - FAmbientSound *ambient = Ambients.CheckKey(args[0]); + PARAM_SELF_PROLOGUE(AAmbientSound); + + FAmbientSound *ambient = Ambients.CheckKey(self->args[0]); if (ambient != NULL) { ambient->sound.MarkUsed(); } + return 0; } //========================================================================== @@ -2458,3 +2460,11 @@ void S_ParseMusInfo() } +DEFINE_ACTION_FUNCTION(DObject, MarkSound) +{ + PARAM_PROLOGUE; + PARAM_SOUND(sound_id); + sound_id.MarkUsed(); + return 0; +} + diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 2cf78382f..c10c9578c 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -494,14 +494,9 @@ void S_PrecacheLevel () { IFVIRTUALPTR(actor, AActor, MarkPrecacheSounds) { - // Without the type cast this picks the 'void *' assignment... VMValue params[1] = { actor }; VMCall(func, params, 1, nullptr, 0); } - else - { - actor->MarkPrecacheSounds(); - } } for (auto snd : gameinfo.PrecachedSounds) { diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index 3ef66b760..c4fcf6399 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -179,6 +179,7 @@ std2: 'none' { RET(TK_None); } 'auto' { RET(TK_Auto); } 'property' { RET(TK_Property); } + 'flagdef' { RET(ParseVersion >= MakeVersion(3, 7, 0)? TK_FlagDef : TK_Identifier); } 'native' { RET(TK_Native); } 'var' { RET(TK_Var); } 'out' { RET(ParseVersion >= MakeVersion(1, 0, 0)? TK_Out : TK_Identifier); } diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h index dfe49518a..82dac84d8 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -67,6 +67,7 @@ xx(TK_Long, "'long'") xx(TK_ULong, "'ulong'") xx(TK_Void, "'void'") xx(TK_Struct, "'struct'") +xx(TK_FlagDef, "'flagdef'") xx(TK_Property, "'property'") xx(TK_Class, "'class'") xx(TK_Enum, "'enum'") diff --git a/src/scripting/backend/vmbuilder.cpp b/src/scripting/backend/vmbuilder.cpp index 74d3cdc41..46da3d7fa 100644 --- a/src/scripting/backend/vmbuilder.cpp +++ b/src/scripting/backend/vmbuilder.cpp @@ -803,7 +803,6 @@ VMFunction *FFunctionBuildList::AddFunction(PNamespace *gnspc, const VersionInfo void FFunctionBuildList::Build() { - int errorcount = 0; int codesize = 0; int datasize = 0; FILE *dump = nullptr; @@ -912,7 +911,8 @@ void FFunctionBuildList::Build() } VMFunction::CreateRegUseInfo(); FScriptPosition::StrictErrors = false; - if (Args->CheckParm("-dumpjit")) DumpJit(); + + if (FScriptPosition::ErrorCounter == 0 && Args->CheckParm("-dumpjit")) DumpJit(); mItems.Clear(); mItems.ShrinkToFit(); FxAlloc.FreeAllBlocks(); diff --git a/src/scripting/decorate/thingdef_parse.cpp b/src/scripting/decorate/thingdef_parse.cpp index eb553028d..d3d633551 100644 --- a/src/scripting/decorate/thingdef_parse.cpp +++ b/src/scripting/decorate/thingdef_parse.cpp @@ -1187,7 +1187,7 @@ static void ParseActor(FScanner &sc, PNamespace *ns) } try { - GetDefaultByType(info)->Finalize(bag.statedef); + FinalizeClass(info, bag.statedef); } catch (CRecoverableError &err) { diff --git a/src/scripting/symbols.cpp b/src/scripting/symbols.cpp index 3245057dc..a89248937 100644 --- a/src/scripting/symbols.cpp +++ b/src/scripting/symbols.cpp @@ -195,6 +195,29 @@ PProperty::PProperty(FName name, TArray &fields) Variables = std::move(fields); } +/* PProperty *****************************************************************/ + +IMPLEMENT_CLASS(PPropFlag, false, false) + +//========================================================================== +// +// PField - Default Constructor +// +//========================================================================== + +PPropFlag::PPropFlag() + : PSymbol(NAME_None) +{ +} + +PPropFlag::PPropFlag(FName name, PField * field, int bitValue, bool forDecorate) + : PSymbol(name) +{ + Offset = field; + bitval = bitValue; + decorateOnly = forDecorate; +} + //========================================================================== // // diff --git a/src/scripting/symbols.h b/src/scripting/symbols.h index d563e8c5d..d211aaaba 100644 --- a/src/scripting/symbols.h +++ b/src/scripting/symbols.h @@ -99,10 +99,11 @@ class PPropFlag : public PSymbol { DECLARE_CLASS(PPropFlag, PSymbol); public: - PPropFlag(FName name, PField *offset, int bitval); + PPropFlag(FName name, PField *offset, int bitval, bool decorateonly); PField *Offset; int bitval; + bool decorateOnly; protected: PPropFlag(); diff --git a/src/scripting/thingdef.cpp b/src/scripting/thingdef.cpp index d7869c26a..287a49908 100644 --- a/src/scripting/thingdef.cpp +++ b/src/scripting/thingdef.cpp @@ -51,6 +51,8 @@ #include "v_text.h" #include "backend/codegen.h" #include "stats.h" +#include "info.h" +#include "thingdef.h" // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- void InitThingdef(); @@ -60,6 +62,69 @@ void InitThingdef(); static TMap StateSourceLines; static FScriptPosition unknownstatesource("unknown file", 0); + +//========================================================================== +// +// PClassActor :: Finalize +// +// Installs the parsed states and does some sanity checking +// +//========================================================================== + +void FinalizeClass(PClass *ccls, FStateDefinitions &statedef) +{ + if (!ccls->IsDescendantOf(NAME_Actor)) return; + auto cls = static_cast(ccls); + try + { + statedef.FinishStates(cls); + } + catch (CRecoverableError &) + { + statedef.MakeStateDefines(nullptr); + throw; + } + auto def = GetDefaultByType(cls); + statedef.InstallStates(cls, def); + statedef.MakeStateDefines(nullptr); + + if (cls->IsDescendantOf(NAME_Inventory)) + { + def->flags |= MF_SPECIAL; + } + + if (cls->IsDescendantOf(NAME_Weapon)) + { + FState *ready = def->FindState(NAME_Ready); + FState *select = def->FindState(NAME_Select); + FState *deselect = def->FindState(NAME_Deselect); + FState *fire = def->FindState(NAME_Fire); + auto TypeName = cls->TypeName; + + // Consider any weapon without any valid state abstract and don't output a warning + // This is for creating base classes for weapon groups that only set up some properties. + if (ready || select || deselect || fire) + { + if (!ready) + { + I_Error("Weapon %s doesn't define a ready state.", TypeName.GetChars()); + } + if (!select) + { + I_Error("Weapon %s doesn't define a select state.", TypeName.GetChars()); + } + if (!deselect) + { + I_Error("Weapon %s doesn't define a deselect state.", TypeName.GetChars()); + } + if (!fire) + { + I_Error("Weapon %s doesn't define a fire state.", TypeName.GetChars()); + } + } + } +} + //========================================================================== // // Saves the state's source lines for error messages during postprocessing @@ -186,6 +251,7 @@ PFunction *FindClassMemberFunction(PContainerType *selfcls, PContainerType *func auto cls_target = funcsym ? PType::toClass(funcsym->OwningClass) : nullptr; if (funcsym == nullptr) { + if (PClass::FindClass(name)) return nullptr; // Special case when a class's member variable hides a global class name. This should still work. sc.Message(MSG_ERROR, "%s is not a member function of %s", name.GetChars(), selfcls->TypeName.GetChars()); } else if ((funcsym->Variants[0].Flags & VARF_Private) && symtable != &funccls->Symbols) @@ -244,9 +310,10 @@ static void CheckForUnsafeStates(PClassActor *obj) TMap checked; ENamedName *test; - if (obj->IsDescendantOf(NAME_Weapon)) + auto cwtype = PClass::FindActor(NAME_Weapon); + if (obj->IsDescendantOf(cwtype)) { - if (obj->Size == RUNTIME_CLASS(AWeapon)->Size) return; // This class cannot have user variables. + if (obj->Size == cwtype->Size) return; // This class cannot have user variables. test = weaponstates; } else @@ -257,7 +324,7 @@ static void CheckForUnsafeStates(PClassActor *obj) if (obj->Size == citype->Size) return; // This class cannot have user variables. test = pickupstates; } - else return; // something else derived from AStateProvider. We do not know what this may be. + else return; // something else derived from StateProvider. We do not know what this may be. } for (; *test != NAME_None; test++) @@ -416,7 +483,7 @@ void LoadActors() CheckStates(ti); - if (ti->bDecorateClass && ti->IsDescendantOf(RUNTIME_CLASS(AStateProvider))) + if (ti->bDecorateClass && ti->IsDescendantOf(NAME_StateProvider)) { // either a DECORATE based weapon or CustomInventory. // These are subject to relaxed rules for user variables in states. diff --git a/src/scripting/thingdef.h b/src/scripting/thingdef.h index 0d59dce5c..b26ddd1d7 100644 --- a/src/scripting/thingdef.h +++ b/src/scripting/thingdef.h @@ -68,6 +68,7 @@ struct FFlagDef int varflags; }; +void FinalizeClass(PClass *cls, FStateDefinitions &statedef); FFlagDef *FindFlag (const PClass *type, const char *part1, const char *part2, bool strict = false); void HandleDeprecatedFlags(AActor *defaults, PClassActor *info, bool set, int index); bool CheckDeprecatedFlags(const AActor *actor, PClassActor *info, int index); diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index c257a8047..8190fb94d 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -442,37 +442,6 @@ static FFlagDef InventoryFlagDefs[] = DEFINE_DEPRECATED_FLAG(INTERHUBSTRIP), }; -static FFlagDef WeaponFlagDefs[] = -{ - // Weapon flags - DEFINE_FLAG(WIF, NOAUTOFIRE, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, READYSNDHALF, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, DONTBOB, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, AXEBLOOD, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, NOALERT, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, AMMO_OPTIONAL, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, ALT_AMMO_OPTIONAL, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, PRIMARY_USES_BOTH, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, WIMPY_WEAPON, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, POWERED_UP, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, STAFF2_KICKBACK, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF_BOT, EXPLOSIVE, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, MELEEWEAPON, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF_BOT, BFG, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, CHEATNOTWEAPON, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, NO_AUTO_SWITCH, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, AMMO_CHECKBOTH, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, NOAUTOAIM, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, NODEATHDESELECT, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, NODEATHINPUT, AWeapon, WeaponFlags), - DEFINE_FLAG(WIF, ALT_USES_BOTH, AWeapon, WeaponFlags), - - DEFINE_DUMMY_FLAG(NOLMS, false), - DEFINE_DUMMY_FLAG(ALLOW_WITH_RESPAWN_INVUL, false), -}; - - - static FFlagDef PlayerPawnFlagDefs[] = { // PlayerPawn flags @@ -505,12 +474,13 @@ static const struct FFlagList { const PClass * const *Type; FFlagDef *Defs; int { &RUNTIME_CLASS_CASTLESS(AActor), MoreFlagDefs, countof(MoreFlagDefs), 1 }, { &RUNTIME_CLASS_CASTLESS(AActor), InternalActorFlagDefs, countof(InternalActorFlagDefs), 2 }, { &RUNTIME_CLASS_CASTLESS(AInventory), InventoryFlagDefs, countof(InventoryFlagDefs), 3 }, - { &RUNTIME_CLASS_CASTLESS(AWeapon), WeaponFlagDefs, countof(WeaponFlagDefs), 3 }, { &RUNTIME_CLASS_CASTLESS(APlayerPawn), PlayerPawnFlagDefs, countof(PlayerPawnFlagDefs), 3 }, { &RUNTIME_CLASS_CASTLESS(ADynamicLight),DynLightFlagDefs, countof(DynLightFlagDefs), 3 }, }; #define NUM_FLAG_LISTS (countof(FlagLists)) +static FFlagDef forInternalFlags; + //========================================================================== // // Find a flag by name using a binary search @@ -548,6 +518,47 @@ static FFlagDef *FindFlag (FFlagDef *flags, int numflags, const char *flag) FFlagDef *FindFlag (const PClass *type, const char *part1, const char *part2, bool strict) { + + if (part2 == nullptr) + { + FStringf internalname("@flagdef@%s", part1); + FName name(internalname, true); + if (name != NAME_None) + { + auto field = dyn_cast(type->FindSymbol(name, true)); + if (field != nullptr && (!strict || !field->decorateOnly)) + { + forInternalFlags.fieldsize = 4; + forInternalFlags.name = ""; + forInternalFlags.flagbit = field->Offset? 1 << field->bitval : DEPF_UNUSED; + forInternalFlags.structoffset = field->Offset? (int)field->Offset->Offset : -1; + forInternalFlags.varflags = 0; + return &forInternalFlags; + } + } + } + else + { + FStringf internalname("@flagdef@%s.%s", part1, part2); + FName name(internalname, true); + if (name != NAME_None) + { + auto field = dyn_cast(type->FindSymbol(name, true)); + if (field != nullptr) + { + forInternalFlags.fieldsize = 4; + forInternalFlags.name = ""; + forInternalFlags.flagbit = field->Offset ? 1 << field->bitval : DEPF_UNUSED; + forInternalFlags.structoffset = field->Offset ? (int)field->Offset->Offset : -1; + forInternalFlags.varflags = 0; + return &forInternalFlags; + } + } + } + + // Not found. Try the internal flag definitions. + + FFlagDef *def; if (part2 == NULL) @@ -949,6 +960,11 @@ void InitThingdef() fcp->Size = sizeof(FCheckPosition); fcp->Align = alignof(FCheckPosition); + //This must also have its size set. + auto frp = NewStruct("FRailParams", nullptr); + frp->Size = sizeof(FRailParams); + frp->Align = alignof(FRailParams); + FieldTable.Clear(); if (FieldTable.Size() == 0) @@ -967,17 +983,6 @@ void InitThingdef() void SynthesizeFlagFields() { - // These are needed for inserting the flag symbols - /* - NewClassType(RUNTIME_CLASS(DObject)); - NewClassType(RUNTIME_CLASS(DThinker)); - NewClassType(RUNTIME_CLASS(AActor)); - NewClassType(RUNTIME_CLASS(AInventory)); - NewClassType(RUNTIME_CLASS(AStateProvider)); - NewClassType(RUNTIME_CLASS(AWeapon)); - NewClassType(RUNTIME_CLASS(APlayerPawn)); - NewClassType(RUNTIME_CLASS(ADynamicLight)); - */ // synthesize a symbol for each flag from the flag name tables to avoid redundant declaration of them. for (auto &fl : FlagLists) { diff --git a/src/scripting/thingdef_properties.cpp b/src/scripting/thingdef_properties.cpp index 00864f4f1..4d7908c6b 100644 --- a/src/scripting/thingdef_properties.cpp +++ b/src/scripting/thingdef_properties.cpp @@ -1181,20 +1181,20 @@ DEFINE_CLASS_PROPERTY(pickupannouncerentry, S, Inventory) //========================================================================== // //========================================================================== -DEFINE_CLASS_PROPERTY(defaultkickback, 0, Weapon) +DEFINE_SCRIPTED_PROPERTY(defaultkickback, 0, Weapon) { - defaults->Kickback = gameinfo.defKickback; + defaults->IntVar(NAME_Kickback) = gameinfo.defKickback; } //========================================================================== // //========================================================================== -DEFINE_CLASS_PROPERTY(bobstyle, S, Weapon) +DEFINE_SCRIPTED_PROPERTY(bobstyle, S, Weapon) { static const char *names[] = { "Normal", "Inverse", "Alpha", "InverseAlpha", "Smooth", "InverseSmooth", NULL }; - static const int styles[] = { AWeapon::BobNormal, - AWeapon::BobInverse, AWeapon::BobAlpha, AWeapon::BobInverseAlpha, - AWeapon::BobSmooth, AWeapon::BobInverseSmooth, }; + static const EBobStyle styles[] = { EBobStyle::BobNormal, + EBobStyle::BobInverse, EBobStyle::BobAlpha, EBobStyle::BobInverseAlpha, + EBobStyle::BobSmooth, EBobStyle::BobInverseSmooth, }; PROP_STRING_PARM(id, 0); int match = MatchString(id, names); if (match < 0) @@ -1202,22 +1202,13 @@ DEFINE_CLASS_PROPERTY(bobstyle, S, Weapon) I_Error("Unknown bobstyle %s", id); match = 0; } - defaults->BobStyle = styles[match]; + defaults->IntVar(NAME_BobStyle) = (int)styles[match]; } //========================================================================== // //========================================================================== -DEFINE_CLASS_PROPERTY(slotpriority, F, Weapon) -{ - PROP_DOUBLE_PARM(i, 0); - defaults->SlotPriority = int(i*65536); -} - -//========================================================================== -// -//========================================================================== -DEFINE_CLASS_PROPERTY(preferredskin, S, Weapon) +DEFINE_SCRIPTED_PROPERTY(preferredskin, S, Weapon) { PROP_STRING_PARM(str, 0); // NoOp - only for Skulltag compatibility diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index cf429ce37..8ed78b854 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -179,6 +179,11 @@ struct VMReturn Location = loc; RegType = REGT_FLOAT; } + void Vec2At(DVector2 *loc) + { + Location = loc; + RegType = REGT_FLOAT | REGT_MULTIREG2; + } void StringAt(FString *loc) { Location = loc; @@ -192,6 +197,7 @@ struct VMReturn VMReturn() { } VMReturn(int *loc) { IntAt(loc); } VMReturn(double *loc) { FloatAt(loc); } + VMReturn(DVector2 *loc) { Vec2At(loc); } VMReturn(FString *loc) { StringAt(loc); } VMReturn(void **loc) { PointerAt(loc); } }; @@ -691,6 +697,15 @@ VMFunction *FindVMFunction(PClass *cls, const char *name); FString FStringFormat(VM_ARGS, int offset = 0); +#define IFVM(cls, funcname) \ + static VMFunction * func = nullptr; \ + if (func == nullptr) { \ + PClass::FindFunction(&func, #cls, #funcname); \ + assert(func); \ + } \ + if (func != nullptr) + + unsigned GetVirtualIndex(PClass *cls, const char *funcname); diff --git a/src/scripting/zscript/ast.cpp b/src/scripting/zscript/ast.cpp index 773173503..99453901d 100644 --- a/src/scripting/zscript/ast.cpp +++ b/src/scripting/zscript/ast.cpp @@ -348,6 +348,17 @@ static void PrintProperty(FLispString &out, ZCC_TreeNode *node) out.Close(); } +static void PrintFlagDef(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_FlagDef *snode = (ZCC_FlagDef *)node; + out.Break(); + out.Open("flagdef"); + out.AddName(snode->NodeName); + out.AddName(snode->RefName); + out.AddInt(snode->BitValue); + out.Close(); +} + static void PrintStaticArrayState(FLispString &out, ZCC_TreeNode *node) { auto *snode = (ZCC_StaticArrayStatement *)node; @@ -959,6 +970,7 @@ void (* const TreeNodePrinter[NUM_AST_NODE_TYPES])(FLispString &, ZCC_TreeNode * PrintExprClassCast, PrintStaticArrayState, PrintProperty, + PrintFlagDef, }; FString ZCC_PrintAST(ZCC_TreeNode *root) diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon index 97b63198a..83da14100 100644 --- a/src/scripting/zscript/zcc-parse.lemon +++ b/src/scripting/zscript/zcc-parse.lemon @@ -313,6 +313,7 @@ class_innards(X) ::= . { X = NULL; } class_innards(X) ::= class_innards(X) class_member(B). { SAFE_APPEND(X,B); } %type property_def{ZCC_Property *} +%type flag_def{ZCC_FlagDef *} %type struct_def{ZCC_Struct *} %type enum_def {ZCC_Enum *} %type states_def {ZCC_States *} @@ -325,6 +326,7 @@ class_member(X) ::= states_def(A). { X = A; /*X-overwrites-A*/ } class_member(X) ::= default_def(A). { X = A; /*X-overwrites-A*/ } class_member(X) ::= const_def(A). { X = A; /*X-overwrites-A*/ } class_member(X) ::= property_def(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= flag_def(A). { X = A; /*X-overwrites-A*/ } class_member(X) ::= staticarray_statement(A). { X = A; /*X-overwrites-A*/ } @@ -344,6 +346,16 @@ property_def(X) ::= PROPERTY(T) IDENTIFIER(A) COLON identifier_list(B) SEMICOLON X = def; } +flag_def(X) ::= FLAGDEF(T) IDENTIFIER(A) COLON IDENTIFIER(B) COMMA INTCONST(C) SEMICOLON. +{ + NEW_AST_NODE(FlagDef,def,T); + def->NodeName = A.Name(); + def->RefName = B.Name(); + def->BitValue = C.Int; + X = def; +} + + identifier_list(X) ::= IDENTIFIER(A). { NEW_AST_NODE(Identifier,id,A); diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index a5b8d875a..52ab582fa 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -175,6 +175,10 @@ void ZCCCompiler::ProcessClass(ZCC_Class *cnode, PSymbolTreeNode *treenode) cls->Properties.Push(static_cast(node)); break; + case AST_FlagDef: + cls->FlagDefs.Push(static_cast(node)); + break; + case AST_VarDeclarator: cls->Fields.Push(static_cast(node)); break; @@ -1375,6 +1379,10 @@ void ZCCCompiler::CompileAllProperties() { if (c->Properties.Size() > 0) CompileProperties(c->ClassType(), c->Properties, c->Type()->TypeName); + + if (c->FlagDefs.Size() > 0) + CompileFlagDefs(c->ClassType(), c->FlagDefs, c->Type()->TypeName); + } } @@ -1415,20 +1423,88 @@ bool ZCCCompiler::CompileProperties(PClass *type, TArray &Proper fields.Push(f); id = (ZCC_Identifier*)id->SiblingNext; } while (id != p->Body); + + FString qualifiedname; + // Store the full qualified name and prepend some 'garbage' to the name so that no conflicts with other symbol types can happen. + // All these will be removed from the symbol table after the compiler finishes to free up the allocated space. + FName name = FName(p->NodeName); + if (prefix == NAME_None) qualifiedname.Format("@property@%s", name.GetChars()); + else qualifiedname.Format("@property@%s.%s", prefix.GetChars(), name.GetChars()); + + fields.ShrinkToFit(); + if (!type->VMType->Symbols.AddSymbol(Create(qualifiedname, fields))) + { + Error(id, "Unable to add property %s to class %s", FName(p->NodeName).GetChars(), type->TypeName.GetChars()); + } } + } + return true; +} - FString qualifiedname; - // Store the full qualified name and prepend some 'garbage' to the name so that no conflicts with other symbol types can happen. - // All these will be removed from the symbol table after the compiler finishes to free up the allocated space. - FName name = FName(p->NodeName); - if (prefix == NAME_None) qualifiedname.Format("@property@%s", name.GetChars()); - else qualifiedname.Format("@property@%s.%s", prefix.GetChars(), name.GetChars()); +//========================================================================== +// +// ZCCCompiler :: CompileProperties +// +// builds the internal structure of a single class or struct +// +//========================================================================== - fields.ShrinkToFit(); - if (!type->VMType->Symbols.AddSymbol(Create(qualifiedname, fields))) +bool ZCCCompiler::CompileFlagDefs(PClass *type, TArray &Properties, FName prefix) +{ + if (!type->IsDescendantOf(RUNTIME_CLASS(AActor))) + { + Error(Properties[0], "Flags can only be defined for actors"); + return false; + } + for (auto p : Properties) + { + PField *field; + FName referenced = FName(p->RefName); + + if (FName(p->NodeName) == FName("prefix") && Wads.GetLumpFile(Lump) == 0) { - Error(id, "Unable to add property %s to class %s", FName(p->NodeName).GetChars(), type->TypeName.GetChars()); + // only for internal definitions: Allow setting a prefix. This is only for compatiblity with the old DECORATE property parser, but not for general use. + prefix = referenced; } + else + { + if (referenced != NAME_None) + { + field = dyn_cast(type->FindSymbol(referenced, true)); + if (field == nullptr) + { + Error(p, "Variable %s not found in %s", referenced.GetChars(), type->TypeName.GetChars()); + } + if (!field->Type->isInt() || field->Type->Size != 4) + { + Error(p, "Variable %s in %s must have a size of 4 bytes for use as flag storage", referenced.GetChars(), type->TypeName.GetChars()); + } + } + else field = nullptr; + + + FString qualifiedname; + // Store the full qualified name and prepend some 'garbage' to the name so that no conflicts with other symbol types can happen. + // All these will be removed from the symbol table after the compiler finishes to free up the allocated space. + FName name = FName(p->NodeName); + for (int i = 0; i < 2; i++) + { + if (i == 0) qualifiedname.Format("@flagdef@%s", name.GetChars()); + else + { + if (prefix == NAME_None) continue; + qualifiedname.Format("@flagdef@%s.%s", prefix.GetChars(), name.GetChars()); + } + + if (!type->VMType->Symbols.AddSymbol(Create(qualifiedname, field, p->BitValue, i == 0 && prefix != NAME_None))) + { + Error(p, "Unable to add flag definition %s to class %s", FName(p->NodeName).GetChars(), type->TypeName.GetChars()); + } + } + + if (field != nullptr) + type->VMType->AddNativeField(FStringf("b%s", name.GetChars()), TypeSInt32, field->Offset, 0, 1 << p->BitValue); + } } return true; } @@ -3105,7 +3181,7 @@ void ZCCCompiler::CompileStates() } try { - GetDefaultByType(c->ClassType())->Finalize(statedef); + FinalizeClass(c->ClassType(), statedef); } catch (CRecoverableError &err) { diff --git a/src/scripting/zscript/zcc_compile.h b/src/scripting/zscript/zcc_compile.h index 9a4a0af8b..238b5431c 100644 --- a/src/scripting/zscript/zcc_compile.h +++ b/src/scripting/zscript/zcc_compile.h @@ -54,6 +54,7 @@ struct ZCC_ClassWork : public ZCC_StructWork TArray Defaults; TArray States; TArray Properties; + TArray FlagDefs; ZCC_ClassWork(ZCC_Class * s, PSymbolTreeNode *n) { @@ -110,6 +111,7 @@ private: bool CompileFields(PContainerType *type, TArray &Fields, PClass *Outer, PSymbolTable *TreeNodes, bool forstruct, bool hasnativechildren = false); void CompileAllProperties(); bool CompileProperties(PClass *type, TArray &Properties, FName prefix); + bool CompileFlagDefs(PClass *type, TArray &FlagDefs, FName prefix); FString FlagsToString(uint32_t flags); PType *DetermineType(PType *outertype, ZCC_TreeNode *field, FName name, ZCC_Type *ztype, bool allowarraytypes, bool formember); PType *ResolveArraySize(PType *baseType, ZCC_Expression *arraysize, PContainerType *cls); diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp index 9093b7b51..fde8e1f3f 100644 --- a/src/scripting/zscript/zcc_parser.cpp +++ b/src/scripting/zscript/zcc_parser.cpp @@ -150,6 +150,7 @@ static void InitTokenMap() TOKENDEF ('}', ZCC_RBRACE); TOKENDEF (TK_Struct, ZCC_STRUCT); TOKENDEF (TK_Property, ZCC_PROPERTY); + TOKENDEF (TK_FlagDef, ZCC_FLAGDEF); TOKENDEF (TK_Transient, ZCC_TRANSIENT); TOKENDEF (TK_Enum, ZCC_ENUM); TOKENDEF2(TK_SByte, ZCC_SBYTE, NAME_sByte); diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h index 1089b2ed8..272d7e141 100644 --- a/src/scripting/zscript/zcc_parser.h +++ b/src/scripting/zscript/zcc_parser.h @@ -134,6 +134,7 @@ enum EZCCTreeNodeType AST_ClassCast, AST_StaticArrayStatement, AST_Property, + AST_FlagDef, NUM_AST_NODE_TYPES }; @@ -226,6 +227,12 @@ struct ZCC_Property : ZCC_NamedNode ZCC_TreeNode *Body; }; +struct ZCC_FlagDef : ZCC_NamedNode +{ + ENamedName RefName; + int BitValue; +}; + struct ZCC_Class : ZCC_Struct { ZCC_Identifier *ParentName; diff --git a/src/scriptutil.cpp b/src/scriptutil.cpp new file mode 100644 index 000000000..f5d88087b --- /dev/null +++ b/src/scriptutil.cpp @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// +// Copyright 2018 Christoph Oelckers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//----------------------------------------------------------------------------- +// +// +// DESCRIPTION: generalized interface for implementing ACS/FS functions +// in ZScript. +// +//----------------------------------------------------------------------------- + +#include "i_system.h" +#include "tarray.h" +#include "dobject.h" +#include "vm.h" +#include "scriptutil.h" +#include "p_acs.h" + + +static TArray parameters; +static TMap functions; + + +void ScriptUtil::BuildParameters(va_list ap) +{ + for(int type = va_arg(ap, int); type != End; type = va_arg(ap, int)) + { + switch (type) + { + case Int: + parameters.Push(VMValue(va_arg(ap, int))); + break; + + case Pointer: + case Class: // this is just a pointer. + case String: // must be passed by reference to a persistent location! + parameters.Push(VMValue(va_arg(ap, void*))); + break; + + case Float: + parameters.Push(VMValue(va_arg(ap, double))); + break; + + case ACSClass: + parameters.Push(VMValue(PClass::FindActor(FBehavior::StaticLookupString(va_arg(ap, int))))); + break; + } + } +} + +void ScriptUtil::RunFunction(FName functionname, unsigned paramstart, VMReturn &returns) +{ + VMFunction *func = nullptr; + auto check = functions.CheckKey(functionname); + if (!check) + { + PClass::FindFunction(&func, NAME_ScriptUtil, functionname); + if (func == nullptr) + { + I_Error("Call to undefined function ScriptUtil.%s", functionname.GetChars()); + } + functions.Insert(functionname, func); + } + else func = *check; + + VMCall(func, ¶meters[paramstart], parameters.Size() - paramstart, &returns, 1); +} + +int ScriptUtil::Exec(FName functionname, ...) +{ + unsigned paramstart = parameters.Size(); + va_list ap; + va_start(ap, functionname); + try + { + BuildParameters(ap); + int ret = 0; + VMReturn returns(&ret); + RunFunction(functionname, paramstart, returns); + va_end(ap); + parameters.Clamp(paramstart); + return ret; + } + catch(...) + { + va_end(ap); + parameters.Clamp(paramstart); + throw; + } +} diff --git a/src/scriptutil.h b/src/scriptutil.h new file mode 100644 index 000000000..6d8f83cc8 --- /dev/null +++ b/src/scriptutil.h @@ -0,0 +1,27 @@ +#pragma once + + +#include +#include "name.h" + + +class ScriptUtil +{ + static void BuildParameters(va_list ap); + static void RunFunction(FName function, unsigned paramstart, VMReturn &returns); + +public: + enum + { + End, + Int, + Pointer, + Float, + String, + Class, + ACSString, // convenience helpers taking an ACS string index instead of a string + ACSClass, + }; + + static int Exec(FName functionname, ...); +}; diff --git a/src/swrenderer/things/r_playersprite.cpp b/src/swrenderer/things/r_playersprite.cpp index 1a6f52e2d..a51b3b5fd 100644 --- a/src/swrenderer/things/r_playersprite.cpp +++ b/src/swrenderer/things/r_playersprite.cpp @@ -295,18 +295,7 @@ namespace swrenderer viewheight == viewport->RenderTarget->GetHeight() || (viewport->RenderTarget->GetWidth() > (BASEXCENTER * 2)))) { // Adjust PSprite for fullscreen views - AWeapon *weapon = dyn_cast(pspr->GetCaller()); - if (weapon != nullptr && weapon->YAdjust != 0) - { - if (renderToCanvas || viewheight == viewport->RenderTarget->GetHeight()) - { - vis.texturemid -= weapon->YAdjust; - } - else - { - vis.texturemid -= StatusBar->GetDisplacement() * weapon->YAdjust; - } - } + vis.texturemid -= pspr->GetYAdjust(renderToCanvas || viewheight == viewport->RenderTarget->GetHeight()); } if (pspr->GetID() < PSP_TARGETCENTER) { // Move the weapon down for 1280x1024. diff --git a/src/version.h b/src/version.h index 61c6ceff8..de314043b 100644 --- a/src/version.h +++ b/src/version.h @@ -87,11 +87,11 @@ const char *GetVersionString(); #define SAVEGAME_EXT "zds" // MINSAVEVER is the minimum level snapshot version that can be loaded. -#define MINSAVEVER 4551 +#define MINSAVEVER 4553 // Use 4500 as the base git save version, since it's higher than the // SVN revision ever got. -#define SAVEVER 4552 +#define SAVEVER 4553 // This is so that derivates can use the same savegame versions without worrying about engine compatibility #define GAMESIG "GZDOOM" diff --git a/wadsrc/static/botsupp.txt b/wadsrc/static/botsupp.txt new file mode 100644 index 000000000..730fb056a --- /dev/null +++ b/wadsrc/static/botsupp.txt @@ -0,0 +1,37 @@ +// This is just a straight dump of an internal table. Contents are: Weapon type, distance coefficient, optional flags and an optional projectile type. +Pistol, 25000000 +Shotgun, 24000000 +SuperShotgun, 15000000 +Chaingun, 27000000 +RocketLauncher, 18350080, BOT_REACTION_SKILL_THING, BOT_EXPLOSIVE, Rocket +PlasmaRifle, 27000000, PlasmaBall +BFG9000, 10000000, BOT_REACTION_SKILL_THING, BOT_BFG, BFGBall +GoldWand, 25000000 +GoldWandPowered, 25000000 +Crossbow, 24000000, CrossbowFX1 +CrossbowPowered, 24000000, CrossbowFX2 +Blaster, 27000000 +BlasterPowered, 27000000, BlasterFX1 +SkullRod, 27000000, HornRodFX1 +SkullRodPowered, 27000000, HornRodFX2 +PhoenixRod, 18350080, BOT_REACTION_SKILL_THING, BOT_EXPLOSIVE, PhoenixFX1 +Mace, 27000000, BOT_REACTION_SKILL_THING, MaceFX2 +MacePowered, 27000000, BOT_REACTION_SKILL_THING, BOT_EXPLOSIVE, MaceFX4 +FWeapHammer, 22000000, HammerMissile +FWeapQuietus, 20000000, FSwordMissile +CWeapStaff, 25000000, CStaffMissile +CWeapFlame, 27000000, CFlameMissile +MWeapWand, 25000000, MageWandMissile +CWeapWraithverge, 22000000, HolyMissile +MWeapFrost, 19000000, FrostMissile +MWeapLightning, 23000000, LightningFloor +MWeapBloodscourge, 20000000, MageStaffFX2 +StrifeCrossbow, 24000000, ElectricBolt +StrifeCrossbow2, 24000000, PoisonBolt +AssaultGun, 27000000 +MiniMissileLauncher, 18350080, BOT_REACTION_SKILL_THING, BOT_EXPLOSIVE, MiniMissile +FlameThrower, 24000000, FlameMissile +Mauler, 15000000 +Mauler2, 10000000, MaulerTorpedo +StrifeGrenadeLauncher, 18350080, BOT_REACTION_SKILL_THING, BOT_EXPLOSIVE, HEGrenade +StrifeGrenadeLauncher2, 18350080, BOT_REACTION_SKILL_THING, BOT_EXPLOSIVE, PhosphorousGrenade diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 41b385141..d04f99ae0 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -5,7 +5,9 @@ version "3.7" #include "zscript/dynarrays.txt" #include "zscript/constants.txt" #include "zscript/actor.txt" +#include "zscript/actor_attacks.txt" #include "zscript/actor_checks.txt" +#include "zscript/actor_interaction.txt" #include "zscript/events.txt" #include "zscript/destructible.txt" #include "zscript/level_compatibility.txt" @@ -260,3 +262,5 @@ version "3.7" #include "zscript/chex/chexitems.txt" #include "zscript/chex/chexdecorations.txt" #include "zscript/chex/chexplayer.txt" + +#include "zscript/scriptutil/scriptutil.txt" diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 46c25451c..adb5f679d 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -79,6 +79,7 @@ class Actor : Thinker native const LARGE_MASS = 10000000; // not INT_MAX on purpose const ORIG_FRICTION = (0xE800/65536.); // original value const ORIG_FRICTION_FACTOR = (2048/65536.); // original value + const DEFMORPHTICS = 40 * TICRATE; // flags are not defined here, the native fields for those get synthesized from the internal tables. @@ -333,6 +334,7 @@ class Actor : Thinker native // need some definition work first //FRenderStyle RenderStyle; + native private int RenderStyle; // This is kept private until its real type has been implemented into the VM. But some code needs to copy this. //int ConversationRoot; // THe root of the current dialogue // deprecated things. @@ -422,6 +424,18 @@ class Actor : Thinker native native clearscope static Vector2 RotateVector(Vector2 vec, double angle); native clearscope static double Normalize180(double ang); + virtual void MarkPrecacheSounds() + { + MarkSound(SeeSound); + MarkSound(AttackSound); + MarkSound(PainSound); + MarkSound(DeathSound); + MarkSound(ActiveSound); + MarkSound(UseSound); + MarkSound(BounceSound); + MarkSound(WallBounceSound); + MarkSound(CrushPainSound); + } bool IsPointerEqual(int ptr_select1, int ptr_select2) { @@ -441,7 +455,7 @@ class Actor : Thinker native virtual native void Die(Actor source, Actor inflictor, int dmgflags = 0, Name MeansOfDeath = 'none'); virtual native bool Slam(Actor victim); virtual native void Touch(Actor toucher); - virtual native void MarkPrecacheSounds(); + native void Substitute(Actor replacement); // Called by PIT_CheckThing to check if two actors actually can collide. virtual bool CanCollideWith(Actor other, bool passive) @@ -586,7 +600,6 @@ class Actor : Thinker native native clearscope int PlayerNumber() const; native void SetFriendPlayer(PlayerInfo player); native void SoundAlert(Actor target, bool splash = false, double maxdist = 0); - native void DaggerAlert(Actor target); native void ClearBounce(); native TerrainDef GetFloorTerrain(); native bool CheckLocalView(int consoleplayer); @@ -595,6 +608,7 @@ class Actor : Thinker native native bool IsZeroDamage(); native void ClearInterpolation(); native clearscope Vector3 PosRelative(sector sec) const; + native void RailAttack(FRailParams p); native void HandleSpawnFlags(); native void ExplodeMissile(line lin = null, Actor target = null, bool onsky = false); @@ -729,6 +743,7 @@ class Actor : Thinker native native void AddInventory(Inventory inv); native void RemoveInventory(Inventory inv); native void ClearInventory(); + protected native void DestroyAllInventory(); // This is not supposed to be called by user code! native bool GiveInventory(class type, int amount, bool givecheat = false); native bool SetInventory(class itemclass, int amount, bool beyondMax = false); native bool TakeInventory(class itemclass, int amount, bool fromdecorate = false, bool notakeinfinite = false); @@ -740,7 +755,7 @@ class Actor : Thinker native native bool GiveAmmo (Class type, int amount); native bool UsePuzzleItem(int PuzzleItemType); native float AccuracyFactor(); - native bool MorphMonster (Class spawntype, int duration, int style, Class enter_flash, Class exit_flash); + action native void SetCamera(Actor cam, bool revert = false); native bool Warp(Actor dest, double xofs = 0, double yofs = 0, double zofs = 0, double angle = 0, int flags = 0, double heightoffset = 0, double radiusoffset = 0, double pitch = 0); @@ -966,89 +981,20 @@ class Actor : Thinker native native bool A_LineEffect(int boomspecial = 0, int tag = 0); // End of MBF redundant functions. - - //========================================================================== - // - // old customizable attack functions which use actor parameters. - // - //========================================================================== - - private void DoAttack (bool domelee, bool domissile, int MeleeDamage, Sound MeleeSound, Class MissileType,double MissileHeight) - { - let targ = target; - if (targ == NULL) return; - - A_FaceTarget (); - if (domelee && MeleeDamage>0 && CheckMeleeRange ()) - { - int damage = random[CustomMelee](1, 8) * MeleeDamage; - if (MeleeSound) A_PlaySound (MeleeSound, CHAN_WEAPON); - int newdam = targ.DamageMobj (self, self, damage, 'Melee'); - targ.TraceBleed (newdam > 0 ? newdam : damage, self); - } - else if (domissile && MissileType != NULL) - { - // This seemingly senseless code is needed for proper aiming. - double add = MissileHeight + GetBobOffset() - 32; - AddZ(add); - Actor missile = SpawnMissileXYZ (Pos + (0, 0, 32), targ, MissileType, false); - AddZ(-add); - - if (missile) - { - // automatic handling of seeker missiles - if (missile.bSeekerMissile) - { - missile.tracer = targ; - } - missile.CheckMissileSpawn(radius); - } - } - } - - deprecated("2.3") void A_MeleeAttack() - { - DoAttack(true, false, MeleeDamage, MeleeSound, NULL, 0); - } - - deprecated("2.3") void A_MissileAttack() - { - Class MissileType = MissileName; - DoAttack(false, true, 0, 0, MissileType, MissileHeight); - } - - deprecated("2.3") void A_ComboAttack() - { - Class MissileType = MissileName; - DoAttack(true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); - } - - void A_BasicAttack(int melee_damage, sound melee_sound, class missile_type, double missile_height) - { - DoAttack(true, true, melee_damage, melee_sound, missile_type, missile_height); - } - - native void A_MonsterRail(); native void A_Pain(); native void A_NoBlocking(bool drop = true); void A_Fall() { A_NoBlocking(); } - native void A_XScream(); native void A_Look(); native void A_Chase(statelabel melee = null, statelabel missile = null, int flags = 0x40000000); - native void A_Scream(); native void A_VileChase(); native bool A_CheckForResurrection(); native void A_BossDeath(); - native void A_Detonate(); bool A_CallSpecial(int special, int arg1=0, int arg2=0, int arg3=0, int arg4=0, int arg5=0) { return Level.ExecuteSpecial(special, self, null, false, arg1, arg2, arg3, arg4, arg5); } - - native void A_ActiveSound(); - native void A_FastChase(); native void A_PlayerScream(); native void A_SkullPop(class skulltype = "BloodySkull"); @@ -1069,13 +1015,10 @@ class Actor : Thinker native native void A_SeekerMissile(int threshold, int turnmax, int flags = 0, int chance = 50, int distance = 10); native action state A_Jump(int chance, statelabel label, ...); native Actor A_SpawnProjectile(class missiletype, double spawnheight = 32, double spawnofs_xy = 0, double angle = 0, int flags = 0, double pitch = 0, int ptr = AAPTR_TARGET); - native void A_CustomBulletAttack(double spread_xy, double spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", double range = 0, int flags = 0, int ptr = AAPTR_TARGET, class missile = null, double Spawnheight = 32, double Spawnofs_xy = 0); native void A_CustomRailgun(int damage, int spawnofs_xy = 0, color color1 = 0, color color2 = 0, int flags = 0, int aim = 0, double maxdiff = 0, class pufftype = "BulletPuff", double spread_xy = 0, double spread_z = 0, double range = 0, int duration = 0, double sparsity = 1.0, double driftspeed = 1.0, class spawnclass = null, double spawnofs_z = 0, int spiraloffset = 270, int limit = 0, double veleffect = 3); native bool A_SetInventory(class itemtype, int amount, int ptr = AAPTR_DEFAULT, bool beyondMax = false); native bool A_GiveInventory(class itemtype, int amount = 0, int giveto = AAPTR_DEFAULT); native bool A_TakeInventory(class itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT); - action native bool, Actor A_SpawnItem(class itemtype = "Unknown", double distance = 0, double zheight = 0, bool useammo = true, bool transfer_translation = false); - native bool, Actor A_SpawnItemEx(class itemtype, double xofs = 0, double yofs = 0, double zofs = 0, double xvel = 0, double yvel = 0, double zvel = 0, double angle = 0, int flags = 0, int failchance = 0, int tid=0); native void A_Print(string whattoprint, double time = 0, name fontname = "none"); native void A_PrintBold(string whattoprint, double time = 0, name fontname = "none"); native void A_Log(string whattoprint, bool local = false); @@ -1100,10 +1043,7 @@ class Actor : Thinker native native bool RaiseActor(Actor other, int flags = 0); native bool CanRaise(); native void Revive(); - action native bool, Actor A_ThrowGrenade(class itemtype, double zheight = 0, double xyvel = 0, double zvel = 0, bool useammo = true); native void A_Weave(int xspeed, int yspeed, double xdist, double ydist); - native bool A_Morph(class type, int duration = 0, int flags = 0, class enter_flash = null, class exit_flash = null); - action native state, bool A_Teleport(statelabel teleportstate = null, class targettype = "BossSpot", class fogtype = "TeleportFog", int flags = 0, double mindist = 128, double maxdist = 0, int ptr = AAPTR_DEFAULT); action native state, bool A_Warp(int ptr_destination, double xofs = 0, double yofs = 0, double zofs = 0, double angle = 0, int flags = 0, statelabel success_state = null, double heightoffset = 0, double radiusoffset = 0, double pitch = 0); @@ -1118,10 +1058,10 @@ class Actor : Thinker native native void A_CustomMeleeAttack(int damage = 0, sound meleesound = "", sound misssound = "", name damagetype = "none", bool bleed = true); native void A_CustomComboAttack(class missiletype, double spawnheight, int damage, sound meleesound = "", name damagetype = "none", bool bleed = true); native void A_Burst(class chunktype); - native void A_RadiusThrust(int force = 128, int distance = -1, int flags = RTF_AFFECTSOURCE, int fullthrustdistance = 0); native void A_RadiusDamageSelf(int damage = 128, double distance = 128, int flags = 0, class flashtype = null); - 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 int GetRadiusDamage(Actor thing, int damage, int distance, int fulldmgdistance = 0, bool oldradiusdmg = false); + native int RadiusAttack(Actor bombsource, int bombdamage, int bombdistance, Name bombmod = 'none', int flags = RADF_HURTSOURCE, int fulldamagedistance = 0); + native void A_Stop(); native void A_Respawn(int flags = 1); native void A_RestoreSpecialPosition(); @@ -1142,7 +1082,7 @@ class Actor : Thinker native native void A_Quake(int intensity, int duration, int damrad, int tremrad, sound sfx = "world/quake"); native void A_QuakeEx(int intensityX, int intensityY, int intensityZ, int duration, int damrad, int tremrad, sound sfx = "world/quake", int flags = 0, double mulWaveX = 1, double mulWaveY = 1, double mulWaveZ = 1, int falloff = 0, int highpoint = 0, double rollIntensity = 0, double rollWave = 0); action native void A_SetTics(int tics); - native void A_DropItem(class item, int dropamount = -1, int chance = 256); + native Actor A_DropItem(class item, int dropamount = -1, int chance = 256); native void A_DamageSelf(int amount, name damagetype = "none", int flags = 0, class filter = null, name species = "None", int src = AAPTR_DEFAULT, int inflict = AAPTR_DEFAULT); native void A_DamageTarget(int amount, name damagetype = "none", int flags = 0, class filter = null, name species = "None", int src = AAPTR_DEFAULT, int inflict = AAPTR_DEFAULT); native void A_DamageMaster(int amount, name damagetype = "none", int flags = 0, class filter = null, name species = "None", int src = AAPTR_DEFAULT, int inflict = AAPTR_DEFAULT); @@ -1223,6 +1163,33 @@ class Actor : Thinker native return ACS_ExecuteWithResult(-int(script), arg1, arg2, arg3, arg4); } + //=========================================================================== + // + // Sounds + // + //=========================================================================== + + void A_Scream() + { + if (DeathSound) + { + A_PlaySound(DeathSound, CHAN_VOICE, 1, false, bBoss? ATTN_NONE : ATTN_NORM); + } + } + + void A_XScream() + { + A_PlaySound(player? Sound("*gibbed") : Sound("misc/gibbed"), CHAN_VOICE); + } + + void A_ActiveSound() + { + if (ActiveSound) + { + A_PlaySound(ActiveSound, CHAN_VOICE); + } + } + States(Actor, Overlay, Weapon, Item) { Spawn: diff --git a/wadsrc/static/zscript/actor_attacks.txt b/wadsrc/static/zscript/actor_attacks.txt new file mode 100644 index 000000000..d5dc1813f --- /dev/null +++ b/wadsrc/static/zscript/actor_attacks.txt @@ -0,0 +1,741 @@ +extend class Actor +{ + //--------------------------------------------------------------------------- + // + // Used by A_CustomBulletAttack and A_FireBullets + // + //--------------------------------------------------------------------------- + + static void AimBulletMissile(Actor proj, Actor puff, int flags, bool temp, bool cba) + { + if (proj && puff) + { + // FAF_BOTTOM = 1 + // Aim for the base of the puff as that's where blood puffs will spawn... roughly. + + proj.A_Face(puff, 0., 0., 0., 0., 1); + proj.Vel3DFromAngle(proj.Speed, proj.Angle, proj.Pitch); + + if (!temp) + { + if (cba) + { + if (flags & CBAF_PUFFTARGET) proj.target = puff; + if (flags & CBAF_PUFFMASTER) proj.master = puff; + if (flags & CBAF_PUFFTRACER) proj.tracer = puff; + } + else + { + if (flags & FBF_PUFFTARGET) proj.target = puff; + if (flags & FBF_PUFFMASTER) proj.master = puff; + if (flags & FBF_PUFFTRACER) proj.tracer = puff; + } + } + } + if (puff && temp) + { + puff.Destroy(); + } + } + + //--------------------------------------------------------------------------- + // + // + // + //--------------------------------------------------------------------------- + + void A_CustomBulletAttack(double spread_xy, double spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", double range = 0, int flags = 0, int ptr = AAPTR_TARGET, class missile = null, double Spawnheight = 32, double Spawnofs_xy = 0) + { + let ref = GetPointer(ptr); + + if (range == 0) + range = MISSILERANGE; + + int i; + double bangle; + double bslope = 0.; + int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; + + if (ref != NULL || (flags & CBAF_AIMFACING)) + { + if (!(flags & CBAF_AIMFACING)) + { + A_Face(ref); + } + bangle = self.Angle; + + if (!(flags & CBAF_NOPITCH)) bslope = AimLineAttack (bangle, MISSILERANGE); + if (pufftype == null) pufftype = 'BulletPuff'; + + A_PlaySound(AttackSound, CHAN_WEAPON); + for (i = 0; i < numbullets; i++) + { + double pangle = bangle; + double slope = bslope; + + if (flags & CBAF_EXPLICITANGLE) + { + pangle += spread_xy; + slope += spread_z; + } + else + { + pangle += spread_xy * Random2[cwbullet]() / 255.; + slope += spread_z * Random2[cwbullet]() / 255.; + } + + int damage = damageperbullet; + + if (!(flags & CBAF_NORANDOM)) + damage *= random[cwbullet](1, 3); + + let puff = LineAttack(pangle, range, slope, damage, 'Hitscan', pufftype, laflags); + if (missile != null && pufftype != null) + { + double x = Spawnofs_xy * cos(pangle); + double y = Spawnofs_xy * sin(pangle); + + SetXYZ(Vec3Offset(x, y, 0.)); + let proj = SpawnMissileAngleZSpeed(Pos.Z + GetBobOffset() + Spawnheight, missile, self.Angle, 0, GetDefaultByType(missile).Speed, self, false); + SetXYZ(pos); + + if (proj) + { + bool temp = (puff == null); + if (!puff) + { + puff = LineAttack(pangle, range, slope, 0, 'Hitscan', pufftype, laflags | LAF_NOINTERACT); + } + if (puff) + { + AimBulletMissile(proj, puff, flags, temp, true); + } + } + } + } + } + } + + //============================================================================ + // + // P_DaggerAlert + // + //============================================================================ + + void DaggerAlert(Actor target) + { + Actor looker; + + if (LastHeard != NULL) + return; + if (health <= 0) + return; + if (!bIsMonster) + return; + if (bInCombat) + return; + bInCombat = true; + + self.target = target; + let painstate = FindState('Pain', 'Dagger'); + if (painstate != NULL) + { + SetState(painstate); + } + + for (looker = cursector.thinglist; looker != NULL; looker = looker.snext) + { + if (looker == self || looker == target) + continue; + + if (looker.health <= 0) + continue; + + if (!looker.bSeesDaggers) + continue; + + if (!looker.bInCombat) + { + if (!looker.CheckSight(target) && !looker.CheckSight(self)) + continue; + + looker.target = target; + if (looker.SeeSound) + { + looker.A_PlaySound(looker.SeeSound, CHAN_VOICE); + } + looker.SetState(looker.SeeState); + looker.bInCombat = true; + } + } + } + + //=========================================================================== + // + // Common code for A_SpawnItem and A_SpawnItemEx + // + //=========================================================================== + + bool InitSpawnedItem(Actor mo, int flags) + { + if (mo == NULL) + { + return false; + } + Actor originator = self; + + if (!(mo.bDontTranslate)) + { + if (flags & SXF_TRANSFERTRANSLATION) + { + mo.Translation = Translation; + } + else if (flags & SXF_USEBLOODCOLOR) + { + // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. + mo.Translation = BloodTranslation; + } + } + if (flags & SXF_TRANSFERPOINTERS) + { + mo.target = self.target; + mo.master = self.master; // This will be overridden later if SXF_SETMASTER is set + mo.tracer = self.tracer; + } + + mo.Angle = self.Angle; + if (flags & SXF_TRANSFERPITCH) + { + mo.Pitch = self.Pitch; + } + if (!(flags & SXF_ORIGINATOR)) + { + while (originator && (originator.bMissile || originator.default.bMissile)) + { + originator = originator.target; + } + } + if (flags & SXF_TELEFRAG) + { + mo.TeleportMove(mo.Pos, true); + // This is needed to ensure consistent behavior. + // Otherwise it will only spawn if nothing gets telefragged + flags |= SXF_NOCHECKPOSITION; + } + if (mo.bIsMonster) + { + if (!(flags & SXF_NOCHECKPOSITION) && !mo.TestMobjLocation()) + { + // The monster is blocked so don't spawn it at all! + mo.ClearCounters(); + mo.Destroy(); + return false; + } + else if (originator && !(flags & SXF_NOPOINTERS)) + { + if (originator.bIsMonster) + { + // If this is a monster transfer all friendliness information + mo.CopyFriendliness(originator, true); + } + else if (originator.player) + { + // A player always spawns a monster friendly to him + mo.bFriendly = true; + mo.SetFriendPlayer(originator.player); + + Actor attacker=originator.player.attacker; + if (attacker) + { + if (!(attacker.bFriendly) || + (deathmatch && attacker.FriendPlayer != 0 && attacker.FriendPlayer != mo.FriendPlayer)) + { + // Target the monster which last attacked the player + mo.LastHeard = mo.target = attacker; + } + } + } + } + } + else if (!(flags & SXF_TRANSFERPOINTERS)) + { + // If this is a missile or something else set the target to the originator + mo.target = originator ? originator : self; + } + if (flags & SXF_NOPOINTERS) + { + //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. + mo.LastHeard = NULL; //Sanity check. + mo.target = NULL; + mo.master = NULL; + mo.tracer = NULL; + } + if (flags & SXF_SETMASTER) + { // don't let it attack you (optional)! + mo.master = originator; + } + if (flags & SXF_SETTARGET) + { + mo.target = originator; + } + if (flags & SXF_SETTRACER) + { + mo.tracer = originator; + } + if (flags & SXF_TRANSFERSCALE) + { + mo.Scale = self.Scale; + } + if (flags & SXF_TRANSFERAMBUSHFLAG) + { + mo.bAmbush = bAmbush; + } + if (flags & SXF_CLEARCALLERTID) + { + self.ChangeTid(0); + } + if (flags & SXF_TRANSFERSPECIAL) + { + mo.special = self.special; + mo.args[0] = self.args[0]; + mo.args[1] = self.args[1]; + mo.args[2] = self.args[2]; + mo.args[3] = self.args[3]; + mo.args[4] = self.args[4]; + } + if (flags & SXF_CLEARCALLERSPECIAL) + { + self.special = 0; + mo.args[0] = 0; + mo.args[1] = 0; + mo.args[2] = 0; + mo.args[3] = 0; + mo.args[4] = 0; + } + if (flags & SXF_TRANSFERSTENCILCOL) + { + mo.SetShade(self.fillcolor); + } + if (flags & SXF_TRANSFERALPHA) + { + mo.Alpha = self.Alpha; + } + if (flags & SXF_TRANSFERRENDERSTYLE) + { + mo.RenderStyle = self.RenderStyle; + } + + if (flags & SXF_TRANSFERSPRITEFRAME) + { + mo.sprite = self.sprite; + mo.frame = self.frame; + } + + if (flags & SXF_TRANSFERROLL) + { + mo.Roll = self.Roll; + } + + if (flags & SXF_ISTARGET) + { + self.target = mo; + } + if (flags & SXF_ISMASTER) + { + self.master = mo; + } + if (flags & SXF_ISTRACER) + { + self.tracer = mo; + } + return true; + } + + + //=========================================================================== + // + // A_SpawnItem + // + // Spawns an item in front of the caller like Heretic's time bomb + // + //=========================================================================== + + action bool, Actor A_SpawnItem(class missile = "Unknown", double distance = 0, double zheight = 0, bool useammo = true, bool transfer_translation = false) + { + if (missile == NULL) + { + return false, null; + } + + // Don't spawn monsters if this actor has been massacred + if (DamageType == 'Massacre' && GetDefaultByType(missile).bIsMonster) + { + return true, null; + } + + if (stateinfo != null && stateinfo.mStateType == STATE_Psprite) + { + let player = self.player; + if (player == null) return false, null; + let weapon = player.ReadyWeapon; + // Used from a weapon, so use some ammo + + if (weapon == NULL || (useammo && !weapon.DepleteAmmo(weapon.bAltFire))) + { + return true, null; + } + } + + let mo = Spawn(missile, Vec3Angle(distance, Angle, -Floorclip + GetBobOffset() + zheight), ALLOW_REPLACE); + + int flags = (transfer_translation ? SXF_TRANSFERTRANSLATION : 0) + (useammo ? SXF_SETMASTER : 0); + bool res = InitSpawnedItem(mo, flags); // for an inventory item's use state + return res, mo; + } + + //=========================================================================== + // + // A_SpawnItemEx + // + // Enhanced spawning function + // + //=========================================================================== + bool, Actor A_SpawnItemEx(class missile, double xofs = 0, double yofs = 0, double zofs = 0, double xvel = 0, double yvel = 0, double zvel = 0, double angle = 0, int flags = 0, int chance = 0, int tid=0) + { + if (missile == NULL) + { + return false, null; + } + if (chance > 0 && random[spawnitemex]() < chance) + { + return true, null; + } + // Don't spawn monsters if this actor has been massacred + if (DamageType == 'Massacre' && GetDefaultByType(missile).bIsMonster) + { + return true, null; + } + + Vector2 pos; + + if (!(flags & SXF_ABSOLUTEANGLE)) + { + angle += self.Angle; + } + double s = sin(angle); + double c = cos(angle); + + if (flags & SXF_ABSOLUTEPOSITION) + { + pos = Vec2Offset(xofs, yofs); + } + else + { + // in relative mode negative y values mean 'left' and positive ones mean 'right' + // This is the inverse orientation of the absolute mode! + pos = Vec2Offset(xofs * c + yofs * s, xofs * s - yofs*c); + } + + if (!(flags & SXF_ABSOLUTEVELOCITY)) + { + // Same orientation issue here! + double newxvel = xvel * c + yvel * s; + yvel = xvel * s - yvel * c; + xvel = newxvel; + } + + let mo = Spawn(missile, (pos, self.pos.Z - Floorclip + GetBobOffset() + zofs), ALLOW_REPLACE); + bool res = InitSpawnedItem(mo, flags); + if (res) + { + if (tid != 0) + { + mo.ChangeTid(tid); + } + mo.Vel = (xvel, yvel, zvel); + if (flags & SXF_MULTIPLYSPEED) + { + mo.Vel *= mo.Speed; + } + mo.Angle = angle; + } + return res, mo; + } + + + //=========================================================================== + // + // A_ThrowGrenade + // + // Throws a grenade (like Hexen's fighter flechette) + // + //=========================================================================== + action bool, Actor A_ThrowGrenade(class missile, double zheight = 0, double xyvel = 0, double zvel = 0, bool useammo = true) + { + if (missile == NULL) + { + return false, null; + } + if (stateinfo != null && stateinfo.mStateType == STATE_Psprite) + { + let player = self.player; + if (player == null) return false, null; + let weapon = player.ReadyWeapon; + // Used from a weapon, so use some ammo + + if (weapon == NULL || (useammo && !weapon.DepleteAmmo(weapon.bAltFire))) + { + return true, null; + } + } + + let bo = Spawn(missile, pos + (0, 0, (-Floorclip + GetBobOffset() + zheight + 35 + (player? player.crouchoffset : 0.))), ALLOW_REPLACE); + if (bo) + { + bo.PlaySpawnSound(self); + if (xyvel != 0) + bo.Speed = xyvel; + bo.Angle = Angle + (((random[grenade]()&7) - 4) * (360./256.)); + + let pitch = -self.Pitch; + let angle = bo.Angle; + + // There are two vectors we are concerned about here: xy and z. We rotate + // them separately according to the shooter's pitch and then sum them to + // get the final velocity vector to shoot with. + + double xy_xyscale = bo.Speed * cos(pitch); + double xy_velz = bo.Speed * sin(pitch); + double xy_velx = xy_xyscale * cos(angle); + double xy_vely = xy_xyscale * sin(angle); + + pitch = self.Pitch; + double z_xyscale = zvel * sin(pitch); + double z_velz = zvel * cos(pitch); + double z_velx = z_xyscale * cos(angle); + double z_vely = z_xyscale * sin(angle); + + bo.Vel.X = xy_velx + z_velx + Vel.X / 2; + bo.Vel.Y = xy_vely + z_vely + Vel.Y / 2; + bo.Vel.Z = xy_velz + z_velz; + + bo.target = self; + if (!bo.CheckMissileSpawn(radius)) bo = null; + return true, bo; + } + else + { + return false, null; + } + } + + //--------------------------------------------------------------------------- + // + // P_CheckSplash + // + // Checks for splashes caused by explosions + // + //--------------------------------------------------------------------------- + + void CheckSplash(double distance) + { + double floorh; + sector floorsec; + [floorh, floorsec] = curSector.LowestFloorAt(pos.XY); + + if (pos.Z <= floorz + distance && floorsector == floorsec && curSector.GetHeightSec() == NULL && floorsec.heightsec == NULL) + { + // Explosion splashes never alert monsters. This is because A_Explode has + // a separate parameter for that so this would get in the way of proper + // behavior. + Vector3 pos = PosRelative(floorsec); + pos.Z = floorz; + HitWater (floorsec, pos, false, false); + } + } + + //========================================================================== + // + // Parameterized version of A_Explode + // + //========================================================================== + + int A_Explode(int damage = -1, int distance = -1, int flags = XF_HURTSOURCE, bool alert = false, int fulldmgdistance = 0, int nails = 0, int naildamage = 10, class pufftype = "BulletPuff", name damagetype = "none") + { + + if (damage < 0) // get parameters from metadata + { + damage = ExplosionDamage; + distance = ExplosionRadius; + flags = !DontHurtShooter; + alert = false; + } + if (distance <= 0) distance = damage; + + // NailBomb effect, from SMMU but not from its source code: instead it was implemented and + // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html + + if (nails) + { + double ang; + for (int i = 0; i < nails; i++) + { + ang = i*360./nails; + // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim + LineAttack(ang, MISSILERANGE, 0., + //P_AimLineAttack (self, ang, MISSILERANGE), + naildamage, 'Hitscan', pufftype, bMissile ? LAF_TARGETISSOURCE : 0); + } + } + + if (!(flags & XF_EXPLICITDAMAGETYPE) && damagetype == 'None') + { + damagetype = self.DamageType; + } + + int pflags = 0; + if (flags & XF_HURTSOURCE) pflags |= RADF_HURTSOURCE; + if (flags & XF_NOTMISSILE) pflags |= RADF_SOURCEISSPOT; + + int count = RadiusAttack (target, damage, distance, damagetype, pflags, fulldmgdistance); + if (!(flags & XF_NOSPLASH)) CheckSplash(distance); + if (alert && target != NULL && target.player != NULL) + { + SoundAlert(target); + } + return count; + } + + //========================================================================== + // + // A_RadiusThrust + // + //========================================================================== + + void A_RadiusThrust(int force = 128, int distance = -1, int flags = RTF_AFFECTSOURCE, int fullthrustdistance = 0) + { + if (force == 0) force = 128; + if (distance <= 0) distance = abs(force); + + bool nothrust = target.bNoDamageThrust; + // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. + if (!(flags & RTF_NOTMISSILE) && target != NULL) + { + target.bNoDamageThrust = false; + } + + RadiusAttack (target, force, distance, DamageType, flags | RADF_NODAMAGE, fullthrustdistance); + CheckSplash(distance); + target.bNoDamageThrust = nothrust; + } + + //========================================================================== + // + // A_Detonate + // killough 8/9/98: same as A_Explode, except that the damage is variable + // + //========================================================================== + + void A_Detonate() + { + int damage = GetMissileDamage(0, 1); + RadiusAttack (target, damage, damage, DamageType, RADF_HURTSOURCE); + CheckSplash(damage); + } + + //========================================================================== + // + // old customizable attack functions which use actor parameters. + // + //========================================================================== + + private void DoAttack (bool domelee, bool domissile, int MeleeDamage, Sound MeleeSound, Class MissileType,double MissileHeight) + { + let targ = target; + if (targ == NULL) return; + + A_FaceTarget (); + if (domelee && MeleeDamage>0 && CheckMeleeRange ()) + { + int damage = random[CustomMelee](1, 8) * MeleeDamage; + if (MeleeSound) A_PlaySound (MeleeSound, CHAN_WEAPON); + int newdam = targ.DamageMobj (self, self, damage, 'Melee'); + targ.TraceBleed (newdam > 0 ? newdam : damage, self); + } + else if (domissile && MissileType != NULL) + { + // This seemingly senseless code is needed for proper aiming. + double add = MissileHeight + GetBobOffset() - 32; + AddZ(add); + Actor missile = SpawnMissileXYZ (Pos + (0, 0, 32), targ, MissileType, false); + AddZ(-add); + + if (missile) + { + // automatic handling of seeker missiles + if (missile.bSeekerMissile) + { + missile.tracer = targ; + } + missile.CheckMissileSpawn(radius); + } + } + } + + deprecated("2.3") void A_MeleeAttack() + { + DoAttack(true, false, MeleeDamage, MeleeSound, NULL, 0); + } + + deprecated("2.3") void A_MissileAttack() + { + Class MissileType = MissileName; + DoAttack(false, true, 0, 0, MissileType, MissileHeight); + } + + deprecated("2.3") void A_ComboAttack() + { + Class MissileType = MissileName; + DoAttack(true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); + } + + void A_BasicAttack(int melee_damage, sound melee_sound, class missile_type, double missile_height) + { + DoAttack(true, true, melee_damage, melee_sound, missile_type, missile_height); + } + + + //========================================================================== + // + // called with the victim as 'self' + // + //========================================================================== + + virtual void SpawnLineAttackBlood(Actor attacker, Vector3 bleedpos, double SrcAngleFromTarget, int originaldamage, int actualdamage) + { + if (!bNoBlood && !bDormant && !bInvulnerable) + { + let player = attacker.player; + let weapon = player? player.ReadyWeapon : null; + let axeBlood = (weapon && weapon.bAxeBlood); + let bloodsplatter = attacker.bBloodSplatter || axeBlood; + + if (!bloodsplatter) + { + SpawnBlood(bleedpos, SrcAngleFromTarget, actualdamage > 0 ? actualdamage : originaldamage); + } + else if (damage) + { + if (axeBlood) + { + BloodSplatter(bleedpos, SrcAngleFromTarget, true); + } + // No else here... + if (random[LineAttack]() < 192) + { + BloodSplatter(bleedpos, SrcAngleFromTarget, false); + } + } + } + } +} diff --git a/wadsrc/static/zscript/actor_interaction.txt b/wadsrc/static/zscript/actor_interaction.txt new file mode 100644 index 000000000..51c48deac --- /dev/null +++ b/wadsrc/static/zscript/actor_interaction.txt @@ -0,0 +1,79 @@ +extend class Actor +{ + + virtual void ApplyKickback(Actor inflictor, Actor source, int damage, double angle, Name mod, int flags) + { + double ang; + int kickback; + double thrust; + + if (inflictor && inflictor.projectileKickback) + kickback = inflictor.projectileKickback; + else if (!source || !source.player || !source.player.ReadyWeapon) + kickback = gameinfo.defKickback; + else + kickback = source.player.ReadyWeapon.Kickback; + + kickback = int(kickback * G_SkillPropertyFloat(SKILLP_KickbackFactor)); + if (kickback) + { + Actor origin = (source && (flags & DMG_INFLICTOR_IS_PUFF)) ? source : inflictor; + + if (flags & DMG_USEANGLE) + { + ang = angle; + } + else if (origin.pos.xy == pos.xy) + { + // If the origin and target are in exactly the same spot, choose a random direction. + // (Most likely cause is from telefragging somebody during spawning because they + // haven't moved from their spawn spot at all.) + ang = frandom[Kickback](0., 360.); + } + else + { + ang = origin.AngleTo(self); + } + + thrust = mod == 'MDK' ? 10 : 32; + if (Mass > 0) + { + thrust = clamp((damage * 0.125 * kickback) / Mass, 0., thrust); + } + + // Don't apply ultra-small damage thrust + if (thrust < 0.01) thrust = 0; + + // make fall forwards sometimes + if ((damage < 40) && (damage > health) + && (pos.Z - origin.pos.Z > 64) + && (random[Kickback]() & 1) + // [RH] But only if not too fast and not flying + && thrust < 10 + && !bNoGravity + && !bNoForwardFall + && (inflictor == NULL || !inflictor.bNoForwardFall) + ) + { + ang += 180.; + thrust *= 4; + } + if (source && source.player && (flags & DMG_INFLICTOR_IS_PUFF) + && source.player.ReadyWeapon != NULL && (source.player.ReadyWeapon.bSTAFF2_KICKBACK)) + { + // Staff power level 2 + Thrust(10, ang); + if (!bNoGravity) + { + Vel.Z += 5.; + } + } + else + { + Thrust(thrust, ang); + } + } + } + + +} \ No newline at end of file diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 5ddf93844..148300d1c 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -367,6 +367,8 @@ struct GameInfoStruct native native bool intermissioncounter; native Name mSliderColor; native Color defaultbloodcolor; + native double telefogheight; + native int defKickback; } class Object native @@ -393,6 +395,7 @@ class Object native native static void S_ResumeSound (bool notsfx); native static bool S_ChangeMusic(String music_name, int order = 0, bool looping = true, bool force = false); native static float S_GetLength(Sound sound_id); + native static void MarkSound(Sound snd); native static uint BAM(double angle); native static void SetMusicVolume(float vol); native static uint MSTime(); @@ -664,6 +667,7 @@ struct LevelLocals native native readonly int outsidefogdensity; native readonly int skyfog; native readonly float pixelstretch; + native name deathsequence; // level_info_t *info cannot be done yet. native String GetUDMFString(int type, int index, Name key); @@ -679,6 +683,7 @@ struct LevelLocals native native bool IsJumpingAllowed() const; native bool IsCrouchingAllowed() const; native bool IsFreelookAllowed() const; + native void StartIntermission(Name type, int state) const; native static clearscope bool IsPointInMap(vector3 p); @@ -967,3 +972,24 @@ struct Shader native native clearscope static void SetUniform3f(PlayerInfo player, string shaderName, string uniformName, vector3 value); native clearscope static void SetUniform1i(PlayerInfo player, string shaderName, string uniformName, int value); } + +struct FRailParams +{ + native int damage; + native double offset_xy; + native double offset_z; + native int color1, color2; + native double maxdiff; + native int flags; + native Class puff; + native double angleoffset; + native double pitchoffset; + native double distance; + native int duration; + native double sparsity; + native double drift; + native Class spawnclass; + native int SpiralOffset; + native int limit; +}; // [RH] Shoot a railgun + diff --git a/wadsrc/static/zscript/constants.txt b/wadsrc/static/zscript/constants.txt index 316f858f4..bcd465fc2 100644 --- a/wadsrc/static/zscript/constants.txt +++ b/wadsrc/static/zscript/constants.txt @@ -884,6 +884,7 @@ enum EAimFlags ALF_CHECKCONVERSATION = 8, ALF_NOFRIENDS = 16, ALF_PORTALRESTRICT = 32, // only work through portals with a global offset (to be used for stuff that cannot remember the calculated FTranslatedLineTarget info) + ALF_NOWEAPONCHECK = 64, // ignore NOAUTOAIM flag on a player's weapon. } enum ELineAttackFlags @@ -1001,6 +1002,7 @@ enum EFSkillProperty // floating point properties SKILLP_Aggressiveness, SKILLP_MonsterHealth, SKILLP_FriendlyHealth, + SKILLP_KickbackFactor, }; enum EWeaponPos @@ -1247,3 +1249,30 @@ enum SPAC SPAC_PlayerActivate = (SPAC_Cross|SPAC_Use|SPAC_Impact|SPAC_Push|SPAC_AnyCross|SPAC_UseThrough|SPAC_UseBack), }; + +enum RadiusDamageFlags +{ + RADF_HURTSOURCE = 1, + RADF_NOIMPACTDAMAGE = 2, + RADF_SOURCEISSPOT = 4, + RADF_NODAMAGE = 8, + RADF_THRUSTZ = 16, + RADF_OLDRADIUSDAMAGE = 32 +}; + +enum IntermissionSequenceType +{ + FSTATE_EndingGame = 0, + FSTATE_ChangingLevel = 1, + FSTATE_InLevel = 2 +}; + +enum Bobbing +{ + Bob_Normal, + Bob_Inverse, + Bob_Alpha, + Bob_InverseAlpha, + Bob_Smooth, + Bob_InverseSmooth +}; diff --git a/wadsrc/static/zscript/heretic/hereticartifacts.txt b/wadsrc/static/zscript/heretic/hereticartifacts.txt index 3b7f14ad8..688ca6ebf 100644 --- a/wadsrc/static/zscript/heretic/hereticartifacts.txt +++ b/wadsrc/static/zscript/heretic/hereticartifacts.txt @@ -71,7 +71,7 @@ Class ArtiTomeOfPower : PowerupGiver Playerinfo p = Owner.player; if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYTOMEOFPOWER)) { // Attempt to undo chicken - if (!p.UndoPlayerMorph (p, MRF_UNDOBYTOMEOFPOWER)) + if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYTOMEOFPOWER)) { // Failed if (!(p.MorphStyle & MRF_FAILNOTELEFRAG)) { diff --git a/wadsrc/static/zscript/heretic/weaponphoenix.txt b/wadsrc/static/zscript/heretic/weaponphoenix.txt index 162184f6e..5feeb46d0 100644 --- a/wadsrc/static/zscript/heretic/weaponphoenix.txt +++ b/wadsrc/static/zscript/heretic/weaponphoenix.txt @@ -73,7 +73,6 @@ class PhoenixRodPowered : PhoenixRod Default { +WEAPON.POWERED_UP - +WEAPON.MELEEWEAPON Weapon.SisterWeapon "PhoenixRod"; Weapon.AmmoGive 0; Tag "$TAG_PHOENIXRODP"; diff --git a/wadsrc/static/zscript/hexen/fighterhammer.txt b/wadsrc/static/zscript/hexen/fighterhammer.txt index 27d85bdab..82f2040d9 100644 --- a/wadsrc/static/zscript/hexen/fighterhammer.txt +++ b/wadsrc/static/zscript/hexen/fighterhammer.txt @@ -9,7 +9,7 @@ class FWeapHammer : FighterWeapon { +BLOODSPLATTER Weapon.SelectionOrder 900; - +WEAPON.AMMO_OPTIONAL +WEAPON.MELEEWEAPON + +WEAPON.AMMO_OPTIONAL Weapon.AmmoUse1 3; Weapon.AmmoGive1 25; Weapon.KickBack 150; diff --git a/wadsrc/static/zscript/inventory/ammo.txt b/wadsrc/static/zscript/inventory/ammo.txt index 092a93846..b08f39601 100644 --- a/wadsrc/static/zscript/inventory/ammo.txt +++ b/wadsrc/static/zscript/inventory/ammo.txt @@ -184,6 +184,48 @@ class Ammo : Inventory return copy; } + //--------------------------------------------------------------------------- + // + // Modifies the drop amount of this item according to the current skill's + // settings (also called by ADehackedPickup::TryPickup) + // + //--------------------------------------------------------------------------- + + override void ModifyDropAmount(int dropamount) + { + bool ignoreskill = true; + double dropammofactor = G_SkillPropertyFloat(SKILLP_DropAmmoFactor); + // Default drop amount is half of regular amount * regular ammo multiplication + if (dropammofactor == -1) + { + dropammofactor = 0.5; + ignoreskill = false; + } + + if (dropamount > 0) + { + if (ignoreskill) + { + self.Amount = int(dropamount * dropammofactor); + bIgnoreSkill = true; + } + else + { + self.Amount = dropamount; + } + } + else + { + // Half ammo when dropped by bad guys. + int amount = self.DropAmount; + if (amount <= 0) + { + amount = MAX(1, int(self.Amount * dropammofactor)); + } + self.Amount = amount; + bIgnoreSkill = ignoreskill; + } + } } diff --git a/wadsrc/static/zscript/inventory/inventory.txt b/wadsrc/static/zscript/inventory/inventory.txt index 8645cf74f..da2e70555 100644 --- a/wadsrc/static/zscript/inventory/inventory.txt +++ b/wadsrc/static/zscript/inventory/inventory.txt @@ -50,9 +50,6 @@ class Inventory : Actor native } native bool DoRespawn(); - native void BecomeItem(); - native void BecomePickup(); - native void ModifyDropAmount(int dropamount); native static void PrintPickupMessage (bool localview, String str); States(Actor) @@ -77,6 +74,17 @@ class Inventory : Actor native Stop; } + //=========================================================================== + // + // AInventory :: MarkPrecacheSounds + // + //=========================================================================== + + override void MarkPrecacheSounds() + { + Super.MarkPrecacheSounds(); + MarkSound(PickupSound); + } //=========================================================================== // @@ -172,6 +180,55 @@ class Inventory : Actor native return Super.Grind(items); } + //=========================================================================== + // + // AInventory :: BecomeItem + // + // Lets this actor know that it's about to be placed in an inventory. + // + //=========================================================================== + + void BecomeItem () + { + if (!bNoBlockmap || !bNoSector) + { + A_ChangeLinkFlags(1, 1); + } + ChangeTid(0); + bSpecial = false; + ChangeStatNum(STAT_INVENTORY); + // stop all sounds this item is playing. + for(int i = 1;i<=7;i++) A_StopSound(i); + SetState (FindState("Held")); + } + + //=========================================================================== + // + // AInventory :: BecomePickup + // + // Lets this actor know it should wait to be picked up. + // + //=========================================================================== + + void BecomePickup () + { + if (Owner != NULL) + { + Owner.RemoveInventory (self); + } + if (bNoBlockmap || bNoSector) + { + A_ChangeLinkFlags(0, 0); + FindFloorCeiling(); + } + bSpecial = true; + bDropped = true; + bCountItem = false; + bInvisible = false; + ChangeStatNum(STAT_DEFAULT); + SetState (SpawnState); + } + //=========================================================================== // // AInventory :: CreateCopy @@ -943,6 +1000,22 @@ class Inventory : Actor native //=========================================================================== virtual void OnDrop (Actor dropper) {} + + //--------------------------------------------------------------------------- + // + // Modifies the drop amount of this item according to the current skill's + // settings (also called by ADehackedPickup::TryPickup) + // + //--------------------------------------------------------------------------- + + virtual void ModifyDropAmount(int dropamount) + { + if (dropamount > 0) + { + Amount = dropamount; + } + } + } //=========================================================================== @@ -1040,6 +1113,14 @@ class DehackedPickup : Inventory } Super.OnDestroy(); } + + override void ModifyDropAmount(int dropamount) + { + // Must forward the adjustment to the real item. + // dropamount is not relevant here because Dehacked cannot change it. + droppedbymonster = true; + } + } //=========================================================================== diff --git a/wadsrc/static/zscript/inventory/powerups.txt b/wadsrc/static/zscript/inventory/powerups.txt index 5ef03d420..bcfdca31a 100644 --- a/wadsrc/static/zscript/inventory/powerups.txt +++ b/wadsrc/static/zscript/inventory/powerups.txt @@ -1893,7 +1893,7 @@ class PowerMorph : Powerup if (Owner != null && Owner.player != null && PlayerClass != null) { let realplayer = Owner.player; // Remember the identity of the player - if (realplayer.MorphPlayer(realplayer, PlayerClass, 0x7fffffff/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash)) + if (realplayer.mo.MorphPlayer(realplayer, PlayerClass, 0x7fffffff/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash)) { Owner = realplayer.mo; // Replace the new owner in our owner; safe because we are not attached to anything yet bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor) @@ -1930,7 +1930,7 @@ class PowerMorph : Powerup } int savedMorphTics = MorphedPlayer.morphTics; - MorphedPlayer.UndoPlayerMorph (MorphedPlayer, 0, !!(MorphedPlayer.MorphStyle & MRF_UNDOALWAYS)); + MorphedPlayer.mo.UndoPlayerMorph (MorphedPlayer, 0, !!(MorphedPlayer.MorphStyle & MRF_UNDOALWAYS)); MorphedPlayer = null; } diff --git a/wadsrc/static/zscript/inventory/stateprovider.txt b/wadsrc/static/zscript/inventory/stateprovider.txt index abbc8db71..28024fe44 100644 --- a/wadsrc/static/zscript/inventory/stateprovider.txt +++ b/wadsrc/static/zscript/inventory/stateprovider.txt @@ -1,13 +1,387 @@ -class StateProvider : Inventory native +class StateProvider : Inventory { - action native state A_JumpIfNoAmmo(statelabel label); - action native void A_CustomPunch(int damage, bool norandom = false, int flags = CPF_USEAMMO, class pufftype = "BulletPuff", double range = 0, double lifesteal = 0, int lifestealmax = 0, class armorbonustype = "ArmorBonus", sound MeleeSound = 0, sound MissSound = ""); - action native void A_FireBullets(double spread_xy, double spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", int flags = 1, double range = 0, class missile = null, double Spawnheight = 32, double Spawnofs_xy = 0); - action native Actor A_FireProjectile(class missiletype, double angle = 0, bool useammo = true, double spawnofs_xy = 0, double spawnheight = 0, int flags = 0, double pitch = 0); - action native void A_RailAttack(int damage, int spawnofs_xy = 0, bool useammo = true, color color1 = 0, color color2 = 0, int flags = 0, double maxdiff = 0, class pufftype = "BulletPuff", double spread_xy = 0, double spread_z = 0, double range = 0, int duration = 0, double sparsity = 1.0, double driftspeed = 1.0, class spawnclass = "none", double spawnofs_z = 0, int spiraloffset = 270, int limit = 0); - action native void A_WeaponReady(int flags = 0); + //========================================================================== + // + // State jump function + // + //========================================================================== + action state A_JumpIfNoAmmo(statelabel label) + { + if (stateinfo == null || stateinfo.mStateType != STATE_Psprite || player == null || player.ReadyWeapon == null || + !player.ReadyWeapon.CheckAmmo(player.ReadyWeapon.bAltFire, false, true)) + { + return null; + } + else return ResolveState(label); + } + + //=========================================================================== + // + // Modified code pointer from Skulltag + // + //=========================================================================== + + action state A_CheckForReload(int count, statelabel jump, bool dontincrement = false) + { + if (stateinfo == null || stateinfo.mStateType != STATE_Psprite || player == null || player.ReadyWeapon == null) + { + return null; + } + + state ret = null; + + let weapon = player.ReadyWeapon; + + int ReloadCounter = weapon.ReloadCounter; + if (!dontincrement || ReloadCounter != 0) + ReloadCounter = (weapon.ReloadCounter+1) % count; + else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though. + ReloadCounter = 1; + + // If we have not made our last shot... + if (ReloadCounter != 0) + { + // Go back to the refire frames, instead of continuing on to the reload frames. + ret = ResolveState(jump); + } + else + { + // We need to reload. However, don't reload if we're out of ammo. + weapon.CheckAmmo(false, false); + } + if (!dontincrement) + { + weapon.ReloadCounter = ReloadCounter; + } + return ret; + } + + //=========================================================================== + // + // Resets the counter for the above function + // + //=========================================================================== + + action void A_ResetReloadCounter() + { + if (stateinfo != null && stateinfo.mStateType == STATE_Psprite && player != null && player.ReadyWeapon != null) + player.ReadyWeapon.ReloadCounter = 0; + } + + + //--------------------------------------------------------------------------- + // + // + // + //--------------------------------------------------------------------------- + + action void A_FireBullets(double spread_xy, double spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", int flags = 1, double range = 0, class missile = null, double Spawnheight = 32, double Spawnofs_xy = 0) + { + let player = player; + if (!player) return; + + let pawn = PlayerPawn(self); + let weapon = player.ReadyWeapon; + + int i; + double bangle; + double bslope = 0.; + int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; + + if ((flags & FBF_USEAMMO) && weapon && stateinfo != null && stateinfo.mStateType == STATE_Psprite) + { + if (!weapon.DepleteAmmo(weapon.bAltFire, true)) + return; // out of ammo + } + + if (range == 0) range = PLAYERMISSILERANGE; + + if (!(flags & FBF_NOFLASH)) pawn.PlayAttacking2 (); + + if (!(flags & FBF_NOPITCH)) bslope = BulletSlope(); + bangle = Angle; + + if (pufftype == NULL) pufftype = 'BulletPuff'; + + if (weapon != NULL) + { + A_PlaySound(weapon.AttackSound, CHAN_WEAPON); + } + + if ((numbullets == 1 && !player.refire) || numbullets == 0) + { + int damage = damageperbullet; + + if (!(flags & FBF_NORANDOM)) + damage *= random[cabullet](1, 3); + + let puff = LineAttack(bangle, range, bslope, damage, 'Hitscan', pufftype, laflags); + + if (missile != null) + { + bool temp = false; + double ang = Angle - 90; + Vector2 ofs = AngleToVector(Spawnofs_xy); + Actor proj = SpawnPlayerMissile(missile, bangle, ofs.X, ofs.Y, Spawnheight); + if (proj) + { + if (!puff) + { + temp = true; + puff = LineAttack(bangle, range, bslope, 0, 'Hitscan', pufftype, laflags | LAF_NOINTERACT); + } + AimBulletMissile(proj, puff, flags, temp, false); + } + } + } + else + { + if (numbullets < 0) + numbullets = 1; + for (i = 0; i < numbullets; i++) + { + double pangle = bangle; + double slope = bslope; + + if (flags & FBF_EXPLICITANGLE) + { + pangle += spread_xy; + slope += spread_z; + } + else + { + pangle += spread_xy * Random2[cabullet]() / 255.; + slope += spread_z * Random2[cabullet]() / 255.; + } + + int damage = damageperbullet; + + if (!(flags & FBF_NORANDOM)) + damage *= random[cabullet](1, 3); + + let puff = LineAttack(pangle, range, slope, damage, 'Hitscan', pufftype, laflags); + + if (missile != null) + { + bool temp = false; + double ang = Angle - 90; + Vector2 ofs = AngleToVector(Spawnofs_xy); + Actor proj = SpawnPlayerMissile(missile, bangle, ofs.X, ofs.Y, Spawnheight); + if (proj) + { + if (!puff) + { + temp = true; + puff = LineAttack(bangle, range, bslope, 0, 'Hitscan', pufftype, laflags | LAF_NOINTERACT); + } + AimBulletMissile(proj, puff, flags, temp, false); + } + } + } + } + } + + + //========================================================================== + // + // A_FireProjectile + // + //========================================================================== + + action Actor A_FireProjectile(class ti, double spawnangle = 0, bool useammo = true, double spawnofs_xy = 0, double spawnheight = 0, int flags = 0, double pitch = 0) + { + let player = self.player; + if (!player) return null; + + let weapon = player.ReadyWeapon; + + FTranslatedLineTarget t; + + // Only use ammo if called from a weapon + if (useammo && weapon && stateinfo != null && stateinfo.mStateType == STATE_Psprite) + { + if (!weapon.DepleteAmmo(weapon.bAltFire, true)) + return null; // out of ammo + } + + if (ti) + { + double ang = Angle - 90; + Vector2 ofs = AngleToVector(Spawnofs_xy); + double shootangle = Angle; + + if (flags & FPF_AIMATANGLE) shootangle += spawnangle; + + // Temporarily adjusts the pitch + double saved_player_pitch = self.Pitch; + self.Pitch += pitch; + let misl = SpawnPlayerMissile (ti, shootangle, ofs.X, ofs.Y, spawnheight, t, NULL, false, (flags & FPF_NOAUTOAIM) != 0); + self.Pitch = saved_player_pitch; + + // automatic handling of seeker missiles + if (misl) + { + if (flags & FPF_TRANSFERTRANSLATION) + misl.Translation = Translation; + if (t.linetarget && !t.unlinked && misl.bSeekerMissile) + misl.tracer = t.linetarget; + if (!(flags & FPF_AIMATANGLE)) + { + // This original implementation is to aim straight ahead and then offset + // the angle from the resulting direction. + misl.Angle += spawnangle; + misl.VelFromAngle(misl.Vel.XY.Length()); + } + } + return misl; + } + return null; + } + +//========================================================================== +// +// A_CustomPunch +// +// Berserk is not handled here. That can be done with A_CheckIfInventory +// +//========================================================================== + + action void A_CustomPunch(int damage, bool norandom = false, int flags = CPF_USEAMMO, class pufftype = "BulletPuff", double range = 0, double lifesteal = 0, int lifestealmax = 0, class armorbonustype = "ArmorBonus", sound MeleeSound = 0, sound MissSound = "") + { + let player = self.player; + if (!player) return; + + let weapon = player.ReadyWeapon; + + double angle; + double pitch; + FTranslatedLineTarget t; + int actualdamage; + + if (!norandom) + damage *= random[cwpunch](1, 8); + + angle = self.Angle + random2[cwpunch]() * (5.625 / 256); + if (range == 0) range = DEFMELEERANGE; + pitch = AimLineAttack (angle, range, t, 0., ALF_CHECK3D); + + // only use ammo when actually hitting something! + if ((flags & CPF_USEAMMO) && t.linetarget && weapon && stateinfo != null && stateinfo.mStateType == STATE_Psprite) + { + if (!weapon.DepleteAmmo(weapon.bAltFire, true)) + return; // out of ammo + } + + if (pufftype == NULL) + pufftype = 'BulletPuff'; + int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0); + + Actor puff; + [puff, actualdamage] = LineAttack (angle, range, pitch, damage, 'Melee', pufftype, puffFlags, t); + + if (!t.linetarget) + { + if (MissSound) A_PlaySound(MissSound, CHAN_WEAPON); + } + else + { + if (lifesteal > 0 && !(t.linetarget.bDontDrain)) + { + if (flags & CPF_STEALARMOR) + { + if (armorbonustype == NULL) + { + armorbonustype = 'ArmorBonus'; + } + if (armorbonustype != NULL) + { + let armorbonus = ArmorBonus(Spawn(armorbonustype)); + armorbonus.SaveAmount *= int(actualdamage * lifesteal); + if (lifestealmax > 0) armorbonus.MaxSaveAmount = lifestealmax; + armorbonus.bDropped = true; + armorbonus.ClearCounters(); + + if (!armorbonus.CallTryPickup(self)) + { + armorbonus.Destroy (); + } + } + } + else + { + GiveBody (int(actualdamage * lifesteal), lifestealmax); + } + } + if (weapon != NULL) + { + if (MeleeSound) A_PlaySound(MeleeSound, CHAN_WEAPON); + else A_PlaySound(AttackSound, CHAN_WEAPON); + } + + if (!(flags & CPF_NOTURN)) + { + // turn to face target + self.Angle = t.angleFromSource; + } + + if (flags & CPF_PULLIN) self.bJustAttacked = true; + if (flags & CPF_DAGGER) t.linetarget.DaggerAlert (self); + } + } + + //========================================================================== + // + // customizable railgun attack function + // + //========================================================================== + + action void A_RailAttack(int damage, int spawnofs_xy = 0, bool useammo = true, color color1 = 0, color color2 = 0, int flags = 0, double maxdiff = 0, class pufftype = "BulletPuff", double spread_xy = 0, double spread_z = 0, double range = 0, int duration = 0, double sparsity = 1.0, double driftspeed = 1.0, class spawnclass = "none", double spawnofs_z = 0, int spiraloffset = 270, int limit = 0) + { + if (range == 0) range = 8192; + if (sparsity == 0) sparsity=1.0; + + let player = self.player; + if (!player) return; + + let weapon = player.ReadyWeapon; + + if (useammo && weapon != NULL && stateinfo != null && stateinfo.mStateType == STATE_Psprite) + { + if (!weapon.DepleteAmmo(weapon.bAltFire, true)) + return; // out of ammo + } + + if (!(flags & RGF_EXPLICITANGLE)) + { + spread_xy = spread_xy * Random2[crailgun]() / 255.; + spread_z = spread_z * Random2[crailgun]() / 255.; + } + + FRailParams p; + p.damage = damage; + p.offset_xy = spawnofs_xy; + p.offset_z = spawnofs_z; + p.color1 = color1; + p.color2 = color2; + p.maxdiff = maxdiff; + p.flags = flags; + p.puff = pufftype; + p.angleoffset = spread_xy; + p.pitchoffset = spread_z; + p.distance = range; + p.duration = duration; + p.sparsity = sparsity; + p.drift = driftspeed; + p.spawnclass = spawnclass; + p.SpiralOffset = SpiralOffset; + p.limit = limit; + self.RailAttack(p); + } + + + //--------------------------------------------------------------------------- // // PROC A_ReFire @@ -18,7 +392,7 @@ class StateProvider : Inventory native action void A_ReFire(statelabel flash = null) { - let player = self.player; + let player = player; bool pending; if (NULL == player) @@ -45,8 +419,7 @@ class StateProvider : Inventory native } } - action native state A_CheckForReload(int counter, statelabel label, bool dontincrement = false); - action native void A_ResetReloadCounter(); + action void A_ClearReFire() { @@ -71,6 +444,7 @@ class CustomInventory : StateProvider deprecated("2.3") action void A_Lower() {} deprecated("2.3") action void A_Raise() {} deprecated("2.3") action void A_CheckReload() {} + deprecated("3.7") action void A_WeaponReady(int flags = 0) {} // this was somehow missed in 2.3 ... native bool CallStateChain (Actor actor, State state); //=========================================================================== diff --git a/wadsrc/static/zscript/inventory/weapons.txt b/wadsrc/static/zscript/inventory/weapons.txt index 4786e44d7..add040912 100644 --- a/wadsrc/static/zscript/inventory/weapons.txt +++ b/wadsrc/static/zscript/inventory/weapons.txt @@ -1,4 +1,4 @@ -class Weapon : StateProvider native +class Weapon : StateProvider { enum EFireMode { @@ -10,32 +10,31 @@ class Weapon : StateProvider native const ZOOM_INSTANT = 1; const ZOOM_NOSCALETURNING = 2; - native uint WeaponFlags; - native class AmmoType1, AmmoType2; // Types of ammo used by self weapon - native int AmmoGive1, AmmoGive2; // Amount of each ammo to get when picking up weapon - native int MinAmmo1, MinAmmo2; // Minimum ammo needed to switch to self weapon - native int AmmoUse1, AmmoUse2; // How much ammo to use with each shot - native int Kickback; - native float YAdjust; // For viewing the weapon fullscreen (visual only so no need to be a double) - native sound UpSound, ReadySound; // Sounds when coming up and idle - native class SisterWeaponType; // Another weapon to pick up with self one - native class ProjectileType; // Projectile used by primary attack - native class AltProjectileType; // Projectile used by alternate attack - native int SelectionOrder; // Lower-numbered weapons get picked first - native int MinSelAmmo1, MinSelAmmo2; // Ignore in BestWeapon() if inadequate ammo - native double MoveCombatDist; // Used by bots, but do they *really* need it? - native int ReloadCounter; // For A_CheckForReload - native int BobStyle; // [XA] Bobbing style. Defines type of bobbing (e.g. Normal, Alpha) (visual only so no need to be a double) - native float BobSpeed; // [XA] Bobbing speed. Defines how quickly a weapon bobs. - native float BobRangeX, BobRangeY; // [XA] Bobbing range. Defines how far a weapon bobs in either direction. - native Ammo Ammo1, Ammo2; // In-inventory instance variables - native Weapon SisterWeapon; - native float FOVScale; - native int Crosshair; // 0 to use player's crosshair - native bool GivenAsMorphWeapon; - native bool bAltFire; // Set when this weapon's alternate fire is used. - native readonly bool bDehAmmo; - native readonly int SlotNumber; + deprecated("3.7") uint WeaponFlags; // not to be used directly. + class AmmoType1, AmmoType2; // Types of ammo used by self weapon + int AmmoGive1, AmmoGive2; // Amount of each ammo to get when picking up weapon + deprecated("3.7") int MinAmmo1, MinAmmo2; // not used anywhere and thus deprecated. + int AmmoUse1, AmmoUse2; // How much ammo to use with each shot + int Kickback; + double YAdjust; // For viewing the weapon fullscreen + sound UpSound, ReadySound; // Sounds when coming up and idle + class SisterWeaponType; // Another weapon to pick up with self one + int SelectionOrder; // Lower-numbered weapons get picked first + int MinSelAmmo1, MinSelAmmo2; // Ignore in BestWeapon() if inadequate ammo + int ReloadCounter; // For A_CheckForReload + int BobStyle; // [XA] Bobbing style. Defines type of bobbing (e.g. Normal, Alpha) (visual only so no need to be a double) + float BobSpeed; // [XA] Bobbing speed. Defines how quickly a weapon bobs. + float BobRangeX, BobRangeY; // [XA] Bobbing range. Defines how far a weapon bobs in either direction. + Ammo Ammo1, Ammo2; // In-inventory instance variables + Weapon SisterWeapon; + double FOVScale; + int Crosshair; // 0 to use player's crosshair + bool GivenAsMorphWeapon; + bool bAltFire; // Set when this weapon's alternate fire is used. + readonly bool bDehAmmo; // Uses Doom's original amount of ammo for the respective attack functions so that old DEHACKED patches work as intended. + // AmmoUse1 will be set to the first attack's ammo use so that checking for empty weapons still works + meta int SlotNumber; + meta int SlotPriority; property AmmoGive: AmmoGive1; property AmmoGive1: AmmoGive1; @@ -58,6 +57,33 @@ class Weapon : StateProvider native property BobRangeX: BobRangeX; property BobRangeY: BobRangeY; property SlotNumber: SlotNumber; + property SlotPriority: SlotPriority; + + flagdef NoAutoFire: WeaponFlags, 0; // weapon does not autofire + flagdef ReadySndHalf: WeaponFlags, 1; // ready sound is played ~1/2 the time + flagdef DontBob: WeaponFlags, 2; // don't bob the weapon + flagdef AxeBlood: WeaponFlags, 3; // weapon makes axe blood on impact + flagdef NoAlert: WeaponFlags, 4; // weapon does not alert monsters + flagdef Ammo_Optional: WeaponFlags, 5; // weapon can use ammo but does not require it + flagdef Alt_Ammo_Optional: WeaponFlags, 6; // alternate fire can use ammo but does not require it + flagdef Primary_Uses_Both: WeaponFlags, 7; // primary fire uses both ammo + flagdef Alt_Uses_Both: WeaponFlags, 8; // alternate fire uses both ammo + flagdef Wimpy_Weapon:WeaponFlags, 9; // change away when ammo for another weapon is replenished + flagdef Powered_Up: WeaponFlags, 10; // this is a tome-of-power'ed version of its sister + flagdef Ammo_CheckBoth: WeaponFlags, 11; // check for both primary and secondary fire before switching it off + flagdef No_Auto_Switch: WeaponFlags, 12; // never switch to this weapon when it's picked up + flagdef Staff2_Kickback: WeaponFlags, 13; // the powered-up Heretic staff has special kickback + flagdef NoAutoaim: WeaponFlags, 14; // this weapon never uses autoaim (useful for ballistic projectiles) + flagdef MeleeWeapon: WeaponFlags, 15; // melee weapon. Used by monster AI with AVOIDMELEE. + flagdef NoDeathDeselect: WeaponFlags, 16; // Don't jump to the Deselect state when the player dies + flagdef NoDeathInput: WeaponFlags, 17; // The weapon cannot be fired/reloaded/whatever when the player is dead + flagdef CheatNotWeapon: WeaponFlags, 18; // Give cheat considers this not a weapon (used by Sigil) + + // no-op flags + flagdef NoLMS: none, 0; + flagdef Allow_With_Respawn_Invul: none, 0; + flagdef BFG: none, 0; + flagdef Explosive: none, 0; Default { @@ -77,9 +103,28 @@ class Weapon : StateProvider native SHTG E 0 A_Light0; Stop; } + + //=========================================================================== + // + // Weapon :: MarkPrecacheSounds + // + //=========================================================================== + + override void MarkPrecacheSounds() + { + Super.MarkPrecacheSounds(); + MarkSound(UpSound); + MarkSound(ReadySound); + } - native virtual bool CheckAmmo(int fireMode, bool autoSwitch, bool requireAmmo = false, int ammocount = -1); - native virtual bool DepleteAmmo(bool altFire, bool checkEnough = true, int ammouse = -1); + virtual int, int CheckAddToSlots() + { + if (GetReplacement(GetClass()) == null && !bPowered_Up) + { + return SlotNumber, SlotPriority*65536; + } + return -1, 0; + } virtual State GetReadyState () { @@ -112,6 +157,14 @@ class Weapon : StateProvider native return s; } + virtual void PlayUpSound(Actor origin) + { + if (UpSound) + { + origin.A_PlaySound(UpSound, CHAN_WEAPON); + } + } + override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) { // Weapons may never return HitObituary by default. Override this if it is needed. @@ -168,7 +221,7 @@ class Weapon : StateProvider native } if (null == player.ReadyWeapon) { - player.BringUpWeapon(); + player.mo.BringUpWeapon(); return; } let psp = player.GetPSprite(PSP_WEAPON); @@ -193,7 +246,7 @@ class Weapon : StateProvider native } // [RH] Clear the flash state. Only needed for Strife. player.SetPsprite(PSP_FLASH, null); - player.BringUpWeapon (); + player.mo.BringUpWeapon (); return; } @@ -213,7 +266,7 @@ class Weapon : StateProvider native } if (player.PendingWeapon != WP_NOCHANGE) { - player.DropWeapon(); + player.mo.DropWeapon(); return; } if (player.ReadyWeapon == null) @@ -231,6 +284,110 @@ class Weapon : StateProvider native return; } + //============================================================================ + // + // PROC A_WeaponReady + // + // Readies a weapon for firing or bobbing with its three ancillary functions, + // DoReadyWeaponToSwitch(), DoReadyWeaponToFire() and DoReadyWeaponToBob(). + // [XA] Added DoReadyWeaponToReload() and DoReadyWeaponToZoom() + // + //============================================================================ + + static void DoReadyWeaponToSwitch (PlayerInfo player, bool switchable) + { + // Prepare for switching action. + if (switchable) + { + player.WeaponState |= WF_WEAPONSWITCHOK | WF_REFIRESWITCHOK; + } + else + { + // WF_WEAPONSWITCHOK is automatically cleared every tic by P_SetPsprite(). + player.WeaponState &= ~WF_REFIRESWITCHOK; + } + } + + static void DoReadyWeaponDisableSwitch (PlayerInfo player, int disable) + { + // Discard all switch attempts? + if (disable) + { + player.WeaponState |= WF_DISABLESWITCH; + player.WeaponState &= ~WF_REFIRESWITCHOK; + } + else + { + player.WeaponState &= ~WF_DISABLESWITCH; + } + } + + static void DoReadyWeaponToFire (PlayerPawn pawn, bool prim, bool alt) + { + let player = pawn.player; + let weapon = player.ReadyWeapon; + if (!weapon) + { + return; + } + + // Change player from attack state + if (pawn.InStateSequence(pawn.curstate, pawn.MissileState) || + pawn.InStateSequence(pawn.curstate, pawn.MeleeState)) + { + pawn.PlayIdle (); + } + + // Play ready sound, if any. + if (weapon.ReadySound && player.GetPSprite(PSP_WEAPON).curState == weapon.FindState('Ready')) + { + if (!weapon.bReadySndHalf || random[WpnReadySnd]() < 128) + { + pawn.A_PlaySound(weapon.ReadySound, CHAN_WEAPON); + } + } + + // Prepare for firing action. + player.WeaponState |= ((prim ? WF_WEAPONREADY : 0) | (alt ? WF_WEAPONREADYALT : 0)); + return; + } + + static void DoReadyWeaponToBob (PlayerInfo player) + { + if (player.ReadyWeapon) + { + // Prepare for bobbing action. + player.WeaponState |= WF_WEAPONBOBBING; + let pspr = player.GetPSprite(PSP_WEAPON); + pspr.x = 0; + pspr.y = WEAPONTOP; + } + } + + static int GetButtonStateFlags(int flags) + { + // Rewritten for efficiency and clarity + int outflags = 0; + if (flags & WRF_AllowZoom) outflags |= WF_WEAPONZOOMOK; + if (flags & WRF_AllowReload) outflags |= WF_WEAPONRELOADOK; + if (flags & WRF_AllowUser1) outflags |= WF_USER1OK; + if (flags & WRF_AllowUser2) outflags |= WF_USER2OK; + if (flags & WRF_AllowUser3) outflags |= WF_USER3OK; + if (flags & WRF_AllowUser4) outflags |= WF_USER4OK; + return outflags; + } + + action void A_WeaponReady(int flags = 0) + { + if (!player) return; + DoReadyWeaponToSwitch(player, !(flags & WRF_NoSwitch)); + if ((flags & WRF_NoFire) != WRF_NoFire) DoReadyWeaponToFire(player.mo, !(flags & WRF_NoPrimary), !(flags & WRF_NoSecondary)); + if (!(flags & WRF_NoBob)) DoReadyWeaponToBob(player); + + player.WeaponState |= GetButtonStateFlags(flags); + DoReadyWeaponDisableSwitch(player, flags & WRF_DisableSwitch); + } + //--------------------------------------------------------------------------- // // PROC A_CheckReload @@ -262,13 +419,13 @@ class Weapon : StateProvider native zoom = 1 / clamp(zoom, 0.1, 50.0); if (flags & 1) { // Make the zoom instant. - self.player.FOV = self.player.DesiredFOV * zoom; + player.FOV = player.DesiredFOV * zoom; } if (flags & 2) { // Disable pitch/yaw scaling. zoom = -zoom; } - self.player.ReadyWeapon.FOVScale = zoom; + player.ReadyWeapon.FOVScale = zoom; } } @@ -677,7 +834,193 @@ class Weapon : StateProvider native } } + + //=========================================================================== + // + // Weapon :: PostMorphWeapon + // + // Bring this weapon up after a player unmorphs. + // + //=========================================================================== + void PostMorphWeapon () + { + if (Owner == null) + { + return; + } + let p = owner.player; + p.PendingWeapon = WP_NOCHANGE; + p.ReadyWeapon = self; + p.refire = 0; + + let pspr = p.GetPSprite(PSP_WEAPON); + pspr.y = WEAPONBOTTOM; + pspr.ResetInterpolation(); + pspr.SetState(GetUpState()); + } + + //=========================================================================== + // + // Weapon :: CheckAmmo + // + // Returns true if there is enough ammo to shoot. If not, selects the + // next weapon to use. + // + //=========================================================================== + + virtual bool CheckAmmo(int fireMode, bool autoSwitch, bool requireAmmo = false, int ammocount = -1) + { + int count1, count2; + int enough, enoughmask; + int lAmmoUse1; + + if (sv_infiniteammo || (Owner.FindInventory ('PowerInfiniteAmmo', true) != null)) + { + return true; + } + if (fireMode == EitherFire) + { + bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false); + if (!gotSome && autoSwitch) + { + PlayerPawn(Owner).PickNewWeapon (null); + } + return gotSome; + } + let altFire = (fireMode == AltFire); + let optional = (altFire? bAlt_Ammo_Optional : bAmmo_Optional); + let useboth = (altFire? bPrimary_Uses_Both : bAlt_Uses_Both); + + if (!requireAmmo && optional) + { + return true; + } + count1 = (Ammo1 != null) ? Ammo1.Amount : 0; + count2 = (Ammo2 != null) ? Ammo2.Amount : 0; + + if (bDehAmmo && Ammo1 == null) + { + lAmmoUse1 = 0; + } + else if (ammocount >= 0 && bDehAmmo) + { + lAmmoUse1 = ammocount; + } + else + { + lAmmoUse1 = AmmoUse1; + } + + enough = (count1 >= lAmmoUse1) | ((count2 >= AmmoUse2) << 1); + if (useboth) + { + enoughmask = 3; + } + else + { + enoughmask = 1 << altFire; + } + if (altFire && FindState('AltFire') == null) + { // If this weapon has no alternate fire, then there is never enough ammo for it + enough &= 1; + } + if (((enough & enoughmask) == enoughmask) || (enough && bAmmo_CheckBoth)) + { + return true; + } + // out of ammo, pick a weapon to change to + if (autoSwitch) + { + PlayerPawn(Owner).PickNewWeapon (null); + } + return false; + } + + + //=========================================================================== + // + // Weapon :: DepleteAmmo + // + // Use up some of the weapon's ammo. Returns true if the ammo was successfully + // depleted. If checkEnough is false, then the ammo will always be depleted, + // even if it drops below zero. + // + //=========================================================================== + + virtual bool DepleteAmmo(bool altFire, bool checkEnough = true, int ammouse = -1) + { + if (!(sv_infiniteammo || (Owner.FindInventory ('PowerInfiniteAmmo', true) != null))) + { + if (checkEnough && !CheckAmmo (altFire ? AltFire : PrimaryFire, false, false, ammouse)) + { + return false; + } + if (!altFire) + { + if (Ammo1 != null) + { + if (ammouse >= 0 && bDehAmmo) + { + Ammo1.Amount -= ammouse; + } + else + { + Ammo1.Amount -= AmmoUse1; + } + } + if (bPRIMARY_USES_BOTH && Ammo2 != null) + { + Ammo2.Amount -= AmmoUse2; + } + } + else + { + if (Ammo2 != null) + { + Ammo2.Amount -= AmmoUse2; + } + if (bALT_USES_BOTH && Ammo1 != null) + { + Ammo1.Amount -= AmmoUse1; + } + } + if (Ammo1 != null && Ammo1.Amount < 0) + Ammo1.Amount = 0; + if (Ammo2 != null && Ammo2.Amount < 0) + Ammo2.Amount = 0; + } + return true; + } + + + //--------------------------------------------------------------------------- + // + // Modifies the drop amount of this item according to the current skill's + // settings (also called by ADehackedPickup::TryPickup) + // + //--------------------------------------------------------------------------- + override void ModifyDropAmount(int dropamount) + { + bool ignoreskill = true; + double dropammofactor = G_SkillPropertyFloat(SKILLP_DropAmmoFactor); + // Default drop amount is half of regular amount * regular ammo multiplication + if (dropammofactor == -1) + { + dropammofactor = 0.5; + ignoreskill = false; + } + + if (dropamount > 0) + { + self.Amount = dropamount; + } + // Adjust the ammo given by this weapon + AmmoGive1 = int(AmmoGive1 * dropammofactor); + AmmoGive2 = int(AmmoGive2 * dropammofactor); + bIgnoreSkill = ignoreskill; + } + } class WeaponGiver : Weapon @@ -741,9 +1084,35 @@ class WeaponGiver : Weapon return false; } + //--------------------------------------------------------------------------- + // + // Modifies the drop amount of this item according to the current skill's + // settings (also called by ADehackedPickup::TryPickup) + // + //--------------------------------------------------------------------------- + + override void ModifyDropAmount(int dropamount) + { + bool ignoreskill = true; + double dropammofactor = G_SkillPropertyFloat(SKILLP_DropAmmoFactor); + // Default drop amount is half of regular amount * regular ammo multiplication + if (dropammofactor == -1) + { + dropammofactor = 0.5; + ignoreskill = false; + } + + AmmoFactor = dropammofactor; + bIgnoreSkill = ignoreskill; + } + + } struct WeaponSlots native { native bool, int, int LocateWeapon(class weap); + native static void SetupWeaponSlots(PlayerPawn pp); + native class GetWeapon(int slot, int index); + native int SlotSize(int slot); } diff --git a/wadsrc/static/zscript/raven/artitele.txt b/wadsrc/static/zscript/raven/artitele.txt index 77a233dbe..0e65fe051 100644 --- a/wadsrc/static/zscript/raven/artitele.txt +++ b/wadsrc/static/zscript/raven/artitele.txt @@ -42,7 +42,7 @@ class ArtiTeleport : Inventory Playerinfo p = Owner.player; if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYCHAOSDEVICE)) { // Teleporting away will undo any morph effects (pig) - if (!p.UndoPlayerMorph (p, MRF_UNDOBYCHAOSDEVICE) && (p.MorphStyle & MRF_FAILNOLAUGH)) + if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYCHAOSDEVICE) && (p.MorphStyle & MRF_FAILNOLAUGH)) { canlaugh = false; } diff --git a/wadsrc/static/zscript/scriptutil/scriptutil.txt b/wadsrc/static/zscript/scriptutil/scriptutil.txt new file mode 100644 index 000000000..d29fb3f34 --- /dev/null +++ b/wadsrc/static/zscript/scriptutil/scriptutil.txt @@ -0,0 +1,151 @@ + +// Container for utility functions used by ACS and FraggleScript. + +class ScriptUtil play +{ + + //========================================================================== + // + // + // + //========================================================================== + + static int SetWeapon(Actor activator, class cls) + { + if(activator != NULL && activator.player != NULL && cls != null) + { + let item = Weapon(activator.FindInventory(cls)); + + if(item != NULL) + { + if(activator.player.ReadyWeapon == item) + { + // The weapon is already selected, so setweapon succeeds by default, + // but make sure the player isn't switching away from it. + activator.player.PendingWeapon = WP_NOCHANGE; + return 1; + } + else + { + if(item.CheckAmmo(Weapon.EitherFire, false)) + { + // There's enough ammo, so switch to it. + activator.player.PendingWeapon = item; + return 1; + } + } + } + } + return 0; + } + + //========================================================================== + // + // + // + //========================================================================== + + static void SetMarineWeapon(Actor activator, int tid, int marineweapontype) + { + if (tid != 0) + { + let it = ActorIterator.Create(tid, 'ScriptedMarine'); + ScriptedMarine marine; + + while ((marine = ScriptedMarine(it.Next())) != NULL) + { + marine.SetWeapon(marineweapontype); + } + } + else + { + let marine = ScriptedMarine(activator); + if (marine != null) + { + marine.SetWeapon(marineweapontype); + } + } + } + + //========================================================================== + // + // + // + //========================================================================== + + static void SetMarineSprite(Actor activator, int tid, class type) + { + if (type != NULL) + { + if (tid != 0) + { + let it = ActorIterator.Create(tid, 'ScriptedMarine'); + ScriptedMarine marine; + + while ((marine = ScriptedMarine(it.Next())) != NULL) + { + marine.SetSprite(type); + } + } + else + { + let marine = ScriptedMarine(activator); + if (marine != null) + { + marine.SetSprite(type); + } + } + } + else + { + Console.Printf ("Unknown actor type: %s\n", type.GetClassName()); + } + } + + + //========================================================================== + // + // + // + //========================================================================== + + static int PlayerMaxAmmo(Actor activator, class type, int newmaxamount = int.min, int newbpmaxamount = int.min) + { + if (activator == null) return 0; + let ammotype = (class)(type); + if (ammotype == null) return 0; + + if (newmaxamount != int.min) + { + let iammo = Ammo(activator.FindInventory(ammotype)); + if(newmaxamount < 0) newmaxamount = 0; + if (!iammo) + { + activator.GiveAmmo(ammotype, 1); + iammo = Ammo(activator.FindInventory(ammotype)); + if (iammo) + iammo.Amount = 0; + } + + for (Inventory item = activator.Inv; item != NULL; item = item.Inv) + { + if (item is 'BackpackItem') + { + if (newbpmaxamount == int.min) newbpmaxamount = newmaxamount * 2; + break; + } + } + if (iammo) + { + iammo.MaxAmount = newmaxamount; + iammo.BackpackMaxAmount = newbpmaxamount; + } + } + + let rammo = activator.FindInventory(ammotype); + if (rammo) return rammo.maxamount; + else return GetDefaultByType(ammotype).MaxAmount; + } + + +} diff --git a/wadsrc/static/zscript/shared/morph.txt b/wadsrc/static/zscript/shared/morph.txt index 149cf3cea..3a1662915 100644 --- a/wadsrc/static/zscript/shared/morph.txt +++ b/wadsrc/static/zscript/shared/morph.txt @@ -1,3 +1,588 @@ +//----------------------------------------------------------------------------- +// +// Copyright 1994-1996 Raven Software +// Copyright 1999-2016 Randy Heit +// Copyright 2002-2018 Christoph Oelckers +// Copyright 2005-2008 Martin Howe +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//----------------------------------------------------------------------------- +// + +extend class Actor +{ + virtual Actor, int, int MorphedDeath() + { + return null, 0, 0; + } + + + //=========================================================================== + // + // Main entry point + // + //=========================================================================== + + bool Morph(Actor activator, class playerclass, class monsterclass, int duration = 0, int style = 0, class morphflash = null, classunmorphflash = null) + { + if (player != null && player.mo != null && playerclass != null) + { + return player.mo.MorphPlayer(activator? activator.player : null, playerclass, duration, style, morphflash, unmorphflash); + } + else + { + return MorphMonster(monsterclass, duration, style, morphflash, unmorphflash); + } + } + + //=========================================================================== + // + // Action function variant whose arguments differ from the generic one. + // + //=========================================================================== + + bool A_Morph(class type, int duration = 0, int style = 0, class morphflash = null, classunmorphflash = null) + { + if (self.player != null) + { + let playerclass = (class)(type); + if (playerclass && self.player.mo != null) return player.mo.MorphPlayer(self.player, playerclass, duration, style, morphflash, unmorphflash); + } + else + { + return MorphMonster(type, duration, style, morphflash, unmorphflash); + } + return false; + } + + //=========================================================================== + // + // Main entry point + // + //=========================================================================== + + bool UnMorph(Actor activator, int flags, bool force) + { + if (player) + { + return player.mo.UndoPlayerMorph(activator? activator.player : null, flags, force); + } + else + { + let morphed = MorphedMonster(self); + if (morphed) + return morphed.UndoMonsterMorph(force); + } + return false; + } + + + //--------------------------------------------------------------------------- + // + // FUNC P_MorphMonster + // + // Returns true if the monster gets turned into a chicken/pig. + // + //--------------------------------------------------------------------------- + + virtual bool MorphMonster (Class spawntype, int duration, int style, Class enter_flash, Class exit_flash) + { + if (player || spawntype == NULL || bDontMorph || !bIsMonster || !(spawntype is 'MorphedMonster')) + { + return false; + } + + let morphed = MorphedMonster(Spawn (spawntype, Pos, NO_REPLACE)); + Substitute (morphed); + if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate) + { + morphed.Translation = Translation; + } + morphed.ChangeTid(tid); + ChangeTid(0); + morphed.Angle = Angle; + morphed.UnmorphedMe = self; + morphed.Alpha = Alpha; + morphed.RenderStyle = RenderStyle; + morphed.Score = Score; + + morphed.UnmorphTime = level.time + ((duration) ? duration : DEFMORPHTICS) + random[morphmonst](); + morphed.MorphStyle = style; + morphed.MorphExitFlash = (exit_flash) ? exit_flash : (class)("TeleportFog"); + morphed.FlagsSave = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility + + morphed.special = special; + morphed.args[0] = args[0]; + morphed.args[1] = args[1]; + morphed.args[2] = args[2]; + morphed.args[3] = args[3]; + morphed.args[4] = args[4]; + morphed.CopyFriendliness (self, true); + morphed.bShadow |= bShadow; + morphed.bGhost |= bGhost; + special = 0; + bSolid = false; + bShootable = false; + bUnmorphed = true; + bInvisible = true; + let eflash = Spawn(enter_flash ? enter_flash : (class)("TeleportFog"), Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE); + if (eflash) + eflash.target = morphed; + return true; + } +} + +extend class PlayerPawn +{ + //=========================================================================== + // + // EndAllPowerupEffects + // + // Calls EndEffect() on every Powerup in the inventory list. + // + //=========================================================================== + + void InitAllPowerupEffects() + { + let item = Inv; + while (item != null) + { + let power = Powerup(item); + if (power != null) + { + power.InitEffect(); + } + item = item.Inv; + } + } + + //=========================================================================== + // + // EndAllPowerupEffects + // + // Calls EndEffect() on every Powerup in the inventory list. + // + //=========================================================================== + + void EndAllPowerupEffects() + { + let item = Inv; + while (item != null) + { + let power = Powerup(item); + if (power != null) + { + power.EndEffect(); + } + item = item.Inv; + } + } + + //=========================================================================== + // + // + // + //=========================================================================== + + virtual void ActivateMorphWeapon () + { + class morphweaponcls = MorphWeapon; + player.PendingWeapon = WP_NOCHANGE; + + if (player.ReadyWeapon != null) + { + player.GetPSprite(PSP_WEAPON).y = WEAPONTOP; + } + + if (morphweaponcls == null || !(morphweaponcls is 'Weapon')) + { // No weapon at all while morphed! + player.ReadyWeapon = null; + } + else + { + player.ReadyWeapon = Weapon(FindInventory (morphweaponcls)); + if (player.ReadyWeapon == null) + { + player.ReadyWeapon = Weapon(GiveInventoryType (morphweaponcls)); + if (player.ReadyWeapon != null) + { + player.ReadyWeapon.GivenAsMorphWeapon = true; // flag is used only by new beastweap semantics in UndoPlayerMorph + } + } + if (player.ReadyWeapon != null) + { + player.SetPsprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState()); + } + } + + if (player.ReadyWeapon != null) + { + player.SetPsprite(PSP_FLASH, null); + } + + player.PendingWeapon = WP_NOCHANGE; + } + + //--------------------------------------------------------------------------- + // + // MorphPlayer + // + // Returns true if the player gets turned into a chicken/pig. + // + // TODO: Allow morphed players to receive weapon sets (not just one weapon), + // since they have their own weapon slots now. + // + //--------------------------------------------------------------------------- + + virtual bool MorphPlayer(playerinfo activator, Class spawntype, int duration, int style, Class enter_flash = null, Class exit_flash = null) + { + if (bDontMorph) + { + return false; + } + if (bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))) + { // Immune when invulnerable unless this is a power we activated + return false; + } + if (player.morphTics) + { // Player is already a beast + if ((GetClass() == spawntype) && bCanSuperMorph + && (player.morphTics < (((duration) ? duration : DEFMORPHTICS) - TICRATE)) + && FindInventory('PowerWeaponLevel2', true) == null) + { // Make a super chicken + GiveInventoryType ('PowerWeaponLevel2'); + } + return false; + } + if (health <= 0) + { // Dead players cannot morph + return false; + } + if (spawntype == null) + { + return false; + } + if (!(spawntype is 'PlayerPawn')) + { + return false; + } + if (spawntype == GetClass()) + { + return false; + } + + let morphed = PlayerPawn(Spawn (spawntype, Pos, NO_REPLACE)); + EndAllPowerupEffects(); + Substitute(morphed); + if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate) + { + morphed.Translation = Translation; + } + if (tid != 0 && (style & MRF_NEWTIDBEHAVIOUR)) + { + morphed.ChangeTid(tid); + ChangeTid(0); + } + morphed.Angle = Angle; + morphed.target = target; + morphed.tracer = tracer; + morphed.alternative = self; + morphed.FriendPlayer = FriendPlayer; + morphed.DesignatedTeam = DesignatedTeam; + morphed.Score = Score; + player.PremorphWeapon = player.ReadyWeapon; + + morphed.special2 = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility + morphed.player = player; + + if (morphed.ViewHeight > player.viewheight && player.deltaviewheight == 0) + { // If the new view height is higher than the old one, start moving toward it. + player.deltaviewheight = player.GetDeltaViewHeight(); + } + morphed.bShadow |= bShadow; + morphed.bNoGravity |= bNoGravity; + morphed.bFly |= bFly; + morphed.bGhost |= bGhost; + + if (enter_flash == null) enter_flash = 'TeleportFog'; + let eflash = Spawn(enter_flash, Pos + (0, 0, gameinfo.telefogheight), ALLOW_REPLACE); + let p = player; + player = null; + alternative = morphed; + bSolid = false; + bShootable = false; + bUnmorphed = true; + bInvisible = true; + + p.morphTics = (duration) ? duration : DEFMORPHTICS; + + // [MH] Used by SBARINFO to speed up face drawing + p.MorphedPlayerClass = spawntype; + + p.MorphStyle = style; + if (exit_flash == null) exit_flash = 'TeleportFog'; + p.MorphExitFlash = exit_flash; + p.health = morphed.health; + p.mo = morphed; + p.vel = (0, 0); + morphed.ObtainInventory (self); + // Remove all armor + for (Inventory item = morphed.Inv; item != null; ) + { + let next = item.Inv; + if (item is 'Armor') + { + item.DepleteOrDestroy(); + } + item = next; + } + morphed.InitAllPowerupEffects(); + morphed.ActivateMorphWeapon (); + if (p.camera == self) // can this happen? + { + p.camera = morphed; + } + morphed.ScoreIcon = ScoreIcon; // [GRB] + if (eflash) + eflash.target = morphed; + return true; + } + + //---------------------------------------------------------------------------- + // + // FUNC UndoPlayerMorph + // + //---------------------------------------------------------------------------- + + virtual bool UndoPlayerMorph(playerinfo activator, int unmorphflag = 0, bool force = false) + { + if (alternative == null) + { + return false; + } + + let player = self.player; + bool DeliberateUnmorphIsOkay = !!(MRF_STANDARDUNDOING & unmorphflag); + + if ((bInvulnerable) // If the player is invulnerable + && ((player != activator) // and either did not decide to unmorph, + || (!((player.MorphStyle & MRF_WHENINVULNERABLE) // or the morph style does not allow it + || (DeliberateUnmorphIsOkay))))) // (but standard morph styles always allow it), + { // Then the player is immune to the unmorph. + return false; + } + + let altmo = PlayerPawn(alternative); + altmo.SetOrigin (Pos, false); + altmo.bSolid = true; + bSolid = false; + if (!force && !altmo.TestMobjLocation()) + { // Didn't fit + altmo.bSolid = false; + bSolid = true; + player.morphTics = 2*TICRATE; + return false; + } + // No longer using tracer as morph storage. That is what 'alternative' is for. + // If the tracer has changed on the morph, change the original too. + altmo.target = target; + altmo.tracer = tracer; + self.player = null; + altmo.alternative = alternative = null; + + // Remove the morph power if the morph is being undone prematurely. + for (Inventory item = Inv; item != null;) + { + let next = item.Inv; + if (item is "PowerMorph") + { + item.Destroy(); + } + item = next; + } + EndAllPowerupEffects(); + altmo.ObtainInventory (self); + Substitute(altmo); + if ((tid != 0) && (player.MorphStyle & MRF_NEWTIDBEHAVIOUR)) + { + altmo.ChangeTid(tid); + } + altmo.Angle = Angle; + altmo.player = player; + altmo.reactiontime = 18; + altmo.bSolid = !!(special2 & 2); + altmo.bShootable = !!(special2 & 4); + altmo.bInvisible = !!(special2 & 0x40); + altmo.Vel = (0, 0, Vel.Z); + player.Vel = (0, 0); + altmo.floorz = floorz; + altmo.bShadow = bShadow; + altmo.bNoGravity = bNoGravity; + altmo.bGhost = bGhost; + altmo.bUnmorphed = false; + altmo.Score = Score; + altmo.InitAllPowerupEffects(); + + let exit_flash = player.MorphExitFlash; + bool correctweapon = !!(player.MorphStyle & MRF_LOSEACTUALWEAPON); + bool undobydeathsaves = !!(player.MorphStyle & MRF_UNDOBYDEATHSAVES); + + player.morphTics = 0; + player.MorphedPlayerClass = null; + player.MorphStyle = 0; + player.MorphExitFlash = null; + player.viewheight = altmo.ViewHeight; + Inventory level2 = altmo.FindInventory("PowerWeaponLevel2", true); + if (level2 != null) + { + level2.Destroy (); + } + + if ((player.health > 0) || undobydeathsaves) + { + player.health = altmo.health = altmo.SpawnHealth(); + } + else // killed when morphed so stay dead + { + altmo.health = player.health; + } + + player.mo = altmo; + if (player.camera == self) + { + player.camera = altmo; + } + + // [MH] + // If the player that was morphed is the one + // taking events, reset up the face, if any; + // this is only needed for old-skool skins + // and for the original DOOM status bar. + if (player == players[consoleplayer]) + { + if (face != 'None') + { + // Assume root-level base skin to begin with + let skinindex = 0; + let skin = player.GetSkin(); + // If a custom skin was in use, then reload it + // or else the base skin for the player class. + if (skin >= PlayerClasses.Size () && skin < PlayerSkins.Size()) + { + skinindex = skin; + } + else if (PlayerClasses.Size () > 1) + { + let whatami = altmo.GetClass(); + for (int i = 0; i < PlayerClasses.Size (); ++i) + { + if (PlayerClasses[i].Type == whatami) + { + skinindex = i; + break; + } + } + } + } + } + + Actor eflash = null; + if (exit_flash != null) + { + eflash = Spawn(exit_flash, Vec3Angle(20., altmo.Angle, gameinfo.telefogheight), ALLOW_REPLACE); + if (eflash) eflash.target = altmo; + } + WeaponSlots.SetupWeaponSlots(altmo); // Use original class's weapon slots. + let beastweap = player.ReadyWeapon; + if (player.PremorphWeapon != null) + { + player.PremorphWeapon.PostMorphWeapon (); + } + else + { + player.ReadyWeapon = player.PendingWeapon = null; + } + if (correctweapon) + { // Better "lose morphed weapon" semantics + class morphweaponcls = MorphWeapon; + if (morphweaponcls != null && morphweaponcls is 'Weapon') + { + let OriginalMorphWeapon = Weapon(altmo.FindInventory (morphweapon)); + if ((OriginalMorphWeapon != null) && (OriginalMorphWeapon.GivenAsMorphWeapon)) + { // You don't get to keep your morphed weapon. + if (OriginalMorphWeapon.SisterWeapon != null) + { + OriginalMorphWeapon.SisterWeapon.Destroy (); + } + OriginalMorphWeapon.Destroy (); + } + } + } + else // old behaviour (not really useful now) + { // Assumptions made here are no longer valid + if (beastweap != null) + { // You don't get to keep your morphed weapon. + if (beastweap.SisterWeapon != null) + { + beastweap.SisterWeapon.Destroy (); + } + beastweap.Destroy (); + } + } + Destroy (); + // Restore playerclass armor to its normal amount. + let hxarmor = HexenArmor(altmo.FindInventory('HexenArmor')); + if (hxarmor != null) + { + hxarmor.Slots[4] = altmo.HexenArmor[0]; + } + return true; + } + + //=========================================================================== + // + // + // + //=========================================================================== + + override Actor, int, int MorphedDeath() + { + // Voodoo dolls should not unmorph the real player here. + if (player && (player.mo == self) && + (player.morphTics) && + (player.MorphStyle & MRF_UNDOBYDEATH) && + (alternative)) + { + Actor realme = alternative; + int realstyle = player.MorphStyle; + int realhealth = health; + if (UndoPlayerMorph(player, 0, !!(player.MorphStyle & MRF_UNDOBYDEATHFORCED))) + { + return realme, realstyle, realhealth; + } + } + return null, 0, 0; + } +} + +//=========================================================================== +// +// +// +//=========================================================================== + class MorphProjectile : Actor { @@ -17,7 +602,8 @@ class MorphProjectile : Actor { if (target.player) { - target.player.MorphPlayer (NULL, PlayerClass, Duration, MorphStyle, MorphFlash, UnMorphFlash); + // Voodoo dolls forward this to the real player + target.player.mo.MorphPlayer (NULL, PlayerClass, Duration, MorphStyle, MorphFlash, UnMorphFlash); } else { @@ -29,11 +615,18 @@ class MorphProjectile : Actor } -class MorphedMonster : Actor native +//=========================================================================== +// +// +// +//=========================================================================== + +class MorphedMonster : Actor { - native Actor UnmorphedMe; - native int UnmorphTime, MorphStyle; - native Class MorphExitFlash; + Actor UnmorphedMe; + int UnmorphTime, MorphStyle; + Class MorphExitFlash; + int FlagsSave; Default { @@ -41,5 +634,118 @@ class MorphedMonster : Actor native -COUNTKILL +FLOORCLIP } + + override void OnDestroy () + { + if (UnmorphedMe != NULL) + { + UnmorphedMe.Destroy (); + } + Super.OnDestroy(); + } + + override void Die (Actor source, Actor inflictor, int dmgflags, Name MeansOfDeath) + { + Super.Die (source, inflictor, dmgflags, MeansOfDeath); + if (UnmorphedMe != NULL && UnmorphedMe.bUnmorphed) + { + UnmorphedMe.health = health; + UnmorphedMe.Die (source, inflictor, dmgflags, MeansOfDeath); + } + } + + override void Tick () + { + if (UnmorphTime > level.time || !UndoMonsterMorph()) + { + Super.Tick(); + } + } + + //---------------------------------------------------------------------------- + // + // FUNC P_UndoMonsterMorph + // + // Returns true if the monster unmorphs. + // + //---------------------------------------------------------------------------- + + virtual bool UndoMonsterMorph(bool force = false) + { + if (UnmorphTime == 0 || UnmorphedMe == NULL || bStayMorphed || UnmorphedMe.bStayMorphed) + { + return false; + } + let unmorphed = UnmorphedMe; + unmorphed.SetOrigin (Pos, false); + unmorphed.bSolid = true; + bSolid = false; + bool save = bTouchy; + bTouchy = false; + if (!force && !unmorphed.TestMobjLocation ()) + { // Didn't fit + unmorphed.bSolid = false; + bSolid = true; + bTouchy = save; + UnmorphTime = level.time + 5*TICRATE; // Next try in 5 seconds + return false; + } + unmorphed.Angle = Angle; + unmorphed.target = target; + unmorphed.bShadow = bShadow; + unmorphed.bGhost = bGhost; + unmorphed.bSolid = !!(flagssave & 2); + unmorphed.bShootable = !!(flagssave & 4); + unmorphed.bInvisible = !!(flagssave & 0x40); + unmorphed.health = unmorphed.SpawnHealth(); + unmorphed.Vel = Vel; + unmorphed.ChangeTid(tid); + unmorphed.special = special; + unmorphed.Score = Score; + unmorphed.args[0] = args[0]; + unmorphed.args[1] = args[1]; + unmorphed.args[2] = args[2]; + unmorphed.args[3] = args[3]; + unmorphed.args[4] = args[4]; + unmorphed.CopyFriendliness (self, true); + UnmorphedMe = NULL; + Substitute(unmorphed); + Destroy (); + let eflash = Spawn(MorphExitFlash, Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE); + if (eflash) + eflash.target = unmorphed; + return true; + } + + //=========================================================================== + // + // + // + //=========================================================================== + + override Actor, int, int MorphedDeath() + { + let realme = UnmorphedMe; + if (realme != NULL) + { + if ((UnmorphTime) && + (MorphStyle & MRF_UNDOBYDEATH)) + { + int realstyle = MorphStyle; + int realhealth = health; + if (UndoMonsterMorph(!!(MorphStyle & MRF_UNDOBYDEATHFORCED))) + { + return realme, realstyle, realhealth; + } + } + if (realme.bBossDeath) + { + realme.health = 0; // make sure that A_BossDeath considers it dead. + realme.A_BossDeath(); + } + } + return null, 0, 0; + } + } diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt index dce27e106..a213e2c0c 100644 --- a/wadsrc/static/zscript/shared/player.txt +++ b/wadsrc/static/zscript/shared/player.txt @@ -49,9 +49,11 @@ class PlayerPawn : Actor native native color DamageFade; // [CW] Fades for when you are being damaged. native double ViewBob; // [SP] ViewBob Multiplier native double FullHeight; + native double curBob; meta Name HealingRadiusType; meta Name InvulMode; + meta int TeleportFreezeTime; property prefix: Player; property HealRadiusType: HealingradiusType; @@ -69,6 +71,7 @@ class PlayerPawn : Actor native property MorphWeapon: MorphWeapon; property FlechetteType: FlechetteType; property Portrait: Portrait; + property TeleportFreezeTime: TeleportFreezeTime; Default { @@ -106,9 +109,22 @@ class PlayerPawn : Actor native Player.FlechetteType "ArtiPoisonBag3"; Player.AirCapacity 1; Player.ViewBob 1; + Player.TeleportFreezeTime 18; Obituary "$OB_MPDEFAULT"; } + //=========================================================================== + // + // APlayerPawn :: MarkPrecacheSounds + // + //=========================================================================== + + override void MarkPrecacheSounds() + { + Super.MarkPrecacheSounds(); + MarkPlayerSounds(); + } + //---------------------------------------------------------------------------- // // @@ -154,7 +170,7 @@ class PlayerPawn : Actor native let invul = Powerup(Spawn("PowerInvulnerable")); invul.EffectTics = 3 * TICRATE; invul.BlendColor = 0; // don't mess with the view - invul.bUndroppable = true; // Don't drop this + invul.bUndroppable = true; // Don't drop self bRespawnInvul = true; // [RH] special effect } } @@ -363,7 +379,7 @@ class PlayerPawn : Actor native // // PROC P_CheckWeaponChange // - // The player can change to another weapon at this time. + // The player can change to another weapon at self time. // [GZ] This was cut from P_CheckWeaponFire. // //--------------------------------------------------------------------------- @@ -382,7 +398,7 @@ class PlayerPawn : Actor native if ((player.PendingWeapon != WP_NOCHANGE || player.health <= 0) && player.WeaponState & WF_WEAPONSWITCHOK) { - player.DropWeapon(); + DropWeapon(); } } @@ -421,7 +437,7 @@ class PlayerPawn : Actor native if (player.ReadyWeapon == null) { if (player.PendingWeapon != WP_NOCHANGE) - player.BringUpWeapon(); + player.mo.BringUpWeapon(); } else { @@ -537,6 +553,191 @@ class PlayerPawn : Actor native } } + //=========================================================================== + // + // APlayerPawn :: Die + // + //=========================================================================== + + override void Die (Actor source, Actor inflictor, int dmgflags, Name MeansOfDeath) + { + Super.Die (source, inflictor, dmgflags, MeansOfDeath); + + if (player != NULL && player.mo == self) player.bonuscount = 0; + + if (player != NULL && player.mo != self) + { // Make the real player die, too + player.mo.Die (source, inflictor, dmgflags, MeansOfDeath); + } + else + { + if (player != NULL && sv_weapondrop) + { // Voodoo dolls don't drop weapons + let weap = player.ReadyWeapon; + if (weap != NULL) + { + // kgDROP - start - modified copy from a_action.cpp + let di = weap.GetDropItems(); + + if (di != NULL) + { + while (di != NULL) + { + if (di.Name != 'None') + { + class ti = di.Name; + if (ti) A_DropItem (ti, di.Amount, di.Probability); + } + di = di.Next; + } + } + else if (weap.SpawnState != NULL && + weap.SpawnState != GetDefaultByType('Actor').SpawnState) + { + let weapitem = Weapon(A_DropItem (weap.GetClass(), -1, 256)); + if (weapitem) + { + if (weap.AmmoGive1 && weap.Ammo1) + { + weapitem.AmmoGive1 = weap.Ammo1.Amount; + } + if (weap.AmmoGive2 && weap.Ammo2) + { + weapitem.AmmoGive2 = weap.Ammo2.Amount; + } + weapitem.bIgnoreSkill = true; + } + } + else + { + let item = Inventory(A_DropItem (weap.AmmoType1, -1, 256)); + if (item != NULL) + { + item.Amount = weap.Ammo1.Amount; + item.bIgnoreSkill = true; + } + item = Inventory(A_DropItem (weap.AmmoType2, -1, 256)); + if (item != NULL) + { + item.Amount = weap.Ammo2.Amount; + item.bIgnoreSkill = true; + } + } + } + } + if (!multiplayer && level.deathsequence != 'None') + { + level.StartIntermission(level.deathsequence, FSTATE_EndingGame); + } + } + } + + //=========================================================================== + // + // APlayerPawn :: FilterCoopRespawnInventory + // + // When respawning in coop, this function is called to walk through the dead + // player's inventory and modify it according to the current game flags so + // that it can be transferred to the new live player. This player currently + // has the default inventory, and the oldplayer has the inventory at the time + // of death. + // + //=========================================================================== + + void FilterCoopRespawnInventory (PlayerPawn oldplayer) + { + // If we're losing everything, this is really simple. + if (sv_cooploseinventory) + { + oldplayer.DestroyAllInventory(); + return; + } + + // Walk through the old player's inventory and destroy or modify + // according to dmflags. + Inventory next; + for (Inventory item = oldplayer.Inv; item != NULL; item = next) + { + next = item.Inv; + + // If this item is part of the default inventory, we never want + // to destroy it, although we might want to copy the default + // inventory amount. + let defitem = FindInventory (item.GetClass()); + + if (sv_cooplosekeys && defitem == NULL && item is 'Key') + { + item.Destroy(); + } + else if (sv_cooploseweapons && defitem == NULL && item is 'Weapon') + { + item.Destroy(); + } + else if (sv_cooplosearmor && item is 'Armor') + { + if (defitem == NULL) + { + item.Destroy(); + } + else if (item is 'BasicArmor') + { + BasicArmor(item).SavePercent = BasicArmor(defitem).SavePercent; + item.Amount = defitem.Amount; + } + else if (item is 'HexenArmor') + { + let to = HexenArmor(item); + let from = HexenArmor(defitem); + to.Slots[0] = from.Slots[0]; + to.Slots[1] = from.Slots[1]; + to.Slots[2] = from.Slots[2]; + to.Slots[3] = from.Slots[3]; + } + } + else if (sv_cooplosepowerups && defitem == NULL && item is 'Powerup') + { + item.Destroy(); + } + else if ((sv_cooploseammo || sv_coophalveammo) && item is 'Ammo') + { + if (defitem == NULL) + { + if (sv_cooploseammo) + { + // Do NOT destroy the ammo, because a weapon might reference it. + item.Amount = 0; + } + else if (item.Amount > 1) + { + item.Amount /= 2; + } + } + else + { + // When set to lose ammo, you get to keep all your starting ammo. + // When set to halve ammo, you won't be left with less than your starting amount. + if (sv_cooploseammo) + { + item.Amount = defitem.Amount; + } + else if (item.Amount > 1) + { + item.Amount = MAX(item.Amount / 2, defitem.Amount); + } + } + } + } + + // Now destroy the default inventory this player is holding and move + // over the old player's remaining inventory. + DestroyAllInventory(); + ObtainInventory (oldplayer); + + player.ReadyWeapon = NULL; + PickNewWeapon (NULL); + } + + //---------------------------------------------------------------------------- // // PROC P_CheckFOV @@ -1110,7 +1311,7 @@ class PlayerPawn : Actor native } if (!--player.morphTics) { // Attempt to undo the chicken/pig - player.UndoPlayerMorph(player, MRF_UNDOBYTIMEOUT); + player.mo.UndoPlayerMorph(player, MRF_UNDOBYTIMEOUT); } } } @@ -1244,7 +1445,7 @@ class PlayerPawn : Actor native CheckUse(); CheckUndoMorph(); // Cycle psprites. - // Note that after this point the PlayerPawn may have changed due to getting unmorphed so 'self' is no longer safe to use. + // Note that after self point the PlayerPawn may have changed due to getting unmorphed so 'self' is no longer safe to use. player.mo.TickPSprites(); // Other Counters if (player.damagecount) player.damagecount--; @@ -1261,6 +1462,606 @@ class PlayerPawn : Actor native player.mo.CheckAirSupply(); } } + + //--------------------------------------------------------------------------- + // + // PROC P_BringUpWeapon + // + // Starts bringing the pending weapon up from the bottom of the screen. + // This is only called to start the rising, not throughout it. + // + //--------------------------------------------------------------------------- + + void BringUpWeapon () + { + if (player.PendingWeapon == WP_NOCHANGE) + { + if (player.ReadyWeapon != null) + { + player.GetPSprite(PSP_WEAPON).y = WEAPONTOP; + player.SetPsprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState()); + } + return; + } + + let weapon = player.PendingWeapon; + + // If the player has a tome of power, use self weapon's powered up + // version, if one is available. + if (weapon != null && + weapon.SisterWeapon && + weapon.SisterWeapon.bPowered_Up && + player.mo.FindInventory ('PowerWeaponLevel2', true)) + { + weapon = weapon.SisterWeapon; + } + + player.PendingWeapon = WP_NOCHANGE; + player.ReadyWeapon = weapon; + player.mo.weaponspecial = 0; + + if (weapon != null) + { + weapon.PlayUpSound(self); + player.refire = 0; + + player.GetPSprite(PSP_WEAPON).y = player.cheats & CF_INSTANTWEAPSWITCH? WEAPONTOP : WEAPONBOTTOM; + // make sure that the previous weapon's flash state is terminated. + // When coming here from a weapon drop it may still be active. + player.SetPsprite(PSP_FLASH, null); + player.SetPsprite(PSP_WEAPON, weapon.GetUpState()); + } + } + + //=========================================================================== + // + // APlayerPawn :: BestWeapon + // + // Returns the best weapon a player has, possibly restricted to a single + // type of ammo. + // + //=========================================================================== + + Weapon BestWeapon(Class ammotype) + { + Weapon bestMatch = NULL; + int bestOrder = int.max; + Inventory item; + bool tomed = !!FindInventory ('PowerWeaponLevel2', true); + + // Find the best weapon the player has. + for (item = Inv; item != NULL; item = item.Inv) + { + let weap = Weapon(item); + if (weap == null) + continue; + + // Don't select it if it's worse than what was already found. + if (weap.SelectionOrder > bestOrder) + continue; + + // Don't select it if its primary fire doesn't use the desired ammo. + if (ammotype != NULL && + (weap.Ammo1 == NULL || + weap.Ammo1.GetClass() != ammotype)) + continue; + + // Don't select it if the Tome is active and self isn't the powered-up version. + if (tomed && weap.SisterWeapon != NULL && weap.SisterWeapon.bPowered_Up) + continue; + + // Don't select it if it's powered-up and the Tome is not active. + if (!tomed && weap.bPowered_Up) + continue; + + // Don't select it if there isn't enough ammo to use its primary fire. + if (!(weap.bAMMO_OPTIONAL) && + !weap.CheckAmmo (Weapon.PrimaryFire, false)) + continue; + + // Don't select if if there isn't enough ammo as determined by the weapon's author. + if (weap.MinSelAmmo1 > 0 && (weap.Ammo1 == NULL || weap.Ammo1.Amount < weap.MinSelAmmo1)) + continue; + if (weap.MinSelAmmo2 > 0 && (weap.Ammo2 == NULL || weap.Ammo2.Amount < weap.MinSelAmmo2)) + continue; + + // This weapon is usable! + bestOrder = weap.SelectionOrder; + bestMatch = weap; + } + return bestMatch; + } + + + //--------------------------------------------------------------------------- + // + // PROC P_DropWeapon + // + // The player died, so put the weapon away. + // + //--------------------------------------------------------------------------- + + void DropWeapon () + { + let player = self.player; + if (player == null) + { + return; + } + // Since the weapon is dropping, stop blocking switching. + player.WeaponState &= ~WF_DISABLESWITCH; + Weapon weap = player.ReadyWeapon; + if ((weap != null) && (player.health > 0 || !weap.bNoDeathDeselect)) + { + player.SetPsprite(PSP_WEAPON, weap.GetDownState()); + } + } + + //=========================================================================== + // + // APlayerPawn :: PickNewWeapon + // + // Picks a new weapon for self player. Used mostly for running out of ammo, + // but it also works when an ACS script explicitly takes the ready weapon + // away or the player picks up some ammo they had previously run out of. + // + //=========================================================================== + + Weapon PickNewWeapon(Class ammotype) + { + Weapon best = BestWeapon (ammotype); + + if (best != NULL) + { + player.PendingWeapon = best; + if (player.ReadyWeapon != NULL) + { + DropWeapon(); + } + else if (player.PendingWeapon != WP_NOCHANGE) + { + BringUpWeapon (); + } + } + return best; + } + + //=========================================================================== + // + // APlayerPawn :: GiveDefaultInventory + // + //=========================================================================== + + virtual void GiveDefaultInventory () + { + let player = self.player; + if (player == NULL) return; + + // HexenArmor must always be the first item in the inventory because + // it provides player class based protection that should not affect + // any other protection item. + let myclass = GetClass(); + GiveInventoryType('HexenArmor'); + let harmor = HexenArmor(FindInventory('HexenArmor')); + + harmor.Slots[4] = self.HexenArmor[0]; + for (int i = 0; i < 4; ++i) + { + harmor.SlotsIncrement[i] = self.HexenArmor[i + 1]; + } + + // BasicArmor must come right after that. It should not affect any + // other protection item as well but needs to process the damage + // before the HexenArmor does. + let barmor = BasicArmor(Spawn('BasicArmor')); + barmor.BecomeItem (); + AddInventory (barmor); + + // Now add the items from the DECORATE definition + let di = GetDropItems(); + + while (di) + { + Class ti = di.Name; + if (ti) + { + let tinv = (class)(ti); + if (!tinv) + { + Console.Printf(TEXTCOLOR_ORANGE .. "%s is not an inventory item and cannot be given to a player as start item.\n", di.Name); + } + else + { + let item = FindInventory(tinv); + if (item != NULL) + { + item.Amount = clamp( + item.Amount + (di.Amount ? di.Amount : item.default.Amount), 0, item.MaxAmount); + } + else + { + item = Inventory(Spawn(ti)); + item.bIgnoreSkill = true; // no skill multipliers here + item.Amount = di.Amount; + let weap = Weapon(item); + if (weap) + { + // To allow better control any weapon is emptied of + // ammo before being given to the player. + weap.AmmoGive1 = weap.AmmoGive2 = 0; + } + bool res; + Actor check; + [res, check] = item.CallTryPickup(self); + if (check != self) + { + // Player was morphed. This is illegal at game start. + // This problem is only detectable when it's too late to do something about it... + ThrowAbortException("Cannot give morph item '%s' when starting a game!", di.Name); + } + else if (!res) + { + item.Destroy(); + item = NULL; + } + } + let weap = Weapon(item); + if (weap != NULL && weap.CheckAmmo(Weapon.EitherFire, false)) + { + player.ReadyWeapon = player.PendingWeapon = weap; + } + } + } + di = di.Next; + } + } + + //=========================================================================== + // + // + // + //=========================================================================== + + virtual int GetTeleportFreezeTime() + { + if (TeleportFreezeTime <= 0) return 0; + let item = inv; + while (item != null) + { + if (item.GetNoTeleportFreeze()) return 0; + item = item.inv; + } + return TeleportFreezeTime; + } + + //=========================================================================== + // + // FWeaponSlot :: PickWeapon + // + // Picks a weapon from this slot. If no weapon is selected in this slot, + // or the first weapon in this slot is selected, returns the last weapon. + // Otherwise, returns the previous weapon in this slot. This means + // precedence is given to the last weapon in the slot, which by convention + // is probably the strongest. Does not return weapons you have no ammo + // for or which you do not possess. + // + //=========================================================================== + + virtual Weapon PickWeapon(int slot, bool checkammo) + { + int i, j; + + let player = self.player; + int Size = player.weapons.SlotSize(slot); + // Does this slot even have any weapons? + if (Size == 0) + { + return player.ReadyWeapon; + } + let ReadyWeapon = player.ReadyWeapon; + if (ReadyWeapon != null) + { + for (i = 0; i < Size; i++) + { + let weapontype = player.weapons.GetWeapon(slot, i); + if (weapontype == ReadyWeapon.GetClass() || + (ReadyWeapon.bPOWERED_UP && ReadyWeapon.SisterWeapon != null && ReadyWeapon.SisterWeapon.GetClass() == weapontype)) + { + for (j = (i == 0 ? Size - 1 : i - 1); + j != i; + j = (j == 0 ? Size - 1 : j - 1)) + { + let weapontype2 = player.weapons.GetWeapon(slot, j); + let weap = Weapon(player.mo.FindInventory(weapontype2)); + + if (weap != null) + { + if (!checkammo || weap.CheckAmmo(Weapon.EitherFire, false)) + { + return weap; + } + } + } + } + } + } + for (i = Size - 1; i >= 0; i--) + { + let weapontype = player.weapons.GetWeapon(slot, i); + let weap = Weapon(player.mo.FindInventory(weapontype)); + + if (weap != null) + { + if (!checkammo || weap.CheckAmmo(Weapon.EitherFire, false)) + { + return weap; + } + } + } + return ReadyWeapon; + } + + //=========================================================================== + // + // FindMostRecentWeapon + // + // Locates the slot and index for the most recently selected weapon. If the + // player is in the process of switching to a new weapon, that is the most + // recently selected weapon. Otherwise, the current weapon is the most recent + // weapon. + // + //=========================================================================== + + bool, int, int FindMostRecentWeapon() + { + let player = self.player; + let ReadyWeapon = player.ReadyWeapon; + if (player.PendingWeapon != WP_NOCHANGE) + { + return player.weapons.LocateWeapon(player.PendingWeapon.GetClass()); + } + else if (ReadyWeapon != null) + { + bool found; + int slot; + int index; + [found, slot, index] = player.weapons.LocateWeapon(ReadyWeapon.GetClass()); + if (!found) + { + // If the current weapon wasn't found and is powered up, + // look for its non-powered up version. + if (ReadyWeapon.bPOWERED_UP && ReadyWeapon.SisterWeaponType != null) + { + return player.weapons.LocateWeapon(ReadyWeapon.SisterWeaponType); + } + return false, 0, 0; + } + return true, slot, index; + } + else + { + return false, 0, 0; + } + } + + //=========================================================================== + // + // FWeaponSlots :: PickNextWeapon + // + // Returns the "next" weapon for this player. If the current weapon is not + // in a slot, then it just returns that weapon, since there's nothing to + // consider it relative to. + // + //=========================================================================== + const NUM_WEAPON_SLOTS = 10; + + virtual Weapon PickNextWeapon() + { + let player = self.player; + bool found; + int startslot, startindex; + int slotschecked = 0; + + [found, startslot, startindex] = FindMostRecentWeapon(); + let ReadyWeapon = player.ReadyWeapon; + if (ReadyWeapon == null || found) + { + int slot; + int index; + + if (ReadyWeapon == null) + { + startslot = NUM_WEAPON_SLOTS - 1; + startindex = player.weapons.SlotSize(startslot) - 1; + } + + slot = startslot; + index = startindex; + do + { + if (++index >= player.weapons.SlotSize(slot)) + { + index = 0; + slotschecked++; + if (++slot >= NUM_WEAPON_SLOTS) + { + slot = 0; + } + } + let type = player.weapons.GetWeapon(slot, index); + let weap = Weapon(FindInventory(type)); + if (weap != null && weap.CheckAmmo(Weapon.EitherFire, false)) + { + return weap; + } + } while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS); + } + return ReadyWeapon; + } + + //=========================================================================== + // + // FWeaponSlots :: PickPrevWeapon + // + // Returns the "previous" weapon for this player. If the current weapon is + // not in a slot, then it just returns that weapon, since there's nothing to + // consider it relative to. + // + //=========================================================================== + + virtual Weapon PickPrevWeapon() + { + let player = self.player; + int startslot, startindex; + bool found; + int slotschecked = 0; + + [found, startslot, startindex] = FindMostRecentWeapon(); + if (player.ReadyWeapon == null || found) + { + int slot; + int index; + + if (player.ReadyWeapon == null) + { + startslot = 0; + startindex = 0; + } + + slot = startslot; + index = startindex; + do + { + if (--index < 0) + { + slotschecked++; + if (--slot < 0) + { + slot = NUM_WEAPON_SLOTS - 1; + } + index = player.weapons.SlotSize(slot) - 1; + } + let type = player.weapons.GetWeapon(slot, index); + let weap = Weapon(FindInventory(type)); + if (weap != null && weap.CheckAmmo(Weapon.EitherFire, false)) + { + return weap; + } + } while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS); + } + return player.ReadyWeapon; + } + + //============================================================================ + // + // P_BobWeapon + // + // [RH] Moved this out of A_WeaponReady so that the weapon can bob every + // tic and not just when A_WeaponReady is called. Not all weapons execute + // A_WeaponReady every tic, and it looks bad if they don't bob smoothly. + // + // [XA] Added new bob styles and exposed bob properties. Thanks, Ryan Cordell! + // [SP] Added new user option for bob speed + // + //============================================================================ + + virtual Vector2 BobWeapon (double ticfrac) + { + Vector2 p1, p2, r; + Vector2 result; + + float bobtarget; + + let player = self.player; + if (!player) return (0, 0); + let weapon = player.ReadyWeapon; + + if (weapon == null || weapon.bDontBob) + { + return (0, 0); + } + + // [XA] Get the current weapon's bob properties. + int bobstyle = weapon.BobStyle; + double BobSpeed = (weapon.BobSpeed * 128); + double Rangex = weapon.BobRangeX; + double Rangey = weapon.BobRangeY; + + for (int i = 0; i < 2; i++) + { + // Bob the weapon based on movement speed. ([SP] And user's bob speed setting) + double angle = (BobSpeed * player.GetWBobSpeed() * 35 / TICRATE*(level.time - 1 + i)) * (360. / 8192.); + + // [RH] Smooth transitions between bobbing and not-bobbing frames. + // This also fixes the bug where you can "stick" a weapon off-center by + // shooting it when it's at the peak of its swing. + bobtarget = double((player.WeaponState & WF_WEAPONBOBBING) ? player.bob : 0.); + if (curbob != bobtarget) + { + if (abs(bobtarget - curbob) <= 1) + { + curbob = bobtarget; + } + else + { + double zoom = MAX(1., abs(curbob - bobtarget) / 40); + if (curbob > bobtarget) + { + curbob -= zoom; + } + else + { + curbob += zoom; + } + } + } + + if (curbob != 0) + { + //[SP] Added in decorate player.viewbob checks + double bobx = (player.bob * Rangex * ViewBob); + double boby = (player.bob * Rangey * ViewBob); + switch (bobstyle) + { + case Bob_Normal: + r.X = bobx * cos(angle); + r.Y = boby * abs(sin(angle)); + break; + + case Bob_Inverse: + r.X = bobx*cos(angle); + r.Y = boby * (1. - abs(sin(angle))); + break; + + case Bob_Alpha: + r.X = bobx * sin(angle); + r.Y = boby * abs(sin(angle)); + break; + + case Bob_InverseAlpha: + r.X = bobx * sin(angle); + r.Y = boby * (1. - abs(sin(angle))); + break; + + case Bob_Smooth: + r.X = bobx*cos(angle); + r.Y = 0.5f * (boby * (1. - (cos(angle * 2)))); + break; + + case Bob_InverseSmooth: + r.X = bobx*cos(angle); + r.Y = 0.5f * (boby * (1. + (cos(angle * 2)))); + } + } + else + { + r = (0, 0); + } + if (i == 0) p1 = r; else p2 = r; + } + return p1 * (1. - ticfrac) + p2 * ticfrac; + } + //---------------------------------------------------------------------------- // @@ -1276,9 +2077,7 @@ class PlayerPawn : Actor native native void CheckEnvironment(); native void CheckUse(); native void CheckWeaponButtons(); - native Weapon BestWeapon(class ammotype); - native Weapon PickNewWeapon(class ammotype); - + native void MarkPlayerSounds(); } class PlayerChunk : PlayerPawn @@ -1355,6 +2154,13 @@ class PSprite : Object native play } } } + + void ResetInterpolation() + { + oldx = x; + oldy = y; + } + } enum EPlayerState @@ -1374,7 +2180,7 @@ enum EPlayerGender GENDER_OTHER } -struct PlayerInfo native play // this is what internally is known as player_t +struct PlayerInfo native play // self is what internally is known as player_t { // technically engine constants but the only part of the playsim using them is the player. const NOFIXEDCOLORMAP = -1; @@ -1434,7 +2240,7 @@ struct PlayerInfo native play // this is what internally is known as player_t native ClassMorphedPlayerClass; native int MorphStyle; native Class MorphExitFlash; - native Class PremorphWeapon; + native Weapon PremorphWeapon; native int chickenPeck; native int jumpTics; native bool onground; @@ -1466,9 +2272,6 @@ struct PlayerInfo native play // this is what internally is known as player_t native @UserCmd cmd; native readonly @UserCmd original_cmd; - - native bool MorphPlayer(playerinfo p, Class spawntype, int duration, int style, Class enter_flash = null, Class exit_flash = null); - native bool UndoPlayerMorph(playerinfo player, int unmorphflag = 0, bool force = false); native bool PoisonPlayer(Actor poisoner, Actor source, int poison); native void PoisonDamage(Actor source, int damage, bool playPainSound); native void SetPsprite(int id, State stat, bool pending = false); @@ -1477,8 +2280,6 @@ struct PlayerInfo native play // this is what internally is known as player_t native PSprite FindPSprite(int id) const; native void SetLogNumber (int text); native void SetLogText (String text); - native void DropWeapon(); - native void BringUpWeapon(); native bool Resurrect(); native String GetUserName() const; @@ -1492,10 +2293,39 @@ struct PlayerInfo native play // this is what internally is known as player_t native int GetTeam() const; native float GetAutoaim() const; native bool GetNoAutostartMap() const; + native double GetWBobSpeed() const; native void SetFOV(float fov); native bool GetClassicFlight() const; native clearscope bool HasWeaponsInSlot(int slot) const; + // The actual implementation is on PlayerPawn where it can be overridden. Use that directly in the future. + deprecated("3.7") bool MorphPlayer(playerinfo p, Class spawntype, int duration, int style, Class enter_flash = null, Class exit_flash = null) + { + if (mo != null) + { + return mo.MorphPlayer(p, spawntype, duration, style, enter_flash, exit_flash); + } + return false; + } + + // This somehow got its arguments mixed up. 'self' should have been the player to be unmorphed, not the activator + deprecated("3.7") bool UndoPlayerMorph(playerinfo player, int unmorphflag = 0, bool force = false) + { + if (player.mo != null) + { + return player.mo.UndoPlayerMorph(self, unmorphflag, force); + } + return false; + } + + deprecated("3.7") void DropWeapon() + { + if (mo != null) + { + mo.DropWeapon(); + } + } + bool IsTotallyFrozen() { return @@ -1538,6 +2368,11 @@ struct PlayerInfo native play // this is what internally is known as player_t return allfrags; } + double GetDeltaViewHeight() + { + return (mo.ViewHeight + crouchviewdelta - viewheight) / 8; + } + } struct PlayerClass native diff --git a/wadsrc/static/zscript/shared/player_cheat.txt b/wadsrc/static/zscript/shared/player_cheat.txt index 949c89a3c..8c10b5898 100644 --- a/wadsrc/static/zscript/shared/player_cheat.txt +++ b/wadsrc/static/zscript/shared/player_cheat.txt @@ -399,4 +399,55 @@ extend class PlayerPawn } return; } + + virtual String CheatMorph(class morphClass, bool quickundo) + { + let oldclass = GetClass(); + + // Set the standard morph style for the current game + int style = MRF_UNDOBYTOMEOFPOWER; + if (gameinfo.gametype == GAME_Hexen) style |= MRF_UNDOBYCHAOSDEVICE; + + if (player.morphTics) + { + if (UndoPlayerMorph (player)) + { + if (!quickundo && oldclass != morphclass && MorphPlayer (player, morphclass, 0, style)) + { + return StringTable.Localize("$TXT_STRANGER"); + } + return StringTable.Localize("$TXT_NOTSTRANGE"); + } + } + else if (MorphPlayer (player, morphclass, 0, style)) + { + return StringTable.Localize("$TXT_STRANGE"); + } + return ""; + } + + virtual void CheatTakeWeaps() + { + if (player.morphTics || health <= 0) + { + return; + } + // Do not mass-delete directly from the linked list. That can cause problems. + Array collect; + // Take away all weapons that are either non-wimpy or use ammo. + for(let item = Inv; item; item = item.Inv) + { + let weap = Weapon(item); + if (weap && (!weap.bWimpy_Weapon || weap.AmmoType1 != null)) + { + collect.Push(item); + } + } + // Destroy them in a second loop. We have to look out for indirect destructions here as will happen with powered up weapons. + for(int i = 0; i < collect.Size(); i++) + { + let item = collect[i]; + if (item) item.Destroy(); + } + } } diff --git a/wadsrc/static/zscript/shared/soundsequence.txt b/wadsrc/static/zscript/shared/soundsequence.txt index 727b66e38..6dd3d55f9 100644 --- a/wadsrc/static/zscript/shared/soundsequence.txt +++ b/wadsrc/static/zscript/shared/soundsequence.txt @@ -66,6 +66,14 @@ class AmbientSound : Actor native +NOSECTOR +DONTSPLASH } + + native void MarkAmbientSounds(); + + override void MarkPrecacheSounds() + { + Super.MarkPrecacheSounds(); + MarkAmbientSounds(); + } } class AmbientSoundNoGravity : AmbientSound