mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-27 14:22:13 +00:00
434 lines
9.1 KiB
Text
434 lines
9.1 KiB
Text
|
|
//===========================================================================
|
|
//
|
|
// Boss Brain
|
|
//
|
|
//===========================================================================
|
|
|
|
class BossBrain : Actor
|
|
{
|
|
Default
|
|
{
|
|
Health 250;
|
|
Mass 10000000;
|
|
PainChance 255;
|
|
+SOLID +SHOOTABLE
|
|
+NOICEDEATH
|
|
+OLDRADIUSDMG
|
|
PainSound "brain/pain";
|
|
DeathSound "brain/death";
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
BBRN A -1;
|
|
Stop;
|
|
Pain:
|
|
BBRN B 36 A_BrainPain;
|
|
Goto Spawn;
|
|
Death:
|
|
BBRN A 100 A_BrainScream;
|
|
BBRN AA 10;
|
|
BBRN A -1 A_BrainDie;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// Boss Eye
|
|
//
|
|
//===========================================================================
|
|
|
|
class BossEye : Actor
|
|
{
|
|
Default
|
|
{
|
|
Height 32;
|
|
+NOBLOCKMAP
|
|
+NOSECTOR
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
SSWV A 10 A_Look;
|
|
Loop;
|
|
See:
|
|
SSWV A 181 A_BrainAwake;
|
|
SSWV A 150 A_BrainSpit;
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Boss Target
|
|
//
|
|
//===========================================================================
|
|
|
|
class BossTarget : SpecialSpot
|
|
{
|
|
Default
|
|
{
|
|
Height 32;
|
|
+NOBLOCKMAP;
|
|
+NOSECTOR;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Spawn shot
|
|
//
|
|
//===========================================================================
|
|
|
|
class SpawnShot : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 6;
|
|
Height 32;
|
|
Speed 10;
|
|
Damage 3;
|
|
Projectile;
|
|
+NOCLIP
|
|
-ACTIVATEPCROSS
|
|
+RANDOMIZE
|
|
SeeSound "brain/spit";
|
|
DeathSound "brain/cubeboom";
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
BOSF A 3 BRIGHT A_SpawnSound;
|
|
BOSF BCD 3 BRIGHT A_SpawnFly;
|
|
Loop;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Spawn fire
|
|
//
|
|
//===========================================================================
|
|
|
|
class SpawnFire : Actor
|
|
{
|
|
Default
|
|
{
|
|
Height 78;
|
|
+NOBLOCKMAP
|
|
+NOGRAVITY
|
|
+ZDOOMTRANS
|
|
RenderStyle "Add";
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
FIRE ABCDEFGH 4 Bright A_Fire;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// Code (must be attached to Actor)
|
|
//
|
|
//===========================================================================
|
|
|
|
extend class Actor
|
|
{
|
|
|
|
void A_BrainAwake()
|
|
{
|
|
A_PlaySound("brain/sight", CHAN_VOICE, 1, false, ATTN_NONE);
|
|
}
|
|
|
|
void A_BrainPain()
|
|
{
|
|
A_PlaySound("brain/pain", CHAN_VOICE, 1, false, ATTN_NONE);
|
|
}
|
|
|
|
private static void BrainishExplosion(vector3 pos)
|
|
{
|
|
Actor boom = Actor.Spawn("Rocket", pos, NO_REPLACE);
|
|
if (boom)
|
|
{
|
|
boom.DeathSound = "misc/brainexplode";
|
|
boom.Vel.z = random[BrainScream](0, 255)/128.;
|
|
|
|
boom.SetStateLabel ("Brainexplode");
|
|
boom.bRocketTrail = false;
|
|
boom.SetDamage(0); // disables collision detection which is not wanted here
|
|
boom.tics -= random[BrainScream](0, 7);
|
|
if (boom.tics < 1) boom.tics = 1;
|
|
}
|
|
}
|
|
|
|
void A_BrainScream()
|
|
{
|
|
for (double x = -196; x < +320; x += 8)
|
|
{
|
|
// (1 / 512.) is actually what the original value of 128 did, even though it probably meant 128 map units.
|
|
BrainishExplosion(Vec2OffsetZ(x, -320, (1 / 512.) + random[BrainExplode](0, 255) * 2));
|
|
}
|
|
A_PlaySound("brain/death", CHAN_VOICE, 1, false, ATTN_NONE);
|
|
}
|
|
|
|
void A_BrainExplode()
|
|
{
|
|
double x = random2[BrainExplode]() / 32.;
|
|
Vector3 pos = Vec2OffsetZ(x, 0, 1 / 512. + random[BrainExplode]() * 2);
|
|
BrainishExplosion(pos);
|
|
}
|
|
|
|
void A_BrainDie()
|
|
{
|
|
// [RH] If noexit, then don't end the level.
|
|
if ((deathmatch || alwaysapplydmflags) && sv_noexit)
|
|
return;
|
|
|
|
// New dmflag: Kill all boss spawned monsters before ending the level.
|
|
if (sv_killbossmonst)
|
|
{
|
|
int count; // Repeat until we have no more boss-spawned monsters.
|
|
ThinkerIterator it = ThinkerIterator.Create("Actor");
|
|
do // (e.g. Pain Elementals can spawn more to kill upon death.)
|
|
{
|
|
Actor mo;
|
|
it.Reinit();
|
|
count = 0;
|
|
while (mo = Actor(it.Next()))
|
|
{
|
|
if (mo.health > 0 && mo.bBossSpawned)
|
|
{
|
|
mo.DamageMobj(self, self, mo.health, "None", DMG_NO_ARMOR|DMG_FORCED|DMG_THRUSTLESS|DMG_NO_FACTOR);
|
|
|
|
// [Blue Shadow] If 'mo' is a RandomSpawner or another actor which can't be killed,
|
|
// it could cause this code to loop indefinitely. So only let it trigger a loop if it
|
|
// has been actually killed.
|
|
if (mo.bKilled) count++;
|
|
}
|
|
}
|
|
} while (count != 0);
|
|
}
|
|
Exit_Normal(0);
|
|
}
|
|
|
|
void A_BrainSpit(class<Actor> spawntype = null)
|
|
{
|
|
SpotState spstate = SpotState.GetSpotState();
|
|
Actor targ;
|
|
Actor spit;
|
|
bool isdefault = false;
|
|
|
|
// shoot a cube at current target
|
|
targ = spstate.GetNextInList("BossTarget", G_SkillPropertyInt(SKILLP_EasyBossBrain));
|
|
|
|
if (targ)
|
|
{
|
|
if (spawntype == null)
|
|
{
|
|
spawntype = "SpawnShot";
|
|
isdefault = true;
|
|
}
|
|
|
|
// spawn brain missile
|
|
spit = SpawnMissile (targ, spawntype);
|
|
|
|
if (spit)
|
|
{
|
|
// Boss cubes should move freely to their destination so it's
|
|
// probably best to disable all collision detection for them.
|
|
spit.bNoInteraction = spit.bNoClip;
|
|
|
|
spit.target = targ;
|
|
spit.master = self;
|
|
// [RH] Do this correctly for any trajectory. Doom would divide by 0
|
|
// if the target had the same y coordinate as the spitter.
|
|
if (spit.Vel.xy == (0, 0))
|
|
{
|
|
spit.special2 = 0;
|
|
}
|
|
else if (abs(spit.Vel.y) > abs(spit.Vel.x))
|
|
{
|
|
spit.special2 = int((targ.pos.y - pos.y) / spit.Vel.y);
|
|
}
|
|
else
|
|
{
|
|
spit.special2 = int((targ.pos.x - pos.x) / spit.Vel.x);
|
|
}
|
|
// [GZ] Calculates when the projectile will have reached destination
|
|
spit.special2 += level.maptime;
|
|
spit.bBossCube = true;
|
|
}
|
|
|
|
if (!isdefault)
|
|
{
|
|
A_PlaySound(self.AttackSound, CHAN_WEAPON, 1., false, ATTN_NONE);
|
|
}
|
|
else
|
|
{
|
|
// compatibility fallback
|
|
A_PlaySound("brain/spit", CHAN_WEAPON, 1., false, ATTN_NONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SpawnFly(class<Actor> spawntype, sound snd)
|
|
{
|
|
Actor newmobj;
|
|
Actor fog;
|
|
Actor eye = master; // The eye is the spawnshot's master, not the target!
|
|
Actor targ = target; // Unlike other projectiles, the target is the intended destination.
|
|
int r;
|
|
|
|
if (targ == null)
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
|
|
// [GZ] Should be more viable than a countdown...
|
|
if (special2 != 0)
|
|
{
|
|
if (special2 > level.maptime)
|
|
return; // still flying
|
|
}
|
|
else
|
|
{
|
|
if (reactiontime == 0 || --reactiontime != 0)
|
|
return; // still flying
|
|
}
|
|
|
|
if (spawntype)
|
|
{
|
|
fog = Spawn (spawntype, targ.pos, ALLOW_REPLACE);
|
|
if (fog) A_PlaySound(snd, CHAN_BODY);
|
|
}
|
|
|
|
class<Actor> SpawnName = null;
|
|
|
|
DropItem di; // di will be our drop item list iterator
|
|
DropItem drop; // while drop stays as the reference point.
|
|
int n = 0;
|
|
|
|
// First see if this cube has its own actor list
|
|
drop = GetDropItems();
|
|
|
|
// If not, then default back to its master's list
|
|
if (drop == null && eye != null)
|
|
drop = eye.GetDropItems();
|
|
|
|
if (drop != null)
|
|
{
|
|
for (di = drop; di != null; di = di.Next)
|
|
{
|
|
if (di.Name != 'None')
|
|
{
|
|
int amt = di.Amount;
|
|
if (amt < 0)
|
|
{
|
|
amt = 1; // default value is -1, we need a positive value.
|
|
}
|
|
n += amt; // this is how we can weight the list.
|
|
}
|
|
}
|
|
di = drop;
|
|
n = random[pr_spawnfly](0, n);
|
|
while (n >= 0)
|
|
{
|
|
if (di.Name != 'none')
|
|
{
|
|
int amt = di.Amount;
|
|
if (amt < 0)
|
|
{
|
|
amt = 1;
|
|
}
|
|
n -= amt;
|
|
}
|
|
if ((di.Next != null) && (n >= 0))
|
|
{
|
|
di = di.Next;
|
|
}
|
|
else
|
|
{
|
|
n = -1;
|
|
}
|
|
}
|
|
SpawnName = di.Name;
|
|
}
|
|
if (SpawnName == null)
|
|
{
|
|
// Randomly select monster to spawn.
|
|
r = random[pr_spawnfly](0, 255);
|
|
|
|
// Probability distribution (kind of :),
|
|
// decreasing likelihood.
|
|
if (r < 50) SpawnName = "DoomImp";
|
|
else if (r < 90) SpawnName = "Demon";
|
|
else if (r < 120) SpawnName = "Spectre";
|
|
else if (r < 130) SpawnName = "PainElemental";
|
|
else if (r < 160) SpawnName = "Cacodemon";
|
|
else if (r < 162) SpawnName = "Archvile";
|
|
else if (r < 172) SpawnName = "Revenant";
|
|
else if (r < 192) SpawnName = "Arachnotron";
|
|
else if (r < 222) SpawnName = "Fatso";
|
|
else if (r < 246) SpawnName = "HellKnight";
|
|
else SpawnName = "BaronOfHell";
|
|
}
|
|
if (spawnname != null)
|
|
{
|
|
newmobj = Spawn (spawnname, targ.pos, ALLOW_REPLACE);
|
|
if (newmobj != null)
|
|
{
|
|
// Make the new monster hate what the boss eye hates
|
|
if (eye != null)
|
|
{
|
|
newmobj.CopyFriendliness (eye, false);
|
|
}
|
|
// Make it act as if it was around when the player first made noise
|
|
// (if the player has made noise).
|
|
newmobj.LastHeard = newmobj.CurSector.SoundTarget;
|
|
|
|
if (newmobj.SeeState != null && newmobj.LookForPlayers (true))
|
|
{
|
|
newmobj.SetState (newmobj.SeeState);
|
|
}
|
|
if (!newmobj.bDestroyed)
|
|
{
|
|
// telefrag anything in this spot
|
|
newmobj.TeleportMove (newmobj.pos, true);
|
|
}
|
|
newmobj.bBossSpawned = true;
|
|
}
|
|
}
|
|
|
|
// remove self (i.e., cube).
|
|
Destroy ();
|
|
}
|
|
|
|
void A_SpawnFly(class<Actor> spawntype = null)
|
|
{
|
|
sound snd;
|
|
if (spawntype != null)
|
|
{
|
|
snd = GetDefaultByType(spawntype).SeeSound;
|
|
}
|
|
else
|
|
{
|
|
spawntype = "SpawnFire";
|
|
snd = "brain/spawn";
|
|
}
|
|
SpawnFly(spawntype, snd);
|
|
}
|
|
|
|
void A_SpawnSound()
|
|
{
|
|
// travelling cube sound
|
|
A_PlaySound("brain/cube", CHAN_BODY);
|
|
SpawnFly("SpawnFire", "brain/spawn");
|
|
}
|
|
}
|