gzdoom/src/g_shared/a_pickups.cpp

1877 lines
48 KiB
C++
Raw Normal View History

#include <assert.h>
#include "info.h"
#include "m_random.h"
#include "p_local.h"
#include "s_sound.h"
#include "gi.h"
#include "p_lnspec.h"
#include "sbar.h"
#include "statnums.h"
#include "c_dispatch.h"
#include "gstrings.h"
#include "templates.h"
May 6, 2006 (Changes by Graf Zahl) - Converted a_zombie.cpp and most of a_strifestuff.cpp to DECORATE. - Converted a_strifekeys.cpp to DECORATE and moved the pickup messages to the string table. - Removed the WIF_HITS_GHOSTS weapon flag and replaced it with MF2_THRUGHOST. There is no need to keep two flags around with virtually the same meaning. - Changed the ShadowArmor to use the VISIBILITYPULSE flag to change its translucency. It looks much better now than the cheap code pointer based blinking it used before. - Converted most of a_strifeitems.cpp to DECORATE and moved the pickup messages to the string table. - Converted a_strifearmor.cpp to DECORATE and moved the pickup messages to the string table. - Moved the messages for killing spectres to the string table. - Converted the quest items to DECORATE. Also changed A_GiveQuestItem to get the messages it prints from the string table instead of the quest item's tag string. May 5, 2006 (Changes by Graf Zahl) - Removed the hopelessly outdated thingdef_doc.txt file from the repository. - Converted a_peasant.cpp and a_ratbuddy.cpp to DECORATE. - Fixed: C_DoKey didn't treat an empty string as 'no binding' when checking for valid double bindings. - Converted a_merchants.cpp to DECORATE. - Added MF5_NODAMAGE flag to generalize the behavior of Strife's merchants which can be shot but take no damage from getting hurt. - Converted a_beggars.cpp to DECORATE. - Added an Inventory.GiveQuest property. This makes it possible to define all of Strife's original items that also give a quest item in DECORATE but it is also useful to define items like the ones in Day of the Acolyte without ugly workarounds. - Added a Tag property and Strife teaser conversation IDs to DECORATE so now it is possible to define many of Strife's items. - Added a FastSpeed property to DECORATE so that projectiles can finally be assigned a higher speed for fast mode. - Added a ACS_LockedExecuteDoor special. It is basically the same as the existing ACS_LockedExecute but it uses the 'door' message instead of 'remote'. This cannot be integrated into ACS_LockedExecute because all its arguments are already in use. - Added a fully customizable A_CustomMeleeAttack function for DECORATE. SVN r83 (trunk)
2006-05-07 00:27:22 +00:00
#include "a_strifeglobal.h"
#include "a_morph.h"
#include "a_specialspot.h"
#include "thingdef/thingdef.h"
#include "g_level.h"
#include "g_game.h"
#include "doomstat.h"
#include "farchive.h"
static FRandom pr_restore ("RestorePos");
IMPLEMENT_CLASS (AAmmo)
//===========================================================================
//
// AAmmo :: Serialize
//
//===========================================================================
void AAmmo::Serialize (FArchive &arc)
{
Super::Serialize (arc);
2006-04-11 16:27:41 +00:00
arc << BackpackAmount << BackpackMaxAmount;
}
//===========================================================================
//
// AAmmo :: GetParentAmmo
//
// Returns the least-derived ammo type that this ammo is a descendant of.
// That is, if this ammo is an immediate subclass of Ammo, then this ammo's
// type is returned. If this ammo's superclass is not Ammo, then this
// function travels up the inheritance chain until it finds a type that is
// an immediate subclass of Ammo and returns that.
//
// The intent of this is that all unique ammo types will be immediate
// subclasses of Ammo. To make different pickups with different ammo amounts,
// you subclass the type of ammo you want a different amount for and edit
// that.
//
//===========================================================================
const PClass *AAmmo::GetParentAmmo () const
{
const PClass *type = GetClass ();
while (type->ParentClass != RUNTIME_CLASS(AAmmo) && type->ParentClass != NULL)
{
type = type->ParentClass;
}
return type;
}
//===========================================================================
//
// AAmmo :: HandlePickup
//
//===========================================================================
EXTERN_CVAR(Bool, sv_unlimited_pickup)
bool AAmmo::HandlePickup (AInventory *item)
{
if (GetClass() == item->GetClass() ||
(item->IsKindOf (RUNTIME_CLASS(AAmmo)) && static_cast<AAmmo*>(item)->GetParentAmmo() == GetClass()))
{
if (Amount < MaxAmount || sv_unlimited_pickup)
{
int receiving = item->Amount;
if (!(item->ItemFlags & IF_IGNORESKILL))
{ // extra ammo in baby mode and nightmare mode
receiving = FixedMul(receiving, G_SkillProperty(SKILLP_AmmoFactor));
}
int oldamount = Amount;
if (Amount > 0 && Amount + receiving < 0)
{
Amount = 0x7fffffff;
}
else
{
Amount += receiving;
}
if (Amount > MaxAmount && !sv_unlimited_pickup)
{
Amount = MaxAmount;
}
item->ItemFlags |= IF_PICKUPGOOD;
// If the player previously had this ammo but ran out, possibly switch
// to a weapon that uses it, but only if the player doesn't already
// have a weapon pending.
assert (Owner != NULL);
if (oldamount == 0 && Owner != NULL && Owner->player != NULL)
{
barrier_cast<APlayerPawn *>(Owner)->CheckWeaponSwitch(GetClass());
}
}
return true;
}
if (Inventory != NULL)
{
return Inventory->HandlePickup (item);
}
return false;
}
//===========================================================================
//
// AAmmo :: CreateCopy
//
//===========================================================================
AInventory *AAmmo::CreateCopy (AActor *other)
{
AInventory *copy;
int amount = Amount;
// extra ammo in baby mode and nightmare mode
if (!(ItemFlags&IF_IGNORESKILL))
{
amount = FixedMul(amount, G_SkillProperty(SKILLP_AmmoFactor));
}
if (GetClass()->ParentClass != RUNTIME_CLASS(AAmmo) && GetClass() != RUNTIME_CLASS(AAmmo))
{
const PClass *type = GetParentAmmo();
assert (type->ActorInfo != NULL);
if (!GoAway ())
{
Destroy ();
}
2006-07-16 09:10:45 +00:00
copy = static_cast<AInventory *>(Spawn (type, 0, 0, 0, NO_REPLACE));
copy->Amount = amount;
copy->BecomeItem ();
}
else
{
copy = Super::CreateCopy (other);
copy->Amount = amount;
}
if (copy->Amount > copy->MaxAmount)
{ // Don't pick up more ammo than you're supposed to be able to carry.
copy->Amount = copy->MaxAmount;
}
return copy;
}
//===========================================================================
//
// AAmmo :: CreateTossable
//
//===========================================================================
AInventory *AAmmo::CreateTossable()
{
AInventory *copy = Super::CreateTossable();
if (copy != NULL)
{ // Do not increase ammo by dropping it and picking it back up at
// certain skill levels.
copy->ItemFlags |= IF_IGNORESKILL;
}
return copy;
}
//---------------------------------------------------------------------------
//
// FUNC P_GiveBody
//
// Returns false if the body isn't needed at all.
//
//---------------------------------------------------------------------------
bool P_GiveBody (AActor *actor, int num, int max)
{
if (actor->health <= 0 || (actor->player != NULL && actor->player->playerstate == PST_DEAD))
{ // Do not heal dead things.
return false;
}
player_t *player = actor->player;
num = clamp(num, -65536, 65536); // prevent overflows for bad values
if (player != NULL)
{
// Max is 0 by default, preserving default behavior for P_GiveBody()
// calls while supporting AHealth.
if (max <= 0)
{
max = static_cast<APlayerPawn*>(actor)->GetMaxHealth() + player->mo->stamina;
// [MH] First step in predictable generic morph effects
if (player->morphTics)
{
if (player->MorphStyle & MORPH_FULLHEALTH)
{
if (!(player->MorphStyle & MORPH_ADDSTAMINA))
{
max -= player->mo->stamina;
}
}
else // old health behaviour
{
max = MAXMORPHHEALTH;
if (player->MorphStyle & MORPH_ADDSTAMINA)
{
max += player->mo->stamina;
}
}
}
}
// [RH] For Strife: A negative body sets you up with a percentage
// of your full health.
if (num < 0)
{
num = max * -num / 100;
if (player->health < num)
{
player->health = num;
actor->health = num;
return true;
}
}
else
{
if (player->health < max)
{
player->health += num;
if (player->health > max)
{
player->health = max;
}
actor->health = player->health;
return true;
}
}
}
else
{
// Parameter value for max is ignored on monsters, preserving original
// behaviour on AHealth as well as on existing calls to P_GiveBody().
max = actor->SpawnHealth();
if (num < 0)
{
num = max * -num / 100;
if (actor->health < num)
{
actor->health = num;
return true;
}
}
else if (actor->health < max)
{
actor->health += num;
if (actor->health > max)
{
actor->health = max;
}
return true;
}
}
return false;
}
//---------------------------------------------------------------------------
//
// PROC A_RestoreSpecialThing1
//
// Make a special thing visible again.
//
//---------------------------------------------------------------------------
DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialThing1)
{
self->renderflags &= ~RF_INVISIBLE;
if (static_cast<AInventory *>(self)->DoRespawn ())
{
S_Sound (self, CHAN_VOICE, "misc/spawn", 1, ATTN_IDLE);
}
}
//---------------------------------------------------------------------------
//
// PROC A_RestoreSpecialThing2
//
//---------------------------------------------------------------------------
DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialThing2)
{
self->flags |= MF_SPECIAL;
if (!(self->GetDefault()->flags & MF_NOGRAVITY))
{
self->flags &= ~MF_NOGRAVITY;
}
self->SetState (self->SpawnState);
}
//---------------------------------------------------------------------------
//
// PROC A_RestoreSpecialDoomThing
//
//---------------------------------------------------------------------------
DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialDoomThing)
{
self->renderflags &= ~RF_INVISIBLE;
self->flags |= MF_SPECIAL;
if (!(self->GetDefault()->flags & MF_NOGRAVITY))
{
self->flags &= ~MF_NOGRAVITY;
}
if (static_cast<AInventory *>(self)->DoRespawn ())
{
self->SetState (self->SpawnState);
S_Sound (self, CHAN_VOICE, "misc/spawn", 1, ATTN_IDLE);
Spawn ("ItemFog", self->x, self->y, self->z, ALLOW_REPLACE);
}
}
//---------------------------------------------------------------------------
//
// PROP A_RestoreSpecialPosition
//
//---------------------------------------------------------------------------
DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialPosition)
{
// Move item back to its original location
fixed_t _x, _y;
sector_t *sec;
_x = self->SpawnPoint[0];
_y = self->SpawnPoint[1];
self->UnlinkFromWorld();
self->x = _x;
self->y = _y;
self->LinkToWorld(true);
sec = self->Sector;
self->z =
self->dropoffz =
self->floorz = sec->floorplane.ZatPoint(_x, _y);
self->ceilingz = sec->ceilingplane.ZatPoint(_x, _y);
P_FindFloorCeiling(self, FFCF_ONLYSPAWNPOS);
if (self->flags & MF_SPAWNCEILING)
{
self->z = self->ceilingz - self->height - self->SpawnPoint[2];
}
else if (self->flags2 & MF2_SPAWNFLOAT)
{
fixed_t space = self->ceilingz - self->height - self->floorz;
if (space > 48*FRACUNIT)
{
space -= 40*FRACUNIT;
self->z = ((space * pr_restore())>>8) + self->floorz + 40*FRACUNIT;
}
else
{
self->z = self->floorz;
}
}
else
{
self->z = self->SpawnPoint[2] + self->floorz;
}
// Redo floor/ceiling check, in case of 3D floors
P_FindFloorCeiling(self, FFCF_SAMESECTOR | FFCF_ONLY3DFLOORS | FFCF_3DRESTRICT);
if (self->z < self->floorz)
{ // Do not reappear under the floor, even if that's where we were for the
// initial spawn.
self->z = self->floorz;
}
if ((self->flags & MF_SOLID) && (self->z + self->height > self->ceilingz))
{ // Do the same for the ceiling.
self->z = self->ceilingz - self->height;
}
// Do not interpolate from the position the actor was at when it was
// picked up, in case that is different from where it is now.
self->PrevX = self->x;
self->PrevY = self->y;
self->PrevZ = self->z;
}
int AInventory::StaticLastMessageTic;
const char *AInventory::StaticLastMessage;
IMPLEMENT_POINTY_CLASS (AInventory)
DECLARE_POINTER (Owner)
END_POINTERS
//===========================================================================
//
// AInventory :: Tick
//
//===========================================================================
void AInventory::Tick ()
{
if (Owner == NULL)
{
// AActor::Tick is only handling interaction with the world
// and we don't want that for owned inventory items.
Super::Tick ();
}
else if (tics != -1) // ... but at least we have to advance the states
{
tics--;
// you can cycle through multiple states in a tic
// [RH] Use <= 0 instead of == 0 so that spawnstates
// of 0 tics work as expected.
if (tics <= 0)
{
assert (state != NULL);
if (state == NULL)
{
Destroy();
return;
}
if (!SetState (state->GetNextState()))
return; // freed itself
}
}
if (DropTime)
{
if (--DropTime == 0)
{
flags |= GetDefault()->flags & (MF_SPECIAL|MF_SOLID);
}
}
}
//===========================================================================
//
// AInventory :: Serialize
//
//===========================================================================
void AInventory::Serialize (FArchive &arc)
{
Super::Serialize (arc);
arc << Owner << Amount << MaxAmount << RespawnTics << ItemFlags << Icon << PickupSound << SpawnPointClass;
}
//===========================================================================
//
// AInventory :: MarkPrecacheSounds
//
//===========================================================================
void AInventory::MarkPrecacheSounds() const
{
Super::MarkPrecacheSounds();
PickupSound.MarkUsed();
}
//===========================================================================
//
// AInventory :: SpecialDropAction
//
// Called by P_DropItem. Return true to prevent the standard drop tossing.
// A few Strife items that are meant to trigger actions rather than be
// picked up use this. Normal items shouldn't need it.
//
//===========================================================================
bool AInventory::SpecialDropAction (AActor *dropper)
{
return false;
}
//===========================================================================
//
// AInventory :: ShouldRespawn
//
// Returns true if the item should hide itself and reappear later when picked
// up.
//
//===========================================================================
bool AInventory::ShouldRespawn ()
{
if ((ItemFlags & IF_BIGPOWERUP) && !(dmflags2 & DF2_RESPAWN_SUPER)) return false;
if (ItemFlags & IF_NEVERRESPAWN) return false;
return !!(dmflags & DF_ITEMS_RESPAWN);
}
//===========================================================================
//
// AInventory :: BeginPlay
//
//===========================================================================
void AInventory::BeginPlay ()
{
Super::BeginPlay ();
ChangeStatNum (STAT_INVENTORY);
flags |= MF_DROPPED; // [RH] Items are dropped by default
}
//===========================================================================
//
// AInventory :: Grind
//
//===========================================================================
bool AInventory::Grind(bool items)
{
// Does this grind request even care about items?
if (!items)
{
return false;
}
// Dropped items are normally destroyed by crushers. Set the DONTGIB flag,
// and they'll act like corpses with it set and be immune to crushers.
if (flags & MF_DROPPED)
{
if (!(flags3 & MF3_DONTGIB))
{
Destroy();
}
return false;
}
// Non-dropped items call the super method for compatibility.
return Super::Grind(items);
}
- Converted the StrifePlayer to DECORATE. Even though it requires exporting 3 new code pointers without general use it was necessary to handle GiveDefaultInventory consistently for all players without the need to subclass this function. - Added a Player.RunHealth property to expose the StrifePlayer's behavior of not being able to run when its health is below 10. - Changed APlayerPawn::GiveDefaultInventory so that it always adds a HexenArmor and a BasicArmor item to the inventory. If these items are not the first ones added to the inventory anything else that might absorb damage is not guaranteed to work consistently because their function depends on the order in the inventory. - Changed handling of APowerup's DoEffect so that it is called from the owner's Tick function, not the item's. This is so that the order of execution is determined by the order in the inventory. When done in the item's Tick function order depends on the global thinker table which can cause problems with the order in which conflicting powerups apply their effect. Now it is guaranteed that the item that was added to the inventory first applies its effect last. - Fixed: Added checks for Speed==0 to A_Tracer and A_Tracer2 because this could cause a divide by zero. - Fixed: P_MoveThing must also set the moved actor's previous position to prevent interpolation of the move. - Fixed: APowerInvisibility and its subclasses need to constantly update the owner's translucency information in case of interference between different subclasses. Also changed Hexen's Cleric's invulnerability mode to disable the translucency effect if an invisibility powerup is active. SVN r448 (trunk)
2007-01-12 15:24:10 +00:00
//===========================================================================
//
// AInventory :: DoEffect
//
// Handles any effect an item might apply to its owner
// Normally only used by subclasses of APowerup
//
//===========================================================================
void AInventory::DoEffect ()
{
}
//===========================================================================
//
// AInventory :: Travelled
//
// Called when an item in somebody's inventory is carried over to another
// map, in case it needs to do special reinitialization.
//
//===========================================================================
void AInventory::Travelled ()
{
}
//===========================================================================
//
// AInventory :: OwnerDied
//
// Items receive this message when their owners die.
//
//===========================================================================
void AInventory::OwnerDied ()
{
}
//===========================================================================
//
// AInventory :: HandlePickup
//
// Returns true if the pickup was handled (or should not happen at all),
// false if not.
//
//===========================================================================
bool AInventory::HandlePickup (AInventory *item)
{
if (item->GetClass() == GetClass())
{
if (Amount < MaxAmount || sv_unlimited_pickup)
{
if (Amount > 0 && Amount + item->Amount < 0)
{
Amount = 0x7fffffff;
}
else
{
Amount += item->Amount;
}
if (Amount > MaxAmount && !sv_unlimited_pickup)
{
Amount = MaxAmount;
}
item->ItemFlags |= IF_PICKUPGOOD;
}
return true;
}
if (Inventory != NULL)
{
return Inventory->HandlePickup (item);
}
return false;
}
//===========================================================================
//
// AInventory :: GoAway
//
// Returns true if you must create a copy of this item to give to the player
// or false if you can use this one instead.
//
//===========================================================================
bool AInventory::GoAway ()
{
// Dropped items never stick around
if (flags & MF_DROPPED)
{
return false;
}
if (!ShouldStay ())
{
Hide ();
if (ShouldRespawn ())
{
return true;
}
return false;
}
return true;
}
//===========================================================================
//
// AInventory :: GoAwayAndDie
//
// Like GoAway but used by items that don't insert themselves into the
// inventory. If they won't be respawning, then they can destroy themselves.
//
//===========================================================================
void AInventory::GoAwayAndDie ()
{
if (!GoAway ())
{
flags &= ~MF_SPECIAL;
SetState (FindState("HoldAndDestroy"));
}
}
//===========================================================================
//
// AInventory :: CreateCopy
//
// Returns an actor suitable for placing in an inventory, either itself or
// a copy based on whether it needs to respawn or not. Returning NULL
// indicates the item should not be picked up.
//
//===========================================================================
AInventory *AInventory::CreateCopy (AActor *other)
{
AInventory *copy;
if (GoAway ())
{
2006-07-16 09:10:45 +00:00
copy = static_cast<AInventory *>(Spawn (GetClass(), 0, 0, 0, NO_REPLACE));
copy->Amount = Amount;
copy->MaxAmount = MaxAmount;
}
else
{
copy = this;
}
return copy;
}
//===========================================================================
//
// AInventory::CreateTossable
//
// Creates a copy of the item suitable for dropping. If this actor embodies
// only one item, then it is tossed out itself. Otherwise, the count drops
// by one and a new item with an amount of 1 is spawned.
//
//===========================================================================
AInventory *AInventory::CreateTossable ()
{
AInventory *copy;
// If this actor lacks a SpawnState, don't drop it. (e.g. A base weapon
// like the fist can't be dropped because you'll never see it.)
if (SpawnState == ::GetDefault<AActor>()->SpawnState ||
SpawnState == NULL)
{
return NULL;
}
if ((ItemFlags & (IF_UNDROPPABLE|IF_UNTOSSABLE)) || Owner == NULL || Amount <= 0)
{
return NULL;
}
if (Amount == 1 && !(ItemFlags & IF_KEEPDEPLETED))
{
BecomePickup ();
DropTime = 30;
flags &= ~(MF_SPECIAL|MF_SOLID);
return this;
}
copy = static_cast<AInventory *>(Spawn (GetClass(), Owner->x,
2006-07-16 09:10:45 +00:00
Owner->y, Owner->z, NO_REPLACE));
if (copy != NULL)
{
copy->MaxAmount = MaxAmount;
copy->Amount = 1;
copy->DropTime = 30;
copy->flags &= ~(MF_SPECIAL|MF_SOLID);
Amount--;
}
return copy;
}
//===========================================================================
//
// 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 ();
if (sector_list)
{
P_DelSeclist (sector_list);
sector_list = NULL;
}
flags |= MF_NOBLOCKMAP|MF_NOSECTOR;
LinkToWorld ();
}
RemoveFromHash ();
flags &= ~MF_SPECIAL;
SetState (FindState("Held"));
}
//===========================================================================
//
// 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 ();
flags &= ~(MF_NOBLOCKMAP|MF_NOSECTOR);
LinkToWorld ();
P_FindFloorCeiling (this);
}
flags = (GetDefault()->flags | MF_DROPPED) & ~MF_COUNTITEM;
renderflags &= ~RF_INVISIBLE;
SetState (SpawnState);
}
//===========================================================================
//
// AInventory :: AbsorbDamage
//
// Allows inventory items (primarily armor) to reduce the amount of damage
// taken. Damage is the amount of damage that would be done without armor,
// and newdamage is the amount that should be done after the armor absorbs
// it.
//
//===========================================================================
void AInventory::AbsorbDamage (int damage, FName damageType, int &newdamage)
{
if (Inventory != NULL)
{
Inventory->AbsorbDamage (damage, damageType, newdamage);
}
}
//===========================================================================
//
// AInventory :: ModifyDamage
//
// Allows inventory items to manipulate the amount of damage
// inflicted. Damage is the amount of damage that would be done without manipulation,
// and newdamage is the amount that should be done after the item has changed
// it.
// 'active' means it is called by the inflictor, 'passive' by the target.
// It may seem that this is redundant and AbsorbDamage is the same. However,
// AbsorbDamage is called only for players and also depends on other settings
// which are undesirable for a protection artifact.
//
//===========================================================================
void AInventory::ModifyDamage (int damage, FName damageType, int &newdamage, bool passive)
{
if (Inventory != NULL)
{
Inventory->ModifyDamage (damage, damageType, newdamage, passive);
}
}
//===========================================================================
//
// AInventory :: GetSpeedFactor
//
//===========================================================================
fixed_t AInventory::GetSpeedFactor ()
{
if (Inventory != NULL)
{
return Inventory->GetSpeedFactor();
}
else
{
return FRACUNIT;
}
}
//===========================================================================
//
// AInventory :: AlterWeaponSprite
//
// Allows inventory items to alter a player's weapon sprite just before it
// is drawn.
//
//===========================================================================
int AInventory::AlterWeaponSprite (visstyle_t *vis)
{
if (Inventory != NULL)
{
return Inventory->AlterWeaponSprite (vis);
}
return 0;
}
//===========================================================================
//
// AInventory :: Use
//
//===========================================================================
bool AInventory::Use (bool pickup)
{
return false;
}
//===========================================================================
//
// AInventory :: Hide
//
// Hides this actor until it's time to respawn again.
//
//===========================================================================
void AInventory::Hide ()
{
FState *HideSpecialState = NULL, *HideDoomishState = NULL;
flags = (flags & ~MF_SPECIAL) | MF_NOGRAVITY;
renderflags |= RF_INVISIBLE;
if (gameinfo.gametype & GAME_Raven)
{
HideSpecialState = FindState("HideSpecial");
if (HideSpecialState == NULL)
{
HideDoomishState = FindState("HideDoomish");
}
}
else
{
HideDoomishState = FindState("HideDoomish");
if (HideDoomishState == NULL)
{
HideSpecialState = FindState("HideSpecial");
}
}
assert(HideDoomishState != NULL || HideSpecialState != NULL);
if (HideSpecialState != NULL)
{
SetState (HideSpecialState);
tics = 1400;
if (PickupFlash != NULL) tics += 30;
}
else if (HideDoomishState != NULL)
{
SetState (HideDoomishState);
tics = 1050;
}
if (RespawnTics != 0)
{
tics = RespawnTics;
}
}
2006-04-30 21:49:18 +00:00
//===========================================================================
//
//
//===========================================================================
static void PrintPickupMessage (const char *str)
{
if (str != NULL)
{
if (str[0]=='$')
2006-04-30 21:49:18 +00:00
{
str=GStrings(str+1);
2006-04-30 21:49:18 +00:00
}
if (str[0] != 0) Printf (PRINT_LOW, "%s\n", str);
2006-04-30 21:49:18 +00:00
}
}
//===========================================================================
//
// AInventory :: Touch
//
// Handles collisions from another actor, possible adding itself to the
// collider's inventory.
//
//===========================================================================
void AInventory::Touch (AActor *toucher)
{
// If a voodoo doll touches something, pretend the real player touched it instead.
if (toucher->player != NULL)
{
toucher = toucher->player->mo;
}
bool localview = toucher->CheckLocalView(consoleplayer);
if (!CallTryPickup (toucher, &toucher)) return;
// This is the only situation when a pickup flash should ever play.
if (PickupFlash != NULL && !ShouldStay())
{
Spawn(PickupFlash, x, y, z, ALLOW_REPLACE);
}
if (!(ItemFlags & IF_QUIET))
{
const char * message = PickupMessage ();
if (message != NULL && *message != 0 && localview
&& (StaticLastMessageTic != gametic || StaticLastMessage != message))
{
StaticLastMessageTic = gametic;
StaticLastMessage = message;
2006-04-30 21:49:18 +00:00
PrintPickupMessage (message);
StatusBar->FlashCrosshair ();
}
// Special check so voodoo dolls picking up items cause the
// real player to make noise.
if (toucher->player != NULL)
{
PlayPickupSound (toucher->player->mo);
if (!(ItemFlags & IF_NOSCREENFLASH))
{
toucher->player->bonuscount = BONUSADD;
}
}
else
{
PlayPickupSound (toucher);
}
2006-04-30 21:49:18 +00:00
}
// [RH] Execute an attached special (if any)
DoPickupSpecial (toucher);
if (flags & MF_COUNTITEM)
{
if (toucher->player != NULL)
{
toucher->player->itemcount++;
}
level.found_items++;
}
if (flags5 & MF5_COUNTSECRET)
{
P_GiveSecret(toucher, true, true, -1);
}
//Added by MC: Check if item taken was the roam destination of any bot
for (int i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && this == players[i].dest)
players[i].dest = NULL;
}
}
//===========================================================================
//
// AInventory :: DoPickupSpecial
//
// Executes this actor's special when it is picked up.
//
//===========================================================================
void AInventory::DoPickupSpecial (AActor *toucher)
{
if (special)
{
P_ExecuteSpecial(special, NULL, toucher, false,
args[0], args[1], args[2], args[3], args[4]);
special = 0;
}
}
//===========================================================================
//
// AInventory :: PickupMessage
//
// Returns the message to print when this actor is picked up.
//
//===========================================================================
const char *AInventory::PickupMessage ()
{
return GetClass()->Meta.GetMetaString (AIMETA_PickupMessage);
}
//===========================================================================
//
// AInventory :: PlayPickupSound
//
//===========================================================================
void AInventory::PlayPickupSound (AActor *toucher)
{
float atten;
int chan;
if (ItemFlags & IF_NOATTENPICKUPSOUND)
{
atten = ATTN_NONE;
}
#if 0
else if ((ItemFlags & IF_FANCYPICKUPSOUND) &&
(toucher == NULL || toucher->CheckLocalView(consoeplayer)))
{
atten = ATTN_NONE;
}
#endif
else
{
atten = ATTN_NORM;
}
if (toucher != NULL && toucher->CheckLocalView(consoleplayer))
{
chan = CHAN_PICKUP|CHAN_NOPAUSE;
}
else
{
chan = CHAN_PICKUP;
}
S_Sound (toucher, chan, PickupSound, 1, atten);
}
//===========================================================================
//
// AInventory :: ShouldStay
//
// Returns true if the item should not disappear, even temporarily.
//
//===========================================================================
bool AInventory::ShouldStay ()
{
return false;
}
//===========================================================================
//
// AInventory :: Destroy
//
//===========================================================================
void AInventory::Destroy ()
{
if (Owner != NULL)
{
Owner->RemoveInventory (this);
}
Inventory = NULL;
Super::Destroy ();
// Although contrived it can theoretically happen that these variables still got a pointer to this item
if (SendItemUse == this) SendItemUse = NULL;
if (SendItemDrop == this) SendItemDrop = NULL;
}
//===========================================================================
//
// AInventory :: GetBlend
//
// Returns a color to blend to the player's view as long as they possess this
// item.
//
//===========================================================================
PalEntry AInventory::GetBlend ()
{
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
//
// Returns the previous item with IF_INVBAR set.
//
//===========================================================================
AInventory *AInventory::PrevInv ()
{
AInventory *lastgood = NULL;
AInventory *item = Owner->Inventory;
while (item != NULL && item != this)
{
if (item->ItemFlags & IF_INVBAR)
{
lastgood = item;
}
item = item->Inventory;
}
return lastgood;
}
//===========================================================================
//
// AInventory :: NextInv
//
// Returns the next item with IF_INVBAR set.
//
//===========================================================================
AInventory *AInventory::NextInv ()
{
AInventory *item = Inventory;
while (item != NULL && !(item->ItemFlags & IF_INVBAR))
{
item = item->Inventory;
}
return item;
}
//===========================================================================
//
// AInventory :: DrawPowerup
//
// Gives this item a chance to draw a special status indicator on the screen.
// Returns false if it didn't draw anything.
//
//===========================================================================
bool AInventory::DrawPowerup (int x, int y)
{
return false;
}
/***************************************************************************/
/* AArtifact implementation */
/***************************************************************************/
IMPLEMENT_CLASS (APowerupGiver)
//===========================================================================
//
// AInventory :: DoRespawn
//
//===========================================================================
bool AInventory::DoRespawn ()
{
if (SpawnPointClass != NULL)
{
AActor *spot = NULL;
DSpotState *state = DSpotState::GetSpotState();
if (state != NULL) spot = state->GetRandomSpot(SpawnPointClass);
if (spot != NULL)
{
SetOrigin (spot->x, spot->y, spot->z);
z = floorz;
}
}
return true;
}
May 6, 2006 (Changes by Graf Zahl) - Converted a_zombie.cpp and most of a_strifestuff.cpp to DECORATE. - Converted a_strifekeys.cpp to DECORATE and moved the pickup messages to the string table. - Removed the WIF_HITS_GHOSTS weapon flag and replaced it with MF2_THRUGHOST. There is no need to keep two flags around with virtually the same meaning. - Changed the ShadowArmor to use the VISIBILITYPULSE flag to change its translucency. It looks much better now than the cheap code pointer based blinking it used before. - Converted most of a_strifeitems.cpp to DECORATE and moved the pickup messages to the string table. - Converted a_strifearmor.cpp to DECORATE and moved the pickup messages to the string table. - Moved the messages for killing spectres to the string table. - Converted the quest items to DECORATE. Also changed A_GiveQuestItem to get the messages it prints from the string table instead of the quest item's tag string. May 5, 2006 (Changes by Graf Zahl) - Removed the hopelessly outdated thingdef_doc.txt file from the repository. - Converted a_peasant.cpp and a_ratbuddy.cpp to DECORATE. - Fixed: C_DoKey didn't treat an empty string as 'no binding' when checking for valid double bindings. - Converted a_merchants.cpp to DECORATE. - Added MF5_NODAMAGE flag to generalize the behavior of Strife's merchants which can be shot but take no damage from getting hurt. - Converted a_beggars.cpp to DECORATE. - Added an Inventory.GiveQuest property. This makes it possible to define all of Strife's original items that also give a quest item in DECORATE but it is also useful to define items like the ones in Day of the Acolyte without ugly workarounds. - Added a Tag property and Strife teaser conversation IDs to DECORATE so now it is possible to define many of Strife's items. - Added a FastSpeed property to DECORATE so that projectiles can finally be assigned a higher speed for fast mode. - Added a ACS_LockedExecuteDoor special. It is basically the same as the existing ACS_LockedExecute but it uses the 'door' message instead of 'remote'. This cannot be integrated into ACS_LockedExecute because all its arguments are already in use. - Added a fully customizable A_CustomMeleeAttack function for DECORATE. SVN r83 (trunk)
2006-05-07 00:27:22 +00:00
//===========================================================================
//
// AInventory :: GiveQuest
//
//===========================================================================
void AInventory::GiveQuest (AActor *toucher)
{
int quest = GetClass()->Meta.GetMetaInt(AIMETA_GiveQuest);
if (quest>0 && quest<31)
{
toucher->GiveInventoryType (QuestItemClasses[quest-1]);
}
}
//===========================================================================
//
// AInventory :: TryPickup
//
//===========================================================================
bool AInventory::TryPickup (AActor *&toucher)
{
AActor *newtoucher = toucher; // in case changed by the powerup
// If HandlePickup() returns true, it will set the IF_PICKUPGOOD flag
// to indicate that this item has been picked up. If the item cannot be
// picked up, then it leaves the flag cleared.
ItemFlags &= ~IF_PICKUPGOOD;
if (toucher->Inventory != NULL && toucher->Inventory->HandlePickup (this))
{
// Let something else the player is holding intercept the pickup.
if (!(ItemFlags & IF_PICKUPGOOD))
{
return false;
}
ItemFlags &= ~IF_PICKUPGOOD;
GoAwayAndDie ();
}
else if (MaxAmount == 0 && !IsKindOf(RUNTIME_CLASS(AAmmo)))
{
// Special case: If an item's MaxAmount is 0, you can still pick it
// up if it is autoactivate-able.
if (!(ItemFlags & IF_AUTOACTIVATE))
{
return false;
}
// The item is placed in the inventory just long enough to be used.
toucher->AddInventory (this);
bool usegood = Use (true);
toucher->RemoveInventory (this);
if (usegood)
{
GoAwayAndDie ();
}
else
{
return false;
}
}
else
{
// Add the item to the inventory. It is not already there, or HandlePickup
// would have already taken care of it.
AInventory *copy = CreateCopy (toucher);
if (copy == NULL)
{
return false;
}
// Some powerups cannot activate absolutely, for
// example, PowerMorph; fail the pickup if so.
if (copy->ItemFlags & IF_INITEFFECTFAILED)
{
if (copy != this) copy->Destroy();
else ItemFlags &= ~IF_INITEFFECTFAILED;
return false;
}
// Handle owner-changing powerups
if (copy->ItemFlags & IF_CREATECOPYMOVED)
{
newtoucher = copy->Owner;
copy->Owner = NULL;
copy->ItemFlags &= ~IF_CREATECOPYMOVED;
}
// Continue onwards with the rest
copy->AttachToOwner (newtoucher);
if (ItemFlags & IF_AUTOACTIVATE)
{
if (copy->Use (true))
{
if (--copy->Amount <= 0)
{
2006-04-17 16:04:27 +00:00
copy->flags &= ~MF_SPECIAL;
copy->SetState (copy->FindState("HoldAndDestroy"));
}
}
}
}
return true;
}
//===========================================================================
//
// AInventory :: TryPickupRestricted
//
//===========================================================================
bool AInventory::TryPickupRestricted (AActor *&toucher)
{
return false;
}
//===========================================================================
//
// AInventory :: CallTryPickup
//
//===========================================================================
bool AInventory::CallTryPickup (AActor *toucher, AActor **toucher_return)
{
// unmorphed versions of a currently morphed actor cannot pick up anything.
if (toucher->flags & MF_UNMORPHED) return false;
bool res;
if (CanPickup(toucher))
res = TryPickup(toucher);
else if (!(ItemFlags & IF_RESTRICTABSOLUTELY))
res = TryPickupRestricted(toucher); // let an item decide for itself how it will handle this
else
return false;
// Morph items can change the toucher so we need an option to return this info.
if (toucher_return != NULL) *toucher_return = toucher;
if (!res && (ItemFlags & IF_ALWAYSPICKUP) && !ShouldStay())
{
res = true;
GoAwayAndDie();
}
if (res) GiveQuest(toucher);
return res;
}
//===========================================================================
//
// AInventory :: CanPickup
//
//===========================================================================
bool AInventory::CanPickup (AActor *toucher)
{
if (!toucher)
return false;
FActorInfo *ai = GetClass()->ActorInfo;
// Is the item restricted to certain player classes?
if (ai->RestrictedToPlayerClass.Size() != 0)
{
for (unsigned i = 0; i < ai->RestrictedToPlayerClass.Size(); ++i)
{
if (toucher->IsKindOf(ai->RestrictedToPlayerClass[i]))
return true;
}
return false;
}
// Or is it forbidden to certain other classes?
else
{
for (unsigned i = 0; i < ai->ForbiddenToPlayerClass.Size(); ++i)
{
if (toucher->IsKindOf(ai->ForbiddenToPlayerClass[i]))
return false;
}
}
return true;
}
//===========================================================================
//
// CCMD printinv
//
// Prints the console player's current inventory.
//
//===========================================================================
CCMD (printinv)
{
AInventory *item;
int pnum = consoleplayer;
#ifdef _DEBUG
// Only allow peeking on other players' inventory in debug builds.
if (argv.argc() > 1)
{
pnum = atoi (argv[1]);
if (pnum < 0 || pnum >= MAXPLAYERS)
{
return;
}
}
#endif
if (players[pnum].mo == NULL)
{
return;
}
for (item = players[pnum].mo->Inventory; item != NULL; item = item->Inventory)
{
Printf ("%s #%u (%d/%d)\n", item->GetClass()->TypeName.GetChars(),
item->InventoryID,
item->Amount, item->MaxAmount);
}
}
//===========================================================================
//
// AInventory :: AttachToOwner
//
//===========================================================================
void AInventory::AttachToOwner (AActor *other)
{
BecomeItem ();
other->AddInventory (this);
}
//===========================================================================
//
// AInventory :: DetachFromOwner
//
// Performs any special work needed when the item leaves an inventory,
// either through destruction or becoming a pickup.
//
//===========================================================================
void AInventory::DetachFromOwner ()
{
}
IMPLEMENT_CLASS (ACustomInventory)
//===========================================================================
//
// ACustomInventory :: SpecialDropAction
//
//===========================================================================
bool ACustomInventory::SpecialDropAction (AActor *dropper)
{
return CallStateChain (dropper, FindState(NAME_Drop));
}
//===========================================================================
//
// ACustomInventory :: Use
//
//===========================================================================
bool ACustomInventory::Use (bool pickup)
{
return CallStateChain (Owner, FindState(NAME_Use));
}
//===========================================================================
//
// ACustomInventory :: TryPickup
//
//===========================================================================
bool ACustomInventory::TryPickup (AActor *&toucher)
{
FState *pickupstate = FindState(NAME_Pickup);
bool useok = CallStateChain (toucher, pickupstate);
if ((useok || pickupstate == NULL) && FindState(NAME_Use) != NULL)
{
useok = Super::TryPickup (toucher);
}
else if (useok)
{
GoAwayAndDie();
}
return useok;
}
IMPLEMENT_CLASS (AHealth)
//===========================================================================
//
// AHealth :: PickupMessage
//
//===========================================================================
const char *AHealth::PickupMessage ()
{
int threshold = GetClass()->Meta.GetMetaInt(AIMETA_LowHealth, 0);
if (PrevHealth < threshold)
{
const char *message = GetClass()->Meta.GetMetaString (AIMETA_LowHealthMessage);
if (message != NULL)
{
return message;
}
}
return Super::PickupMessage();
}
//===========================================================================
//
// AHealth :: TryPickup
//
//===========================================================================
bool AHealth::TryPickup (AActor *&other)
{
PrevHealth = other->player != NULL ? other->player->health : other->health;
// P_GiveBody adds one new feature, applied only if it is possible to pick up negative health:
// Negative values are treated as positive percentages, ie Amount -100 means 100% health, ignoring max amount.
if (P_GiveBody(other, Amount, MaxAmount))
{
GoAwayAndDie();
return true;
}
return false;
}
IMPLEMENT_CLASS (AHealthPickup)
//===========================================================================
//
// AHealthPickup :: CreateCopy
//
//===========================================================================
AInventory *AHealthPickup::CreateCopy (AActor *other)
{
AInventory *copy = Super::CreateCopy (other);
copy->health = health;
return copy;
}
//===========================================================================
//
// AHealthPickup :: CreateTossable
//
//===========================================================================
AInventory *AHealthPickup::CreateTossable ()
{
AInventory *copy = Super::CreateTossable ();
if (copy != NULL)
{
copy->health = health;
}
return copy;
}
//===========================================================================
//
// AHealthPickup :: HandlePickup
//
//===========================================================================
bool AHealthPickup::HandlePickup (AInventory *item)
{
// HealthPickups that are the same type but have different health amounts
// do not count as the same item.
if (item->health == health)
{
return Super::HandlePickup (item);
}
if (Inventory != NULL)
{
return Inventory->HandlePickup (item);
}
return false;
}
//===========================================================================
//
// AHealthPickup :: Use
//
//===========================================================================
bool AHealthPickup::Use (bool pickup)
{
return P_GiveBody (Owner, health);
}
//===========================================================================
//
// AHealthPickup :: Serialize
//
//===========================================================================
void AHealthPickup::Serialize (FArchive &arc)
{
Super::Serialize(arc);
arc << autousemode;
}
// Backpack -----------------------------------------------------------------
//===========================================================================
//
// ABackpackItem :: Serialize
//
//===========================================================================
void ABackpackItem::Serialize (FArchive &arc)
{
Super::Serialize (arc);
2006-04-11 16:27:41 +00:00
arc << bDepleted;
}
//===========================================================================
//
// ABackpackItem :: CreateCopy
//
// A backpack is being added to a player who doesn't yet have one. Give them
// every kind of ammo, and increase their max amounts.
//
//===========================================================================
AInventory *ABackpackItem::CreateCopy (AActor *other)
{
// Find every unique type of ammo. Give it to the player if
// he doesn't have it already, and double its maximum capacity.
for (unsigned int i = 0; i < PClass::m_Types.Size(); ++i)
{
const PClass *type = PClass::m_Types[i];
if (type->ParentClass == RUNTIME_CLASS(AAmmo))
{
AAmmo *ammo = static_cast<AAmmo *>(other->FindInventory (type));
int amount = static_cast<AAmmo *>(GetDefaultByType(type))->BackpackAmount;
// extra ammo in baby mode and nightmare mode
if (!(ItemFlags&IF_IGNORESKILL))
{
amount = FixedMul(amount, G_SkillProperty(SKILLP_AmmoFactor));
}
if (amount < 0) amount = 0;
if (ammo == NULL)
{ // The player did not have the ammo. Add it.
2006-07-16 09:10:45 +00:00
ammo = static_cast<AAmmo *>(Spawn (type, 0, 0, 0, NO_REPLACE));
ammo->Amount = bDepleted ? 0 : amount;
if (ammo->BackpackMaxAmount > ammo->MaxAmount)
{
ammo->MaxAmount = ammo->BackpackMaxAmount;
}
if (ammo->Amount > ammo->MaxAmount)
{
ammo->Amount = ammo->MaxAmount;
}
ammo->AttachToOwner (other);
}
else
{ // The player had the ammo. Give some more.
if (ammo->MaxAmount < ammo->BackpackMaxAmount)
{
ammo->MaxAmount = ammo->BackpackMaxAmount;
}
if (!bDepleted && ammo->Amount < ammo->MaxAmount)
{
ammo->Amount += amount;
if (ammo->Amount > ammo->MaxAmount)
{
ammo->Amount = ammo->MaxAmount;
}
}
}
}
}
return Super::CreateCopy (other);
}
//===========================================================================
//
// ABackpackItem :: HandlePickup
//
// When the player picks up another backpack, just give them more ammo.
//
//===========================================================================
bool ABackpackItem::HandlePickup (AInventory *item)
{
// Since you already have a backpack, that means you already have every
// kind of ammo in your inventory, so we don't need to look at the
// entire PClass list to discover what kinds of ammo exist, and we don't
// have to alter the MaxAmount either.
if (item->IsKindOf (RUNTIME_CLASS(ABackpackItem)))
{
for (AInventory *probe = Owner->Inventory; probe != NULL; probe = probe->Inventory)
{
if (probe->GetClass()->ParentClass == RUNTIME_CLASS(AAmmo))
{
if (probe->Amount < probe->MaxAmount || sv_unlimited_pickup)
{
int amount = static_cast<AAmmo*>(probe->GetDefault())->BackpackAmount;
// extra ammo in baby mode and nightmare mode
if (!(item->ItemFlags&IF_IGNORESKILL))
{
amount = FixedMul(amount, G_SkillProperty(SKILLP_AmmoFactor));
}
probe->Amount += amount;
if (probe->Amount > probe->MaxAmount && !sv_unlimited_pickup)
{
probe->Amount = probe->MaxAmount;
}
}
}
}
// The pickup always succeeds, even if you didn't get anything
item->ItemFlags |= IF_PICKUPGOOD;
return true;
}
else if (Inventory != NULL)
{
return Inventory->HandlePickup (item);
}
else
{
return false;
}
}
//===========================================================================
//
// ABackpackItem :: CreateTossable
//
// The tossed backpack must not give out any more ammo, otherwise a player
// could cheat by dropping their backpack and picking it up for more ammo.
//
//===========================================================================
AInventory *ABackpackItem::CreateTossable ()
{
ABackpackItem *pack = static_cast<ABackpackItem *>(Super::CreateTossable());
if (pack != NULL)
{
pack->bDepleted = true;
}
return pack;
}
//===========================================================================
//
// ABackpackItem :: DetachFromOwner
//
//===========================================================================
void ABackpackItem::DetachFromOwner ()
{
// When removing a backpack, drop the player's ammo maximums to normal
AInventory *item;
for (item = Owner->Inventory; item != NULL; item = item->Inventory)
{
if (item->GetClass()->ParentClass == RUNTIME_CLASS(AAmmo) &&
item->MaxAmount == static_cast<AAmmo*>(item)->BackpackMaxAmount)
{
item->MaxAmount = static_cast<AInventory*>(item->GetDefault())->MaxAmount;
if (item->Amount > item->MaxAmount)
{
item->Amount = item->MaxAmount;
}
}
}
}
//===========================================================================
//
// ABackpack
//
//===========================================================================
IMPLEMENT_CLASS(ABackpackItem)
IMPLEMENT_CLASS (AMapRevealer)
//===========================================================================
//
// AMapRevealer :: TryPickup
//
// The MapRevealer doesn't actually go in your inventory. Instead, it sets
// a flag on the level.
//
//===========================================================================
bool AMapRevealer::TryPickup (AActor *&toucher)
{
level.flags2 |= LEVEL2_ALLMAP;
GoAwayAndDie ();
return true;
}
//===========================================================================
//
// AScoreItem
//
//===========================================================================
IMPLEMENT_CLASS(AScoreItem)
//===========================================================================
//
// AScoreItem :: TryPickup
//
// Adds the value (Amount) of the item to the toucher's Score property.
//
//===========================================================================
bool AScoreItem::TryPickup (AActor *&toucher)
{
toucher->Score += Amount;
GoAwayAndDie();
return true;
}