Merge remote-tracking branch 'remotes/origin/weapon_scriptification' into asmjit

# Conflicts:
#	src/g_inventory/a_pickups.cpp
This commit is contained in:
Christoph Oelckers 2018-11-30 21:28:44 +01:00
commit a0c0e8bdfe
96 changed files with 4822 additions and 4753 deletions

View file

@ -998,6 +998,7 @@ set (PCH_SOURCES
s_sound.cpp
serializer.cpp
sc_man.cpp
scriptutil.cpp
st_stuff.cpp
statistics.cpp
stats.cpp

View file

@ -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);

View file

@ -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++)
{

View file

@ -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
{

View file

@ -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))

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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());
}
}
}

View file

@ -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;
}
}

View file

@ -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)
{

View file

@ -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

View file

@ -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));

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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__

View file

@ -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, &param, 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

View file

@ -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
};

View file

@ -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;
}

View file

@ -175,6 +175,7 @@ struct FLevelLocals
int outsidefogdensity;
int skyfog;
FName deathsequence;
float pixelstretch;
float MusicVolume;

View file

@ -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);
}

View file

@ -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__

View file

@ -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__

View file

@ -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);
}
}
}

View file

@ -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();

View file

@ -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;

View file

@ -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.

View file

@ -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)
{

View file

@ -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] =
{

View file

@ -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);

View file

@ -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

View file

@ -872,7 +872,6 @@ void F_StartIntermission(FName seq, uint8_t state)
}
}
//==========================================================================
//
// Called by main loop.

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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);
}
}

View file

@ -43,6 +43,7 @@
class FFont;
class FileReader;
struct line_t;
class FSerializer;
enum

File diff suppressed because it is too large Load diff

View file

@ -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))

View file

@ -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;

View file

@ -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);

View file

@ -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, &param, 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);
}
}

View file

@ -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);

View file

@ -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

View file

@ -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;

View file

@ -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, &param, 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;

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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, &param, 1, &ret, 1);
}
thing->reactiontime = time;
}
if (thing->flags & MF_MISSILE)
{

View file

@ -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, &param, 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)

View file

@ -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.

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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); }

View file

@ -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'")

View file

@ -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();

View file

@ -1187,7 +1187,7 @@ static void ParseActor(FScanner &sc, PNamespace *ns)
}
try
{
GetDefaultByType(info)->Finalize(bag.statedef);
FinalizeClass(info, bag.statedef);
}
catch (CRecoverableError &err)
{

View file

@ -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;
}
//==========================================================================
//
//

View file

@ -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();

View file

@ -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.

View file

@ -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);

View file

@ -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)
{

View file

@ -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

View file

@ -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);

View file

@ -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)

View file

@ -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);

View file

@ -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)
{

View file

@ -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);

View file

@ -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);

View file

@ -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
View 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, &parameters[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
View 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, ...);
};

View file

@ -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.

View file

@ -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
View 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

View file

@ -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"

View file

@ -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:

View 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);
}
}
}
}
}

View 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);
}
}
}
}

View file

@ -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

View file

@ -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
};

View file

@ -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))
{

View file

@ -73,7 +73,6 @@ class PhoenixRodPowered : PhoenixRod
Default
{
+WEAPON.POWERED_UP
+WEAPON.MELEEWEAPON
Weapon.SisterWeapon "PhoenixRod";
Weapon.AmmoGive 0;
Tag "$TAG_PHOENIXRODP";

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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;
}
}
//===========================================================================

View file

@ -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;
}

View file

@ -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);
//===========================================================================

View file

@ -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);
}

View file

@ -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;
}

View 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;
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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();
}
}
}

View file

@ -66,6 +66,14 @@ class AmbientSound : Actor native
+NOSECTOR
+DONTSPLASH
}
native void MarkAmbientSounds();
override void MarkPrecacheSounds()
{
Super.MarkPrecacheSounds();
MarkAmbientSounds();
}
}
class AmbientSoundNoGravity : AmbientSound