Merge remote-tracking branch 'gzdoom/master' into qzdoom

# Conflicts:
#	src/r_things.cpp
#	src/r_things.h
This commit is contained in:
Magnus Norddahl 2017-01-16 23:05:34 +01:00
commit 2848ca53dc
23 changed files with 531 additions and 494 deletions

View file

@ -609,7 +609,7 @@ endif()
# Libraries ZDoom needs # Libraries ZDoom needs
message( STATUS "Fluid synth libs: ${FLUIDSYNTH_LIBRARIES}" ) message( STATUS "Fluid synth libs: ${FLUIDSYNTH_LIBRARIES}" )
set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" "${GME_LIBRARIES}" ) set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" "${GME_LIBRARIES}" "${CMAKE_DL_LIBS}" )
include_directories( "${ZLIB_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" "${GME_INCLUDE_DIR}" ) include_directories( "${ZLIB_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" "${GME_INCLUDE_DIR}" )
if( SNDFILE_FOUND ) if( SNDFILE_FOUND )
@ -625,8 +625,6 @@ if( NOT DYN_FLUIDSYNTH )
set( ZDOOM_LIBS ${ZDOOM_LIBS} "${FLUIDSYNTH_LIBRARIES}" ) set( ZDOOM_LIBS ${ZDOOM_LIBS} "${FLUIDSYNTH_LIBRARIES}" )
include_directories( "${FLUIDSYNTH_INCLUDE_DIR}" ) include_directories( "${FLUIDSYNTH_INCLUDE_DIR}" )
endif() endif()
else()
set( ZDOOM_LIBS ${ZDOOM_LIBS} ${CMAKE_DL_LIBS} )
endif() endif()
# Start defining source files for ZDoom # Start defining source files for ZDoom

View file

@ -46,7 +46,7 @@
struct subsector_t; struct subsector_t;
struct FBlockNode; struct FBlockNode;
struct FPortalGroupArray; struct FPortalGroupArray;
struct visstyle_t;
// //
// NOTES: AActor // NOTES: AActor
// //
@ -616,6 +616,8 @@ public:
// Adjusts the angle for deflection/reflection of incoming missiles // Adjusts the angle for deflection/reflection of incoming missiles
// Returns true if the missile should be allowed to explode anyway // Returns true if the missile should be allowed to explode anyway
bool AdjustReflectionAngle (AActor *thing, DAngle &angle); bool AdjustReflectionAngle (AActor *thing, DAngle &angle);
int AbsorbDamage(int damage, FName dmgtype);
void AlterWeaponSprite(visstyle_t *vis);
// Returns true if this actor is within melee range of its target // Returns true if this actor is within melee range of its target
bool CheckMeleeRange(); bool CheckMeleeRange();

View file

@ -229,16 +229,14 @@ void ABasicArmor::AbsorbDamage (int damage, FName damageType, int &newdamage)
// This code is taken and adapted from APowerProtection::ModifyDamage(). // This code is taken and adapted from APowerProtection::ModifyDamage().
// The differences include not using a default value, and of course the way // The differences include not using a default value, and of course the way
// the damage factor info is obtained. // the damage factor info is obtained.
// ApplyDamageFactors(ArmorType, damageType, damage, damage);
DmgFactors *df = PClass::FindActor(ArmorType)->DamageFactors; DmgFactors *df = PClass::FindActor(ArmorType)->DamageFactors;
if (df != NULL) if (df != NULL)
{ {
damage = newdamage = df->Apply(damageType, damage); damage = newdamage = df->Apply(damageType, damage);
} }
} }
if (Inventory != NULL)
{
Inventory->AbsorbDamage (damage, damageType, newdamage);
}
} }
//=========================================================================== //===========================================================================
@ -629,10 +627,6 @@ void AHexenArmor::AbsorbDamage (int damage, FName damageType, int &newdamage)
damage = newdamage; damage = newdamage;
} }
} }
if (Inventory != NULL)
{
Inventory->AbsorbDamage (damage, damageType, newdamage);
}
} }
//=========================================================================== //===========================================================================

View file

