#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 "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, false, false) // Powerup-Giver ------------------------------------------------------------- IMPLEMENT_CLASS(PClassPowerupGiver, false, false, false, false) void PClassPowerupGiver::ReplaceClassRef(PClass *oldclass, PClass *newclass) { Super::ReplaceClassRef(oldclass, newclass); APowerupGiver *def = (APowerupGiver*)Defaults; if (def != NULL) { if (def->PowerupType == oldclass) def->PowerupType = static_cast(newclass); } } 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 APowerup *power = static_cast (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 (EffectTics <= BLINKTHRESHOLD && !(EffectTics & 8)) return 0; if (IsSpecialColormap(BlendColor)) return 0; return BlendColor; } //=========================================================================== // // APowerup :: InitEffect // //=========================================================================== void APowerup::InitEffect () { } //=========================================================================== // // APowerup :: DoEffect // //=========================================================================== void APowerup::DoEffect () { if (Owner == NULL || Owner->player == NULL) { return; } if (EffectTics > 0) { int Colormap = GetSpecialColormap(BlendColor); if (Colormap != NOFIXEDCOLORMAP) { if (EffectTics > BLINKTHRESHOLD || (EffectTics & 8)) { 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; } } //=========================================================================== // // APowerup :: Destroy // //=========================================================================== void APowerup::Destroy () { EndEffect (); Super::Destroy (); } //=========================================================================== // // APowerup :: DrawPowerup // //=========================================================================== bool APowerup::DrawPowerup (int x, int y) { if (!Icon.isValid()) { return false; } if (EffectTics > BLINKTHRESHOLD || !(EffectTics & 16)) { 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(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; } if (Inventory != NULL) { return Inventory->HandlePickup (item); } 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. InitEffect (); // 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 (); } //=========================================================================== // // AInventory :: GetNoTeleportFreeze // //=========================================================================== bool APowerup::GetNoTeleportFreeze () { if (ItemFlags & IF_NOTELEPORTFREEZE) return true; return Super::GetNoTeleportFreeze(); } // Invulnerability Powerup --------------------------------------------------- IMPLEMENT_CLASS(APowerInvulnerable, false, false, 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(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; } } //=========================================================================== // // 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(0.25f + (float)Owner->Alpha*0.75f, 1.f); } } return changed; } // Strength (aka Berserk) Powerup -------------------------------------------- IMPLEMENT_CLASS(APowerStrength, false, false, 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, 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; DoEffect(); } } //=========================================================================== // // 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(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(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, 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, 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); } } // Light-Amp Powerup --------------------------------------------------------- IMPLEMENT_CLASS(APowerLightAmp, false, false, false, false) //=========================================================================== // // APowerLightAmp :: DoEffect // //=========================================================================== void APowerLightAmp::DoEffect () { Super::DoEffect (); if (Owner->player != NULL && Owner->player->fixedcolormap < NUMCOLORMAPS) { if (EffectTics > BLINKTHRESHOLD || (EffectTics & 8)) { Owner->player->fixedlightlevel = 1; } else { Owner->player->fixedlightlevel = -1; } } } //=========================================================================== // // APowerLightAmp :: EndEffect // //=========================================================================== void APowerLightAmp::EndEffect () { Super::EndEffect(); if (Owner != NULL && Owner->player != NULL && Owner->player->fixedcolormap < NUMCOLORMAPS) { Owner->player->fixedlightlevel = -1; } } // Torch Powerup ------------------------------------------------------------- IMPLEMENT_CLASS(APowerTorch, false, false, false, false) //=========================================================================== // // APowerTorch :: Serialize // //=========================================================================== void APowerTorch::Serialize(FSerializer &arc) { Super::Serialize (arc); arc("newtorch", NewTorch) ("newtorchdelta", NewTorchDelta); } //=========================================================================== // // APowerTorch :: DoEffect // //=========================================================================== void APowerTorch::DoEffect () { if (Owner == NULL || Owner->player == NULL) { return; } if (EffectTics <= BLINKTHRESHOLD || Owner->player->fixedcolormap >= NUMCOLORMAPS) { Super::DoEffect (); } else { APowerup::DoEffect (); if (!(level.time & 16) && Owner->player != NULL) { if (NewTorch != 0) { if (Owner->player->fixedlightlevel + NewTorchDelta > 7 || Owner->player->fixedlightlevel + NewTorchDelta < 0 || NewTorch == Owner->player->fixedlightlevel) { NewTorch = 0; } else { Owner->player->fixedlightlevel += NewTorchDelta; } } else { NewTorch = (pr_torch() & 7) + 1; NewTorchDelta = (NewTorch == Owner->player->fixedlightlevel) ? 0 : ((NewTorch > Owner->player->fixedlightlevel) ? 1 : -1); } } } } // Flight (aka Wings of Wrath) powerup --------------------------------------- IMPLEMENT_CLASS(APowerFlight, false, false, false, false) //=========================================================================== // // APowerFlight :: Serialize // //=========================================================================== void APowerFlight::Serialize(FSerializer &arc) { Super::Serialize (arc); arc("hitcenterframe", HitCenterFrame); } //=========================================================================== // // APowerFlight :: InitEffect // //=========================================================================== void APowerFlight::InitEffect () { Super::InitEffect(); Owner->flags2 |= MF2_FLY; Owner->flags |= MF_NOGRAVITY; if (Owner->Z() <= Owner->floorz) { Owner->Vel.Z = 4;; // thrust the player in the air a bit } if (Owner->Vel.Z <= -35) { // stop falling scream S_StopSound (Owner, CHAN_VOICE); } } //=========================================================================== // // APowerFlight :: DoEffect // //=========================================================================== void APowerFlight::Tick () { // The Wings of Wrath only expire in multiplayer and non-hub games if (!multiplayer && (level.flags2 & LEVEL2_INFINITE_FLIGHT)) { assert(EffectTics < INT_MAX); // I can't see a game lasting nearly two years, but... EffectTics++; } Super::Tick (); // Owner->flags |= MF_NOGRAVITY; // Owner->flags2 |= MF2_FLY; } //=========================================================================== // // APowerFlight :: EndEffect // //=========================================================================== void APowerFlight::EndEffect () { Super::EndEffect(); if (Owner == NULL || Owner->player == NULL) { return; } if (!(Owner->flags7 & MF7_FLYCHEAT)) { if (Owner->Z() != Owner->floorz) { Owner->player->centering = true; } Owner->flags2 &= ~MF2_FLY; Owner->flags &= ~MF_NOGRAVITY; } // BorderTopRefresh = screen->GetPageCount (); //make sure the sprite's cleared out } //=========================================================================== // // APowerFlight :: DrawPowerup // //=========================================================================== bool APowerFlight::DrawPowerup (int x, int y) { // If this item got a valid icon use that instead of the default spinning wings. if (Icon.isValid()) { return Super::DrawPowerup(x, y); } if (EffectTics > BLINKTHRESHOLD || !(EffectTics & 16)) { FTextureID picnum = TexMan.CheckForTexture ("SPFLY0", FTexture::TEX_MiscPatch); int frame = (level.time/3) & 15; if (!picnum.isValid()) { return false; } if (Owner->flags & MF_NOGRAVITY) { if (HitCenterFrame && (frame != 15 && frame != 0)) { screen->DrawTexture (TexMan[picnum+15], x, y, DTA_HUDRules, HUD_Normal, TAG_DONE); } else { screen->DrawTexture (TexMan[picnum+frame], x, y, DTA_HUDRules, HUD_Normal, TAG_DONE); HitCenterFrame = false; } } else { if (!HitCenterFrame && (frame != 15 && frame != 0)) { screen->DrawTexture (TexMan[picnum+frame], x, y, DTA_HUDRules, HUD_Normal, TAG_DONE); HitCenterFrame = false; } else { screen->DrawTexture (TexMan[picnum+15], x, y, DTA_HUDRules, HUD_Normal, TAG_DONE); HitCenterFrame = true; } } } return true; } // Weapon Level 2 (aka Tome of Power) Powerup -------------------------------- IMPLEMENT_CLASS(APowerWeaponLevel2, false, false, false, false) //=========================================================================== // // APowerWeaponLevel2 :: InitEffect // //=========================================================================== void APowerWeaponLevel2::InitEffect () { AWeapon *weapon, *sister; Super::InitEffect(); if (Owner->player == nullptr) return; weapon = Owner->player->ReadyWeapon; if (weapon == nullptr) return; sister = weapon->SisterWeapon; if (sister == nullptr) return; if (!(sister->WeaponFlags & WIF_POWERED_UP)) return; assert (sister->SisterWeapon == weapon); if (weapon->GetReadyState() != sister->GetReadyState()) { Owner->player->ReadyWeapon = sister; P_SetPsprite(Owner->player, PSP_WEAPON, sister->GetReadyState()); } else { DPSprite *psp = Owner->player->FindPSprite(PSP_WEAPON); if (psp != nullptr && psp->GetCaller() == Owner->player->ReadyWeapon) { // If the weapon changes but the state does not, we have to manually change the PSprite's caller here. psp->SetCaller(sister); Owner->player->ReadyWeapon = sister; } else { // Something went wrong. Initiate a regular weapon change. Owner->player->PendingWeapon = sister; } } } //=========================================================================== // // APowerWeaponLevel2 :: EndEffect // //=========================================================================== void APowerWeaponLevel2::EndEffect () { player_t *player = Owner != NULL ? Owner->player : NULL; Super::EndEffect(); if (player != NULL) { if (player->ReadyWeapon != NULL && player->ReadyWeapon->WeaponFlags & WIF_POWERED_UP) { player->ReadyWeapon->EndPowerup (); } if (player->PendingWeapon != NULL && player->PendingWeapon != WP_NOCHANGE && player->PendingWeapon->WeaponFlags & WIF_POWERED_UP && player->PendingWeapon->SisterWeapon != NULL) { player->PendingWeapon = player->PendingWeapon->SisterWeapon; } } } // Speed Powerup ------------------------------------------------------------- IMPLEMENT_CLASS(APowerSpeed, false, false, false, false) DEFINE_FIELD(APowerSpeed, SpeedFlags) //=========================================================================== // // APowerSpeed :: Serialize // //=========================================================================== void APowerSpeed::Serialize(FSerializer &arc) { Super::Serialize (arc); arc("speedflags", SpeedFlags); } //=========================================================================== // // APowerSpeed :: GetSpeedFactor // //=========================================================================== double APowerSpeed ::GetSpeedFactor () { if (Inventory != NULL) return Speed * Inventory->GetSpeedFactor(); else return Speed; } //=========================================================================== // // APowerSpeed :: DoEffect // //=========================================================================== void APowerSpeed::DoEffect () { Super::DoEffect (); if (Owner == NULL || Owner->player == NULL) return; if (Owner->player->cheats & CF_PREDICTING) return; if (SpeedFlags & PSF_NOTRAIL) return; if (level.time & 1) return; // Check if another speed item is present to avoid multiple drawing of the speed trail. // Only the last PowerSpeed without PSF_NOTRAIL set will actually draw the trail. for (AInventory *item = Inventory; item != NULL; item = item->Inventory) { if (item->IsKindOf(RUNTIME_CLASS(APowerSpeed)) && !(static_cast(item)->SpeedFlags & PSF_NOTRAIL)) { return; } } if (Owner->Vel.LengthSquared() <= 12*12) return; AActor *speedMo = Spawn("PlayerSpeedTrail", Owner->Pos(), NO_REPLACE); if (speedMo) { speedMo->Angles.Yaw = Owner->Angles.Yaw; speedMo->Translation = Owner->Translation; speedMo->target = Owner; speedMo->sprite = Owner->sprite; speedMo->frame = Owner->frame; speedMo->Floorclip = Owner->Floorclip; // [BC] Also get the scale from the owner. speedMo->Scale = Owner->Scale; if (Owner == players[consoleplayer].camera && !(Owner->player->cheats & CF_CHASECAM)) { speedMo->renderflags |= RF_INVISIBLE; } } } // Minotaur (aka Dark Servant) powerup --------------------------------------- IMPLEMENT_CLASS(APowerMinotaur, false, false, false, false) // Targeter powerup --------------------------------------------------------- IMPLEMENT_CLASS(APowerTargeter, false, false, false, false) void APowerTargeter::Travelled () { InitEffect (); } void APowerTargeter::InitEffect () { // Why is this called when the inventory isn't even attached yet // in APowerup::CreateCopy? if (!Owner->FindInventory(GetClass(), true)) return; player_t *player; Super::InitEffect(); if ((player = Owner->player) == nullptr) return; FState *state = FindState("Targeter"); if (state != nullptr) { P_SetPsprite(player, PSP_TARGETCENTER, state + 0); P_SetPsprite(player, PSP_TARGETLEFT, state + 1); P_SetPsprite(player, PSP_TARGETRIGHT, state + 2); } player->GetPSprite(PSP_TARGETCENTER)->x = (160-3); player->GetPSprite(PSP_TARGETCENTER)->y = player->GetPSprite(PSP_TARGETLEFT)->y = player->GetPSprite(PSP_TARGETRIGHT)->y = (100-3); PositionAccuracy (); } void APowerTargeter::AttachToOwner(AActor *other) { Super::AttachToOwner(other); // Let's actually properly call this for the targeters. InitEffect(); } bool APowerTargeter::HandlePickup(AInventory *item) { if (Super::HandlePickup(item)) { InitEffect(); // reset the HUD sprites return true; } return false; } void APowerTargeter::DoEffect () { Super::DoEffect (); if (Owner != nullptr && Owner->player != nullptr) { player_t *player = Owner->player; PositionAccuracy (); if (EffectTics < 5*TICRATE) { FState *state = FindState("Targeter"); if (state != nullptr) { if (EffectTics & 32) { P_SetPsprite(player, PSP_TARGETRIGHT, nullptr); P_SetPsprite(player, PSP_TARGETLEFT, state + 1); } else if (EffectTics & 16) { P_SetPsprite(player, PSP_TARGETRIGHT, state + 2); P_SetPsprite(player, PSP_TARGETLEFT, nullptr); } } } } } void APowerTargeter::EndEffect () { Super::EndEffect(); if (Owner != nullptr && Owner->player != nullptr) { // Calling GetPSprite here could crash if we're creating a new game. // This is because P_SetupLevel nulls the player's mo before destroying // every DThinker which in turn ends up calling this. // However P_SetupLevel is only called after G_NewInit which calls // every player's dtor which destroys all their psprites. DPSprite *pspr; if ((pspr = Owner->player->FindPSprite(PSP_TARGETCENTER)) != nullptr) pspr->SetState(nullptr); if ((pspr = Owner->player->FindPSprite(PSP_TARGETLEFT)) != nullptr) pspr->SetState(nullptr); if ((pspr = Owner->player->FindPSprite(PSP_TARGETRIGHT)) != nullptr) pspr->SetState(nullptr); } } void APowerTargeter::PositionAccuracy () { player_t *player = Owner->player; if (player != nullptr) { player->GetPSprite(PSP_TARGETLEFT)->x = (160-3) - ((100 - player->mo->accuracy)); player->GetPSprite(PSP_TARGETRIGHT)->x = (160-3)+ ((100 - player->mo->accuracy)); } } // Frightener Powerup -------------------------------- IMPLEMENT_CLASS(APowerFrightener, false, false, false, false) //=========================================================================== // // APowerFrightener :: InitEffect // //=========================================================================== void APowerFrightener::InitEffect () { Super::InitEffect(); if (Owner== NULL || Owner->player == NULL) return; Owner->player->cheats |= CF_FRIGHTENING; } //=========================================================================== // // APowerFrightener :: EndEffect // //=========================================================================== void APowerFrightener::EndEffect () { Super::EndEffect(); if (Owner== NULL || Owner->player == NULL) return; Owner->player->cheats &= ~CF_FRIGHTENING; } // Buddha Powerup -------------------------------- IMPLEMENT_CLASS(APowerBuddha, false, false, false, false) //=========================================================================== // // APowerBuddha :: InitEffect // //=========================================================================== void APowerBuddha::InitEffect () { Super::InitEffect(); if (Owner== NULL || Owner->player == NULL) return; Owner->player->cheats |= CF_BUDDHA; } //=========================================================================== // // APowerBuddha :: EndEffect // //=========================================================================== void APowerBuddha::EndEffect () { Super::EndEffect(); if (Owner== NULL || Owner->player == NULL) return; Owner->player->cheats &= ~CF_BUDDHA; } // Scanner powerup ---------------------------------------------------------- IMPLEMENT_CLASS(APowerScanner, false, false, false, false) // Time freezer powerup ----------------------------------------------------- IMPLEMENT_CLASS( APowerTimeFreezer, false, false, false, false) //=========================================================================== // // APowerTimeFreezer :: InitEffect // //=========================================================================== void APowerTimeFreezer::InitEffect() { int freezemask; Super::InitEffect(); if (Owner == NULL || Owner->player == NULL) return; // When this powerup is in effect, pause the music. S_PauseSound(false, false); // Give the player and his teammates the power to move when time is frozen. freezemask = 1 << (Owner->player - players); Owner->player->timefreezer |= freezemask; for (int i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && players[i].mo != NULL && players[i].mo->IsTeammate(Owner) ) { players[i].timefreezer |= freezemask; } } // [RH] The effect ends one tic after the counter hits zero, so make // sure we start at an odd count. EffectTics += !(EffectTics & 1); if ((EffectTics & 1) == 0) { EffectTics++; } // Make sure the effect starts and ends on an even tic. if ((level.time & 1) == 0) { level.flags2 |= LEVEL2_FROZEN; } else { // Compensate for skipped tic, but beware of overflow. if(EffectTics < INT_MAX) EffectTics++; } } //=========================================================================== // // APowerTimeFreezer :: DoEffect // //=========================================================================== void APowerTimeFreezer::DoEffect() { Super::DoEffect(); // [RH] Do not change LEVEL_FROZEN on odd tics, or the Revenant's tracer // will get thrown off. // [ED850] Don't change it if the player is predicted either. if (level.time & 1 || (Owner != NULL && Owner->player != NULL && Owner->player->cheats & CF_PREDICTING)) { return; } // [RH] The "blinking" can't check against EffectTics exactly or it will // never happen, because InitEffect ensures that EffectTics will always // be odd when level.time is even. if ( EffectTics > 4*32 || (( EffectTics > 3*32 && EffectTics <= 4*32 ) && ((EffectTics + 1) & 15) != 0 ) || (( EffectTics > 2*32 && EffectTics <= 3*32 ) && ((EffectTics + 1) & 7) != 0 ) || (( EffectTics > 32 && EffectTics <= 2*32 ) && ((EffectTics + 1) & 3) != 0 ) || (( EffectTics > 0 && EffectTics <= 1*32 ) && ((EffectTics + 1) & 1) != 0 )) level.flags2 |= LEVEL2_FROZEN; else level.flags2 &= ~LEVEL2_FROZEN; } //=========================================================================== // // APowerTimeFreezer :: EndEffect // //=========================================================================== void APowerTimeFreezer::EndEffect() { int i; Super::EndEffect(); // If there is an owner, remove the timefreeze flag corresponding to // her from all players. if (Owner != NULL && Owner->player != NULL) { int freezemask = ~(1 << (Owner->player - players)); for (i = 0; i < MAXPLAYERS; ++i) { players[i].timefreezer &= freezemask; } } // Are there any players who still have timefreezer bits set? for (i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i] && players[i].timefreezer != 0) { break; } } if (i == MAXPLAYERS) { // No, so allow other actors to move about freely once again. level.flags2 &= ~LEVEL2_FROZEN; // Also, turn the music back on. S_ResumeSound(false); } } // Damage powerup ------------------------------------------------------ IMPLEMENT_CLASS(APowerDamage, false, false, false, false) //=========================================================================== // // APowerDamage :: InitEffect // //=========================================================================== void APowerDamage::InitEffect( ) { Super::InitEffect(); // Use sound channel 5 to avoid interference with other actions. if (Owner != NULL) S_Sound(Owner, 5, SeeSound, 1.0f, ATTN_NONE); } //=========================================================================== // // APowerDamage :: EndEffect // //=========================================================================== void APowerDamage::EndEffect( ) { Super::EndEffect(); // Use sound channel 5 to avoid interference with other actions. if (Owner != NULL) S_Sound(Owner, 5, DeathSound, 1.0f, ATTN_NONE); } //=========================================================================== // // APowerDamage :: ModifyDamage // //=========================================================================== void APowerDamage::ModifyDamage(int damage, FName damageType, int &newdamage, bool passive) { if (!passive && damage > 0) { int newdam; DmgFactors *df = GetClass()->DamageFactors; if (df != NULL && df->CountUsed() != 0) { newdam = MAX(1, df->Apply(damageType, damage));// don't allow zero damage as result of an underflow } else { newdam = damage * 4; } if (Owner != NULL && newdam > damage) S_Sound(Owner, 5, ActiveSound, 1.0f, ATTN_NONE); newdamage = damage = newdam; } if (Inventory != NULL) Inventory->ModifyDamage(damage, damageType, newdamage, passive); } // Quarter damage powerup ------------------------------------------------------ IMPLEMENT_CLASS(APowerProtection, false, false, false, false) #define PROTECTION_FLAGS3 (MF3_NORADIUSDMG | MF3_DONTMORPH | MF3_DONTSQUASH | MF3_DONTBLAST | MF3_NOTELEOTHER) #define PROTECTION_FLAGS5 (MF5_NOPAIN | MF5_DONTRIP) //=========================================================================== // // APowerProtection :: InitEffect // //=========================================================================== void APowerProtection::InitEffect( ) { Super::InitEffect(); if (Owner != NULL) { S_Sound(Owner, CHAN_AUTO, SeeSound, 1.0f, ATTN_NONE); // Transfer various protection flags if owner does not already have them. // If the owner already has the flag, clear it from the powerup. // If the powerup still has a flag set, add it to the owner. flags3 &= ~(Owner->flags3 & PROTECTION_FLAGS3); Owner->flags3 |= flags3 & PROTECTION_FLAGS3; flags5 &= ~(Owner->flags5 & PROTECTION_FLAGS5); Owner->flags5 |= flags5 & PROTECTION_FLAGS5; } } //=========================================================================== // // APowerProtection :: EndEffect // //=========================================================================== void APowerProtection::EndEffect( ) { Super::EndEffect(); if (Owner != NULL) { S_Sound(Owner, CHAN_AUTO, DeathSound, 1.0f, ATTN_NONE); Owner->flags3 &= ~(flags3 & PROTECTION_FLAGS3); Owner->flags5 &= ~(flags5 & PROTECTION_FLAGS5); } } //=========================================================================== // // APowerProtection :: AbsorbDamage // //=========================================================================== void APowerProtection::ModifyDamage(int damage, FName damageType, int &newdamage, bool passive) { if (passive && damage > 0) { int newdam; DmgFactors *df = GetClass()->DamageFactors; if (df != NULL && df->CountUsed() != 0) { newdam = MAX(0, df->Apply(damageType, damage)); } else { newdam = damage / 4; } if (Owner != NULL && newdam < damage) S_Sound(Owner, CHAN_AUTO, ActiveSound, 1.0f, ATTN_NONE); newdamage = damage = newdam; } if (Inventory != NULL) { Inventory->ModifyDamage(damage, damageType, newdamage, passive); } } // Drain rune ------------------------------------------------------- IMPLEMENT_CLASS(APowerDrain, false, false, false, false) //=========================================================================== // // ARuneDrain :: InitEffect // //=========================================================================== void APowerDrain::InitEffect( ) { Super::InitEffect(); if (Owner== NULL || Owner->player == NULL) return; // Give the player the power to drain life from opponents when he damages them. Owner->player->cheats |= CF_DRAIN; } //=========================================================================== // // ARuneDrain :: EndEffect // //=========================================================================== void APowerDrain::EndEffect( ) { Super::EndEffect(); // Nothing to do if there's no owner. if (Owner != NULL && Owner->player != NULL) { // Take away the drain power. Owner->player->cheats &= ~CF_DRAIN; } } // Regeneration rune ------------------------------------------------------- IMPLEMENT_CLASS(APowerRegeneration, false, false, false, false) //=========================================================================== // // APowerRegeneration :: DoEffect // //=========================================================================== void APowerRegeneration::DoEffect() { Super::DoEffect(); if (Owner != NULL && Owner->health > 0 && (level.time & 31) == 0) { if (P_GiveBody(Owner, int(Strength))) { S_Sound(Owner, CHAN_ITEM, "*regenerate", 1, ATTN_NORM ); } } } // High jump rune ------------------------------------------------------- IMPLEMENT_CLASS(APowerHighJump, false, false, false, false) //=========================================================================== // // ARuneHighJump :: InitEffect // //=========================================================================== void APowerHighJump::InitEffect( ) { Super::InitEffect(); if (Owner== NULL || Owner->player == NULL) return; // Give the player the power to jump much higher. Owner->player->cheats |= CF_HIGHJUMP; } //=========================================================================== // // ARuneHighJump :: EndEffect // //=========================================================================== void APowerHighJump::EndEffect( ) { Super::EndEffect(); // Nothing to do if there's no owner. if (Owner != NULL && Owner->player != NULL) { // Take away the high jump power. Owner->player->cheats &= ~CF_HIGHJUMP; } } // Double firing speed rune --------------------------------------------- IMPLEMENT_CLASS(APowerDoubleFiringSpeed, false, false, false, false) //=========================================================================== // // APowerDoubleFiringSpeed :: InitEffect // //=========================================================================== void APowerDoubleFiringSpeed::InitEffect( ) { Super::InitEffect(); if (Owner== NULL || Owner->player == NULL) return; // Give the player the power to shoot twice as fast. Owner->player->cheats |= CF_DOUBLEFIRINGSPEED; } //=========================================================================== // // APowerDoubleFiringSpeed :: EndEffect // //=========================================================================== void APowerDoubleFiringSpeed::EndEffect( ) { Super::EndEffect(); // Nothing to do if there's no owner. if (Owner != NULL && Owner->player != NULL) { // Take away the shooting twice as fast power. Owner->player->cheats &= ~CF_DOUBLEFIRINGSPEED; } } // Morph powerup ------------------------------------------------------ IMPLEMENT_CLASS(APowerMorph, false, false, false, false) //=========================================================================== // // APowerMorph :: Serialize // //=========================================================================== void APowerMorph::Serialize(FSerializer &arc) { Super::Serialize (arc); arc("playerclass", PlayerClass) ("morphstyle", MorphStyle) ("morphflash", MorphFlash) ("unmorphflash", UnMorphFlash) ("player", Player); } //=========================================================================== // // APowerMorph :: InitEffect // //=========================================================================== void APowerMorph::InitEffect( ) { Super::InitEffect(); if (Owner != nullptr && Owner->player != nullptr && PlayerClass != nullptr) { player_t *realplayer = Owner->player; // Remember the identity of the player if (P_MorphPlayer(realplayer, realplayer, PlayerClass, -1/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash)) { Owner = realplayer->mo; // Replace the new owner in our owner; safe because we are not attached to anything yet ItemFlags |= IF_CREATECOPYMOVED; // Let the caller know the "real" owner has changed (to the morphed actor) Player = realplayer; // Store the player identity (morphing clears the unmorphed actor's "player" field) } else // morph failed - give the caller an opportunity to fail the pickup completely { ItemFlags |= IF_INITEFFECTFAILED; // Let the caller know that the activation failed (can fail the pickup if appropriate) } } } //=========================================================================== // // APowerMorph :: EndEffect // //=========================================================================== void APowerMorph::EndEffect( ) { Super::EndEffect(); // Abort if owner already destroyed if (Owner == NULL) { assert(Player == NULL); return; } // Abort if owner already unmorphed if (Player == NULL) { return; } // Abort if owner is dead; their Die() method will // take care of any required unmorphing on death. if (Player->health <= 0) { return; } // Unmorph if possible if (!bInUndoMorph) { int savedMorphTics = Player->morphTics; P_UndoPlayerMorph (Player, Player, 0, !!(Player->MorphStyle & MORPH_UNDOALWAYS)); // Abort if unmorph failed; in that case, // set the usual retry timer and return. if (Player != NULL && Player->morphTics) { // Transfer retry timeout // to the powerup's timer. EffectTics = Player->morphTics; // Reload negative morph tics; // use actual value; it may // be in use for animation. Player->morphTics = savedMorphTics; // Try again some time later return; } } // Unmorph suceeded Player = NULL; } // Infinite Ammo Powerup ----------------------------------------------------- IMPLEMENT_CLASS(APowerInfiniteAmmo, false, false, false, false) //=========================================================================== // // APowerInfiniteAmmo :: InitEffect // //=========================================================================== void APowerInfiniteAmmo::InitEffect( ) { Super::InitEffect(); if (Owner== NULL || Owner->player == NULL) return; // Give the player infinite ammo Owner->player->cheats |= CF_INFINITEAMMO; } //=========================================================================== // // APowerInfiniteAmmo :: EndEffect // //=========================================================================== void APowerInfiniteAmmo::EndEffect( ) { Super::EndEffect(); // Nothing to do if there's no owner. if (Owner != NULL && Owner->player != NULL) { // Take away the limitless ammo Owner->player->cheats &= ~CF_INFINITEAMMO; } }