mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-27 22:33:17 +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.
809 lines
No EOL
16 KiB
Text
809 lines
No EOL
16 KiB
Text
class Minotaur : Actor
|
|
{
|
|
const MAULATORTICS = 25 * TICRATE;
|
|
const MNTR_CHARGE_SPEED =13.;
|
|
const MINOTAUR_LOOK_DIST = 16*54.;
|
|
|
|
Default
|
|
{
|
|
Health 3000;
|
|
Radius 28;
|
|
Height 100;
|
|
Mass 800;
|
|
Speed 16;
|
|
Damage 7;
|
|
Painchance 25;
|
|
Monster;
|
|
+DROPOFF
|
|
+FLOORCLIP
|
|
+BOSS
|
|
+NORADIUSDMG
|
|
+DONTMORPH
|
|
+NOTARGET
|
|
+BOSSDEATH
|
|
SeeSound "minotaur/sight";
|
|
AttackSound "minotaur/attack1";
|
|
PainSound "minotaur/pain";
|
|
DeathSound "minotaur/death";
|
|
ActiveSound "minotaur/active";
|
|
DropItem "ArtiSuperHealth", 51;
|
|
DropItem "PhoenixRodAmmo", 84, 10;
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
MNTR AB 10 A_MinotaurLook;
|
|
Loop;
|
|
Roam:
|
|
MNTR ABCD 5 A_MinotaurRoam;
|
|
Loop;
|
|
See:
|
|
MNTR ABCD 5 A_MinotaurChase;
|
|
Loop;
|
|
Melee:
|
|
MNTR V 10 A_FaceTarget;
|
|
MNTR W 7 A_FaceTarget;
|
|
MNTR X 12 A_MinotaurAtk1;
|
|
Goto See;
|
|
Missile:
|
|
MNTR V 10 A_MinotaurDecide;
|
|
MNTR Y 4 A_FaceTarget;
|
|
MNTR Z 9 A_MinotaurAtk2;
|
|
Goto See;
|
|
Hammer:
|
|
MNTR V 10 A_FaceTarget;
|
|
MNTR W 7 A_FaceTarget;
|
|
MNTR X 12 A_MinotaurAtk3;
|
|
Goto See;
|
|
HammerLoop:
|
|
MNTR X 12;
|
|
Goto Hammer;
|
|
Charge:
|
|
MNTR U 2 A_MinotaurCharge;
|
|
Loop;
|
|
Pain:
|
|
MNTR E 3;
|
|
MNTR E 6 A_Pain;
|
|
Goto See;
|
|
Death:
|
|
MNTR F 6 A_MinotaurDeath;
|
|
MNTR G 5;
|
|
MNTR H 6 A_Scream;
|
|
MNTR I 5;
|
|
MNTR J 6;
|
|
MNTR K 5;
|
|
MNTR L 6;
|
|
MNTR M 5 A_NoBlocking;
|
|
MNTR N 6;
|
|
MNTR O 5;
|
|
MNTR P 6;
|
|
MNTR Q 5;
|
|
MNTR R 6;
|
|
MNTR S 5;
|
|
MNTR T -1 A_BossDeath;
|
|
Stop;
|
|
FadeOut:
|
|
MNTR E 6;
|
|
MNTR E 2 A_Scream;
|
|
MNTR E 5 A_SpawnItemEx("MinotaurSmokeExit");
|
|
MNTR E 5;
|
|
MNTR E 5 A_NoBlocking;
|
|
MNTR E 5;
|
|
MNTR E 5 A_SetTranslucent(0.66, 0);
|
|
MNTR E 5 A_SetTranslucent(0.33, 0);
|
|
MNTR E 10 A_BossDeath;
|
|
Stop;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// FUNC P_MinotaurSlam
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void MinotaurSlam (Actor target)
|
|
{
|
|
double ang = AngleTo(target);
|
|
double thrust = 16 + random[MinotaurSlam]() / 64.;
|
|
target.VelFromAngle(ang, thrust);
|
|
int damage = random[MinotaurSlam](1, 8) * (bSummonedMonster? 4 : 6);
|
|
int newdam = target.DamageMobj (null, null, damage, 'Melee');
|
|
target.TraceBleedAngle (newdam > 0 ? newdam : damage, ang, 0.);
|
|
if (target.player)
|
|
{
|
|
target.reactiontime = random[MinotaurSlam](14, 21);
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
override void Tick ()
|
|
{
|
|
Super.Tick ();
|
|
|
|
// The unfriendly Minotaur (Heretic's) is invulnerable while charging
|
|
if (!bSummonedMonster)
|
|
{
|
|
bInvulnerable = bSkullFly;
|
|
}
|
|
}
|
|
|
|
override bool Slam (Actor thing)
|
|
{
|
|
// Slamming minotaurs shouldn't move non-creatures
|
|
if (!thing.bIsMonster && !thing.player)
|
|
{
|
|
return false;
|
|
}
|
|
return Super.Slam (thing);
|
|
}
|
|
|
|
override int DoSpecialDamage (Actor target, int damage, Name damagetype)
|
|
{
|
|
damage = Super.DoSpecialDamage (target, damage, damagetype);
|
|
if (damage != -1 && bSkullFly)
|
|
{ // Slam only when in charge mode
|
|
MinotaurSlam (target);
|
|
return -1;
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurAtk1
|
|
//
|
|
// Melee attack.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MinotaurAtk1()
|
|
{
|
|
if (!target)
|
|
{
|
|
return;
|
|
}
|
|
A_PlaySound ("minotaur/melee", CHAN_WEAPON);
|
|
if (CheckMeleeRange())
|
|
{
|
|
int damage = random[MinotaurAtk1](1, 8) * 4;
|
|
int newdam = target.DamageMobj (self, self, damage, 'Melee');
|
|
target.TraceBleed (newdam > 0 ? newdam : damage, self);
|
|
PlayerInfo player = target.player;
|
|
if (player != null && player.mo == target)
|
|
{ // Squish the player
|
|
player.deltaviewheight = -16;
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurDecide
|
|
//
|
|
// Choose a missile attack.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MinotaurDecide()
|
|
{
|
|
bool friendly = bSummonedMonster;
|
|
|
|
if (!target)
|
|
{
|
|
return;
|
|
}
|
|
if (!friendly)
|
|
{
|
|
A_PlaySound ("minotaur/sight", CHAN_WEAPON);
|
|
}
|
|
double dist = Distance2D(target);
|
|
if (target.pos.z + target.height > pos.z
|
|
&& target.pos.z + target.height < pos.z + height
|
|
&& dist < (friendly ? 16*64. : 8*64.)
|
|
&& dist > 1*64.
|
|
&& random[MinotaurDecide]() < 150)
|
|
{ // Charge attack
|
|
// Don't call the state function right away
|
|
SetStateLabel("Charge", true);
|
|
bSkullFly = true;
|
|
if (!friendly)
|
|
{ // Heretic's Minotaur is invulnerable during charge attack
|
|
bInvulnerable = true;
|
|
}
|
|
A_FaceTarget ();
|
|
VelFromAngle(MNTR_CHARGE_SPEED);
|
|
special1 = TICRATE/2; // Charge duration
|
|
}
|
|
else if (target.pos.z == target.floorz
|
|
&& dist < 9*64.
|
|
&& random[MinotaurDecide]() < (friendly ? 100 : 220))
|
|
{ // Floor fire attack
|
|
SetStateLabel("Hammer");
|
|
special2 = 0;
|
|
}
|
|
else
|
|
{ // Swing attack
|
|
A_FaceTarget ();
|
|
// Don't need to call P_SetMobjState because the current state
|
|
// falls through to the swing attack
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurCharge
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MinotaurCharge()
|
|
{
|
|
if (target == null)
|
|
{
|
|
return;
|
|
}
|
|
if (special1 > 0)
|
|
{
|
|
Class<Actor> type;
|
|
|
|
//if (gameinfo.gametype == GAME_Heretic)
|
|
if (gametype() == GAME_Heretic)
|
|
{
|
|
type = "PhoenixPuff";
|
|
}
|
|
else
|
|
{
|
|
type = "PunchPuff";
|
|
}
|
|
Actor puff = Spawn (type, Pos, ALLOW_REPLACE);
|
|
if (puff != null) puff.Vel.Z = 2;
|
|
special1--;
|
|
}
|
|
else
|
|
{
|
|
bSkullFly = false;
|
|
bInvulnerable = false;
|
|
SetState (SeeState);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurAtk2
|
|
//
|
|
// Swing attack.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MinotaurAtk2()
|
|
{
|
|
bool friendly = bSummonedMonster;
|
|
|
|
if (target == null)
|
|
{
|
|
return;
|
|
}
|
|
A_PlaySound ("minotaur/attack2", CHAN_WEAPON);
|
|
if (CheckMeleeRange())
|
|
{
|
|
int damage = random[MinotaurAtk2](1, 8) * (friendly ? 3 : 5);
|
|
int newdam = target.DamageMobj (self, self, damage, 'Melee');
|
|
target.TraceBleed (newdam > 0 ? newdam : damage, self);
|
|
return;
|
|
}
|
|
double z = pos.z + 40;
|
|
Class<Actor> fx = "MinotaurFX1";
|
|
Actor mo = SpawnMissileZ (z, target, fx);
|
|
if (mo != null)
|
|
{
|
|
// S_Sound (mo, CHAN_WEAPON, "minotaur/attack2", 1, ATTN_NORM);
|
|
double vz = mo.Vel.Z;
|
|
double ang = mo.angle;
|
|
SpawnMissileAngleZ (z, fx, ang-(45./8), vz);
|
|
SpawnMissileAngleZ (z, fx, ang+(45./8), vz);
|
|
SpawnMissileAngleZ (z, fx, ang-(45./16), vz);
|
|
SpawnMissileAngleZ (z, fx, ang+(45./16), vz);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurAtk3
|
|
//
|
|
// Floor fire attack.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MinotaurAtk3()
|
|
{
|
|
bool friendly = bSummonedMonster;
|
|
|
|
if (!target)
|
|
{
|
|
return;
|
|
}
|
|
A_PlaySound ("minotaur/attack3", CHAN_VOICE);
|
|
if (CheckMeleeRange())
|
|
{
|
|
int damage = random[MinotaurAtk3](1, 8) * (friendly ? 3 : 5);
|
|
int newdam = target.DamageMobj (self, self, damage, 'Melee');
|
|
target.TraceBleed (newdam > 0 ? newdam : damage, self);
|
|
PlayerInfo player = target.player;
|
|
if (player != null && player.mo == target)
|
|
{ // Squish the player
|
|
player.deltaviewheight = -16;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Floorclip > 0 && compat_minotaur)
|
|
{
|
|
// only play the sound.
|
|
A_PlaySound ("minotaur/fx2hit", CHAN_WEAPON);
|
|
}
|
|
else
|
|
{
|
|
Actor mo = SpawnMissile (target, "MinotaurFX2");
|
|
if (mo != null)
|
|
{
|
|
mo.A_PlaySound ("minotaur/attack1", CHAN_WEAPON);
|
|
}
|
|
}
|
|
}
|
|
if (random[MinotaurAtk3]() < 192 && special2 == 0)
|
|
{
|
|
SetStateLabel ("HammerLoop");
|
|
special2 = 1;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurDeath
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MinotaurDeath()
|
|
{
|
|
if (Wads.CheckNumForName ("MNTRF1", Wads.ns_sprites) < 0 &&
|
|
Wads.CheckNumForName ("MNTRF0", Wads.ns_sprites) < 0)
|
|
SetStateLabel("FadeOut");
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// A_MinotaurRoam
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MinotaurRoam()
|
|
{
|
|
// In case pain caused him to skip his fade in.
|
|
A_SetRenderStyle(1, STYLE_Normal);
|
|
|
|
let mf = MinotaurFriend(self);
|
|
if (mf)
|
|
{
|
|
if (mf.StartTime >= 0 && (level.maptime - mf.StartTime) >= MAULATORTICS)
|
|
{
|
|
DamageMobj (null, null, TELEFRAG_DAMAGE, 'None');
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (random[MinotaurRoam]() < 30)
|
|
A_MinotaurLook(); // adjust to closest target
|
|
|
|
if (random[MinotaurRoam]() < 6)
|
|
{
|
|
//Choose new direction
|
|
movedir = random[MinotaurRoam]() % 8;
|
|
FaceMovementDirection ();
|
|
}
|
|
if (!MonsterMove())
|
|
{
|
|
// Turn
|
|
if (random[MinotaurRoam]() & 1)
|
|
movedir = (movedir + 1) % 8;
|
|
else
|
|
movedir = (movedir + 7) % 8;
|
|
FaceMovementDirection ();
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurLook
|
|
//
|
|
// Look for enemy of player
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MinotaurLook()
|
|
{
|
|
if (!(self is "MinotaurFriend"))
|
|
{
|
|
A_Look();
|
|
return;
|
|
}
|
|
|
|
Actor mo = null;
|
|
PlayerInfo player;
|
|
double dist;
|
|
Actor master = tracer;
|
|
|
|
target = null;
|
|
if (deathmatch) // Quick search for players
|
|
{
|
|
for (int i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i]) continue;
|
|
player = players[i];
|
|
mo = player.mo;
|
|
if (mo == master) continue;
|
|
if (mo.health <= 0) continue;
|
|
dist = Distance2D(mo);
|
|
if (dist > MINOTAUR_LOOK_DIST) continue;
|
|
target = mo;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!target) // Near player monster search
|
|
{
|
|
if (master && (master.health > 0) && (master.player))
|
|
mo = master.RoughMonsterSearch(20);
|
|
else
|
|
mo = RoughMonsterSearch(20);
|
|
target = mo;
|
|
}
|
|
|
|
if (!target) // Normal monster search
|
|
{
|
|
ThinkerIterator it = ThinkerIterator.Create("Actor");
|
|
|
|
while ((mo = Actor(it.Next())) != null)
|
|
{
|
|
if (!mo.bIsMonster) continue;
|
|
if (mo.health <= 0) continue;
|
|
if (!mo.bShootable) continue;
|
|
dist = Distance2D(mo);
|
|
if (dist > MINOTAUR_LOOK_DIST) continue;
|
|
if (mo == master || mo == self) continue;
|
|
if (mo.bSummonedMonster && mo.tracer == master) continue;
|
|
target = mo;
|
|
break; // Found actor to attack
|
|
}
|
|
}
|
|
|
|
if (target)
|
|
{
|
|
SetState (SeeState, true);
|
|
}
|
|
else
|
|
{
|
|
SetStateLabel ("Roam", true);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MinotaurChase
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MinotaurChase()
|
|
{
|
|
let mf = MinotaurFriend(self);
|
|
if (!mf)
|
|
{
|
|
A_Chase();
|
|
return;
|
|
}
|
|
|
|
|
|
// In case pain caused him to skip his fade in.
|
|
A_SetRenderStyle(1, STYLE_Normal);
|
|
|
|
if (mf.StartTime >= 0 && (level.maptime - mf.StartTime) >= MAULATORTICS)
|
|
{
|
|
DamageMobj (null, null, TELEFRAG_DAMAGE, 'None');
|
|
return;
|
|
}
|
|
|
|
if (random[MinotaurChase]() < 30)
|
|
A_MinotaurLook(); // adjust to closest target
|
|
|
|
if (!target || (target.health <= 0) || !target.bShootable)
|
|
{ // look for a new target
|
|
SetIdle();
|
|
return;
|
|
}
|
|
|
|
FaceMovementDirection ();
|
|
reactiontime = 0;
|
|
|
|
// Melee attack
|
|
if (MeleeState && CheckMeleeRange ())
|
|
{
|
|
if (AttackSound)
|
|
{
|
|
A_PlaySound (AttackSound, CHAN_WEAPON);
|
|
}
|
|
SetState (MeleeState);
|
|
return;
|
|
}
|
|
|
|
// Missile attack
|
|
if (MissileState && CheckMissileRange())
|
|
{
|
|
SetState (MissileState);
|
|
return;
|
|
}
|
|
|
|
// chase towards target
|
|
if (!MonsterMove ())
|
|
{
|
|
NewChaseDir ();
|
|
FaceMovementDirection ();
|
|
}
|
|
|
|
// Active sound
|
|
if (random[MinotaurChase]() < 6)
|
|
{
|
|
PlayActiveSound ();
|
|
}
|
|
}
|
|
}
|
|
|
|
class MinotaurFriend : Minotaur
|
|
{
|
|
int StartTime;
|
|
|
|
Default
|
|
{
|
|
Health 2500;
|
|
-DROPOFF
|
|
-BOSS
|
|
-DONTMORPH
|
|
+FRIENDLY
|
|
+NOTARGETSWITCH
|
|
+STAYMORPHED
|
|
+TELESTOMP
|
|
+SUMMONEDMONSTER
|
|
RenderStyle "Translucent";
|
|
Alpha 0.3333;
|
|
DropItem "None";
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
MNTR A 15;
|
|
MNTR A 15 A_SetTranslucent(0.66, 0);
|
|
MNTR A 3 A_SetTranslucent(1, 0);
|
|
Goto Super::Spawn;
|
|
Idle:
|
|
Goto Super::Spawn;
|
|
Death:
|
|
Goto FadeOut;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
override void BeginPlay ()
|
|
{
|
|
Super.BeginPlay ();
|
|
StartTime = -1;
|
|
}
|
|
|
|
override void Die (Actor source, Actor inflictor, int dmgflags)
|
|
{
|
|
Super.Die (source, inflictor, dmgflags);
|
|
|
|
if (tracer && tracer.health > 0 && tracer.player)
|
|
{
|
|
// Search thinker list for minotaur
|
|
ThinkerIterator it = ThinkerIterator.Create("MinotaurFriend");
|
|
MinotaurFriend mo;
|
|
|
|
while ((mo = MinotaurFriend(it.Next())) != null)
|
|
{
|
|
if (mo.health <= 0) continue;
|
|
// [RH] Minotaurs can't be morphed, so this isn't needed
|
|
//if (!(mo.flags&MF_COUNTKILL)) continue; // for morphed minotaurs
|
|
if (mo.bCorpse) continue;
|
|
if (mo.StartTime >= 0 && (level.maptime - StartTime) >= MAULATORTICS) continue;
|
|
if (mo.tracer != null && mo.tracer.player == tracer.player) break;
|
|
}
|
|
|
|
if (mo == null)
|
|
{
|
|
Inventory power = tracer.FindInventory("PowerMinotaur");
|
|
if (power != null)
|
|
{
|
|
power.Destroy ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// Minotaur FX 1 ------------------------------------------------------------
|
|
|
|
class MinotaurFX1 : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 10;
|
|
Height 6;
|
|
Speed 20;
|
|
FastSpeed 26;
|
|
Damage 3;
|
|
DamageType "Fire";
|
|
Projectile;
|
|
-ACTIVATEIMPACT
|
|
-ACTIVATEPCROSS
|
|
RenderStyle "Add";
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
FX12 AB 6 Bright;
|
|
Loop;
|
|
Death:
|
|
FX12 CDEFGH 5 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
|
|
// Minotaur FX 2 ------------------------------------------------------------
|
|
|
|
class MinotaurFX2 : MinotaurFX1
|
|
{
|
|
Default
|
|
{
|
|
Radius 5;
|
|
Height 12;
|
|
Speed 14;
|
|
FastSpeed 20;
|
|
Damage 4;
|
|
+FLOORHUGGER
|
|
ExplosionDamage 24;
|
|
DeathSound "minotaur/fx2hit";
|
|
}
|
|
|
|
states
|
|
{
|
|
Spawn:
|
|
FX13 A 2 Bright A_MntrFloorFire;
|
|
Loop;
|
|
Death:
|
|
FX13 I 4 Bright A_Explode;
|
|
FX13 JKLM 4 Bright;
|
|
Stop;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PROC A_MntrFloorFire
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void A_MntrFloorFire()
|
|
{
|
|
SetZ(floorz);
|
|
double x = Random2[MntrFloorFire]() / 64.;
|
|
double y = Random2[MntrFloorFire]() / 64.;
|
|
|
|
Actor mo = Spawn("MinotaurFX3", Vec2OffsetZ(x, y, floorz), ALLOW_REPLACE);
|
|
if (mo != null)
|
|
{
|
|
mo.target = target;
|
|
mo.Vel.X = MinVel; // Force block checking
|
|
mo.CheckMissileSpawn (radius);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Minotaur FX 3 ------------------------------------------------------------
|
|
|
|
class MinotaurFX3 : MinotaurFX2
|
|
{
|
|
Default
|
|
{
|
|
Radius 8;
|
|
Height 16;
|
|
Speed 0;
|
|
DeathSound "minotaur/fx3hit";
|
|
ExplosionDamage 128;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
FX13 DC 4 Bright;
|
|
FX13 BCDE 5 Bright;
|
|
FX13 FGH 4 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// Minotaur Smoke Exit ------------------------------------------------------
|
|
|
|
class MinotaurSmokeExit : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP
|
|
+NOTELEPORT
|
|
RenderStyle "Translucent";
|
|
Alpha 0.4;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
MNSM ABCDEFGHIJIHGFEDCBA 3;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
extend class Actor
|
|
{
|
|
enum dirtype_t
|
|
{
|
|
DI_EAST,
|
|
DI_NORTHEAST,
|
|
DI_NORTH,
|
|
DI_NORTHWEST,
|
|
DI_WEST,
|
|
DI_SOUTHWEST,
|
|
DI_SOUTH,
|
|
DI_SOUTHEAST,
|
|
DI_NODIR,
|
|
NUMDIRS
|
|
};
|
|
|
|
void FaceMovementDirection()
|
|
{
|
|
switch (movedir)
|
|
{
|
|
case DI_EAST:
|
|
angle = 0.;
|
|
break;
|
|
case DI_NORTHEAST:
|
|
angle = 45.;
|
|
break;
|
|
case DI_NORTH:
|
|
angle = 90.;
|
|
break;
|
|
case DI_NORTHWEST:
|
|
angle = 135.;
|
|
break;
|
|
case DI_WEST:
|
|
angle = 180.;
|
|
break;
|
|
case DI_SOUTHWEST:
|
|
angle = 225.;
|
|
break;
|
|
case DI_SOUTH:
|
|
angle = 270.;
|
|
break;
|
|
case DI_SOUTHEAST:
|
|
angle = 315.;
|
|
break;
|
|
}
|
|
}
|
|
} |