mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-11-24 04:41:53 +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.
547 lines
10 KiB
Text
547 lines
10 KiB
Text
|
|
// Boss spot ----------------------------------------------------------------
|
|
|
|
class BossSpot : SpecialSpot
|
|
{
|
|
Default
|
|
{
|
|
+INVISIBLE
|
|
}
|
|
}
|
|
|
|
// Sorcerer (D'Sparil on his serpent) ---------------------------------------
|
|
|
|
class Sorcerer1 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Health 2000;
|
|
Radius 28;
|
|
Height 100;
|
|
Mass 800;
|
|
Speed 16;
|
|
PainChance 56;
|
|
Monster;
|
|
+BOSS
|
|
+DONTMORPH
|
|
+NORADIUSDMG
|
|
+NOTARGET
|
|
+NOICEDEATH
|
|
+FLOORCLIP
|
|
+DONTGIB
|
|
SeeSound "dsparilserpent/sight";
|
|
AttackSound "dsparilserpent/attack";
|
|
PainSound "dsparilserpent/pain";
|
|
DeathSound "dsparilserpent/death";
|
|
ActiveSound "dsparilserpent/active";
|
|
Obituary "$OB_DSPARIL1";
|
|
HitObituary "$OB_DSPARIL1HIT";
|
|
}
|
|
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SRCR AB 10 A_Look;
|
|
Loop;
|
|
See:
|
|
SRCR ABCD 5 A_Sor1Chase;
|
|
Loop;
|
|
Pain:
|
|
SRCR Q 6 A_Sor1Pain;
|
|
Goto See;
|
|
Missile:
|
|
SRCR Q 7 A_FaceTarget;
|
|
SRCR R 6 A_FaceTarget;
|
|
SRCR S 10 A_Srcr1Attack;
|
|
Goto See;
|
|
Missile2:
|
|
SRCR S 10 A_FaceTarget;
|
|
SRCR Q 7 A_FaceTarget;
|
|
SRCR R 6 A_FaceTarget;
|
|
SRCR S 10 A_Srcr1Attack;
|
|
Goto See;
|
|
Death:
|
|
SRCR E 7;
|
|
SRCR F 7 A_Scream;
|
|
SRCR G 7;
|
|
SRCR HIJK 6;
|
|
SRCR L 25 A_PlaySound("dsparil/zap", CHAN_BODY, 1, false, ATTN_NONE);
|
|
SRCR MN 5;
|
|
SRCR O 4;
|
|
SRCR L 20 A_PlaySound("dsparil/zap", CHAN_BODY, 1, false, ATTN_NONE);
|
|
SRCR MN 5;
|
|
SRCR O 4;
|
|
SRCR L 12;
|
|
SRCR P -1 A_SorcererRise;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_Sor1Pain
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_Sor1Pain ()
|
|
{
|
|
special1 = 20; // Number of steps to walk fast
|
|
A_Pain();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_Sor1Chase
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_Sor1Chase ()
|
|
{
|
|
if (special1)
|
|
{
|
|
special1--;
|
|
tics -= 3;
|
|
}
|
|
A_Chase();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_Srcr1Attack
|
|
//
|
|
// Sorcerer demon attack.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_Srcr1Attack ()
|
|
{
|
|
if (!target)
|
|
{
|
|
return;
|
|
}
|
|
A_PlaySound (AttackSound, CHAN_BODY);
|
|
if (CheckMeleeRange ())
|
|
{
|
|
int damage = random[Srcr1Attack](1,8) * 8;
|
|
int newdam = target.DamageMobj (self, self, damage, 'Melee');
|
|
target.TraceBleed (newdam > 0 ? newdam : damage, self);
|
|
return;
|
|
}
|
|
|
|
if (health > (SpawnHealth()/3)*2)
|
|
{ // Spit one fireball
|
|
SpawnMissileZ (pos.z + 48, target, "SorcererFX1");
|
|
}
|
|
else
|
|
{ // Spit three fireballs
|
|
Actor mo = SpawnMissileZ (pos.z + 48, target, "SorcererFX1");
|
|
if (mo != null)
|
|
{
|
|
double ang = mo.angle;
|
|
SpawnMissileAngleZ(pos.z + 48, "SorcererFX1", ang - 3, mo.Vel.Z);
|
|
SpawnMissileAngleZ(pos.z + 48, "SorcererFX1", ang + 3, mo.Vel.Z);
|
|
}
|
|
if (health < SpawnHealth()/3)
|
|
{ // Maybe attack again
|
|
if (special1)
|
|
{ // Just attacked, so don't attack again
|
|
special1 = 0;
|
|
}
|
|
else
|
|
{ // Set state to attack again
|
|
special1 = 1;
|
|
SetStateLabel("Missile2");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_SorcererRise
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_SorcererRise ()
|
|
{
|
|
bSolid = false;
|
|
Actor mo = Spawn("Sorcerer2", Pos, ALLOW_REPLACE);
|
|
if (mo != null)
|
|
{
|
|
mo.Translation = Translation;
|
|
mo.SetStateLabel("Rise");
|
|
mo.angle = angle;
|
|
mo.CopyFriendliness (self, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Sorcerer FX 1 ------------------------------------------------------------
|
|
|
|
class SorcererFX1 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 10;
|
|
Height 10;
|
|
Speed 20;
|
|
FastSpeed 28;
|
|
Damage 10;
|
|
DamageType "Fire";
|
|
Projectile;
|
|
-ACTIVATEIMPACT
|
|
-ACTIVATEPCROSS
|
|
RenderStyle "Add";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
FX14 ABC 6 BRIGHT;
|
|
Loop;
|
|
Death:
|
|
FX14 DEFGH 5 BRIGHT;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
|
|
// Sorcerer 2 (D'Sparil without his serpent) --------------------------------
|
|
|
|
class Sorcerer2 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Health 3500;
|
|
Radius 16;
|
|
Height 70;
|
|
Mass 300;
|
|
Speed 14;
|
|
Painchance 32;
|
|
Monster;
|
|
+DROPOFF
|
|
+BOSS
|
|
+DONTMORPH
|
|
+FULLVOLACTIVE
|
|
+NORADIUSDMG
|
|
+NOTARGET
|
|
+NOICEDEATH
|
|
+FLOORCLIP
|
|
+BOSSDEATH
|
|
SeeSound "dsparil/sight";
|
|
AttackSound "dsparil/attack";
|
|
PainSound "dsparil/pain";
|
|
ActiveSound "dsparil/active";
|
|
Obituary "$OB_DSPARIL2";
|
|
HitObituary "$OB_DSPARIL2HIT";
|
|
}
|
|
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SOR2 MN 10 A_Look;
|
|
Loop;
|
|
See:
|
|
SOR2 MNOP 4 A_Chase;
|
|
Loop;
|
|
Rise:
|
|
SOR2 AB 4;
|
|
SOR2 C 4 A_PlaySound("dsparil/rise", CHAN_BODY, 1, false, ATTN_NONE);
|
|
SOR2 DEF 4;
|
|
SOR2 G 12 A_PlaySound("dsparil/sight", CHAN_BODY, 1, false, ATTN_NONE);
|
|
Goto See;
|
|
Pain:
|
|
SOR2 Q 3;
|
|
SOR2 Q 6 A_Pain;
|
|
Goto See;
|
|
Missile:
|
|
SOR2 R 9 A_Srcr2Decide;
|
|
SOR2 S 9 A_FaceTarget;
|
|
SOR2 T 20 A_Srcr2Attack;
|
|
Goto See;
|
|
Teleport:
|
|
SOR2 LKJIHG 6;
|
|
Goto See;
|
|
Death:
|
|
SDTH A 8 A_Sor2DthInit;
|
|
SDTH B 8;
|
|
SDTH C 8 A_PlaySound("dsparil/scream", CHAN_BODY, 1, false, ATTN_NONE);
|
|
DeathLoop:
|
|
SDTH DE 7;
|
|
SDTH F 7 A_Sor2DthLoop;
|
|
SDTH G 6 A_PlaySound("dsparil/explode", CHAN_BODY, 1, false, ATTN_NONE);
|
|
SDTH H 6;
|
|
SDTH I 18;
|
|
SDTH J 6 A_NoBlocking;
|
|
SDTH K 6 A_PlaySound("dsparil/bones", CHAN_BODY, 1, false, ATTN_NONE);
|
|
SDTH LMN 6;
|
|
SDTH O -1 A_BossDeath;
|
|
Stop;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC P_DSparilTeleport
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void DSparilTeleport ()
|
|
{
|
|
SpotState state = SpotState.GetSpotState();
|
|
if (state == null) return;
|
|
|
|
Actor spot = state.GetSpotWithMinMaxDistance("BossSpot", pos.x, pos.y, 128, 0);
|
|
if (spot == null) return;
|
|
|
|
Vector3 prev = Pos;
|
|
if (TeleportMove (spot.Pos, false))
|
|
{
|
|
Actor mo = Spawn("Sorcerer2Telefade", prev, ALLOW_REPLACE);
|
|
if (mo)
|
|
{
|
|
mo.Translation = Translation;
|
|
mo.A_PlaySound("misc/teleport", CHAN_BODY);
|
|
}
|
|
SetStateLabel ("Teleport");
|
|
A_PlaySound ("misc/teleport", CHAN_BODY);
|
|
SetZ(floorz);
|
|
angle = spot.angle;
|
|
vel = (0,0,0);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_Srcr2Decide
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_Srcr2Decide ()
|
|
{
|
|
static const int chance[] =
|
|
{
|
|
192, 120, 120, 120, 64, 64, 32, 16, 0
|
|
};
|
|
|
|
int health8 = max(1, SpawnHealth() / 8);
|
|
int chanceindex = min(8, health / health8);
|
|
|
|
if (random[Srcr2Decide]() < chance[chanceindex])
|
|
{
|
|
DSparilTeleport ();
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_Srcr2Attack
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_Srcr2Attack ()
|
|
{
|
|
if (!target)
|
|
{
|
|
return;
|
|
}
|
|
A_PlaySound (AttackSound, CHAN_BODY, 1, false, ATTN_NONE);
|
|
if (CheckMeleeRange())
|
|
{
|
|
int damage = random[Srcr2Atk](1, 8) * 20;
|
|
int newdam = target.DamageMobj (self, self, damage, 'Melee');
|
|
target.TraceBleed (newdam > 0 ? newdam : damage, self);
|
|
return;
|
|
}
|
|
int chance = health < SpawnHealth()/2 ? 96 : 48;
|
|
if (random[Srcr2Atk]() < chance)
|
|
{ // Wizard spawners
|
|
|
|
SpawnMissileAngle("Sorcerer2FX2", Angle - 45, 0.5);
|
|
SpawnMissileAngle("Sorcerer2FX2", Angle + 45, 0.5);
|
|
}
|
|
else
|
|
{ // Blue bolt
|
|
SpawnMissile (target, "Sorcerer2FX1");
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_Sor2DthInit
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_Sor2DthInit ()
|
|
{
|
|
special1 = 7; // Animation loop counter
|
|
Thing_Destroy(0); // Kill monsters early
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_Sor2DthLoop
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_Sor2DthLoop ()
|
|
{
|
|
if (--special1)
|
|
{ // Need to loop
|
|
SetStateLabel("DeathLoop");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Sorcerer 2 FX 1 ----------------------------------------------------------
|
|
|
|
class Sorcerer2FX1 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 10;
|
|
Height 6;
|
|
Speed 20;
|
|
FastSpeed 28;
|
|
Damage 1;
|
|
Projectile;
|
|
-ACTIVATEIMPACT
|
|
-ACTIVATEPCROSS
|
|
RenderStyle "Add";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
FX16 ABC 3 BRIGHT A_BlueSpark;
|
|
Loop;
|
|
Death:
|
|
FX16 G 5 BRIGHT A_Explode(random[S2FX1](80,111));
|
|
FX16 HIJKL 5 BRIGHT;
|
|
Stop;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_BlueSpark
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_BlueSpark ()
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
Actor mo = Spawn("Sorcerer2FXSpark", pos, ALLOW_REPLACE);
|
|
if (mo != null)
|
|
{
|
|
mo.Vel.X = Random2[BlueSpark]() / 128.;
|
|
mo.Vel.Y = Random2[BlueSpark]() / 128.;
|
|
mo.Vel.Z = 1. + Random[BlueSpark]() / 256.;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sorcerer 2 FX Spark ------------------------------------------------------
|
|
|
|
class Sorcerer2FXSpark : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 20;
|
|
Height 16;
|
|
+NOBLOCKMAP
|
|
+NOGRAVITY
|
|
+NOTELEPORT
|
|
+CANNOTPUSH
|
|
RenderStyle "Add";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
FX16 DEF 12 BRIGHT;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// Sorcerer 2 FX 2 ----------------------------------------------------------
|
|
|
|
class Sorcerer2FX2 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 10;
|
|
Height 6;
|
|
Speed 6;
|
|
Damage 10;
|
|
Projectile;
|
|
-ACTIVATEIMPACT
|
|
-ACTIVATEPCROSS
|
|
RenderStyle "Add";
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
FX11 A 35 BRIGHT;
|
|
FX11 A 5 BRIGHT A_GenWizard;
|
|
FX11 B 5 BRIGHT;
|
|
Goto Spawn+1;
|
|
Death:
|
|
FX11 CDEFG 5 BRIGHT;
|
|
Stop;
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_GenWizard
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_GenWizard ()
|
|
{
|
|
Actor mo = Spawn("Wizard", pos, ALLOW_REPLACE);
|
|
if (mo != null)
|
|
{
|
|
mo.AddZ(-mo.Default.Height / 2, false);
|
|
if (!mo.TestMobjLocation ())
|
|
{ // Didn't fit
|
|
mo.ClearCounters();
|
|
mo.Destroy ();
|
|
}
|
|
else
|
|
{ // [RH] Make the new wizards inherit D'Sparil's target
|
|
mo.CopyFriendliness (self.target, true);
|
|
|
|
Vel = (0,0,0);
|
|
SetStateLabel('Death');
|
|
bMissile = false;
|
|
mo.master = target;
|
|
SpawnTeleportFog(pos, false, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sorcerer 2 Telefade ------------------------------------------------------
|
|
|
|
class Sorcerer2Telefade : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
SOR2 GHIJKL 6;
|
|
Stop;
|
|
}
|
|
}
|
|
|