class PowerupGiver : Inventory native { native Class PowerupType; native int EffectTics; // Non-0 to override the powerup's default tics native color BlendColor; // Non-0 to override the powerup's default blend native Name Mode; // Meaning depends on powerup - used for Invulnerability and Invisibility native double Strength; // Meaning depends on powerup - currently used only by Invisibility Default { Inventory.DefMaxAmount; +INVENTORY.INVBAR +INVENTORY.FANCYPICKUPSOUND Inventory.PickupSound "misc/p_pkup"; } } class Powerup : Inventory native { native int EffectTics; native color BlendColor; native Name Mode; // Meaning depends on powerup - used for Invulnerability and Invisibility native double Strength; // Meaning depends on powerup - currently used only by Invisibility // Note, that while this is an inventory flag, it only has meaning on an active powerup. override bool GetNoTeleportFreeze() { return bNoTeleportFreeze; } native virtual void InitEffect(); native virtual void EndEffect(); native bool isBlinking(); } class PowerInvulnerable : Powerup native { Default { Powerup.Duration -30; inventory.icon "SPSHLD0"; } } class PowerStrength : Powerup native { Default { Powerup.Duration 1; Powerup.Color "ff 00 00", 0.5; +INVENTORY.HUBPOWER } } class PowerInvisibility : Powerup native { Default { +SHADOW; Powerup.Duration -60; Powerup.Strength 80; Powerup.Mode "Fuzzy"; } } class PowerGhost : PowerInvisibility { Default { +GHOST; Powerup.Duration -60; Powerup.Strength 60; Powerup.Mode "None"; } } class PowerShadow : PowerInvisibility { Default { +INVENTORY.HUBPOWER Powerup.Duration -55; Powerup.Strength 75; Powerup.Mode "Cumulative"; } } class PowerIronFeet : Powerup native { Default { Powerup.Duration -60; Powerup.Color "00 ff 00", 0.125; } } class PowerMask : PowerIronFeet native { Default { Powerup.Duration -80; Powerup.Color "00 00 00", 0; +INVENTORY.HUBPOWER Inventory.Icon "I_MASK"; } } class PowerLightAmp : Powerup native { Default { Powerup.Duration -120; } } class PowerTorch : PowerLightAmp native {} //=========================================================================== // // Flight // //=========================================================================== class PowerFlight : Powerup { Default { Powerup.Duration -60; +INVENTORY.HUBPOWER } bool HitCenterFrame; //=========================================================================== // // APowerFlight :: InitEffect // //=========================================================================== override void InitEffect () { Super.InitEffect(); Owner.bFly = true; Owner.bNoGravity = true; if (Owner.pos.Z <= Owner.floorz) { Owner.Vel.Z = 4;; // thrust the player in the air a bit } if (Owner.Vel.Z <= -35) { // stop falling scream Owner.A_StopSound (CHAN_VOICE); } } //=========================================================================== // // APowerFlight :: DoEffect // //=========================================================================== override void Tick () { // The Wings of Wrath only expire in multiplayer and non-hub games if (!multiplayer && level.infinite_flight) { EffectTics++; } Super.Tick (); } //=========================================================================== // // APowerFlight :: EndEffect // //=========================================================================== override void EndEffect () { Super.EndEffect(); if (Owner == NULL || Owner.player == NULL) { return; } if (!(Owner.bFlyCheat)) { if (Owner.pos.Z != Owner.floorz) { Owner.player.centering = true; } Owner.bFly = false; Owner.bNoGravity = false; } } //=========================================================================== // // APowerFlight :: DrawPowerup // //=========================================================================== override bool 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)) { TextureID picnum = TexMan.CheckForTexture ("SPFLY0", TexMan.Type_MiscPatch); int frame = (level.time/3) & 15; if (!picnum.isValid()) { return false; } if (Owner.bNoGravity) { if (HitCenterFrame && (frame != 15 && frame != 0)) { screen.DrawHUDTexture (picnum + 15, x, y); } else { screen.DrawHUDTexture (picnum + frame, x, y); HitCenterFrame = false; } } else { if (!HitCenterFrame && (frame != 15 && frame != 0)) { screen.DrawHUDTexture (picnum + frame, x, y); HitCenterFrame = false; } else { screen.DrawHUDTexture (picnum+15, x, y); HitCenterFrame = true; } } } return true; } } //=========================================================================== // // WeaponLevel2 // //=========================================================================== class PowerWeaponLevel2 : Powerup { Default { Powerup.Duration -40; Inventory.Icon "SPINBK0"; +INVENTORY.NOTELEPORTFREEZE } //=========================================================================== // // APowerWeaponLevel2 :: InitEffect // //=========================================================================== override void InitEffect () { Super.InitEffect(); let player = Owner.player; if (player == null) return; let weap = player.ReadyWeapon; if (weap == null) return; let sister = weap.SisterWeapon; if (sister == null) return; if (!sister.bPowered_Up) return; let ready = sister.GetReadyState(); if (weap.GetReadyState() != ready) { player.ReadyWeapon = sister; player.SetPsprite(PSP_WEAPON, ready); } else { PSprite psp = player.FindPSprite(PSprite.WEAPON); if (psp != null && psp.Caller == player.ReadyWeapon) { // If the weapon changes but the state does not, we have to manually change the PSprite's caller here. psp.Caller = sister; player.ReadyWeapon = sister; } else { // Something went wrong. Initiate a regular weapon change. player.PendingWeapon = sister; } } } //=========================================================================== // // APowerWeaponLevel2 :: EndEffect // //=========================================================================== override void EndEffect () { Super.EndEffect(); if (Owner == null) return; let player = Owner.player; if (player != NULL) { if (player.ReadyWeapon != NULL && player.ReadyWeapon.bPowered_Up) { player.ReadyWeapon.EndPowerup (); } if (player.PendingWeapon != NULL && player.PendingWeapon != WP_NOCHANGE && player.PendingWeapon.bPowered_Up && player.PendingWeapon.SisterWeapon != NULL) { player.PendingWeapon = player.PendingWeapon.SisterWeapon; } } } } //=========================================================================== // // Speed // //=========================================================================== class PowerSpeed : Powerup native { native int SpeedFlags; const PSF_NOTRAIL = 1; Default { Powerup.Duration -45; Speed 1.5; Inventory.Icon "SPBOOT0"; +INVENTORY.NOTELEPORTFREEZE } override double GetSpeedFactor() { return Speed; } //=========================================================================== // // APowerSpeed :: DoEffect // //=========================================================================== override void 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 (Inventory item = Inv; item != NULL; item = item.Inv) { let sitem = PowerSpeed(item); if (sitem != null && !(sitem.SpeedFlags & PSF_NOTRAIL)) { return; } } if (Owner.Vel.Length() <= 12) return; Actor speedMo = Spawn("PlayerSpeedTrail", Owner.Pos, NO_REPLACE); if (speedMo) { speedMo.Angle = Owner.Angle; 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.bInvisible = true; } } } } // Player Speed Trail (used by the Speed Powerup) ---------------------------- class PlayerSpeedTrail : Actor { Default { +NOBLOCKMAP +NOGRAVITY Alpha 0.6; RenderStyle "Translucent"; } override void Tick() { Alpha -= .6 / 8; if (Alpha <= 0) { Destroy (); } } } //=========================================================================== // // Minotaur // //=========================================================================== class PowerMinotaur : Powerup { Default { Powerup.Duration -25; Inventory.Icon "SPMINO0"; } } //=========================================================================== // // Targeter // //=========================================================================== class PowerTargeter : Powerup { Default { Powerup.Duration -160; +INVENTORY.HUBPOWER } States { Targeter: TRGT A -1; Stop; TRGT B -1; Stop; TRGT C -1; Stop; } override void Travelled () { InitEffect (); } override void InitEffect () { // Why is this called when the inventory isn't even attached yet // in APowerup.CreateCopy? if (!Owner.FindInventory(GetClass(), true)) return; let player = Owner.player; Super.InitEffect(); if (player == null) return; let stat = FindState("Targeter"); if (stat != null) { player.SetPsprite(PSprite.TARGETCENTER, stat); player.SetPsprite(PSprite.TARGETLEFT, stat + 1); player.SetPsprite(PSprite.TARGETRIGHT, stat + 2); } player.GetPSprite(PSprite.TARGETCENTER).x = (160-3); player.GetPSprite(PSprite.TARGETCENTER).y = player.GetPSprite(PSprite.TARGETLEFT).y = player.GetPSprite(PSprite.TARGETRIGHT).y = (100-3); PositionAccuracy (); } override void AttachToOwner(Actor other) { Super.AttachToOwner(other); // Let's actually properly call this for the targeters. InitEffect(); } override bool HandlePickup(Inventory item) { if (Super.HandlePickup(item)) { InitEffect(); // reset the HUD sprites return true; } return false; } override void DoEffect () { Super.DoEffect (); if (Owner != null && Owner.player != null) { let player = Owner.player; PositionAccuracy (); if (EffectTics < 5*TICRATE) { let stat = FindState("Targeter"); if (stat != null) { if (EffectTics & 32) { player.SetPsprite(PSprite.TARGETRIGHT, null); player.SetPsprite(PSprite.TARGETLEFT, stat + 1); } else if (EffectTics & 16) { player.SetPsprite(PSprite.TARGETRIGHT, stat + 2); player.SetPsprite(PSprite.TARGETLEFT, null); } } } } } override void EndEffect () { Super.EndEffect(); if (Owner != null && Owner.player != null) { // 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. let player = Owner.player; PSprite pspr; if ((pspr = player.FindPSprite(PSprite.TARGETCENTER)) != null) pspr.SetState(null); if ((pspr = player.FindPSprite(PSprite.TARGETLEFT)) != null) pspr.SetState(null); if ((pspr = player.FindPSprite(PSprite.TARGETRIGHT)) != null) pspr.SetState(null); } } private void PositionAccuracy () { let player = Owner.player; if (player != null) { player.GetPSprite(PSprite.TARGETLEFT).x = (160-3) - ((100 - player.mo.accuracy)); player.GetPSprite(PSprite.TARGETRIGHT).x = (160-3)+ ((100 - player.mo.accuracy)); } } } //=========================================================================== // // Frightener // //=========================================================================== class PowerFrightener : Powerup { Default { Powerup.Duration -60; } override void InitEffect () { Super.InitEffect(); if (Owner== null || Owner.player == null) return; Owner.player.cheats |= CF_FRIGHTENING; } override void EndEffect () { Super.EndEffect(); if (Owner== null || Owner.player == null) return; Owner.player.cheats &= ~CF_FRIGHTENING; } } //=========================================================================== // // Buddha // //=========================================================================== class PowerBuddha : Powerup { Default { Powerup.Duration -60; } override void InitEffect () { Super.InitEffect(); if (Owner== null || Owner.player == null) return; Owner.player.cheats |= CF_BUDDHA; } override void EndEffect () { Super.EndEffect(); if (Owner== null || Owner.player == null) return; Owner.player.cheats &= ~CF_BUDDHA; } } //=========================================================================== // // Scanner (this is active just by being present) // //=========================================================================== class PowerScanner : Powerup { Default { Powerup.Duration -80; +INVENTORY.HUBPOWER } } //=========================================================================== // // TimeFreezer // //=========================================================================== class PowerTimeFreezer : Powerup { Default { Powerup.Duration -12; } //=========================================================================== // // InitEffect // //=========================================================================== override void 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.PlayerNumber(); 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.frozen = true;; } else { // Compensate for skipped tic, but beware of overflow. if(EffectTics < 0x7fffffff) EffectTics++; } } //=========================================================================== // // APowerTimeFreezer :: DoEffect // //=========================================================================== override void 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. level.frozen = ( 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 )); } //=========================================================================== // // APowerTimeFreezer :: EndEffect // //=========================================================================== override void EndEffect() { 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.PlayerNumber()); for (int i = 0; i < MAXPLAYERS; ++i) { players[i].timefreezer &= freezemask; } } // Are there any players who still have timefreezer bits set? for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i] && players[i].timefreezer != 0) { return; } } // No, so allow other actors to move about freely once again. level.frozen = false; // Also, turn the music back on. S_ResumeSound(false); } } //=========================================================================== // // Damage // //=========================================================================== class PowerDamage : Powerup { Default { Powerup.Duration -25; } //=========================================================================== // // InitEffect // //=========================================================================== override void InitEffect() { Super.InitEffect(); if (Owner != null) { Owner.A_PlaySound(SeeSound, CHAN_5, 1.0, false, ATTN_NONE); } } //=========================================================================== // // EndEffect // //=========================================================================== override void EndEffect() { Super.EndEffect(); if (Owner != null) { Owner.A_PlaySound(DeathSound, CHAN_5, 1.0, false, ATTN_NONE); } } //=========================================================================== // // ModifyDamage // //=========================================================================== override void ModifyDamage(int damage, Name damageType, out int newdamage, bool passive) { if (!passive && damage > 0) { newdamage = max(1, ApplyDamageFactors(GetClass(), damageType, damage, damage * 4)); if (Owner != null && newdamage > damage) Owner.A_PlaySound(ActiveSound, CHAN_AUTO, 1.0, false, ATTN_NONE); } } } //=========================================================================== // // Protection // //=========================================================================== class PowerProtection : Powerup { Default { Powerup.Duration -25; } //=========================================================================== // // InitEffect // //=========================================================================== override void InitEffect() { Super.InitEffect(); let o = Owner; // copy to a local variable for quicker access. if (o != null) { o.A_PlaySound(SeeSound, CHAN_AUTO, 1.0, false, 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. bNoRadiusDmg &= !o.bNoRadiusDmg; o.bNoRadiusDmg |= bNoRadiusDmg; bDontMorph &= !o.bDontMorph; o.bDontMorph |= bDontMorph; bDontSquash &= !o.bDontSquash; o.bDontSquash |= bDontSquash; bDontBlast &= !o.bDontBlast; o.bDontBlast |= bDontBlast; bNoTeleOther &= !o.bNoTeleOther; o.bNoTeleOther |= bNoTeleOther; bNoPain &= !o.bNoPain; o.bNoPain |= bNoPain; bDontRip &= !o.bDontRip; o.bDontRip |= bDontRip; } } //=========================================================================== // // EndEffect // //=========================================================================== override void EndEffect() { Super.EndEffect(); let o = Owner; // copy to a local variable for quicker access. if (o != null) { o.A_PlaySound(DeathSound, CHAN_AUTO, 1.0, false, ATTN_NONE); o.bNoRadiusDmg &= !bNoRadiusDmg; o.bDontMorph &= !bDontMorph; o.bDontSquash &= !bDontSquash; o.bDontBlast &= !bDontBlast; o.bNoTeleOther &= !bNoTeleOther; o.bNoPain &= !bNoPain; o.bDontRip &= !bDontRip; } } //=========================================================================== // // AbsorbDamage // //=========================================================================== override void ModifyDamage(int damage, Name damageType, out int newdamage, bool passive) { if (passive && damage > 0) { newdamage = max(1, ApplyDamageFactors(GetClass(), damageType, damage, damage / 4)); if (Owner != null && newdamage < damage) Owner.A_PlaySound(ActiveSound, CHAN_AUTO, 1.0, false, ATTN_NONE); } } } //=========================================================================== // // Drain // //=========================================================================== class PowerDrain : Powerup { Default { Powerup.Duration -60; } override void InitEffect() { Super.InitEffect(); if (Owner!= null && Owner.player != null) { // Give the player the power to drain life from opponents when he damages them. Owner.player.cheats |= CF_DRAIN; } } override void 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 // //=========================================================================== class PowerRegeneration : Powerup { Default { Powerup.Duration -120; Powerup.Strength 5; } override void DoEffect() { Super.DoEffect(); if (Owner != null && Owner.health > 0 && (level.time & 31) == 0) { if (Owner.GiveBody(int(Strength))) { Owner.A_PlaySound("*regenerate", CHAN_ITEM); } } } } //=========================================================================== // // HighJump // //=========================================================================== class PowerHighJump : Powerup { override void InitEffect() { Super.InitEffect(); if (Owner!= null && Owner.player != null) { // Give the player the power to jump much higher. Owner.player.cheats |= CF_HIGHJUMP; } } override void 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; } } } //=========================================================================== // // DoubleFiringSpeed // //=========================================================================== class PowerDoubleFiringSpeed : Powerup { override void InitEffect() { Super.InitEffect(); if (Owner!= null && Owner.player != null) { // Give the player the power to shoot twice as fast. Owner.player.cheats |= CF_DOUBLEFIRINGSPEED; } } override void 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; } } } //=========================================================================== // // InfiniteAmmo // //=========================================================================== class PowerInfiniteAmmo : Powerup { Default { Powerup.Duration -30; } override void InitEffect() { Super.InitEffect(); if (Owner!= null && Owner.player != null) { // Give the player infinite ammo Owner.player.cheats |= CF_INFINITEAMMO; } } override void 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; } } } //=========================================================================== // // PowerMorph // //=========================================================================== class PowerMorph : Powerup native { native Class PlayerClass; native Class MorphFlash, UnMorphFlash; native int MorphStyle; native PlayerInfo MorphedPlayer; Default { Powerup.Duration -40; } //=========================================================================== // // InitEffect // //=========================================================================== override void InitEffect() { Super.InitEffect(); if (Owner != null && Owner.player != null && PlayerClass != null) { let realplayer = Owner.player; // Remember the identity of the player if (realplayer.MorphPlayer(realplayer, PlayerClass, 0x7fffffff/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash)) { Owner = realplayer.mo; // Replace the new owner in our owner; safe because we are not attached to anything yet bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor) MorphedPlayer = 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 { bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate) } } } //=========================================================================== // // EndEffect // //=========================================================================== override void EndEffect() { Super.EndEffect(); // Abort if owner already destroyed or unmorphed if (Owner == null || MorphedPlayer == null || Owner.alternative == null) { return; } // Abort if owner is dead; their Die() method will // take care of any required unmorphing on death. if (MorphedPlayer.health <= 0) { return; } int savedMorphTics = MorphedPlayer.morphTics; MorphedPlayer.UndoPlayerMorph (MorphedPlayer, 0, !!(MorphedPlayer.MorphStyle & MRF_UNDOALWAYS)); // Abort if unmorph failed; in that case, // set the usual retry timer and return. if (MorphedPlayer != null && MorphedPlayer.morphTics) { // Transfer retry timeout // to the powerup's timer. EffectTics = MorphedPlayer.morphTics; // Reload negative morph tics; // use actual value; it may // be in use for animation. MorphedPlayer.morphTics = savedMorphTics; // Try again some time later return; } // Unmorph suceeded MorphedPlayer = null; } }