This revealed an interesting bug: When the berserk fadout formula was changed in 2005 the result was essentially broken, resulting in values around 7000 - it only worked by happenstance because the lower 8 bits of the resulting values just happened to work to some degree and never overflowed. But the resulting fade was far too weak and a slightly different handling of the color composition code for the VM made it break down entirely. This restores the pre-2005 formula but weakened intensity which now comes a lot closer to how it is supposed to look.
593 lines
14 KiB
C++
593 lines
14 KiB
C++
#include "info.h"
|
|
#include "a_pickups.h"
|
|
#include "d_player.h"
|
|
#include "p_local.h"
|
|
#include "c_dispatch.h"
|
|
#include "gi.h"
|
|
#include "s_sound.h"
|
|
#include "p_local.h"
|
|
#include "p_spec.h"
|
|
#include "p_lnspec.h"
|
|
#include "p_effect.h"
|
|
#include "a_artifacts.h"
|
|
#include "sbar.h"
|
|
#include "d_player.h"
|
|
#include "m_random.h"
|
|
#include "v_video.h"
|
|
#include "templates.h"
|
|
#include "a_morph.h"
|
|
#include "g_level.h"
|
|
#include "doomstat.h"
|
|
#include "v_palette.h"
|
|
#include "serializer.h"
|
|
#include "r_utility.h"
|
|
#include "virtual.h"
|
|
#include "g_levellocals.h"
|
|
|
|
#include "r_data/colormaps.h"
|
|
|
|
static FRandom pr_torch ("Torch");
|
|
|
|
/* Those are no longer needed, except maybe as reference?
|
|
* They're not used anywhere in the code anymore, except
|
|
* MAULATORTICS as redefined in a_minotaur.cpp...
|
|
#define INVULNTICS (30*TICRATE)
|
|
#define INVISTICS (60*TICRATE)
|
|
#define INFRATICS (120*TICRATE)
|
|
#define IRONTICS (60*TICRATE)
|
|
#define WPNLEV2TICS (40*TICRATE)
|
|
#define FLIGHTTICS (60*TICRATE)
|
|
#define SPEEDTICS (45*TICRATE)
|
|
#define MAULATORTICS (25*TICRATE)
|
|
#define TIMEFREEZE_TICS ( 12 * TICRATE )
|
|
*/
|
|
|
|
|
|
IMPLEMENT_CLASS(APowerup, false, false)
|
|
|
|
// Powerup-Giver -------------------------------------------------------------
|
|
|
|
|
|
IMPLEMENT_CLASS(APowerupGiver, false, true)
|
|
|
|
IMPLEMENT_POINTERS_START(APowerupGiver)
|
|
IMPLEMENT_POINTER(PowerupType)
|
|
IMPLEMENT_POINTERS_END
|
|
|
|
DEFINE_FIELD(APowerupGiver, PowerupType)
|
|
DEFINE_FIELD(APowerupGiver, EffectTics)
|
|
DEFINE_FIELD(APowerupGiver, BlendColor)
|
|
DEFINE_FIELD(APowerupGiver, Mode)
|
|
DEFINE_FIELD(APowerupGiver, Strength)
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerupGiver :: Use
|
|
//
|
|
//===========================================================================
|
|
|
|
bool APowerupGiver::Use (bool pickup)
|
|
{
|
|
if (PowerupType == NULL) return true; // item is useless
|
|
if (Owner == nullptr) return true;
|
|
|
|
APowerup *power = static_cast<APowerup *> (Spawn (PowerupType));
|
|
|
|
if (EffectTics != 0)
|
|
{
|
|
power->EffectTics = EffectTics;
|
|
}
|
|
if (BlendColor != 0)
|
|
{
|
|
if (BlendColor != MakeSpecialColormap(65535)) power->BlendColor = BlendColor;
|
|
else power->BlendColor = 0;
|
|
}
|
|
if (Mode != NAME_None)
|
|
{
|
|
power->Mode = Mode;
|
|
}
|
|
if (Strength != 0)
|
|
{
|
|
power->Strength = Strength;
|
|
}
|
|
|
|
power->ItemFlags |= ItemFlags & (IF_ALWAYSPICKUP|IF_ADDITIVETIME|IF_NOTELEPORTFREEZE);
|
|
if (power->CallTryPickup (Owner))
|
|
{
|
|
return true;
|
|
}
|
|
power->GoAwayAndDie ();
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerupGiver :: Serialize
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerupGiver::Serialize(FSerializer &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
auto def = (APowerupGiver*)GetDefault();
|
|
arc("poweruptype", PowerupType, def->PowerupType)
|
|
("effecttics", EffectTics, def->EffectTics)
|
|
("blendcolor", BlendColor, def->BlendColor)
|
|
("mode", Mode, def->Mode)
|
|
("strength", Strength, def->Strength);
|
|
}
|
|
|
|
// Powerup -------------------------------------------------------------------
|
|
|
|
DEFINE_FIELD(APowerup, EffectTics)
|
|
DEFINE_FIELD(APowerup, BlendColor)
|
|
DEFINE_FIELD(APowerup, Mode)
|
|
DEFINE_FIELD(APowerup, Strength)
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: Tick
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerup::Tick ()
|
|
{
|
|
// Powerups cannot exist outside an inventory
|
|
if (Owner == NULL)
|
|
{
|
|
Destroy ();
|
|
}
|
|
if (EffectTics > 0 && --EffectTics == 0)
|
|
{
|
|
Destroy ();
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: Serialize
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerup::Serialize(FSerializer &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
auto def = (APowerup*)GetDefault();
|
|
arc("effecttics", EffectTics, def->EffectTics)
|
|
("blendcolor", BlendColor, def->BlendColor)
|
|
("mode", Mode, def->Mode)
|
|
("strength", Strength, def->Strength);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: GetBlend
|
|
//
|
|
//===========================================================================
|
|
|
|
PalEntry APowerup::GetBlend ()
|
|
{
|
|
if (isBlinking())
|
|
return 0;
|
|
|
|
if (IsSpecialColormap(BlendColor)) return 0;
|
|
return BlendColor;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: InitEffect
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerup::InitEffect ()
|
|
{
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(APowerup, InitEffect)
|
|
{
|
|
PARAM_SELF_PROLOGUE(APowerup);
|
|
self->InitEffect();
|
|
return 0;
|
|
}
|
|
|
|
void APowerup::CallInitEffect()
|
|
{
|
|
IFVIRTUAL(APowerup, InitEffect)
|
|
{
|
|
VMValue params[1] = { (DObject*)this };
|
|
VMFrameStack stack;
|
|
GlobalVMStack.Call(func, params, 1, nullptr, 0, nullptr);
|
|
}
|
|
else InitEffect();
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: isBlinking (todo: make this virtual so that child classes can configure their blinking)
|
|
//
|
|
//===========================================================================
|
|
|
|
bool APowerup::isBlinking() const
|
|
{
|
|
return (EffectTics <= BLINKTHRESHOLD && (EffectTics & 8) && !(ItemFlags & IF_NOSCREENBLINK));
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(APowerup, isBlinking)
|
|
{
|
|
PARAM_SELF_PROLOGUE(APowerup);
|
|
ACTION_RETURN_BOOL(self->isBlinking());
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: DoEffect
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerup::DoEffect ()
|
|
{
|
|
if (Owner == NULL || Owner->player == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (EffectTics > 0)
|
|
{
|
|
int Colormap = GetSpecialColormap(BlendColor);
|
|
|
|
if (Colormap != NOFIXEDCOLORMAP)
|
|
{
|
|
if (!isBlinking())
|
|
{
|
|
Owner->player->fixedcolormap = Colormap;
|
|
}
|
|
else if (Owner->player->fixedcolormap == Colormap)
|
|
{
|
|
// only unset if the fixed colormap comes from this item
|
|
Owner->player->fixedcolormap = NOFIXEDCOLORMAP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: EndEffect
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerup::EndEffect ()
|
|
{
|
|
int colormap = GetSpecialColormap(BlendColor);
|
|
|
|
if (colormap != NOFIXEDCOLORMAP && Owner && Owner->player && Owner->player->fixedcolormap == colormap)
|
|
{ // only unset if the fixed colormap comes from this item
|
|
Owner->player->fixedcolormap = NOFIXEDCOLORMAP;
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(APowerup, EndEffect)
|
|
{
|
|
PARAM_SELF_PROLOGUE(APowerup);
|
|
self->EndEffect();
|
|
return 0;
|
|
}
|
|
|
|
void APowerup::CallEndEffect()
|
|
{
|
|
IFVIRTUAL(APowerup, EndEffect)
|
|
{
|
|
VMValue params[1] = { (DObject*)this };
|
|
VMFrameStack stack;
|
|
GlobalVMStack.Call(func, params, 1, nullptr, 0, nullptr);
|
|
}
|
|
else EndEffect();
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: Destroy
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerup::OnDestroy ()
|
|
{
|
|
CallEndEffect ();
|
|
Super::OnDestroy();
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: DrawPowerup
|
|
//
|
|
//===========================================================================
|
|
|
|
bool APowerup::DrawPowerup (int x, int y)
|
|
{
|
|
if (!Icon.isValid())
|
|
{
|
|
return false;
|
|
}
|
|
if (!isBlinking())
|
|
{
|
|
FTexture *pic = TexMan(Icon);
|
|
screen->DrawTexture (pic, x, y,
|
|
DTA_HUDRules, HUD_Normal,
|
|
// DTA_TopOffset, pic->GetHeight()/2,
|
|
// DTA_LeftOffset, pic->GetWidth()/2,
|
|
TAG_DONE);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: HandlePickup
|
|
//
|
|
//===========================================================================
|
|
|
|
bool APowerup::HandlePickup (AInventory *item)
|
|
{
|
|
if (item->GetClass() == GetClass())
|
|
{
|
|
APowerup *power = static_cast<APowerup*>(item);
|
|
if (power->EffectTics == 0)
|
|
{
|
|
power->ItemFlags |= IF_PICKUPGOOD;
|
|
return true;
|
|
}
|
|
// Color gets transferred if the new item has an effect.
|
|
|
|
// Increase the effect's duration.
|
|
if (power->ItemFlags & IF_ADDITIVETIME)
|
|
{
|
|
EffectTics += power->EffectTics;
|
|
BlendColor = power->BlendColor;
|
|
}
|
|
// If it's not blinking yet, you can't replenish the power unless the
|
|
// powerup is required to be picked up.
|
|
else if (EffectTics > BLINKTHRESHOLD && !(power->ItemFlags & IF_ALWAYSPICKUP))
|
|
{
|
|
return true;
|
|
}
|
|
// Reset the effect duration.
|
|
else if (power->EffectTics > EffectTics)
|
|
{
|
|
EffectTics = power->EffectTics;
|
|
BlendColor = power->BlendColor;
|
|
}
|
|
power->ItemFlags |= IF_PICKUPGOOD;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: CreateCopy
|
|
//
|
|
//===========================================================================
|
|
|
|
AInventory *APowerup::CreateCopy (AActor *other)
|
|
{
|
|
// Get the effective effect time.
|
|
EffectTics = abs (EffectTics);
|
|
// Abuse the Owner field to tell the
|
|
// InitEffect method who started it;
|
|
// this should be cleared afterwards,
|
|
// as this powerup instance is not
|
|
// properly attached to anything yet.
|
|
Owner = other;
|
|
// Actually activate the powerup.
|
|
CallInitEffect ();
|
|
// Clear the Owner field, unless it was
|
|
// changed by the activation, for example,
|
|
// if this instance is a morph powerup;
|
|
// the flag tells the caller that the
|
|
// ownership has changed so that they
|
|
// can properly handle the situation.
|
|
if (!(ItemFlags & IF_CREATECOPYMOVED))
|
|
{
|
|
Owner = NULL;
|
|
}
|
|
// All done.
|
|
return this;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: CreateTossable
|
|
//
|
|
// Powerups are never droppable, even without IF_UNDROPPABLE set.
|
|
//
|
|
//===========================================================================
|
|
|
|
AInventory *APowerup::CreateTossable ()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerup :: OwnerDied
|
|
//
|
|
// Powerups don't last beyond death.
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerup::OwnerDied ()
|
|
{
|
|
Destroy ();
|
|
}
|
|
|
|
// Invulnerability Powerup ---------------------------------------------------
|
|
|
|
IMPLEMENT_CLASS(APowerInvulnerable, false, false)
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerInvulnerable :: InitEffect
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerInvulnerable::InitEffect ()
|
|
{
|
|
Super::InitEffect();
|
|
Owner->effects &= ~FX_RESPAWNINVUL;
|
|
Owner->flags2 |= MF2_INVULNERABLE;
|
|
if (Mode == NAME_None && Owner->IsKindOf(RUNTIME_CLASS(APlayerPawn)))
|
|
{
|
|
Mode = static_cast<PClassPlayerPawn *>(Owner->GetClass())->InvulMode;
|
|
}
|
|
if (Mode == NAME_Reflective)
|
|
{
|
|
Owner->flags2 |= MF2_REFLECTIVE;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerInvulnerable :: DoEffect
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerInvulnerable::DoEffect ()
|
|
{
|
|
Super::DoEffect ();
|
|
|
|
if (Owner == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Mode == NAME_Ghost)
|
|
{
|
|
if (!(Owner->flags & MF_SHADOW))
|
|
{
|
|
// Don't mess with the translucency settings if an
|
|
// invisibility powerup is active.
|
|
Owner->RenderStyle = STYLE_Translucent;
|
|
if (!(level.time & 7) && Owner->Alpha > 0 && Owner->Alpha < 1)
|
|
{
|
|
if (Owner->Alpha == HX_SHADOW)
|
|
{
|
|
Owner->Alpha = HX_ALTSHADOW;
|
|
}
|
|
else
|
|
{
|
|
Owner->Alpha = 0;
|
|
Owner->flags2 |= MF2_NONSHOOTABLE;
|
|
}
|
|
}
|
|
if (!(level.time & 31))
|
|
{
|
|
if (Owner->Alpha == 0)
|
|
{
|
|
Owner->flags2 &= ~MF2_NONSHOOTABLE;
|
|
Owner->Alpha = HX_ALTSHADOW;
|
|
}
|
|
else
|
|
{
|
|
Owner->Alpha = HX_SHADOW;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Owner->flags2 &= ~MF2_NONSHOOTABLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerInvulnerable :: EndEffect
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerInvulnerable::EndEffect ()
|
|
{
|
|
Super::EndEffect();
|
|
|
|
if (Owner == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Owner->flags2 &= ~MF2_INVULNERABLE;
|
|
Owner->effects &= ~FX_RESPAWNINVUL;
|
|
if (Mode == NAME_Ghost)
|
|
{
|
|
Owner->flags2 &= ~MF2_NONSHOOTABLE;
|
|
if (!(Owner->flags & MF_SHADOW))
|
|
{
|
|
// Don't mess with the translucency settings if an
|
|
// invisibility powerup is active.
|
|
Owner->RenderStyle = STYLE_Normal;
|
|
Owner->Alpha = 1.;
|
|
}
|
|
}
|
|
else if (Mode == NAME_Reflective)
|
|
{
|
|
Owner->flags2 &= ~MF2_REFLECTIVE;
|
|
}
|
|
|
|
if (Owner->player != NULL)
|
|
{
|
|
Owner->player->fixedcolormap = NOFIXEDCOLORMAP;
|
|
}
|
|
}
|
|
|
|
// Speed Powerup -------------------------------------------------------------
|
|
|
|
IMPLEMENT_CLASS(APowerSpeed, false, false)
|
|
|
|
DEFINE_FIELD(APowerSpeed, SpeedFlags)
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerSpeed :: Serialize
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerSpeed::Serialize(FSerializer &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
arc("speedflags", SpeedFlags);
|
|
}
|
|
|
|
// Morph powerup ------------------------------------------------------
|
|
|
|
IMPLEMENT_CLASS(APowerMorph, false, true)
|
|
|
|
IMPLEMENT_POINTERS_START(APowerMorph)
|
|
IMPLEMENT_POINTER(PlayerClass)
|
|
IMPLEMENT_POINTER(MorphFlash)
|
|
IMPLEMENT_POINTER(UnMorphFlash)
|
|
IMPLEMENT_POINTERS_END
|
|
|
|
|
|
DEFINE_FIELD(APowerMorph, PlayerClass)
|
|
DEFINE_FIELD(APowerMorph, MorphFlash)
|
|
DEFINE_FIELD(APowerMorph, UnMorphFlash)
|
|
DEFINE_FIELD(APowerMorph, MorphStyle)
|
|
DEFINE_FIELD(APowerMorph, MorphedPlayer)
|
|
|
|
//===========================================================================
|
|
//
|
|
// APowerMorph :: Serialize
|
|
//
|
|
//===========================================================================
|
|
|
|
void APowerMorph::Serialize(FSerializer &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
arc("playerclass", PlayerClass)
|
|
("morphstyle", MorphStyle)
|
|
("morphflash", MorphFlash)
|
|
("unmorphflash", UnMorphFlash)
|
|
("morphedplayer", MorphedPlayer);
|
|
}
|
|
|