@ -540,370 +540,6 @@ void APowerInvulnerable::EndEffect ()
} }
} }
//===========================================================================
//
// APowerInvulnerable :: AlterWeaponSprite
//
//===========================================================================
int APowerInvulnerable::AlterWeaponSprite (visstyle_t *vis)
{
int changed = Inventory == NULL ? false : Inventory->AlterWeaponSprite(vis);
if (Owner != NULL)
{
if (Mode == NAME_Ghost && !(Owner->flags & MF_SHADOW))
{
vis->Alpha = MIN<float>(0.25f + (float)Owner->Alpha*0.75f, 1.f);
}
}
return changed;
}
// Strength (aka Berserk) Powerup --------------------------------------------
IMPLEMENT_CLASS(APowerStrength, false, false)
//===========================================================================
//
// APowerStrength :: HandlePickup
//
//===========================================================================
bool APowerStrength::HandlePickup (AInventory *item)
{
if (item->GetClass() == GetClass())
{ // Setting EffectTics to 0 will force Powerup's HandlePickup()
// method to reset the tic count so you get the red flash again.
EffectTics = 0;
}
return Super::HandlePickup (item);
}
//===========================================================================
//
// APowerStrength :: InitEffect
//
//===========================================================================
void APowerStrength::InitEffect ()
{
Super::InitEffect();
}
//===========================================================================
//
// APowerStrength :: DoEffect
//
//===========================================================================
void APowerStrength::Tick ()
{
// Strength counts up to diminish the fade.
assert(EffectTics < (INT_MAX - 1)); // I can't see a game lasting nearly two years, but...
EffectTics += 2;
Super::Tick();
}
//===========================================================================
//
// APowerStrength :: GetBlend
//
//===========================================================================
PalEntry APowerStrength::GetBlend ()
{
// slowly fade the berserk out
int cnt = 12 - (EffectTics >> 6);
if (cnt > 0)
{
cnt = (cnt + 7) >> 3;
return PalEntry (BlendColor.a*cnt*255/9,
BlendColor.r, BlendColor.g, BlendColor.b);
}
return 0;
}
// Invisibility Powerup ------------------------------------------------------
IMPLEMENT_CLASS(APowerInvisibility, false, false)
// Invisibility flag combos
#define INVISIBILITY_FLAGS1 (MF_SHADOW)
#define INVISIBILITY_FLAGS3 (MF3_GHOST)
#define INVISIBILITY_FLAGS5 (MF5_CANTSEEK)
//===========================================================================
//
// APowerInvisibility :: InitEffect
//
//===========================================================================
void APowerInvisibility::InitEffect ()
{
Super::InitEffect();
// This used to call CommonInit(), which used to contain all the code that's repeated every
// tic, plus the following code that needs to happen once and only once.
// The CommonInit() code has been moved to DoEffect(), so this now ends with a call to DoEffect(),
// and DoEffect() no longer needs to call InitEffect(). CommonInit() has been removed for being redundant.
if (Owner != NULL)
{
flags &= ~(Owner->flags & INVISIBILITY_FLAGS1);
Owner->flags |= flags & INVISIBILITY_FLAGS1;
flags3 &= ~(Owner->flags3 & INVISIBILITY_FLAGS3);
Owner->flags3 |= flags3 & INVISIBILITY_FLAGS3;
flags5 &= ~(Owner->flags5 & INVISIBILITY_FLAGS5);
Owner->flags5 |= flags5 & INVISIBILITY_FLAGS5;
CallDoEffect();
}
}
//===========================================================================
//
// APowerInvisibility :: DoEffect
//
//===========================================================================
void APowerInvisibility::DoEffect ()
{
Super::DoEffect();
// Due to potential interference with other PowerInvisibility items
// the effect has to be refreshed each tic.
double ts = (Strength / 100) * (special1 + 1);
if (ts > 1.) ts = 1.;
Owner->Alpha = clamp((1. - ts), 0., 1.);
switch (Mode)
{
case (NAME_Fuzzy):
Owner->RenderStyle = STYLE_OptFuzzy;
break;
case (NAME_Opaque):
Owner->RenderStyle = STYLE_Normal;
break;
case (NAME_Additive):
Owner->RenderStyle = STYLE_Add;
break;
case (NAME_Stencil):
Owner->RenderStyle = STYLE_Stencil;
break;
case (NAME_AddStencil) :
Owner->RenderStyle = STYLE_AddStencil;
break;
case (NAME_TranslucentStencil) :
Owner->RenderStyle = STYLE_TranslucentStencil;
break;
case (NAME_None) :
case (NAME_Cumulative):
case (NAME_Translucent):
Owner->RenderStyle = STYLE_Translucent;
break;
default: // Something's wrong
Owner->RenderStyle = STYLE_Normal;
Owner->Alpha = 1.;
break;
}
}
//===========================================================================
//
// APowerInvisibility :: EndEffect
//
//===========================================================================
void APowerInvisibility::EndEffect ()
{
Super::EndEffect();
if (Owner != NULL)
{
Owner->flags &= ~(flags & INVISIBILITY_FLAGS1);
Owner->flags3 &= ~(flags3 & INVISIBILITY_FLAGS3);
Owner->flags5 &= ~(flags5 & INVISIBILITY_FLAGS5);
Owner->RenderStyle = STYLE_Normal;
Owner->Alpha = 1.;
// Check whether there are other invisibility items and refresh their effect.
// If this isn't done there will be one incorrectly drawn frame when this
// item expires.
AInventory *item = Owner->Inventory;
while (item != NULL)
{
if (item->IsKindOf(RUNTIME_CLASS(APowerInvisibility)) && item != this)
{
static_cast<APowerInvisibility*>(item)->DoEffect();
}
item = item->Inventory;
}
}
}
//===========================================================================
//
// APowerInvisibility :: AlterWeaponSprite
//
//===========================================================================
int APowerInvisibility::AlterWeaponSprite (visstyle_t *vis)
{
int changed = Inventory == NULL ? false : Inventory->AlterWeaponSprite(vis);
// Blink if the powerup is wearing off
if (changed == 0 && EffectTics < 4*32 && !(EffectTics & 8))
{
vis->RenderStyle = STYLE_Normal;
vis->Alpha = 1.f;
return 1;
}
else if (changed == 1)
{
// something else set the weapon sprite back to opaque but this item is still active.
float ts = float((Strength / 100) * (special1 + 1));
vis->Alpha = clamp<>((1.f - ts), 0.f, 1.f);
switch (Mode)
{
case (NAME_Fuzzy):
vis->RenderStyle = STYLE_OptFuzzy;
break;
case (NAME_Opaque):
vis->RenderStyle = STYLE_Normal;
break;
case (NAME_Additive):
vis->RenderStyle = STYLE_Add;
break;
case (NAME_Stencil):
vis->RenderStyle = STYLE_Stencil;
break;
case (NAME_TranslucentStencil) :
vis->RenderStyle = STYLE_TranslucentStencil;
break;
case (NAME_AddStencil) :
vis->RenderStyle = STYLE_AddStencil;
break;
case (NAME_None) :
case (NAME_Cumulative):
case (NAME_Translucent):
default:
vis->RenderStyle = STYLE_Translucent;
break;
}
}
// Handling of Strife-like cumulative invisibility powerups, the weapon itself shouldn't become invisible
if ((vis->Alpha < 0.25f && special1 > 0) || (vis->Alpha == 0))
{
vis->Alpha = clamp((1.f - float(Strength/100)), 0.f, 1.f);
vis->colormap = SpecialColormaps[INVERSECOLORMAP].Colormap;
}
return -1; // This item is valid so another one shouldn't reset the translucency
}
//===========================================================================
//
// APowerInvisibility :: HandlePickup
//
// If the player already has the first stage of a cumulative powerup, getting
// it again increases the player's alpha. (But shouldn't this be in Use()?)
//
//===========================================================================
bool APowerInvisibility::HandlePickup (AInventory *item)
{
if (Mode == NAME_Cumulative && ((Strength * special1) < 1.) && item->GetClass() == GetClass())
{
APowerup *power = static_cast<APowerup *>(item);
if (power->EffectTics == 0)
{
power->ItemFlags |= IF_PICKUPGOOD;
return true;
}
// Only increase the EffectTics, not decrease it.
// Color also gets transferred only when the new item has an effect.
if (power->EffectTics > EffectTics)
{
EffectTics = power->EffectTics;
BlendColor = power->BlendColor;
}
special1++; // increases power
power->ItemFlags |= IF_PICKUPGOOD;
return true;
}
return Super::HandlePickup (item);
}
// Ironfeet Powerup ----------------------------------------------------------
IMPLEMENT_CLASS(APowerIronFeet, false, false)
//===========================================================================
//
// APowerIronFeet :: AbsorbDamage
//
//===========================================================================
void APowerIronFeet::AbsorbDamage (int damage, FName damageType, int &newdamage)
{
if (damageType == NAME_Drowning)
{
newdamage = 0;
}
else if (Inventory != NULL)
{
Inventory->AbsorbDamage (damage, damageType, newdamage);
}
}
//===========================================================================
//
// APowerIronFeet :: DoEffect
//
//===========================================================================
void APowerIronFeet::DoEffect ()
{
if (Owner->player != NULL)
{
Owner->player->mo->ResetAirSupply ();
}
}
// Strife Environment Suit Powerup -------------------------------------------
IMPLEMENT_CLASS(APowerMask, false, false)
//===========================================================================
//
// APowerMask :: AbsorbDamage
//
//===========================================================================
void APowerMask::AbsorbDamage (int damage, FName damageType, int &newdamage)
{
if (damageType == NAME_Fire)
{
newdamage = 0;
}
else
{
Super::AbsorbDamage (damage, damageType, newdamage);
}
}
//===========================================================================
//
// APowerMask :: DoEffect
//
//===========================================================================
void APowerMask::DoEffect ()
{
Super::DoEffect ();
if (!(level.time & 0x3f))
{
S_Sound (Owner, CHAN_AUTO, "misc/mask", 1, ATTN_STATIC);
}
}
// Speed Powerup ------------------------------------------------------------- // Speed Powerup -------------------------------------------------------------
IMPLEMENT_CLASS(APowerSpeed, false, false) IMPLEMENT_CLASS(APowerSpeed, false, false)

