mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-11-17 09:41:21 +00:00
ac86a535e7
This could cause problems with functions that take states as parameters but use them to set them internally instead of passing them through the A_Jump interface back to the caller, like A_Chase or A_LookEx. This required some quite significant refactoring because the entire state resolution logic had been baked into the compiler which turned out to be a major maintenance problem. Fixed this by adding a new builtin type 'statelabel'. This is an opaque identifier representing a state, with the actual data either directly encoded into the number for single label state or an index into a state information table. The state resolution is now the task of the called function as it should always have remained. Note, that this required giving back the 'action' qualifier to most state jumping functions. - refactored most A_Jump checkers to a two stage setup with a pure checker that returns a boolean and a scripted A_Jump wrapper, for some simpler checks the checker function was entirely omitted and calculated inline in the A_Jump function. It is strongly recommended to use the boolean checkers unless using an inline function invocation in a state as they lead to vastly clearer code and offer more flexibility. - let Min() and Max() use the OP_MIN and OP_MAX opcodes. Although these were present, these function were implemented using some grossly inefficient branching tests. - the DECORATE 'state' cast kludge will now actually call ResolveState because a state label is not a state and needs conversion.
424 lines
8.9 KiB
Text
424 lines
8.9 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
|
|
{
|
|
BrainExplode:
|
|
MISL BC 10 Bright;
|
|
MISL D 10 A_BrainExplode;
|
|
Stop;
|
|
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
|
|
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 ("BossBrain::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 ((GetCVar("deathmatch") || GetCVar("alwaysapplydmflags")) && GetCVar("sv_noexit"))
|
|
return;
|
|
|
|
// New dmflag: Kill all boss spawned monsters before ending the level.
|
|
if (GetCVar("sv_killbossmonst"))
|
|
{
|
|
int count; // Repeat until we have no more boss-spawned monsters.
|
|
ThinkerIterator it = ThinkerIterator.Create();
|
|
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);
|
|
count++;
|
|
}
|
|
}
|
|
} while (count != 0);
|
|
}
|
|
Exit_Normal(0);
|
|
}
|
|
|
|
native
|
|
void A_BrainSpit(class<Actor> spawntype = null) // needs special treatment for default
|
|
;/*
|
|
{
|
|
SpotState spstate = SpotState.GetSpotState();
|
|
Actor targ;
|
|
Actor spit;
|
|
bool isdefault = false;
|
|
|
|
// shoot a cube at current target
|
|
targ = spstate.GetNextInList("BossTarget", G_SkillProperty(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) > fabs(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.y);
|
|
}
|
|
// [GZ] Calculates when the projectile will have reached destination
|
|
spit.special2 += level.maptime;
|
|
spit.bBossCube = true;
|
|
}
|
|
|
|
if (!isdefault)
|
|
{
|
|
A_PlaySound(self.AttackSound, CHAN_WEAPON);
|
|
}
|
|
else
|
|
{
|
|
// compatibility fallback
|
|
A_PlaySound("brain/spit", CHAN_WEAPON);
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
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;
|
|
|
|
// [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')
|
|
{
|
|
if (di.Amount < 0)
|
|
{
|
|
di.Amount = 1; // default value is -1, we need a positive value.
|
|
}
|
|
n += di.Amount; // this is how we can weight the list.
|
|
}
|
|
}
|
|
di = drop;
|
|
n = random[pr_spawnfly](0, n);
|
|
while (n >= 0)
|
|
{
|
|
if (di.Name != 'none')
|
|
{
|
|
n -= di.Amount; // logically, none of the -1 values have survived by now.
|
|
}
|
|
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.Sector.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");
|
|
}
|
|
}
|