mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-11 05:01:09 +00:00
06900ff8be
Nothing should ever assume that spawning an actor is unconditionally successful. There can always be some edge cases where this is not the case.
676 lines
13 KiB
Text
676 lines
13 KiB
Text
|
|
// Cleric Weapon Piece ------------------------------------------------------
|
|
|
|
class ClericWeaponPiece : WeaponPiece
|
|
{
|
|
Default
|
|
{
|
|
Inventory.PickupSound "misc/w_pkup";
|
|
Inventory.PickupMessage "$TXT_WRAITHVERGE_PIECE";
|
|
Inventory.ForbiddenTo "FighterPlayer", "MagePlayer";
|
|
WeaponPiece.Weapon "CWeapWraithverge";
|
|
+FLOATBOB
|
|
}
|
|
}
|
|
|
|
// Cleric Weapon Piece 1 ----------------------------------------------------
|
|
|
|
class CWeaponPiece1 : ClericWeaponPiece
|
|
{
|
|
Default
|
|
{
|
|
WeaponPiece.Number 1;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
WCH1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// Cleric Weapon Piece 2 ----------------------------------------------------
|
|
|
|
class CWeaponPiece2 : ClericWeaponPiece
|
|
{
|
|
Default
|
|
{
|
|
WeaponPiece.Number 2;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
WCH2 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// Cleric Weapon Piece 3 ----------------------------------------------------
|
|
|
|
class CWeaponPiece3 : ClericWeaponPiece
|
|
{
|
|
Default
|
|
{
|
|
WeaponPiece.Number 3;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
WCH3 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// Wraithverge Drop ---------------------------------------------------------
|
|
|
|
class WraithvergeDrop : Actor
|
|
{
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 1;
|
|
TNT1 A 1 A_DropWeaponPieces("CWeaponPiece1", "CWeaponPiece2", "CWeaponPiece3");
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// Cleric's Wraithverge (Holy Symbol?) --------------------------------------
|
|
|
|
class CWeapWraithverge : ClericWeapon
|
|
{
|
|
int CHolyCount;
|
|
|
|
Default
|
|
{
|
|
Health 3;
|
|
Weapon.SelectionOrder 3000;
|
|
+WEAPON.PRIMARY_USES_BOTH;
|
|
+Inventory.NoAttenPickupSound
|
|
Weapon.AmmoUse1 18;
|
|
Weapon.AmmoUse2 18;
|
|
Weapon.AmmoGive1 20;
|
|
Weapon.AmmoGive2 20;
|
|
Weapon.KickBack 150;
|
|
Weapon.AmmoType1 "Mana1";
|
|
Weapon.AmmoType2 "Mana2";
|
|
Inventory.PickupMessage "$TXT_WEAPON_C4";
|
|
Tag "$TAG_CWEAPWRAITHVERGE";
|
|
Inventory.PickupSound "WeaponBuild";
|
|
}
|
|
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A -1;
|
|
Stop;
|
|
Ready:
|
|
CHLY A 1 A_WeaponReady;
|
|
Loop;
|
|
Select:
|
|
CHLY A 1 A_Raise;
|
|
Loop;
|
|
Deselect:
|
|
CHLY A 1 A_Lower;
|
|
Loop;
|
|
Fire:
|
|
CHLY AB 1 Bright Offset (0, 40);
|
|
CHLY CD 2 Bright Offset (0, 43);
|
|
CHLY E 2 Bright Offset (0, 45);
|
|
CHLY F 6 Bright Offset (0, 48) A_CHolyAttack;
|
|
CHLY GG 2 Bright Offset (0, 40) A_CHolyPalette;
|
|
CHLY G 2 Offset (0, 36) A_CHolyPalette;
|
|
Goto Ready;
|
|
}
|
|
|
|
override color GetBlend ()
|
|
{
|
|
if (paletteflash & PF_HEXENWEAPONS)
|
|
{
|
|
if (CHolyCount == 3)
|
|
return Color(128, 70, 70, 70);
|
|
else if (CHolyCount == 2)
|
|
return Color(128, 100, 100, 100);
|
|
else if (CHolyCount == 1)
|
|
return Color(128, 130, 130, 130);
|
|
else
|
|
return Color(0, 0, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
return Color(CHolyCount * 128 / 3, 131, 131, 131);
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_CHolyAttack
|
|
//
|
|
//============================================================================
|
|
|
|
action void A_CHolyAttack()
|
|
{
|
|
FTranslatedLineTarget t;
|
|
|
|
if (player == null)
|
|
{
|
|
return;
|
|
}
|
|
Weapon weapon = player.ReadyWeapon;
|
|
if (weapon != null)
|
|
{
|
|
if (!weapon.DepleteAmmo (weapon.bAltFire))
|
|
return;
|
|
}
|
|
Actor missile = SpawnPlayerMissile ("HolyMissile", angle, pLineTarget:t);
|
|
if (missile != null && !t.unlinked)
|
|
{
|
|
missile.tracer = t.linetarget;
|
|
}
|
|
|
|
invoker.CHolyCount = 3;
|
|
A_PlaySound ("HolySymbolFire", CHAN_WEAPON);
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_CHolyPalette
|
|
//
|
|
//============================================================================
|
|
|
|
action void A_CHolyPalette()
|
|
{
|
|
if (invoker.CHolyCount > 0) invoker.CHolyCount--;
|
|
}
|
|
}
|
|
|
|
// Holy Missile -------------------------------------------------------------
|
|
|
|
class HolyMissile : Actor
|
|
{
|
|
Default
|
|
{
|
|
Speed 30;
|
|
Radius 15;
|
|
Height 8;
|
|
Damage 4;
|
|
Projectile;
|
|
-ACTIVATEIMPACT -ACTIVATEPCROSS
|
|
+EXTREMEDEATH
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SPIR PPPP 3 Bright A_SpawnItemEx("HolyMissilePuff");
|
|
Death:
|
|
SPIR P 1 Bright A_CHolyAttack2;
|
|
Stop;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_CHolyAttack2
|
|
//
|
|
// Spawns the spirits
|
|
//============================================================================
|
|
|
|
void A_CHolyAttack2()
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
Actor mo = Spawn("HolySpirit", Pos, ALLOW_REPLACE);
|
|
if (!mo)
|
|
{
|
|
continue;
|
|
}
|
|
switch (j)
|
|
{ // float bob index
|
|
|
|
case 0:
|
|
mo.WeaveIndexZ = random[HolyAtk2]() & 7; // upper-left
|
|
break;
|
|
case 1:
|
|
mo.WeaveIndexZ = 32 + (random[HolyAtk2]() & 7); // upper-right
|
|
break;
|
|
case 2:
|
|
mo.WeaveIndexXY = 32 + (random[HolyAtk2]() & 7); // lower-left
|
|
break;
|
|
case 3:
|
|
mo.WeaveIndexXY = 32 + (random[HolyAtk2]() & 7);
|
|
mo.WeaveIndexZ = 32 + (random[HolyAtk2]() & 7);
|
|
break;
|
|
}
|
|
mo.SetZ(pos.z);
|
|
mo.angle = angle + 67.5 - 45.*j;
|
|
mo.Thrust();
|
|
mo.target = target;
|
|
mo.args[0] = 10; // initial turn value
|
|
mo.args[1] = 0; // initial look angle
|
|
if (deathmatch)
|
|
{ // Ghosts last slightly less longer in DeathMatch
|
|
mo.health = 85;
|
|
}
|
|
if (tracer)
|
|
{
|
|
mo.tracer = tracer;
|
|
mo.bNoClip = true;
|
|
mo.bSkullFly = true;
|
|
mo.bMissile = false;
|
|
}
|
|
HolyTail.SpawnSpiritTail (mo);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Holy Missile Puff --------------------------------------------------------
|
|
|
|
class HolyMissilePuff : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 4;
|
|
Height 8;
|
|
+NOBLOCKMAP +NOGRAVITY +DROPOFF
|
|
+NOTELEPORT
|
|
RenderStyle "Translucent";
|
|
Alpha 0.4;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
SPIR QRSTU 3;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// Holy Puff ----------------------------------------------------------------
|
|
|
|
class HolyPuff : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP +NOGRAVITY
|
|
RenderStyle "Translucent";
|
|
Alpha 0.6;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
SPIR KLMNO 3;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// Holy Spirit --------------------------------------------------------------
|
|
|
|
class HolySpirit : Actor
|
|
{
|
|
Default
|
|
{
|
|
Health 105;
|
|
Speed 12;
|
|
Radius 10;
|
|
Height 6;
|
|
Damage 3;
|
|
Projectile;
|
|
+RIPPER +SEEKERMISSILE
|
|
+FOILINVUL +SKYEXPLODE +NOEXPLODEFLOOR +CANBLAST
|
|
+EXTREMEDEATH +NOSHIELDREFLECT
|
|
RenderStyle "Translucent";
|
|
Alpha 0.4;
|
|
DeathSound "SpiritDie";
|
|
Obituary "$OB_MPCWEAPWRAITHVERGE";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SPIR AAB 2 A_CHolySeek;
|
|
SPIR B 2 A_CHolyCheckScream;
|
|
Loop;
|
|
Death:
|
|
SPIR D 4;
|
|
SPIR E 4 A_Scream;
|
|
SPIR FGHI 4;
|
|
Stop;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
//
|
|
//
|
|
//============================================================================
|
|
|
|
override bool Slam(Actor thing)
|
|
{
|
|
if (thing.bShootable && thing != target)
|
|
{
|
|
if (multiplayer && !deathmatch && thing.player && target.player)
|
|
{ // don't attack other co-op players
|
|
return true;
|
|
}
|
|
if (thing.bReflective && (thing.player || thing.bBoss))
|
|
{
|
|
tracer = target;
|
|
target = thing;
|
|
return true;
|
|
}
|
|
if (thing.bIsMonster || thing.player)
|
|
{
|
|
tracer = thing;
|
|
}
|
|
if (random[SpiritSlam]() < 96)
|
|
{
|
|
int dam = 12;
|
|
if (thing.player || thing.bBoss)
|
|
{
|
|
dam = 3;
|
|
// ghost burns out faster when attacking players/bosses
|
|
health -= 6;
|
|
}
|
|
thing.DamageMobj(self, target, dam, 'Melee');
|
|
if (random[SpiritSlam]() < 128)
|
|
{
|
|
Spawn("HolyPuff", Pos, ALLOW_REPLACE);
|
|
A_PlaySound("SpiritAttack", CHAN_WEAPON);
|
|
if (thing.bIsMonster && random[SpiritSlam]() < 128)
|
|
{
|
|
thing.Howl();
|
|
}
|
|
}
|
|
}
|
|
if (thing.health <= 0)
|
|
{
|
|
tracer = null;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
override bool SpecialBlastHandling (Actor source, double strength)
|
|
{
|
|
if (tracer == source)
|
|
{
|
|
tracer = target;
|
|
target = source;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// CHolyFindTarget
|
|
//
|
|
//============================================================================
|
|
|
|
private void CHolyFindTarget ()
|
|
{
|
|
Actor target;
|
|
|
|
if ( (target = RoughMonsterSearch (6, true)) )
|
|
{
|
|
tracer = target;
|
|
bNoClip = true;
|
|
bSkullFly = true;
|
|
bMissile = false;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// CHolySeekerMissile
|
|
//
|
|
// Similar to P_SeekerMissile, but seeks to a random Z on the target
|
|
//============================================================================
|
|
|
|
private void CHolySeekerMissile (double thresh, double turnMax)
|
|
{
|
|
Actor target = tracer;
|
|
if (target == NULL)
|
|
{
|
|
return;
|
|
}
|
|
if (!target.bShootable || (!target.bIsMonster && !target.player))
|
|
{ // Target died/target isn't a player or creature
|
|
tracer = null;
|
|
bNoClip = false;
|
|
bSkullFly = false;
|
|
bMissile = true;
|
|
CHolyFindTarget();
|
|
return;
|
|
}
|
|
double ang = deltaangle(angle, AngleTo(target));
|
|
double delta = abs(ang);
|
|
|
|
if (delta > thresh)
|
|
{
|
|
delta /= 2;
|
|
if (delta > turnMax)
|
|
{
|
|
delta = turnMax;
|
|
}
|
|
}
|
|
if (ang > 0)
|
|
{ // Turn clockwise
|
|
angle += delta;
|
|
}
|
|
else
|
|
{ // Turn counter clockwise
|
|
angle -= delta;
|
|
}
|
|
VelFromAngle();
|
|
|
|
if (!(level.time&15)
|
|
|| pos.z > target.pos.z + target.height
|
|
|| pos.z + height < target.pos.z)
|
|
{
|
|
double newZ = target.pos.z + ((random[HolySeeker]()*target.Height) / 256.);
|
|
double deltaZ = newZ - pos.z;
|
|
if (abs(deltaZ) > 15)
|
|
{
|
|
if (deltaZ > 0)
|
|
{
|
|
deltaZ = 15;
|
|
}
|
|
else
|
|
{
|
|
deltaZ = -15;
|
|
}
|
|
}
|
|
Vel.Z = deltaZ / DistanceBySpeed(target, Speed);
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_CHolySeek
|
|
//
|
|
//============================================================================
|
|
|
|
void A_CHolySeek()
|
|
{
|
|
health--;
|
|
if (health <= 0)
|
|
{
|
|
Vel.X /= 4;
|
|
Vel.Y /= 4;
|
|
Vel.Z = 0;
|
|
SetStateLabel ("Death");
|
|
tics -= random[HolySeeker]()&3;
|
|
return;
|
|
}
|
|
if (tracer)
|
|
{
|
|
CHolySeekerMissile (args[0], args[0]*2.);
|
|
if (!((level.time+7)&15))
|
|
{
|
|
args[0] = 5+(random[HolySeeker]()/20);
|
|
}
|
|
}
|
|
|
|
int xyspeed = (random[HolySeeker]() % 5);
|
|
int zspeed = (random[HolySeeker]() % 5);
|
|
A_Weave(xyspeed, zspeed, 4., 2.);
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_CHolyCheckScream
|
|
//
|
|
//============================================================================
|
|
|
|
void A_CHolyCheckScream()
|
|
{
|
|
A_CHolySeek();
|
|
if (random[HolyScream]() < 20)
|
|
{
|
|
A_PlaySound ("SpiritActive", CHAN_VOICE);
|
|
}
|
|
if (!tracer)
|
|
{
|
|
CHolyFindTarget();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Holy Tail ----------------------------------------------------------------
|
|
|
|
class HolyTail : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 1;
|
|
Height 1;
|
|
+NOBLOCKMAP +NOGRAVITY +DROPOFF +NOCLIP
|
|
+NOTELEPORT
|
|
RenderStyle "Translucent";
|
|
Alpha 0.6;
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SPIR C 1 A_CHolyTail;
|
|
Loop;
|
|
TailTrail:
|
|
SPIR D -1;
|
|
Stop;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// SpawnSpiritTail
|
|
//
|
|
//============================================================================
|
|
|
|
static void SpawnSpiritTail (Actor spirit)
|
|
{
|
|
Actor tail = Spawn ("HolyTail", spirit.Pos, ALLOW_REPLACE);
|
|
if (tail != null)
|
|
{
|
|
tail.target = spirit; // parent
|
|
for (int i = 1; i < 3; i++)
|
|
{
|
|
Actor next = Spawn ("HolyTailTrail", spirit.Pos, ALLOW_REPLACE);
|
|
tail.tracer = next;
|
|
tail = next;
|
|
}
|
|
tail.tracer = null; // last tail bit
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// CHolyTailFollow
|
|
//
|
|
//============================================================================
|
|
|
|
private void CHolyTailFollow(double dist)
|
|
{
|
|
Actor mo = self;
|
|
while (mo)
|
|
{
|
|
Actor child = mo.tracer;
|
|
if (child)
|
|
{
|
|
double an = mo.AngleTo(child);
|
|
double oldDistance = child.Distance2D(mo);
|
|
if (child.TryMove(mo.Pos.XY + AngleToVector(an, dist), true))
|
|
{
|
|
double newDistance = child.Distance2D(mo) - 1;
|
|
if (oldDistance < 1)
|
|
{
|
|
if (child.pos.z < mo.pos.z)
|
|
{
|
|
child.SetZ(mo.pos.z - dist);
|
|
}
|
|
else
|
|
{
|
|
child.SetZ(mo.pos.z + dist);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
child.SetZ(mo.pos.z + (newDistance * (child.pos.z - mo.pos.z) / oldDistance));
|
|
}
|
|
}
|
|
}
|
|
mo = child;
|
|
dist -= 1;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// CHolyTailRemove
|
|
//
|
|
//============================================================================
|
|
|
|
private void CHolyTailRemove ()
|
|
{
|
|
Actor mo = self;
|
|
while (mo)
|
|
{
|
|
Actor next = mo.tracer;
|
|
mo.Destroy ();
|
|
mo = next;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// A_CHolyTail
|
|
//
|
|
//============================================================================
|
|
|
|
void A_CHolyTail()
|
|
{
|
|
Actor parent = target;
|
|
|
|
if (parent == null || parent.health <= 0) // better check for health than current state - it's safer!
|
|
{ // Ghost removed, so remove all tail parts
|
|
CHolyTailRemove ();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (TryMove(parent.Vec2Angle(14., parent.Angle, true), true))
|
|
{
|
|
SetZ(parent.pos.z - 5.);
|
|
}
|
|
CHolyTailFollow(10);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Holy Tail Trail ---------------------------------------------------------
|
|
|
|
class HolyTailTrail : HolyTail
|
|
{
|
|
States
|
|
{
|
|
Spawn:
|
|
Goto TailTrail;
|
|
}
|
|
}
|