View file

@ -64,45 +64,6 @@ protected:
virtual void InitEffect () override; virtual void InitEffect () override;
virtual void DoEffect () override; virtual void DoEffect () override;
virtual void EndEffect () override; virtual void EndEffect () override;
virtual int AlterWeaponSprite (visstyle_t *vis) override;
};
class APowerStrength : public APowerup
{
DECLARE_CLASS (APowerStrength, APowerup)
public:
PalEntry GetBlend ();
protected:
virtual void InitEffect () override;
virtual void Tick () override;
virtual bool HandlePickup (AInventory *item) override;
};
class APowerInvisibility : public APowerup
{
DECLARE_CLASS (APowerInvisibility, APowerup)
protected:
virtual bool HandlePickup (AInventory *item) override;
virtual void InitEffect () override;
virtual void DoEffect () override;
virtual void EndEffect () override;
virtual int AlterWeaponSprite (visstyle_t *vis) override;
};
class APowerIronFeet : public APowerup
{
DECLARE_CLASS (APowerIronFeet, APowerup)
public:
virtual void AbsorbDamage (int damage, FName damageType, int &newdamage) override;
virtual void DoEffect () override;
};
class APowerMask : public APowerIronFeet
{
DECLARE_CLASS (APowerMask, APowerIronFeet)
public:
virtual void AbsorbDamage (int damage, FName damageType, int &newdamage) override;
virtual void DoEffect () override;
}; };
class APowerSpeed : public APowerup class APowerSpeed : public APowerup

View file

@ -697,12 +697,17 @@ DEFINE_ACTION_FUNCTION(AInventory, BecomePickup)
void AInventory::AbsorbDamage (int damage, FName damageType, int &newdamage) void AInventory::AbsorbDamage (int damage, FName damageType, int &newdamage)
{ {
if (Inventory != NULL)
{
Inventory->AbsorbDamage (damage, damageType, newdamage);
}
} }
DEFINE_ACTION_FUNCTION(AInventory, AbsorbDamage)
{
PARAM_SELF_PROLOGUE(AInventory);
PARAM_INT(damage);
PARAM_NAME(type);
PARAM_POINTER(newdmg, int);
self->AbsorbDamage(damage, type, *newdmg);
return 0;
}
//=========================================================================== //===========================================================================
// //
// AInventory :: ModifyDamage // AInventory :: ModifyDamage
@ -778,24 +783,6 @@ bool AInventory::GetNoTeleportFreeze ()
return false; return false;
} }
//===========================================================================
//
// 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 // AInventory :: Use

View file

@ -133,7 +133,6 @@ public:
// visual stuff is for later. Right now the VM has not yet access to the needed functionality. // visual stuff is for later. Right now the VM has not yet access to the needed functionality.
virtual bool DrawPowerup(int x, int y); virtual bool DrawPowerup(int x, int y);
virtual int AlterWeaponSprite(visstyle_t *vis);
// virtual on the script side only. // virtual on the script side only.

View file

@ -297,8 +297,8 @@ static void DrawHealth(player_t *CPlayer, int x, int y)
CR_BLUE; CR_BLUE;
const bool haveBerserk = hud_berserk_health const bool haveBerserk = hud_berserk_health
&& NULL != berserkpic && nullptr != berserkpic
&& NULL != CPlayer->mo->FindInventory< APowerStrength >(); && nullptr != CPlayer->mo->FindInventory(PClass::FindActor(NAME_PowerStrength));
DrawImageToBox(haveBerserk ? berserkpic : healthpic, x, y, 31, 17); DrawImageToBox(haveBerserk ? berserkpic : healthpic, x, y, 31, 17);
DrawHudNumber(HudFont, fontcolor, health, x + 33, y + 17); DrawHudNumber(HudFont, fontcolor, health, x + 33, y + 17);

View file

