mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-25 05:21:02 +00:00
3d61d2c1f4
Nothing should ever assume that spawning an actor is unconditionally successful. There can always be some edge cases where this is not the case.
602 lines
12 KiB
Text
602 lines
12 KiB
Text
|
|
// Poison Bag (Flechette used by Cleric) ------------------------------------
|
|
|
|
class PoisonBag : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 5;
|
|
Height 5;
|
|
+NOBLOCKMAP +NOGRAVITY
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
PSBG A 18 Bright;
|
|
PSBG B 4 Bright;
|
|
PSBG C 3;
|
|
PSBG C 1 A_PoisonBagInit;
|
|
Stop;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_PoisonBagInit
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_PoisonBagInit()
|
|
{
|
|
Actor mo = Spawn("PoisonCloud", pos + (0, 0, 28), ALLOW_REPLACE);
|
|
if (mo)
|
|
{
|
|
mo.target = target;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fire Bomb (Flechette used by Mage) ---------------------------------------
|
|
|
|
class FireBomb : Actor
|
|
{
|
|
Default
|
|
{
|
|
DamageType "Fire";
|
|
+NOGRAVITY
|
|
+FOILINVUL
|
|
RenderStyle "Translucent";
|
|
Alpha 0.6;
|
|
DeathSound "FlechetteExplode";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
PSBG A 20;
|
|
PSBG AA 10;
|
|
PSBG B 4;
|
|
PSBG C 4 A_Scream;
|
|
XPL1 A 4 Bright A_TimeBomb;
|
|
XPL1 BCDEF 4 Bright;
|
|
Stop;
|
|
}
|
|
|
|
void A_TimeBomb()
|
|
{
|
|
AddZ(32, false);
|
|
A_SetRenderStyle(1., STYLE_Add);
|
|
A_Explode();
|
|
}
|
|
}
|
|
|
|
// Throwing Bomb (Flechette used by Fighter) --------------------------------
|
|
|
|
class ThrowingBomb : Actor
|
|
{
|
|
Default
|
|
{
|
|
Health 48;
|
|
Speed 12;
|
|
Radius 8;
|
|
Height 10;
|
|
DamageType "Fire";
|
|
+NOBLOCKMAP +DROPOFF +MISSILE
|
|
BounceType "HexenCompat";
|
|
SeeSound "FlechetteBounce";
|
|
DeathSound "FlechetteExplode";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
THRW A 4 A_CheckThrowBomb;
|
|
THRW BCDE 3 A_CheckThrowBomb;
|
|
THRW F 3 A_CheckThrowBomb2;
|
|
Loop;
|
|
Tail:
|
|
THRW G 6 A_CheckThrowBomb;
|
|
THRW F 4 A_CheckThrowBomb;
|
|
THRW H 6 A_CheckThrowBomb;
|
|
THRW F 4 A_CheckThrowBomb;
|
|
THRW G 6 A_CheckThrowBomb;
|
|
THRW F 3 A_CheckThrowBomb;
|
|
Wait;
|
|
Death:
|
|
CFCF Q 4 Bright A_NoGravity;
|
|
CFCF R 3 Bright A_Scream;
|
|
CFCF S 4 Bright A_Explode;
|
|
CFCF T 3 Bright;
|
|
CFCF U 4 Bright;
|
|
CFCF W 3 Bright;
|
|
CFCF X 4 Bright;
|
|
CFCF Z 3 Bright;
|
|
Stop;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_CheckThrowBomb
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_CheckThrowBomb()
|
|
{
|
|
if (--health <= 0)
|
|
{
|
|
SetStateLabel("Death");
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_CheckThrowBomb2
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_CheckThrowBomb2()
|
|
{
|
|
// [RH] Check using actual velocity, although the vel.z < 2 check still stands
|
|
if (Vel.Z < 2 && Vel.Length() < 1.5)
|
|
{
|
|
SetStateLabel("Tail");
|
|
SetZ(floorz);
|
|
Vel.Z = 0;
|
|
ClearBounce();
|
|
bMissile = false;
|
|
}
|
|
A_CheckThrowBomb();
|
|
}
|
|
|
|
}
|
|
|
|
// Poison Bag Artifact (Flechette) ------------------------------------------
|
|
|
|
class ArtiPoisonBag : Inventory
|
|
{
|
|
Default
|
|
{
|
|
+FLOATBOB
|
|
Inventory.DefMaxAmount;
|
|
Inventory.PickupFlash "PickupFlash";
|
|
+INVENTORY.INVBAR +INVENTORY.FANCYPICKUPSOUND
|
|
Inventory.Icon "ARTIPSBG";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.PickupMessage "$TXT_ARTIPOISONBAG";
|
|
Tag "$TAG_ARTIPOISONBAG";
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
PSBG A -1;
|
|
Stop;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// AArtiPoisonBag :: BeginPlay
|
|
//
|
|
//============================================================================
|
|
|
|
override void BeginPlay ()
|
|
{
|
|
Super.BeginPlay ();
|
|
// If a subclass's specific icon is not defined, let it use the base class's.
|
|
if (!Icon.isValid())
|
|
{
|
|
Icon = GetDefaultByType("ArtiPoisonBag").Icon;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// GetFlechetteType
|
|
//
|
|
//============================================================================
|
|
|
|
private class<Actor> GetFlechetteType(Actor other)
|
|
{
|
|
class<Actor> spawntype = null;
|
|
PlayerPawn pp = PlayerPawn(other);
|
|
if (pp)
|
|
{
|
|
spawntype = pp.FlechetteType;
|
|
}
|
|
if (spawntype == null)
|
|
{
|
|
// default fallback if nothing valid defined.
|
|
spawntype = "ArtiPoisonBag3";
|
|
}
|
|
return spawntype;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// AArtiPoisonBag :: HandlePickup
|
|
//
|
|
//============================================================================
|
|
|
|
override bool HandlePickup (Inventory item)
|
|
{
|
|
// Only do special handling when picking up the base class
|
|
if (item.GetClass() != "ArtiPoisonBag")
|
|
{
|
|
return Super.HandlePickup (item);
|
|
}
|
|
|
|
if (GetClass() == GetFlechetteType(Owner))
|
|
{
|
|
if (Amount < MaxAmount || sv_unlimited_pickup)
|
|
{
|
|
Amount += item.Amount;
|
|
if (Amount > MaxAmount && !sv_unlimited_pickup)
|
|
{
|
|
Amount = MaxAmount;
|
|
}
|
|
item.bPickupGood = true;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// AArtiPoisonBag :: CreateCopy
|
|
//
|
|
//============================================================================
|
|
|
|
override Inventory CreateCopy (Actor other)
|
|
{
|
|
// Only the base class gets special handling
|
|
if (GetClass() != "ArtiPoisonBag")
|
|
{
|
|
return Super.CreateCopy (other);
|
|
}
|
|
|
|
class<Actor> spawntype = GetFlechetteType(other);
|
|
let copy = Inventory(Spawn (spawntype));
|
|
if (copy != null)
|
|
{
|
|
copy.Amount = Amount;
|
|
copy.MaxAmount = MaxAmount;
|
|
GoAwayAndDie ();
|
|
}
|
|
return copy;
|
|
}
|
|
}
|
|
|
|
// Poison Bag 1 (The Cleric's) ----------------------------------------------
|
|
|
|
class ArtiPoisonBag1 : ArtiPoisonBag
|
|
{
|
|
Default
|
|
{
|
|
Inventory.Icon "ARTIPSB1";
|
|
Tag "$TAG_ARTIPOISONBAG1";
|
|
}
|
|
|
|
override bool Use (bool pickup)
|
|
{
|
|
Actor mo = Spawn("PoisonBag", Owner.Vec3Offset(
|
|
16 * cos(Owner.angle),
|
|
24 * sin(Owner.angle),
|
|
-Owner.Floorclip + 8), ALLOW_REPLACE);
|
|
if (mo)
|
|
{
|
|
mo.target = Owner;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
// Poison Bag 2 (The Mage's) ------------------------------------------------
|
|
|
|
class ArtiPoisonBag2 : ArtiPoisonBag
|
|
{
|
|
Default
|
|
{
|
|
Inventory.Icon "ARTIPSB2";
|
|
Tag "$TAG_ARTIPOISONBAG2";
|
|
}
|
|
|
|
override bool Use (bool pickup)
|
|
{
|
|
Actor mo = Spawn("FireBomb", Owner.Vec3Offset(
|
|
16 * cos(Owner.angle),
|
|
24 * sin(Owner.angle),
|
|
-Owner.Floorclip + 8), ALLOW_REPLACE);
|
|
if (mo)
|
|
{
|
|
mo.target = Owner;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
// Poison Bag 3 (The Fighter's) ---------------------------------------------
|
|
|
|
class ArtiPoisonBag3 : ArtiPoisonBag
|
|
{
|
|
Default
|
|
{
|
|
Inventory.Icon "ARTIPSB3";
|
|
Tag "$TAG_ARTIPOISONBAG3";
|
|
}
|
|
|
|
override bool Use (bool pickup)
|
|
{
|
|
Actor mo = Spawn("ThrowingBomb", Owner.Pos + (0,0,35. - Owner.Floorclip + (Owner.player? Owner.player.crouchoffset : 0)), ALLOW_REPLACE);
|
|
if (mo)
|
|
{
|
|
mo.angle = Owner.angle + (((random[PoisonBag]() & 7) - 4) * (360./256.));
|
|
|
|
/* Original flight code from Hexen
|
|
* mo.momz = 4*F.RACUNIT+((player.lookdir)<<(F.RACBITS-4));
|
|
* mo.z += player.lookdir<<(F.RACBITS-4);
|
|
* P_ThrustMobj(mo, mo.ang, mo.info.speed);
|
|
* mo.momx += player.mo.momx>>1;
|
|
* mo.momy += player.mo.momy>>1;
|
|
*/
|
|
|
|
// When looking straight ahead, it uses a z velocity of 4 while the xy velocity
|
|
// is as set by the projectile. To accommodate self with a proper trajectory, we
|
|
// aim the projectile ~20 degrees higher than we're looking at and increase the
|
|
// speed we fire at accordingly.
|
|
double orgpitch = -Owner.Pitch;
|
|
double modpitch = clamp(-Owner.Pitch + 20, -89., 89.);
|
|
double ang = mo.angle;
|
|
double speed = (mo.Speed, 4.).Length();
|
|
double xyscale = speed * cos(modpitch);
|
|
|
|
mo.Vel.Z = speed * sin(modpitch);
|
|
mo.Vel.X = xyscale * cos(ang) + Owner.Vel.X / 2;
|
|
mo.Vel.Y = xyscale * sin(ang) + Owner.Vel.Y / 2;
|
|
mo.AddZ(mo.Speed * sin(modpitch));
|
|
|
|
mo.target = Owner;
|
|
mo.tics -= random[PoisonBag]()&3;
|
|
mo.CheckMissileSpawn(Owner.radius);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// Poison Bag 4 (Custom Giver) ----------------------------------------------
|
|
|
|
class ArtiPoisonBagGiver : ArtiPoisonBag
|
|
{
|
|
Default
|
|
{
|
|
Inventory.Icon "ARTIPSB4";
|
|
}
|
|
|
|
override bool Use (bool pickup)
|
|
{
|
|
Class<Actor> missiletype = MissileName;
|
|
if (missiletype != null)
|
|
{
|
|
Actor mo = Spawn (missiletype, Owner.Pos, ALLOW_REPLACE);
|
|
if (mo != null)
|
|
{
|
|
Inventory inv = Inventory(mo);
|
|
if (inv && inv.CallTryPickup(Owner)) return true;
|
|
mo.Destroy(); // Destroy if not inventory or couldn't be picked up
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
// Poison Bag 5 (Custom Thrower) --------------------------------------------
|
|
|
|
class ArtiPoisonBagShooter : ArtiPoisonBag
|
|
{
|
|
Default
|
|
{
|
|
Inventory.Icon "ARTIPSB5";
|
|
}
|
|
|
|
override bool Use (bool pickup)
|
|
{
|
|
Class<Actor> missiletype = MissileName;
|
|
if (missiletype != null)
|
|
{
|
|
Actor mo = Owner.SpawnPlayerMissile(missiletype);
|
|
if (mo != null)
|
|
{
|
|
// automatic handling of seeker missiles
|
|
if (mo.bSeekerMissile)
|
|
{
|
|
mo.tracer = Owner.target;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
// Poison Cloud -------------------------------------------------------------
|
|
|
|
class PoisonCloud : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 20;
|
|
Height 30;
|
|
Mass 0x7fffffff;
|
|
+NOBLOCKMAP +NOGRAVITY +DROPOFF
|
|
+NODAMAGETHRUST
|
|
+DONTSPLASH +FOILINVUL +CANBLAST +BLOODLESSIMPACT +BLOCKEDBYSOLIDACTORS
|
|
RenderStyle "Translucent";
|
|
Alpha 0.6;
|
|
DeathSound "PoisonShroomDeath";
|
|
DamageType "PoisonCloud";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
PSBG D 1;
|
|
PSBG D 1 A_Scream;
|
|
PSBG DEEEFFFGGGHHHII 2 A_PoisonBagDamage;
|
|
PSBG I 2 A_PoisonBagCheck;
|
|
PSBG I 1 A_PoisonBagCheck;
|
|
Goto Spawn + 3;
|
|
Death:
|
|
PSBG HG 7;
|
|
PSBG FD 6;
|
|
Stop;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
override void BeginPlay ()
|
|
{
|
|
Vel.X = MinVel; // missile objects must move to impact other objects
|
|
special1 = 24+(random[PoisonCloud]() & 7);
|
|
special2 = 0;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
override int DoSpecialDamage (Actor victim, int damage, Name damagetype)
|
|
{
|
|
if (victim.player)
|
|
{
|
|
bool mate = (target != null && victim.player != target.player && victim.IsTeammate (target));
|
|
bool dopoison;
|
|
|
|
if (!mate)
|
|
{
|
|
dopoison = victim.player.poisoncount < 4;
|
|
}
|
|
else
|
|
{
|
|
dopoison = victim.player.poisoncount < (int)(4. * level.teamdamage);
|
|
}
|
|
|
|
if (dopoison)
|
|
{
|
|
damage = 15 + (random[PoisonCloud]() & 15);
|
|
if (mate)
|
|
{
|
|
damage = (int)(damage * level.teamdamage);
|
|
}
|
|
// Handle passive damage modifiers (e.g. PowerProtection)
|
|
damage = victim.GetModifiedDamage(damagetype, damage, true);
|
|
// Modify with damage factors
|
|
damage = victim.ApplyDamageFactor(damagetype, damage);
|
|
if (damage > 0)
|
|
{
|
|
victim.player.PoisonDamage (self, 15 + (random[PoisonCloud]() & 15), false); // Don't play painsound
|
|
|
|
// If successful, play the poison sound.
|
|
if (victim.player.PoisonPlayer (self, self.target, 50))
|
|
victim.A_PlaySound ("*poison", CHAN_VOICE);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
else if (!victim.bIsMonster)
|
|
{ // only damage monsters/players with the poison cloud
|
|
return -1;
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_PoisonBagCheck
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_PoisonBagCheck()
|
|
{
|
|
if (--special1 <= 0)
|
|
{
|
|
SetStateLabel("Death");
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_PoisonBagDamage
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_PoisonBagDamage()
|
|
{
|
|
A_Explode(4, 40);
|
|
AddZ(BobSin(special2) / 16);
|
|
special2 = (special2 + 1) & 63;
|
|
}
|
|
}
|
|
|
|
// Poison Shroom ------------------------------------------------------------
|
|
|
|
class ZPoisonShroom : PoisonBag
|
|
{
|
|
Default
|
|
{
|
|
Radius 6;
|
|
Height 20;
|
|
PainChance 255;
|
|
Health 30;
|
|
Mass 0x7fffffff;
|
|
+SHOOTABLE
|
|
+SOLID
|
|
+NOBLOOD
|
|
+NOICEDEATH
|
|
-NOBLOCKMAP
|
|
-NOGRAVITY
|
|
PainSound "PoisonShroomPain";
|
|
DeathSound "PoisonShroomDeath";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SHRM A 5 A_PoisonShroom;
|
|
Goto Pain+1;
|
|
Pain:
|
|
SHRM A 6;
|
|
SHRM B 8 A_Pain;
|
|
Goto Spawn;
|
|
Death:
|
|
SHRM CD 5;
|
|
SHRM E 5 A_PoisonBagInit;
|
|
SHRM F -1;
|
|
Stop;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_PoisonShroom
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_PoisonShroom()
|
|
{
|
|
tics = 128 + (random[PoisonShroom]() << 1);
|
|
}
|
|
}
|
|
|