mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2025-02-17 17:11:19 +00:00
Merge remote-tracking branch 'remotes/origin/weapon_scriptification' into asmjit
# Conflicts: # src/g_inventory/a_pickups.cpp
This commit is contained in:
commit
a0c0e8bdfe
96 changed files with 4822 additions and 4753 deletions
|
@ -998,6 +998,7 @@ set (PCH_SOURCES
|
|||
s_sound.cpp
|
||||
serializer.cpp
|
||||
sc_man.cpp
|
||||
scriptutil.cpp
|
||||
st_stuff.cpp
|
||||
statistics.cpp
|
||||
stats.cpp
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
101
src/b_bot.cpp
101
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;i<sizeof(botinits)/sizeof(botinits[0]);i++)
|
||||
{
|
||||
const PClass *cls = PClass::FindClass(botinits[i].type);
|
||||
if (cls != NULL && cls->IsDescendantOf(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<countof(warnbotmissiles);i++)
|
||||
{
|
||||
|
|
29
src/b_bot.h
29
src/b_bot.h
|
@ -13,6 +13,7 @@
|
|||
#include "d_protocol.h"
|
||||
#include "r_defs.h"
|
||||
#include "a_pickups.h"
|
||||
#include "a_weapons.h"
|
||||
#include "stats.h"
|
||||
|
||||
#define FORWARDWALK 0x1900
|
||||
|
@ -78,6 +79,34 @@ struct botinfo_t
|
|||
int lastteam;
|
||||
};
|
||||
|
||||
struct BotInfoData
|
||||
{
|
||||
int MoveCombatDist = 0;
|
||||
int flags = 0;
|
||||
PClassActor *projectileType = nullptr;
|
||||
};
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
BIF_BOT_REACTION_SKILL_THING = 1,
|
||||
BIF_BOT_EXPLOSIVE = 2,
|
||||
BIF_BOT_BFG = 4,
|
||||
};
|
||||
|
||||
|
||||
using BotInfoMap = TMap<FName, BotInfoData>;
|
||||
|
||||
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
|
||||
{
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<AWeapon>(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<AInventory>(NAME_Ammo1);
|
||||
auto ammo2 = heldWeapon->PointerVar<AInventory>(NAME_Ammo2);
|
||||
if ((ammo1 == NULL || ammo1->Amount >= ammo1->MaxAmount) &&
|
||||
(ammo2 == NULL || ammo2->Amount >= ammo2->MaxAmount))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -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<AInventory>(NAME_Ammo1) : nullptr;
|
||||
auto ammo2 = weapon ? weapon->PointerVar<AInventory>(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -761,8 +761,8 @@ void SetDehParams(FState *state, int codepointer)
|
|||
|
||||
// Let's identify the codepointer we're dealing with.
|
||||
PFunction *sym;
|
||||
sym = dyn_cast<PFunction>(RUNTIME_CLASS(AWeapon)->FindSymbol(FName(MBFCodePointers[codepointer].name), true));
|
||||
if (sym == NULL) return;
|
||||
sym = dyn_cast<PFunction>(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<PClassActor>(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<PClassActor>(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<PClassActor>(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 <FDecalTemplate *>(decal);
|
||||
if (info) info->DecalGenerator = const_cast <FDecalTemplate *>(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<PClassActor>(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<PFunction>(RUNTIME_CLASS(AWeapon)->FindSymbol(symname, true));
|
||||
PFunction *sym = dyn_cast<PFunction>(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<PFunction>(RUNTIME_CLASS(AWeapon)->FindSymbol(name, true));
|
||||
PFunction *sym = dyn_cast<PFunction>(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<PFunction>(RUNTIME_CLASS(AWeapon)->FindSymbol(AmmoPerAttacks[j].func, true));
|
||||
auto p = dyn_cast<PFunction>(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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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<DPSprite*> 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<AWeapon*> PremorphWeapon = nullptr; // ready weapon before morphing
|
||||
PClassActor *MorphExitFlash = nullptr; // flash to apply when demorphing (cache of value given to MorphPlayer)
|
||||
TObjPtr<AInventory*> 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
|
||||
|
|
|
@ -426,6 +426,8 @@ template<class T> T *dyn_cast(DObject *p)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<class T> const T *dyn_cast(const DObject *p)
|
||||
{
|
||||
return dyn_cast<T>(const_cast<DObject *>(p));
|
||||
|
|
|
@ -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<DObject>();
|
||||
WP_NOCHANGE = (AInventory*)Create<DObject>();
|
||||
WP_NOCHANGE->Release();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<AWeapon *> (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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<AInventory>(NAME_SisterWeapon))
|
||||
{
|
||||
// Unselect powered up weapons if the unpowered counterpart is pending
|
||||
p->ReadyWeapon=p->PendingWeapon;
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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<PClassActor *, int> 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<APlayerPawn *>(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<APlayerPawn *>(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<AWeapon *> (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<AWeapon *> (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<AWeapon *>(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<AWeapon *>(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
|
||||
|
|
|
@ -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<AInventory*> Ammo1, Ammo2;
|
||||
TObjPtr<AWeapon*> 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
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -175,6 +175,7 @@ struct FLevelLocals
|
|||
int outsidefogdensity;
|
||||
int skyfog;
|
||||
|
||||
FName deathsequence;
|
||||
float pixelstretch;
|
||||
float MusicVolume;
|
||||
|
||||
|
|
|
@ -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<APlayerPawn *>(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<APlayerPawn *>(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<AWeapon *>(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<AMorphedMonster *>(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<AMorphedMonster *>(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);
|
||||
}
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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<AActor*> UnmorphedMe;
|
||||
int UnmorphTime, MorphStyle;
|
||||
PClassActor *MorphExitFlash;
|
||||
ActorFlags FlagsSave;
|
||||
};
|
||||
|
||||
#endif //__A_SHAREDGLOBAL_H__
|
||||
|
|
|
@ -507,12 +507,12 @@ static int DrawKeys(player_t * CPlayer, int x, int y)
|
|||
//---------------------------------------------------------------------------
|
||||
static TArray<PClassActor *> 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<PClassActor>(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<PClassActor>(NAME_AmmoType1) || type==wi->PointerVar<PClassActor>(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<AInventory>(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<AWeapon*>(inv)->GetClass(), NULL, NULL))
|
||||
!CPlayer->weapons.LocateWeapon(inv->GetClass(), NULL, NULL))
|
||||
{
|
||||
DrawOneWeapon(CPlayer, x, y, static_cast<AWeapon*>(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<AWeapon*>(inv));
|
||||
DrawOneWeapon(CPlayer, x, y, inv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<AInventory>(NAME_Ammo1);
|
||||
ammo2 = CPlayer->ReadyWeapon->PointerVar<AInventory>(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<PClassActor>(no == 1 ? NAME_AmmoType1 : NAME_AmmoType2));
|
||||
}
|
||||
|
||||
AInventory *ammo1, *ammo2;
|
||||
int ammocount1, ammocount2;
|
||||
AInventory *armor;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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] =
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
23
src/info.cpp
23
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
|
||||
|
|
|
@ -872,7 +872,6 @@ void F_StartIntermission(FName seq, uint8_t state)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Called by main loop.
|
||||
|
|
|
@ -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<AWeapon *> (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<AWeapon *> (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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
163
src/p_acs.cpp
163
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<AWeapon *> (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<AMorphedMonster *>(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<AMorphedMonster *>(actor);
|
||||
if (P_UndoMonsterMorph(morphed_actor, force))
|
||||
{
|
||||
changes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
changes += P_UnmorphActor(activator, actor, 0, force);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
class FFont;
|
||||
class FileReader;
|
||||
struct line_t;
|
||||
class FSerializer;
|
||||
|
||||
|
||||
enum
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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<AWeapon*>(item)->AmmoGive1 = 40;
|
||||
item->IntVar(NAME_AmmoGive1) = 40;
|
||||
}
|
||||
item->flags |= MF_DROPPED;
|
||||
if (!item->CallTryPickup(player->mo))
|
||||
|
|
190
src/p_enemy.cpp
190
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<AWeapon *>(inv)->AmmoGive1 = int(static_cast<AWeapon *>(inv)->AmmoGive1 * dropammofactor);
|
||||
static_cast<AWeapon *>(inv)->AmmoGive2 = int(static_cast<AWeapon *>(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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
336
src/p_pspr.cpp
336
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<APlayerPawn *>(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;
|
||||
|
|
|
@ -92,6 +92,7 @@ public:
|
|||
void ResetInterpolation() { oldx = x; oldy = y; }
|
||||
void OnDestroy() override;
|
||||
std::pair<FRenderStyle, float> 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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
484
src/p_user.cpp
484
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<AWeapon *> (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<int>(
|
||||
item->Amount + (di->Amount ? di->Amount : ((AInventory *)item->GetDefault())->Amount),
|
||||
0, item->MaxAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = static_cast<AInventory *>(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<AWeapon*>(item)->AmmoGive1 =
|
||||
static_cast<AWeapon*>(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<AWeapon*>(item)->CheckAmmo(AWeapon::EitherFire, false))
|
||||
{
|
||||
player->ReadyWeapon = player->PendingWeapon = static_cast<AWeapon *> (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<AWeapon *>(player->mo->FindInventory (morphweapon));
|
||||
if (player->ReadyWeapon == nullptr)
|
||||
{
|
||||
player->ReadyWeapon = static_cast<AWeapon *>(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<AActor>()->SpawnState)
|
||||
{
|
||||
item = P_DropItem (this, weap->GetClass(), -1, 256);
|
||||
if (item != NULL && item->IsKindOf(NAME_Weapon))
|
||||
{
|
||||
if (weap->AmmoGive1 && weap->Ammo1)
|
||||
{
|
||||
static_cast<AWeapon *>(item)->AmmoGive1 = weap->Ammo1->Amount;
|
||||
}
|
||||
if (weap->AmmoGive2 && weap->Ammo2)
|
||||
{
|
||||
static_cast<AWeapon *>(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)
|
||||
|
|
|
@ -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<AWeapon>(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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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'")
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1187,7 +1187,7 @@ static void ParseActor(FScanner &sc, PNamespace *ns)
|
|||
}
|
||||
try
|
||||
{
|
||||
GetDefaultByType(info)->Finalize(bag.statedef);
|
||||
FinalizeClass(info, bag.statedef);
|
||||
}
|
||||
catch (CRecoverableError &err)
|
||||
{
|
||||
|
|
|
@ -195,6 +195,29 @@ PProperty::PProperty(FName name, TArray<PField *> &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;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<FState *, FScriptPosition> 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<PClassActor*>(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<FState *, bool> 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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<PPropFlag>(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<PPropFlag>(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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -175,6 +175,10 @@ void ZCCCompiler::ProcessClass(ZCC_Class *cnode, PSymbolTreeNode *treenode)
|
|||
cls->Properties.Push(static_cast<ZCC_Property *>(node));
|
||||
break;
|
||||
|
||||
case AST_FlagDef:
|
||||
cls->FlagDefs.Push(static_cast<ZCC_FlagDef*>(node));
|
||||
break;
|
||||
|
||||
case AST_VarDeclarator:
|
||||
cls->Fields.Push(static_cast<ZCC_VarDeclarator *>(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<ZCC_Property *> &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<PProperty>(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<PProperty>(qualifiedname, fields)))
|
||||
bool ZCCCompiler::CompileFlagDefs(PClass *type, TArray<ZCC_FlagDef *> &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<PField>(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<PPropFlag>(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)
|
||||
{
|
||||
|
|
|
@ -54,6 +54,7 @@ struct ZCC_ClassWork : public ZCC_StructWork
|
|||
TArray<ZCC_Default *> Defaults;
|
||||
TArray<ZCC_States *> States;
|
||||
TArray<ZCC_Property *> Properties;
|
||||
TArray<ZCC_FlagDef *> FlagDefs;
|
||||
|
||||
ZCC_ClassWork(ZCC_Class * s, PSymbolTreeNode *n)
|
||||
{
|
||||
|
@ -110,6 +111,7 @@ private:
|
|||
bool CompileFields(PContainerType *type, TArray<ZCC_VarDeclarator *> &Fields, PClass *Outer, PSymbolTable *TreeNodes, bool forstruct, bool hasnativechildren = false);
|
||||
void CompileAllProperties();
|
||||
bool CompileProperties(PClass *type, TArray<ZCC_Property *> &Properties, FName prefix);
|
||||
bool CompileFlagDefs(PClass *type, TArray<ZCC_FlagDef *> &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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
104
src/scriptutil.cpp
Normal file
104
src/scriptutil.cpp
Normal file
|
@ -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<VMValue> parameters;
|
||||
static TMap<FName, VMFunction*> 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;
|
||||
}
|
||||
}
|
27
src/scriptutil.h
Normal file
27
src/scriptutil.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
#include <stdarg.h>
|
||||
#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, ...);
|
||||
};
|
|
@ -295,18 +295,7 @@ namespace swrenderer
|
|||
viewheight == viewport->RenderTarget->GetHeight() ||
|
||||
(viewport->RenderTarget->GetWidth() > (BASEXCENTER * 2))))
|
||||
{ // Adjust PSprite for fullscreen views
|
||||
AWeapon *weapon = dyn_cast<AWeapon>(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.
|
||||
|
|
|
@ -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"
|
||||
|
|
37
wadsrc/static/botsupp.txt
Normal file
37
wadsrc/static/botsupp.txt
Normal file
|
@ -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
|
|
@ -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"
|
||||
|
|
|
@ -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<Inventory> type, int amount, bool givecheat = false);
|
||||
native bool SetInventory(class<Inventory> itemclass, int amount, bool beyondMax = false);
|
||||
native bool TakeInventory(class<Inventory> itemclass, int amount, bool fromdecorate = false, bool notakeinfinite = false);
|
||||
|
@ -740,7 +755,7 @@ class Actor : Thinker native
|
|||
native bool GiveAmmo (Class<Ammo> type, int amount);
|
||||
native bool UsePuzzleItem(int PuzzleItemType);
|
||||
native float AccuracyFactor();
|
||||
native bool MorphMonster (Class<Actor> spawntype, int duration, int style, Class<Actor> enter_flash, Class<Actor> 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<Actor> 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<Actor> MissileType = MissileName;
|
||||
DoAttack(false, true, 0, 0, MissileType, MissileHeight);
|
||||
}
|
||||
|
||||
deprecated("2.3") void A_ComboAttack()
|
||||
{
|
||||
Class<Actor> MissileType = MissileName;
|
||||
DoAttack(true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight);
|
||||
}
|
||||
|
||||
void A_BasicAttack(int melee_damage, sound melee_sound, class<actor> 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<PlayerChunk> 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<Actor> 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<Actor> pufftype = "BulletPuff", double range = 0, int flags = 0, int ptr = AAPTR_TARGET, class<Actor> 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<Actor> 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<Actor> spawnclass = null, double spawnofs_z = 0, int spiraloffset = 270, int limit = 0, double veleffect = 3);
|
||||
native bool A_SetInventory(class<Inventory> itemtype, int amount, int ptr = AAPTR_DEFAULT, bool beyondMax = false);
|
||||
native bool A_GiveInventory(class<Inventory> itemtype, int amount = 0, int giveto = AAPTR_DEFAULT);
|
||||
native bool A_TakeInventory(class<Inventory> itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT);
|
||||
action native bool, Actor A_SpawnItem(class<Actor> itemtype = "Unknown", double distance = 0, double zheight = 0, bool useammo = true, bool transfer_translation = false);
|
||||
native bool, Actor A_SpawnItemEx(class<Actor> 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<Actor> 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<Actor> type, int duration = 0, int flags = 0, class<Actor> enter_flash = null, class<Actor> exit_flash = null);
|
||||
|
||||
|
||||
action native state, bool A_Teleport(statelabel teleportstate = null, class<SpecialSpot> targettype = "BossSpot", class<Actor> 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<Actor> missiletype, double spawnheight, int damage, sound meleesound = "", name damagetype = "none", bool bleed = true);
|
||||
native void A_Burst(class<Actor> 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<Actor> 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<Actor> 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<Actor> item, int dropamount = -1, int chance = 256);
|
||||
native Actor A_DropItem(class<Actor> item, int dropamount = -1, int chance = 256);
|
||||
native void A_DamageSelf(int amount, name damagetype = "none", int flags = 0, class<Actor> 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<Actor> 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<Actor> 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:
|
||||
|
|
741
wadsrc/static/zscript/actor_attacks.txt
Normal file
741
wadsrc/static/zscript/actor_attacks.txt
Normal file
|
@ -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<Actor> pufftype = "BulletPuff", double range = 0, int flags = 0, int ptr = AAPTR_TARGET, class<Actor> 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<Actor> 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<Actor> 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<Actor> 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<Actor> 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<Actor> 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<Actor> MissileType = MissileName;
|
||||
DoAttack(false, true, 0, 0, MissileType, MissileHeight);
|
||||
}
|
||||
|
||||
deprecated("2.3") void A_ComboAttack()
|
||||
{
|
||||
Class<Actor> MissileType = MissileName;
|
||||
DoAttack(true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight);
|
||||
}
|
||||
|
||||
void A_BasicAttack(int melee_damage, sound melee_sound, class<actor> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
79
wadsrc/static/zscript/actor_interaction.txt
Normal file
79
wadsrc/static/zscript/actor_interaction.txt
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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<Actor> puff;
|
||||
native double angleoffset;
|
||||
native double pitchoffset;
|
||||
native double distance;
|
||||
native int duration;
|
||||
native double sparsity;
|
||||
native double drift;
|
||||
native Class<Actor> spawnclass;
|
||||
native int SpiralOffset;
|
||||
native int limit;
|
||||
}; // [RH] Shoot a railgun
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -73,7 +73,6 @@ class PhoenixRodPowered : PhoenixRod
|
|||
Default
|
||||
{
|
||||
+WEAPON.POWERED_UP
|
||||
+WEAPON.MELEEWEAPON
|
||||
Weapon.SisterWeapon "PhoenixRod";
|
||||
Weapon.AmmoGive 0;
|
||||
Tag "$TAG_PHOENIXRODP";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Actor> pufftype = "BulletPuff", double range = 0, double lifesteal = 0, int lifestealmax = 0, class<BasicArmorBonus> armorbonustype = "ArmorBonus", sound MeleeSound = 0, sound MissSound = "");
|
||||
action native void A_FireBullets(double spread_xy, double spread_z, int numbullets, int damageperbullet, class<Actor> pufftype = "BulletPuff", int flags = 1, double range = 0, class<Actor> missile = null, double Spawnheight = 32, double Spawnofs_xy = 0);
|
||||
action native Actor A_FireProjectile(class<Actor> 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<Actor> 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<Actor> 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<Actor> pufftype = "BulletPuff", int flags = 1, double range = 0, class<Actor> 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<Actor> 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<Actor> pufftype = "BulletPuff", double range = 0, double lifesteal = 0, int lifestealmax = 0, class<BasicArmorBonus> 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<Actor> 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<Actor> 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);
|
||||
|
||||
//===========================================================================
|
||||
|
|
|
@ -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<Ammo> 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<Weapon> SisterWeaponType; // Another weapon to pick up with self one
|
||||
native class<Actor> ProjectileType; // Projectile used by primary attack
|
||||
native class<Actor> 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<Ammo> 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<Weapon> 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<Weapon> weap);
|
||||
native static void SetupWeaponSlots(PlayerPawn pp);
|
||||
native class<Weapon> GetWeapon(int slot, int index);
|
||||
native int SlotSize(int slot);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
151
wadsrc/static/zscript/scriptutil/scriptutil.txt
Normal file
151
wadsrc/static/zscript/scriptutil/scriptutil.txt
Normal file
|
@ -0,0 +1,151 @@
|
|||
|
||||
// Container for utility functions used by ACS and FraggleScript.
|
||||
|
||||
class ScriptUtil play
|
||||
{
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static int SetWeapon(Actor activator, class<Inventory> 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<Actor> 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<Actor> type, int newmaxamount = int.min, int newbpmaxamount = int.min)
|
||||
{
|
||||
if (activator == null) return 0;
|
||||
let ammotype = (class<Ammo>)(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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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<PlayerPawn> playerclass, class<MorphedMonster> monsterclass, int duration = 0, int style = 0, class<Actor> morphflash = null, class<Actor>unmorphflash = 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<Actor> type, int duration = 0, int style = 0, class<Actor> morphflash = null, class<Actor>unmorphflash = null)
|
||||
{
|
||||
if (self.player != null)
|
||||
{
|
||||
let playerclass = (class<PlayerPawn>)(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<Actor> spawntype, int duration, int style, Class<Actor> enter_flash, Class<Actor> 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<Actor>)("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<Actor>)("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<Weapon> 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<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> 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<Actor> 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<Actor> MorphExitFlash;
|
||||
Actor UnmorphedMe;
|
||||
int UnmorphTime, MorphStyle;
|
||||
Class<Actor> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Actor> 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<Ammo> 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<Ammo> 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<Actor> ti = di.Name;
|
||||
if (ti)
|
||||
{
|
||||
let tinv = (class<Inventory>)(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<Ammo> ammotype);
|
||||
native Weapon PickNewWeapon(class<Ammo> 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 Class<PlayerPawn>MorphedPlayerClass;
|
||||
native int MorphStyle;
|
||||
native Class<Actor> MorphExitFlash;
|
||||
native Class<Weapon> 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<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> 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<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> 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
|
||||
|
|
|
@ -399,4 +399,55 @@ extend class PlayerPawn
|
|||
}
|
||||
return;
|
||||
}
|
||||
|
||||
virtual String CheatMorph(class<PlayerPawn> 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<Inventory> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,14 @@ class AmbientSound : Actor native
|
|||
+NOSECTOR
|
||||
+DONTSPLASH
|
||||
}
|
||||
|
||||
native void MarkAmbientSounds();
|
||||
|
||||
override void MarkPrecacheSounds()
|
||||
{
|
||||
Super.MarkPrecacheSounds();
|
||||
MarkAmbientSounds();
|
||||
}
|
||||
}
|
||||
|
||||
class AmbientSoundNoGravity : AmbientSound
|
||||
|
|
Loading…
Reference in a new issue