@ -310,21 +310,21 @@ void FGLRenderer::DrawPlayerSprites(sector_t * viewsector, bool hudModelStep)
visstyle_t vis; visstyle_t vis;
vis.RenderStyle=playermo->RenderStyle; vis.RenderStyle = STYLE_Count;
vis.Alpha=playermo->Alpha; vis.Alpha = playermo->Alpha;
vis.colormap = NULL; vis.Invert = false;
if (playermo->Inventory) playermo->AlterWeaponSprite(&vis);
FRenderStyle RenderStyle;
if (vis.RenderStyle == STYLE_Count) RenderStyle = playermo->RenderStyle;
else RenderStyle = vis.RenderStyle;
if (vis.Invert)
{ {
playermo->Inventory->AlterWeaponSprite(&vis); // this only happens for Strife's inverted weapon sprite
if (vis.colormap >= SpecialColormaps[0].Colormap && RenderStyle.Flags |= STYLEF_InvertSource;
vis.colormap < SpecialColormaps[SpecialColormaps.Size()].Colormap &&
gl_fixedcolormap == CM_DEFAULT)
{
// this only happens for Strife's inverted weapon sprite
vis.RenderStyle.Flags |= STYLEF_InvertSource;
}
} }
if (vis.RenderStyle.AsDWORD == 0) if (RenderStyle.AsDWORD == 0)
{ {
// This is RenderStyle None. // This is RenderStyle None.
return; return;
@ -334,32 +334,32 @@ void FGLRenderer::DrawPlayerSprites(sector_t * viewsector, bool hudModelStep)
int OverrideShader = -1; int OverrideShader = -1;
float trans = 0.f; float trans = 0.f;
if (vis.RenderStyle.BlendOp >= STYLEOP_Fuzz && vis.RenderStyle.BlendOp <= STYLEOP_FuzzOrRevSub) if (RenderStyle.BlendOp >= STYLEOP_Fuzz && RenderStyle.BlendOp <= STYLEOP_FuzzOrRevSub)
{ {
vis.RenderStyle.CheckFuzz(); RenderStyle.CheckFuzz();
if (vis.RenderStyle.BlendOp == STYLEOP_Fuzz) if (RenderStyle.BlendOp == STYLEOP_Fuzz)
{ {
if (gl_fuzztype != 0) if (gl_fuzztype != 0)
{ {
// Todo: implement shader selection here // Todo: implement shader selection here
vis.RenderStyle = LegacyRenderStyles[STYLE_Translucent]; RenderStyle = LegacyRenderStyles[STYLE_Translucent];
OverrideShader = gl_fuzztype + 4; OverrideShader = gl_fuzztype + 4;
trans = 0.99f; // trans may not be 1 here trans = 0.99f; // trans may not be 1 here
} }
else else
{ {
vis.RenderStyle.BlendOp = STYLEOP_Shadow; RenderStyle.BlendOp = STYLEOP_Shadow;
} }
} }
} }
gl_SetRenderStyle(vis.RenderStyle, false, false); gl_SetRenderStyle(RenderStyle, false, false);
if (vis.RenderStyle.Flags & STYLEF_TransSoulsAlpha) if (RenderStyle.Flags & STYLEF_TransSoulsAlpha)
{ {
trans = transsouls; trans = transsouls;
} }
else if (vis.RenderStyle.Flags & STYLEF_Alpha1) else if (RenderStyle.Flags & STYLEF_Alpha1)
{ {
trans = 1.f; trans = 1.f;
} }
@ -402,7 +402,7 @@ void FGLRenderer::DrawPlayerSprites(sector_t * viewsector, bool hudModelStep)
ll = 255; ll = 255;
} }
// set the lighting parameters // set the lighting parameters
if (vis.RenderStyle.BlendOp == STYLEOP_Shadow) if (RenderStyle.BlendOp == STYLEOP_Shadow)
{ {
gl_RenderState.SetColor(0.2f, 0.2f, 0.2f, 0.33f, cmc.desaturation); gl_RenderState.SetColor(0.2f, 0.2f, 0.2f, 0.33f, cmc.desaturation);
} }
@ -438,7 +438,7 @@ void FGLRenderer::DrawPlayerSprites(sector_t * viewsector, bool hudModelStep)
} }
DrawPSprite(player, psp, sx, sy, hudModelStep, OverrideShader, !!(vis.RenderStyle.Flags & STYLEF_RedIsAlpha)); DrawPSprite(player, psp, sx, sy, hudModelStep, OverrideShader, !!(RenderStyle.Flags & STYLEF_RedIsAlpha));
} }
} }
gl_RenderState.SetObjectColor(0xffffffff); gl_RenderState.SetObjectColor(0xffffffff);

View file

