qzdoom/wadsrc/static/zscript/hexen/clericholy.txt
Christoph Oelckers 06900ff8be - reviewd script code for spawn calls that did not check their results.
Nothing should ever assume that spawning an actor is unconditionally successful. There can always be some edge cases where this is not the case.
2016-12-31 10:27:12 -05:00

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;
}
}