@ -1305,7 +1305,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da
int newdam = damage; int newdam = damage;
if (damage > 0) if (damage > 0)
{ {
player->mo->Inventory->AbsorbDamage(damage, mod, newdam); newdam = player->mo->AbsorbDamage(damage, mod);
} }
if (!telefragDamage || (player->mo->flags7 & MF7_LAXTELEFRAGDMG)) //rawdamage is never modified. if (!telefragDamage || (player->mo->flags7 & MF7_LAXTELEFRAGDMG)) //rawdamage is never modified.
{ {
@ -1385,7 +1385,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da
if (!(flags & (DMG_NO_ARMOR|DMG_FORCED)) && target->Inventory != NULL && damage > 0) if (!(flags & (DMG_NO_ARMOR|DMG_FORCED)) && target->Inventory != NULL && damage > 0)
{ {
int newdam = damage; int newdam = damage;
target->Inventory->AbsorbDamage (damage, mod, newdam); newdam = target->AbsorbDamage(damage, mod);
damage = newdam; damage = newdam;
if (damage <= 0) if (damage <= 0)
{ {

View file

@ -3615,6 +3615,39 @@ bool AActor::AdjustReflectionAngle (AActor *thing, DAngle &angle)
return false; return false;
} }
int AActor::AbsorbDamage(int damage, FName dmgtype)
{
for (AInventory *item = Inventory; item != nullptr; item = item->Inventory)
{
IFVIRTUALPTR(item, AInventory, AbsorbDamage)
{
VMValue params[4] = { item, damage, dmgtype.GetIndex(), &damage };
GlobalVMStack.Call(func, params, 4, nullptr, 0, nullptr);
}
else item->AbsorbDamage(damage, dmgtype, damage);
}
return damage;
}
void AActor::AlterWeaponSprite(visstyle_t *vis)
{
int changed = 0;
TArray<AInventory *> items;
// This needs to go backwards through the items but the list has no backlinks.
for (AInventory *item = Inventory; item != nullptr; item = item->Inventory)
{
items.Push(item);
}
for(int i=items.Size()-1;i>=0;i--)
{
IFVIRTUALPTR(items[i], AInventory, AlterWeaponSprite)
{
VMValue params[3] = { items[i], vis, &changed };
GlobalVMStack.Call(func, params, 3, nullptr, 0, nullptr);
}
}
}
void AActor::PlayActiveSound () void AActor::PlayActiveSound ()
{ {
if (ActiveSound && !S_IsActorPlayingSomething (this, CHAN_VOICE, -1)) if (ActiveSound && !S_IsActorPlayingSomething (this, CHAN_VOICE, -1))

View file

@ -394,9 +394,10 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector)
// Allow subclasses. Better would be to implement it as armor and let that reduce // Allow subclasses. Better would be to implement it as armor and let that reduce
// the damage as part of the normal damage procedure. Unfortunately, I don't have // the damage as part of the normal damage procedure. Unfortunately, I don't have
// different damage types yet, so that's not happening for now. // different damage types yet, so that's not happening for now.
auto pitype = PClass::FindActor(NAME_PowerIronFeet);
for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory) for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
{ {
if (ironfeet->IsKindOf (RUNTIME_CLASS(APowerIronFeet))) if (ironfeet->IsKindOf(pitype))
break; break;
} }
@ -578,9 +579,10 @@ void P_PlayerOnSpecialFlat (player_t *player, int floorType)
if (Terrains[floorType].AllowProtection) if (Terrains[floorType].AllowProtection)
{ {
auto pitype = PClass::FindActor(NAME_PowerIronFeet);
for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory) for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
{ {
if (ironfeet->IsKindOf (RUNTIME_CLASS(APowerIronFeet))) if (ironfeet->IsKindOf (pitype))
break; break;
} }
} }

View file

@ -289,19 +289,26 @@ void RenderPolyPlayerSprites::RenderSprite(DPSprite *sprite, AActor *owner, floa
{ {
visstyle_t visstyle; visstyle_t visstyle;
visstyle.Alpha = Alpha; visstyle.Alpha = Alpha;
visstyle.RenderStyle = RenderStyle; visstyle.RenderStyle = STYLE_Count;
visstyle.colormap = nullptr; // Same as the GL render is doing. visstyle.Invert = false;
camera->Inventory->AlterWeaponSprite(&visstyle); camera->Inventory->AlterWeaponSprite(&visstyle);
RenderStyle = visstyle.RenderStyle;
Alpha = visstyle.Alpha; Alpha = visstyle.Alpha;
// Only bother checking for the one type it changes it to until this has been ZScript'ed.. if (visstyle.RenderStyle != STYLE_Count)
if (visstyle.colormap == SpecialColormaps[INVERSECOLORMAP].Colormap) {
RenderStyle = visstyle.RenderStyle;
}
if (visstyle.Invert)
{ {
BaseColormap = &SpecialColormaps[INVERSECOLORMAP]; BaseColormap = &SpecialColormaps[INVERSECOLORMAP];
ColormapNum = 0; ColormapNum = 0;
if (BaseColormap->Maps < mybasecolormap->Maps || BaseColormap->Maps >= mybasecolormap->Maps + NUMCOLORMAPS * 256)
{
noaccel = true;
}
} }
} }

View file

@ -1488,9 +1488,9 @@ typedef BYTE lighttable_t; // This could be wider for >8 bit display.
// This encapsulates the fields of vissprite_t that can be altered by AlterWeaponSprite // This encapsulates the fields of vissprite_t that can be altered by AlterWeaponSprite
struct visstyle_t struct visstyle_t
{ {
lighttable_t *colormap; bool Invert;
float Alpha; float Alpha;
FRenderStyle RenderStyle; ERenderStyle RenderStyle;
}; };

View file

@ -2127,6 +2127,7 @@ CCMD (soundlist)
{ {
Printf ("%3d. %s **not present**\n", i, sfx->name.GetChars()); Printf ("%3d. %s **not present**\n", i, sfx->name.GetChars());
} }
Printf(" PitchMask = %d\n", sfx->PitchMask);
} }
} }

View file

@ -5921,8 +5921,7 @@ FxExpression *FxMemberIdentifier::Resolve(FCompileContext& ctx)
{ {
Object->ValueType = TypeColorStruct; Object->ValueType = TypeColorStruct;
} }
if (Object->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)))
else if (Object->ValueType->IsKindOf(RUNTIME_CLASS(PPointer)))
{ {
auto ptype = static_cast<PPointer *>(Object->ValueType)->PointedType; auto ptype = static_cast<PPointer *>(Object->ValueType)->PointedType;
if (ptype->IsKindOf(RUNTIME_CLASS(PStruct))) if (ptype->IsKindOf(RUNTIME_CLASS(PStruct)))

View file

@ -819,6 +819,68 @@ static bool ParsePropertyParams(FScanner &sc, FPropertyInfo *prop, AActor *defau
return true; return true;
} }
//==========================================================================
//
// Parses an actor property's parameters and calls the handler
//
//==========================================================================
static void DispatchScriptProperty(FScanner &sc, PProperty *prop, AActor *defaults, Baggage &bag)
{
for (unsigned i=0; i<prop->Variables.Size();i++)
{
auto f = prop->Variables[i];
void *addr;
if (i > 0) sc.MustGetStringName(",");
if (f->Flags & VARF_Meta)
{
addr = ((char*)bag.Info) + f->Offset;
}
else
{
addr = ((char*)defaults) + f->Offset;
}
if (f->Type->IsKindOf(RUNTIME_CLASS(PInt)))
{
sc.MustGetNumber();
static_cast<PInt*>(f->Type)->SetValue(addr, sc.Number);
}
else if (f->Type->IsKindOf(RUNTIME_CLASS(PFloat)))
{
sc.MustGetFloat();
static_cast<PFloat*>(f->Type)->SetValue(addr, sc.Float);
}
else if (f->Type->IsKindOf(RUNTIME_CLASS(PString)))
{
sc.MustGetString();
*(FString*)addr = sc.String;
}
else if (f->Type->IsKindOf(RUNTIME_CLASS(PClassPointer)))
{
sc.MustGetString();
auto cls = PClass::FindClass(sc.String);
*(PClass**)addr = cls;
if (cls == nullptr)
{
cls = static_cast<PClassPointer*>(f->Type)->ClassRestriction->FindClassTentative(sc.String);
}
else if (!cls->IsDescendantOf(static_cast<PClassPointer*>(f->Type)->ClassRestriction))
{
sc.ScriptMessage("class %s is not compatible with property type %s", sc.String, static_cast<PClassPointer*>(f->Type)->ClassRestriction->TypeName.GetChars());
FScriptPosition::ErrorCounter++;
}
*(PClass**)addr = cls;
}
else
{
sc.ScriptMessage("unhandled property type %s", f->Type->DescriptiveName());
FScriptPosition::ErrorCounter++;
}
}
}
//========================================================================== //==========================================================================
// //
// Parses an actor property // Parses an actor property
@ -867,6 +929,17 @@ static void ParseActorProperty(FScanner &sc, Baggage &bag)
} }
else else
{ {
propname.Insert(0, "@property@");
FName name(propname, true);
if (name != NAME_None)
{
auto propp = dyn_cast<PProperty>(bag.Info->Symbols.FindSymbol(name, true));
if (propp != nullptr)
{
DispatchScriptProperty(sc, propp, (AActor *)bag.Info->Defaults, bag);
return;
}
}
sc.ScriptError("'%s' is an unknown actor property\n", propname.GetChars()); sc.ScriptError("'%s' is an unknown actor property\n", propname.GetChars());
} }
} }

View file

@ -1960,12 +1960,17 @@ void ZCCCompiler::DispatchScriptProperty(PProperty *prop, ZCC_PropertyStmt *prop
} }
else if (f->Type->IsKindOf(RUNTIME_CLASS(PClassPointer))) else if (f->Type->IsKindOf(RUNTIME_CLASS(PClassPointer)))
{ {
auto cls = PClass::FindClass(GetString(exp)); auto clsname = GetString(exp);
*(PClass**)addr = cls; auto cls = PClass::FindClass(clsname);
if (!cls->IsDescendantOf(static_cast<PClassPointer*>(f->Type)->ClassRestriction)) if (cls == nullptr)
{ {
Error(property, "class %s is not compatible with property type %s", cls->TypeName.GetChars(), static_cast<PClassPointer*>(f->Type)->ClassRestriction->TypeName.GetChars()); cls = static_cast<PClassPointer*>(f->Type)->ClassRestriction->FindClassTentative(clsname);
} }
else if (!cls->IsDescendantOf(static_cast<PClassPointer*>(f->Type)->ClassRestriction))
{
Error(property, "class %s is not compatible with property type %s", clsname, static_cast<PClassPointer*>(f->Type)->ClassRestriction->TypeName.GetChars());
}
*(PClass**)addr = cls;
} }
else else
{ {

View file

@ -495,7 +495,7 @@ void FSerializer::Close()
{ {
// we must explicitly delete all thinkers in the array which did not get linked into the thinker lists. // we must explicitly delete all thinkers in the array which did not get linked into the thinker lists.
// Otherwise these objects may survive a level deletion and point to incorrect data. // Otherwise these objects may survive a level deletion and point to incorrect data.
for (auto &obj : r->mDObjects) for (auto obj : r->mDObjects)
{ {
auto think = dyn_cast<DThinker>(obj); auto think = dyn_cast<DThinker>(obj);
if (think != nullptr) if (think != nullptr)

View file

@ -493,19 +493,26 @@ namespace swrenderer
{ {
visstyle_t visstyle; visstyle_t visstyle;
visstyle.Alpha = vis->Alpha; visstyle.Alpha = vis->Alpha;
visstyle.RenderStyle = vis->RenderStyle; visstyle.RenderStyle = STYLE_Count;
visstyle.colormap = nullptr; // Same as the GL render is doing. visstyle.Invert = false;
camera->Inventory->AlterWeaponSprite(&visstyle); camera->Inventory->AlterWeaponSprite(&visstyle);
vis->RenderStyle = visstyle.RenderStyle;
vis->Alpha = visstyle.Alpha; vis->Alpha = visstyle.Alpha;
// Only bother checking for the one type it changes it to until this has been ZScript'ed.. if (visstyle.RenderStyle != STYLE_Count)
if (visstyle.colormap == SpecialColormaps[INVERSECOLORMAP].Colormap) {
vis->RenderStyle = visstyle.RenderStyle;
}
if (visstyle.Invert)
{ {
vis->BaseColormap = &SpecialColormaps[INVERSECOLORMAP]; vis->BaseColormap = &SpecialColormaps[INVERSECOLORMAP];
vis->ColormapNum = 0; vis->ColormapNum = 0;
if (vis->BaseColormap->Maps < mybasecolormap->Maps || vis->BaseColormap->Maps >= mybasecolormap->Maps + NUMCOLORMAPS * 256)
{
noaccel = true;
}
} }
} }
// If we're drawing with a special colormap, but shaders for them are disabled, do // If we're drawing with a special colormap, but shaders for them are disabled, do

View file

@ -404,6 +404,7 @@ map E4M1 lookup "HUSTR_E4M1"
secretnext = "E4M9" secretnext = "E4M9"
sky1 = "SKY4" sky1 = "SKY4"
cluster = 4 cluster = 4
par = 165
music = "$MUSIC_E3M4" music = "$MUSIC_E3M4"
} }
@ -415,6 +416,7 @@ map E4M2 lookup "HUSTR_E4M2"
secretnext = "E4M9" secretnext = "E4M9"
sky1 = "SKY4" sky1 = "SKY4"
cluster = 4 cluster = 4
par = 255
music = "$MUSIC_E3M2" music = "$MUSIC_E3M2"
} }
@ -426,6 +428,7 @@ map E4M3 lookup "HUSTR_E4M3"
secretnext = "E4M9" secretnext = "E4M9"
sky1 = "SKY4" sky1 = "SKY4"
cluster = 4 cluster = 4
par = 135
music = "$MUSIC_E3M3" music = "$MUSIC_E3M3"
} }
@ -437,6 +440,7 @@ map E4M4 lookup "HUSTR_E4M4"
secretnext = "E4M9" secretnext = "E4M9"
sky1 = "SKY4" sky1 = "SKY4"
cluster = 4 cluster = 4
par = 150
music = "$MUSIC_E1M5" music = "$MUSIC_E1M5"
} }
@ -448,6 +452,7 @@ map E4M5 lookup "HUSTR_E4M5"
secretnext = "E4M9" secretnext = "E4M9"
sky1 = "SKY4" sky1 = "SKY4"
cluster = 4 cluster = 4
par = 180
music = "$MUSIC_E2M7" music = "$MUSIC_E2M7"
} }
@ -459,6 +464,7 @@ map E4M6 lookup "HUSTR_E4M6"
secretnext = "E4M9" secretnext = "E4M9"
sky1 = "SKY4" sky1 = "SKY4"
cluster = 4 cluster = 4
par = 390
cyberdemonspecial cyberdemonspecial
specialaction_opendoor specialaction_opendoor
music = "$MUSIC_E2M4" music = "$MUSIC_E2M4"
@ -472,6 +478,7 @@ map E4M7 lookup "HUSTR_E4M7"
secretnext = "E4M9" secretnext = "E4M9"
sky1 = "SKY4" sky1 = "SKY4"
cluster = 4 cluster = 4
par = 135
music = "$MUSIC_E2M6" music = "$MUSIC_E2M6"
} }
@ -483,6 +490,7 @@ map E4M8 lookup "HUSTR_E4M8"
secretnext = "E4M9" secretnext = "E4M9"
sky1 = "SKY4" sky1 = "SKY4"
cluster = 4 cluster = 4
par = 360
nointermission nointermission
nosoundclipping nosoundclipping
spidermastermindspecial spidermastermindspecial
@ -498,6 +506,7 @@ map E4M9 lookup "HUSTR_E4M9"
secretnext = "E4M3" secretnext = "E4M3"
sky1 = "SKY4" sky1 = "SKY4"
cluster = 4 cluster = 4
par = 180
music = "$MUSIC_E1M9" music = "$MUSIC_E1M9"
} }

View file

@ -1,3 +1,10 @@
struct VisStyle
{
bool Invert;
float Alpha;
int RenderStyle;
}
class Inventory : Actor native class Inventory : Actor native
{ {
const BLINKTHRESHOLD = (4*32); const BLINKTHRESHOLD = (4*32);
@ -39,6 +46,7 @@ class Inventory : Actor native
virtual native void AttachToOwner(Actor user); virtual native void AttachToOwner(Actor user);
virtual native void DetachFromOwner(); virtual native void DetachFromOwner();
virtual native bool DrawPowerup(int x, int y); virtual native bool DrawPowerup(int x, int y);
virtual native void AbsorbDamage (int damage, Name damageType, out int newdamage);
//=========================================================================== //===========================================================================
// //
@ -55,6 +63,7 @@ class Inventory : Actor native
virtual double GetSpeedFactor() { return 1; } virtual double GetSpeedFactor() { return 1; }
virtual bool GetNoTeleportFreeze() { return false; } virtual bool GetNoTeleportFreeze() { return false; }
virtual void ModifyDamage(int damage, Name damageType, out int newdamage, bool passive) {} virtual void ModifyDamage(int damage, Name damageType, out int newdamage, bool passive) {}
virtual void AlterWeaponSprite(VisStyle vis, in out int changed) {}
native bool GoAway(); native bool GoAway();
native void GoAwayAndDie(); native void GoAwayAndDie();

View file

@ -32,6 +32,12 @@ class Powerup : Inventory native
} }
//===========================================================================
//
// Invulnerable
//
//===========================================================================
class PowerInvulnerable : Powerup native class PowerInvulnerable : Powerup native
{ {
Default Default
@ -39,9 +45,26 @@ class PowerInvulnerable : Powerup native
Powerup.Duration -30; Powerup.Duration -30;
inventory.icon "SPSHLD0"; inventory.icon "SPSHLD0";
} }
override void AlterWeaponSprite (VisStyle vis, in out int changed)
{
if (Owner != NULL)
{
if (Mode == 'Ghost' && !(Owner.bShadow))
{
vis.Alpha = min(0.25 + Owner.Alpha * 0.75, 1.);
}
}
}
} }
class PowerStrength : Powerup native //===========================================================================
//
// Strength
//
//===========================================================================
class PowerStrength : Powerup
{ {
Default Default
{ {
@ -49,9 +72,58 @@ class PowerStrength : Powerup native
Powerup.Color "ff 00 00", 0.5; Powerup.Color "ff 00 00", 0.5;
+INVENTORY.HUBPOWER +INVENTORY.HUBPOWER
} }
override bool HandlePickup (Inventory item)
{
if (item.GetClass() == GetClass())
{ // Setting EffectTics to 0 will force Powerup's HandlePickup()
// method to reset the tic count so you get the red flash again.
EffectTics = 0;
}
return Super.HandlePickup (item);
}
//===========================================================================
//
// APowerStrength :: DoEffect
//
//===========================================================================
override void Tick ()
{
// Strength counts up to diminish the fade.
EffectTics += 2;
Super.Tick();
}
//===========================================================================
//
// APowerStrength :: GetBlend
//
//===========================================================================
override color GetBlend ()
{
// slowly fade the berserk out
int cnt = 128 - (EffectTics>>3);
if (cnt > 0)
{
return Color(BlendColor.a*cnt/256,
BlendColor.r, BlendColor.g, BlendColor.b);
}
return 0;
}
} }
class PowerInvisibility : Powerup native //===========================================================================
//
// Invisibility
//
//===========================================================================
class PowerInvisibility : Powerup
{ {
Default Default
{ {
@ -60,6 +132,202 @@ class PowerInvisibility : Powerup native
Powerup.Strength 80; Powerup.Strength 80;
Powerup.Mode "Fuzzy"; Powerup.Mode "Fuzzy";
} }
//===========================================================================
//
// APowerInvisibility :: InitEffect
//
//===========================================================================
override void InitEffect ()
{
Super.InitEffect();
let Owner = self.Owner;
if (Owner != NULL)
{
let savedShadow = Owner.bShadow;
let savedGhost = Owner.bGhost;
let savedCantSeek = Owner.bCantSeek;
Owner.bShadow = bShadow;
Owner.bGhost = bGhost;
Owner.bCantSeek = bCantSeek;
bShadow = savedShadow;
bGhost = savedGhost;
bCantSeek = savedCantSeek;
DoEffect();
}
}
//===========================================================================
//
// APowerInvisibility :: DoEffect
//
//===========================================================================
override void DoEffect ()
{
Super.DoEffect();
// Due to potential interference with other PowerInvisibility items
// the effect has to be refreshed each tic.
double ts = (Strength / 100) * (special1 + 1);
if (ts > 1.) ts = 1.;
let newAlpha = clamp((1. - ts), 0., 1.);
int newStyle;
switch (Mode)
{
case 'Fuzzy':
newStyle = STYLE_OptFuzzy;
break;
case 'Opaque':
newStyle = STYLE_Normal;
break;
case 'Additive':
newStyle = STYLE_Add;
break;
case 'Stencil':
newStyle = STYLE_Stencil;
break;
case 'AddStencil' :
newStyle = STYLE_AddStencil;
break;
case 'TranslucentStencil':
newStyle = STYLE_TranslucentStencil;
break;
case 'None' :
case 'Cumulative':
case 'Translucent':
newStyle = STYLE_Translucent;
break;
default: // Something's wrong
newStyle = STYLE_Normal;
newAlpha = 1.;
break;
}
Owner.A_SetRenderStyle(newAlpha, newStyle);
}
//===========================================================================
//
// APowerInvisibility :: EndEffect
//
//===========================================================================
override void EndEffect ()
{
Super.EndEffect();
if (Owner != NULL)
{
Owner.bShadow = bShadow;
Owner.bGhost = bGhost;
Owner.bCantSeek = bCantSeek;
Owner.A_SetRenderStyle(1, STYLE_Normal);
// Check whether there are other invisibility items and refresh their effect.
// If this isn't done there will be one incorrectly drawn frame when this
// item expires.
for(let item = Owner.Inv; item != null; item = item.Inv)
{
if (item != self && item is 'PowerInvisibility')
{
item.DoEffect();
}
}
}
}
//===========================================================================
//
// APowerInvisibility :: AlterWeaponSprite
//
//===========================================================================
override void AlterWeaponSprite (VisStyle vis, in out int changed)
{
// Blink if the powerup is wearing off
if (changed == 0 && EffectTics < 4*32 && !(EffectTics & 8))
{
vis.RenderStyle = STYLE_Normal;
vis.Alpha = 1.f;
changed = 1;
return;
}
else if (changed == 1)
{
// something else set the weapon sprite back to opaque but this item is still active.
float ts = float((Strength / 100) * (special1 + 1));
vis.Alpha = clamp((1. - ts), 0., 1.);
switch (Mode)
{
case 'Fuzzy':
vis.RenderStyle = STYLE_OptFuzzy;
break;
case 'Opaque':
vis.RenderStyle = STYLE_Normal;
break;
case 'Additive':
vis.RenderStyle = STYLE_Add;
break;
case 'Stencil':
vis.RenderStyle = STYLE_Stencil;
break;
case 'TranslucentStencil':
vis.RenderStyle = STYLE_TranslucentStencil;
break;
case 'AddStencil':
vis.RenderStyle = STYLE_AddStencil;
break;
case 'None':
case 'Cumulative':
case 'Translucent':
default:
vis.RenderStyle = STYLE_Translucent;
break;
}
}
// Handling of Strife-like cumulative invisibility powerups, the weapon itself shouldn't become invisible
if ((vis.Alpha < 0.25f && special1 > 0) || (vis.Alpha == 0))
{
vis.Alpha = clamp((1. - Strength/100.), 0., 1.);
vis.invert = true;
}
changed = -1; // This item is valid so another one shouldn't reset the translucency
}
//===========================================================================
//
// APowerInvisibility :: HandlePickup
//
// If the player already has the first stage of a cumulative powerup, getting
// it again increases the player's alpha. (But shouldn't this be in Use()?)
//
//===========================================================================
override bool HandlePickup (Inventory item)
{
if (Mode == 'Cumulative' && ((Strength * special1) < 1.) && item.GetClass() == GetClass())
{
let power = Powerup(item);
if (power.EffectTics == 0)
{
power.bPickupGood = true;
return true;
}
// Only increase the EffectTics, not decrease it.
// Color also gets transferred only when the new item has an effect.
if (power.EffectTics > EffectTics)
{
EffectTics = power.EffectTics;
BlendColor = power.BlendColor;
}
special1++; // increases power
power.bPickupGood = true;
return true;
}
return Super.HandlePickup (item);
}
} }
class PowerGhost : PowerInvisibility class PowerGhost : PowerInvisibility
@ -84,16 +352,45 @@ class PowerShadow : PowerInvisibility
} }
} }
class PowerIronFeet : Powerup native //===========================================================================
//
// IronFeet
//
//===========================================================================
class PowerIronFeet : Powerup
{ {
Default Default
{ {
Powerup.Duration -60; Powerup.Duration -60;
Powerup.Color "00 ff 00", 0.125; Powerup.Color "00 ff 00", 0.125;
} }
override void AbsorbDamage (int damage, Name damageType, out int newdamage)
{
if (damageType == 'Drowning')
{
newdamage = 0;
}
}
override void DoEffect ()
{
if (Owner.player != NULL)
{
Owner.player.mo.ResetAirSupply ();
}
}
} }
class PowerMask : PowerIronFeet native //===========================================================================
//
// Mask
//
//===========================================================================
class PowerMask : PowerIronFeet
{ {
Default Default
{ {
@ -102,6 +399,24 @@ class PowerMask : PowerIronFeet native
+INVENTORY.HUBPOWER +INVENTORY.HUBPOWER
Inventory.Icon "I_MASK"; Inventory.Icon "I_MASK";
} }
override void AbsorbDamage (int damage, Name damageType, out int newdamage)
{
if (damageType == 'Fire' || damageType == 'Drowning')
{
newdamage = 0;
}
}
override void DoEffect ()
{
Super.DoEffect ();
if (!(level.time & 0x3f))
{
Owner.A_PlaySound ("misc/mask", CHAN_AUTO);
}
}
} }
//=========================================================================== //===========================================================================