예제 #1
0
        public Mobj SpawnMissile(Mobj source, Mobj dest, MobjType type)
        {
            var missile = SpawnMobj(
                source.X,
                source.Y,
                source.Z + Fixed.FromInt(32), type);

            if (missile.Info.SeeSound != 0)
            {
                world.StartSound(missile, missile.Info.SeeSound);
            }

            // Where it came from?
            missile.Target = source;

            var angle = Geometry.PointToAngle(
                source.X, source.Y,
                dest.X, dest.Y);

            // Fuzzy player.
            if ((dest.Flags & MobjFlags.Shadow) != 0)
            {
                var random = world.Random;
                angle += new Angle((random.Next() - random.Next()) << 20);
            }

            var speed = GetMissileSpeed(missile.Type);

            missile.Angle = angle;
            missile.MomX  = new Fixed(speed) * Trig.Cos(angle);
            missile.MomY  = new Fixed(speed) * Trig.Sin(angle);

            var dist = Geometry.AproxDistance(
                dest.X - source.X,
                dest.Y - source.Y);

            var num = (dest.Z - source.Z).Data;
            var den = (dist / speed).Data;

            if (den < 1)
            {
                den = 1;
            }

            missile.MomZ = new Fixed(num / den);

            CheckMissileSpawn(missile);

            return(missile);
        }
예제 #2
0
        /// <summary>
        /// Looks for special lines in front of the player to activate.
        /// </summary>
        public void UseLines(Player player)
        {
            var pt = world.PathTraversal;

            useThing = player.Mobj;

            var angle = player.Mobj.Angle;

            var x1 = player.Mobj.X;
            var y1 = player.Mobj.Y;
            var x2 = x1 + useRange.ToIntFloor() * Trig.Cos(angle);
            var y2 = y1 + useRange.ToIntFloor() * Trig.Sin(angle);

            pt.PathTraverse(x1, y1, x2, y2, PathTraverseFlags.AddLines, useTraverseFunc);
        }
예제 #3
0
        //
        // P_UseLines
        // Looks for special lines in front of the player to activate.
        //
        public void UseLines(Player player)
        {
            var pt = world.PathTraversal;

            usething = player.Mobj;

            var angle = player.Mobj.Angle;             // >> ANGLETOFINESHIFT;

            var x1 = player.Mobj.X;
            var y1 = player.Mobj.Y;
            var x2 = x1 + (World.USERANGE.Data >> Fixed.FracBits) * Trig.Cos(angle);             // finecosine[angle];
            var y2 = y1 + (World.USERANGE.Data >> Fixed.FracBits) * Trig.Sin(angle);             // finesine[angle];

            pt.PathTraverse(x1, y1, x2, y2, PathTraverseFlags.AddLines, ic => PTR_UseTraverse(ic));
        }
예제 #4
0
        /// <summary>
        /// Fire a hitscan bullet.
        /// If damage == 0, it is just a test trace that will leave linetarget set.
        /// </summary>
        public void LineAttack(Mobj shooter, Angle angle, Fixed range, Fixed slope, int damage)
        {
            currentShooter  = shooter;
            currentShooterZ = shooter.Z + (shooter.Height >> 1) + Fixed.FromInt(8);
            currentRange    = range;
            currentAimSlope = slope;
            currentDamage   = damage;

            var targetX = shooter.X + range.ToIntFloor() * Trig.Cos(angle);
            var targetY = shooter.Y + range.ToIntFloor() * Trig.Sin(angle);

            world.PathTraversal.PathTraverse(
                shooter.X, shooter.Y,
                targetX, targetY,
                PathTraverseFlags.AddLines | PathTraverseFlags.AddThings,
                shootTraverseFunc);
        }
예제 #5
0
        public void SpawnPlayerMissile(Mobj source, MobjType type)
        {
            var hs = world.Hitscan;

            // See which target is to be aimed at.
            var angle = source.Angle;
            var slope = hs.AimLineAttack(source, angle, Fixed.FromInt(16 * 64));

            if (hs.LineTarget == null)
            {
                angle += new Angle(1 << 26);
                slope  = hs.AimLineAttack(source, angle, Fixed.FromInt(16 * 64));

                if (hs.LineTarget == null)
                {
                    angle -= new Angle(2 << 26);
                    slope  = hs.AimLineAttack(source, angle, Fixed.FromInt(16 * 64));
                }

                if (hs.LineTarget == null)
                {
                    angle = source.Angle;
                    slope = Fixed.Zero;
                }
            }

            var x = source.X;
            var y = source.Y;
            var z = source.Z + Fixed.FromInt(32);

            var missile = SpawnMobj(x, y, z, type);

            if (missile.Info.SeeSound != 0)
            {
                world.StartSound(missile, missile.Info.SeeSound);
            }

            missile.Target = source;
            missile.Angle  = angle;
            missile.MomX   = new Fixed(missile.Info.Speed) * Trig.Cos(angle);
            missile.MomY   = new Fixed(missile.Info.Speed) * Trig.Sin(angle);
            missile.MomZ   = new Fixed(missile.Info.Speed) * slope;

            CheckMissileSpawn(missile);
        }
예제 #6
0
        public void FatAttack2(Mobj actor)
        {
            FaceTarget(actor);

            var ta = world.ThingAllocation;

            // Now here choose opposite deviation.
            actor.Angle -= fatSpread;
            ta.SpawnMissile(actor, actor.Target, MobjType.Fatshot);

            var missile = ta.SpawnMissile(actor, actor.Target, MobjType.Fatshot);

            missile.Angle -= fatSpread * 2;
            var angle = missile.Angle;

            missile.MomX = new Fixed(missile.Info.Speed) * Trig.Cos(angle);
            missile.MomY = new Fixed(missile.Info.Speed) * Trig.Sin(angle);
        }
예제 #7
0
        public void FatAttack1(Mobj actor)
        {
            FaceTarget(actor);

            var ta = world.ThingAllocation;

            // Change direction to...
            actor.Angle += fatSpread;
            ta.SpawnMissile(actor, actor.Target, MobjType.Fatshot);

            var missile = ta.SpawnMissile(actor, actor.Target, MobjType.Fatshot);

            missile.Angle += fatSpread;
            var angle = missile.Angle;

            missile.MomX = new Fixed(missile.Info.Speed) * Trig.Cos(angle);
            missile.MomY = new Fixed(missile.Info.Speed) * Trig.Sin(angle);
        }
예제 #8
0
        public void PainShootSkull(Mobj actor, Angle angle)
        {
            // Count total number of skull currently on the level.
            var count = 0;

            foreach (var thinker in world.Thinkers)
            {
                var mobj = thinker as Mobj;
                if (mobj != null && mobj.Type == MobjType.Skull)
                {
                    count++;
                }
            }

            // If there are allready 20 skulls on the level,
            // don't spit another one.
            if (count > 20)
            {
                return;
            }

            // Okay, there's playe for another one.

            var preStep = Fixed.FromInt(4) +
                          3 * (actor.Info.Radius + DoomInfo.MobjInfos[(int)MobjType.Skull].Radius) / 2;

            var x = actor.X + preStep * Trig.Cos(angle);
            var y = actor.Y + preStep * Trig.Sin(angle);
            var z = actor.Z + Fixed.FromInt(8);

            var skull = world.ThingAllocation.SpawnMobj(x, y, z, MobjType.Skull);

            // Check for movements.
            if (!world.ThingMovement.TryMove(skull, skull.X, skull.Y))
            {
                // Kill it immediately.
                world.ThingInteraction.DamageMobj(skull, actor, actor, 10000);
                return;
            }

            skull.Target = actor.Target;

            SkullAttack(skull);
        }
예제 #9
0
        public void FatAttack3(Mobj actor)
        {
            FaceTarget(actor);

            var ta = world.ThingAllocation;

            var missile1 = ta.SpawnMissile(actor, actor.Target, MobjType.Fatshot);

            missile1.Angle -= fatSpread / 2;
            var angle1 = missile1.Angle;

            missile1.MomX = new Fixed(missile1.Info.Speed) * Trig.Cos(angle1);
            missile1.MomY = new Fixed(missile1.Info.Speed) * Trig.Sin(angle1);

            var missile2 = ta.SpawnMissile(actor, actor.Target, MobjType.Fatshot);

            missile2.Angle += fatSpread / 2;
            var angle2 = missile2.Angle;

            missile2.MomX = new Fixed(missile2.Info.Speed) * Trig.Cos(angle2);
            missile2.MomY = new Fixed(missile2.Info.Speed) * Trig.Sin(angle2);
        }
예제 #10
0
        /// <summary>
        /// Adjusts the x and y movement so that the next move will
        /// slide along the wall.
        /// </summary>
        private void HitSlideLine(LineDef line)
        {
            if (line.SlopeType == SlopeType.Horizontal)
            {
                slideMoveY = Fixed.Zero;
                return;
            }

            if (line.SlopeType == SlopeType.Vertical)
            {
                slideMoveX = Fixed.Zero;
                return;
            }

            var side = Geometry.PointOnLineSide(slideThing.X, slideThing.Y, line);

            var lineAngle = Geometry.PointToAngle(Fixed.Zero, Fixed.Zero, line.Dx, line.Dy);

            if (side == 1)
            {
                lineAngle += Angle.Ang180;
            }

            var moveAngle = Geometry.PointToAngle(Fixed.Zero, Fixed.Zero, slideMoveX, slideMoveY);

            var deltaAngle = moveAngle - lineAngle;

            if (deltaAngle > Angle.Ang180)
            {
                deltaAngle += Angle.Ang180;
            }

            var moveDist = Geometry.AproxDistance(slideMoveX, slideMoveY);
            var newDist  = moveDist * Trig.Cos(deltaAngle);

            slideMoveX = newDist * Trig.Cos(lineAngle);
            slideMoveY = newDist * Trig.Sin(lineAngle);
        }
예제 #11
0
        //
        // G_CheckSpot
        // Returns false if the player cannot be respawned
        // at the given mapthing_t spot
        // because something is occupying it
        //
        public bool G_CheckSpot(int playernum, MapThing mthing)
        {
            if (Players[playernum].Mobj == null)
            {
                // first spawn of level, before corpses
                for (var i = 0; i < playernum; i++)
                {
                    if (Players[i].Mobj.X == mthing.X && Players[i].Mobj.Y == mthing.Y)
                    {
                        return(false);
                    }
                }
                return(true);
            }

            var x = mthing.X;
            var y = mthing.Y;

            if (!thingMovement.CheckPosition(Players[playernum].Mobj, x, y))
            {
                return(false);
            }

            // flush an old corpse if needed
            if (bodyqueslot >= BODYQUESIZE)
            {
                thingAllocation.RemoveMobj(bodyque[bodyqueslot % BODYQUESIZE]);
            }
            bodyque[bodyqueslot % BODYQUESIZE] = Players[playernum].Mobj;
            bodyqueslot++;

            // spawn a teleport fog
            var ss = Geometry.PointInSubsector(x, y, map);

            var an = (Angle.Ang45.Data >> Trig.AngleToFineShift) * ((int)Math.Round(mthing.Angle.ToDegree()) / 45);

            Fixed xa;
            Fixed ya;

            switch (an)
            {
            case 4096:               // -4096:
                xa = Trig.Tan(2048); // finecosine[-4096]
                ya = Trig.Tan(0);    // finesine[-4096]
                break;

            case 5120:               // -3072:
                xa = Trig.Tan(3072); // finecosine[-3072]
                ya = Trig.Tan(1024); // finesine[-3072]
                break;

            case 6144:               // -2048:
                xa = Trig.Sin(0);    // finecosine[-2048]
                ya = Trig.Tan(2048); // finesine[-2048]
                break;

            case 7168:               // -1024:
                xa = Trig.Sin(1024); // finecosine[-1024]
                ya = Trig.Tan(3072); // finesine[-1024]
                break;

            case 0:
            case 1024:
            case 2048:
            case 3072:
                xa = Trig.Cos((int)an);
                ya = Trig.Sin((int)an);
                break;

            default:
                throw new Exception("G_CheckSpot: unexpected angle " + an);
            }

            var mo = thingAllocation.SpawnMobj(
                x + 20 * xa, y + 20 * ya,
                ss.Sector.FloorHeight,
                MobjType.Tfog);

            if (Players[Options.ConsolePlayer].ViewZ != new Fixed(1))
            {
                // don't start sound on first frame
                StartSound(mo, Sfx.TELEPT);
            }

            return(true);
        }
예제 #12
0
        /// <summary>
        /// Calculate the angle of the line passing through the two points.
        /// </summary>
        public static Angle PointToAngle(Fixed fromX, Fixed fromY, Fixed toX, Fixed toY)
        {
            var x = toX - fromX;
            var y = toY - fromY;

            if (x == Fixed.Zero && y == Fixed.Zero)
            {
                return(Angle.Ang0);
            }

            if (x >= Fixed.Zero)
            {
                // x >= 0
                if (y >= Fixed.Zero)
                {
                    // y >= 0
                    if (x > y)
                    {
                        // octant 0
                        return(Trig.TanToAngle(SlopeDiv(y, x)));
                    }
                    else
                    {
                        // octant 1
                        return(new Angle(Angle.Ang90.Data - 1) - Trig.TanToAngle(SlopeDiv(x, y)));
                    }
                }
                else
                {
                    // y < 0
                    y = -y;

                    if (x > y)
                    {
                        // octant 8
                        return(-Trig.TanToAngle(SlopeDiv(y, x)));
                    }
                    else
                    {
                        // octant 7
                        return(Angle.Ang270 + Trig.TanToAngle(SlopeDiv(x, y)));
                    }
                }
            }
            else
            {
                // x < 0
                x = -x;

                if (y >= Fixed.Zero)
                {
                    // y >= 0
                    if (x > y)
                    {
                        // octant 3
                        return(new Angle(Angle.Ang180.Data - 1) - Trig.TanToAngle(SlopeDiv(y, x)));
                    }
                    else
                    {
                        // octant 2
                        return(Angle.Ang90 + Trig.TanToAngle(SlopeDiv(x, y)));
                    }
                }
                else
                {
                    // y < 0
                    y = -y;

                    if (x > y)
                    {
                        // octant 4
                        return(Angle.Ang180 + Trig.TanToAngle(SlopeDiv(y, x)));
                    }
                    else
                    {
                        // octant 5
                        return(new Angle(Angle.Ang270.Data - 1) - Trig.TanToAngle(SlopeDiv(x, y)));
                    }
                }
            }
        }
예제 #13
0
        /// <summary>
        /// Calculate the walking / running height adjustment.
        /// </summary>
        public void CalcHeight(Player player)
        {
            // Regular movement bobbing.
            // It needs to be calculated for gun swing even if not on ground.
            player.Bob   = player.Mobj.MomX * player.Mobj.MomX + player.Mobj.MomY * player.Mobj.MomY;
            player.Bob >>= 2;
            if (player.Bob > maxBob)
            {
                player.Bob = maxBob;
            }

            if ((player.Cheats & CheatFlags.NoMomentum) != 0 || !onGround)
            {
                player.ViewZ = player.Mobj.Z + Player.NormalViewHeight;

                if (player.ViewZ > player.Mobj.CeilingZ - Fixed.FromInt(4))
                {
                    player.ViewZ = player.Mobj.CeilingZ - Fixed.FromInt(4);
                }

                player.ViewZ = player.Mobj.Z + player.ViewHeight;

                return;
            }

            var angle = (Trig.FineAngleCount / 20 * world.LevelTime) & Trig.FineMask;

            var bob = (player.Bob / 2) * Trig.Sin(angle);

            // Move viewheight.
            if (player.PlayerState == PlayerState.Live)
            {
                player.ViewHeight += player.DeltaViewHeight;

                if (player.ViewHeight > Player.NormalViewHeight)
                {
                    player.ViewHeight      = Player.NormalViewHeight;
                    player.DeltaViewHeight = Fixed.Zero;
                }

                if (player.ViewHeight < Player.NormalViewHeight / 2)
                {
                    player.ViewHeight = Player.NormalViewHeight / 2;

                    if (player.DeltaViewHeight <= Fixed.Zero)
                    {
                        player.DeltaViewHeight = new Fixed(1);
                    }
                }

                if (player.DeltaViewHeight != Fixed.Zero)
                {
                    player.DeltaViewHeight += Fixed.One / 4;

                    if (player.DeltaViewHeight == Fixed.Zero)
                    {
                        player.DeltaViewHeight = new Fixed(1);
                    }
                }
            }

            player.ViewZ = player.Mobj.Z + player.ViewHeight + bob;

            if (player.ViewZ > player.Mobj.CeilingZ - Fixed.FromInt(4))
            {
                player.ViewZ = player.Mobj.CeilingZ - Fixed.FromInt(4);
            }
        }
예제 #14
0
        /// <summary>
        /// Damages both enemies and players.
        /// "inflictor" is the thing that caused the damage creature
        /// or missile, can be null (slime, etc).
        /// "source" is the thing to target after taking damage creature
        /// or null.
        /// Source and inflictor are the same for melee attacks.
        /// Source can be null for slime, barrel explosions and other
        /// environmental stuff.
        /// </summary>
        public void DamageMobj(Mobj target, Mobj inflictor, Mobj source, int damage)
        {
            if ((target.Flags & MobjFlags.Shootable) == 0)
            {
                // Shouldn't happen...
                return;
            }

            if (target.Health <= 0)
            {
                return;
            }

            if ((target.Flags & MobjFlags.SkullFly) != 0)
            {
                target.MomX = target.MomY = target.MomZ = Fixed.Zero;
            }

            var player = target.Player;

            if (player != null && world.Options.Skill == GameSkill.Baby)
            {
                // Take half damage in trainer mode.
                damage >>= 1;
            }

            // Some close combat weapons should not inflict thrust and
            // push the victim out of reach, thus kick away unless using the chainsaw.
            var notChainsawAttack =
                source == null ||
                source.Player == null ||
                source.Player.ReadyWeapon != WeaponType.Chainsaw;

            if (inflictor != null && (target.Flags & MobjFlags.NoClip) == 0 && notChainsawAttack)
            {
                var ang = Geometry.PointToAngle(
                    inflictor.X,
                    inflictor.Y,
                    target.X,
                    target.Y);

                var thrust = new Fixed(damage * (Fixed.FracUnit >> 3) * 100 / target.Info.Mass);

                // Make fall forwards sometimes.
                if (damage < 40 &&
                    damage > target.Health &&
                    target.Z - inflictor.Z > Fixed.FromInt(64) &&
                    (world.Random.Next() & 1) != 0)
                {
                    ang    += Angle.Ang180;
                    thrust *= 4;
                }

                target.MomX += thrust * Trig.Cos(ang);
                target.MomY += thrust * Trig.Sin(ang);
            }

            // Player specific.
            if (player != null)
            {
                // End of game hell hack.
                if (target.Subsector.Sector.Special == (SectorSpecial)11 && damage >= target.Health)
                {
                    damage = target.Health - 1;
                }

                // Below certain threshold, ignore damage in GOD mode, or with INVUL power.
                if (damage < 1000 && ((player.Cheats & CheatFlags.GodMode) != 0 ||
                                      player.Powers[(int)PowerType.Invulnerability] > 0))
                {
                    return;
                }

                int saved;

                if (player.ArmorType != 0)
                {
                    if (player.ArmorType == 1)
                    {
                        saved = damage / 3;
                    }
                    else
                    {
                        saved = damage / 2;
                    }

                    if (player.ArmorPoints <= saved)
                    {
                        // Armor is used up.
                        saved            = player.ArmorPoints;
                        player.ArmorType = 0;
                    }

                    player.ArmorPoints -= saved;
                    damage             -= saved;
                }

                // Mirror mobj health here for Dave.
                player.Health -= damage;
                if (player.Health < 0)
                {
                    player.Health = 0;
                }

                player.Attacker = source;

                // Add damage after armor / invuln.
                player.DamageCount += damage;

                if (player.DamageCount > 100)
                {
                    // Teleport stomp does 10k points...
                    player.DamageCount = 100;
                }
            }

            // Do the damage.
            target.Health -= damage;
            if (target.Health <= 0)
            {
                KillMobj(source, target);
                return;
            }

            if ((world.Random.Next() < target.Info.PainChance) &&
                (target.Flags & MobjFlags.SkullFly) == 0)
            {
                // Fight back!
                target.Flags |= MobjFlags.JustHit;

                target.SetState(target.Info.PainState);
            }

            // We're awake now...
            target.ReactionTime = 0;

            if ((target.Threshold == 0 || target.Type == MobjType.Vile) &&
                source != null &&
                source != target &&
                source.Type != MobjType.Vile)
            {
                // If not intent on another player, chase after this one.
                target.Target    = source;
                target.Threshold = baseThreshold;
                if (target.State == DoomInfo.States[(int)target.Info.SpawnState] &&
                    target.Info.SeeState != MobjState.Null)
                {
                    target.SetState(target.Info.SeeState);
                }
            }
        }
예제 #15
0
 /// <summary>
 /// Moves the given origin along a given angle.
 /// </summary>
 public void Thrust(Player player, Angle angle, Fixed move)
 {
     player.Mobj.MomX += move * Trig.Cos(angle);
     player.Mobj.MomY += move * Trig.Sin(angle);
 }
예제 #16
0
        public void Tracer(Mobj actor)
        {
            if ((world.Options.GameTic & 3) != 0)
            {
                return;
            }

            // Spawn a puff of smoke behind the rocket.
            world.Hitscan.SpawnPuff(actor.X, actor.Y, actor.Z);

            var smoke = world.ThingAllocation.SpawnMobj(
                actor.X - actor.MomX,
                actor.Y - actor.MomY,
                actor.Z,
                MobjType.Smoke);

            smoke.MomZ  = Fixed.One;
            smoke.Tics -= world.Random.Next() & 3;
            if (smoke.Tics < 1)
            {
                smoke.Tics = 1;
            }

            // Adjust direction.
            var dest = actor.Tracer;

            if (dest == null || dest.Health <= 0)
            {
                return;
            }

            // Change angle.
            var exact = Geometry.PointToAngle(
                actor.X, actor.Y,
                dest.X, dest.Y);

            if (exact != actor.Angle)
            {
                if (exact - actor.Angle > Angle.Ang180)
                {
                    actor.Angle -= traceAngle;
                    if (exact - actor.Angle < Angle.Ang180)
                    {
                        actor.Angle = exact;
                    }
                }
                else
                {
                    actor.Angle += traceAngle;
                    if (exact - actor.Angle > Angle.Ang180)
                    {
                        actor.Angle = exact;
                    }
                }
            }

            exact      = actor.Angle;
            actor.MomX = new Fixed(actor.Info.Speed) * Trig.Cos(exact);
            actor.MomY = new Fixed(actor.Info.Speed) * Trig.Sin(exact);

            // Change slope.
            var dist = Geometry.AproxDistance(
                dest.X - actor.X,
                dest.Y - actor.Y);

            var num = (dest.Z + Fixed.FromInt(40) - actor.Z).Data;
            var den = dist.Data / actor.Info.Speed;

            if (den < 1)
            {
                den = 1;
            }

            var slope = new Fixed(num / den);

            if (slope < actor.MomZ)
            {
                actor.MomZ -= Fixed.One / 8;
            }
            else
            {
                actor.MomZ += Fixed.One / 8;
            }
        }
예제 #17
0
        /// <summary>
        /// Returns false if the player cannot be respawned at the given
        /// mapthing spot because something is occupying it.
        /// </summary>
        public bool CheckSpot(int playernum, MapThing mthing)
        {
            var players = world.Options.Players;

            if (players[playernum].Mobj == null)
            {
                // First spawn of level, before corpses.
                for (var i = 0; i < playernum; i++)
                {
                    if (players[i].Mobj.X == mthing.X && players[i].Mobj.Y == mthing.Y)
                    {
                        return(false);
                    }
                }
                return(true);
            }

            var x = mthing.X;
            var y = mthing.Y;

            if (!world.ThingMovement.CheckPosition(players[playernum].Mobj, x, y))
            {
                return(false);
            }

            // Flush an old corpse if needed.
            if (bodyQueSlot >= bodyQueSize)
            {
                RemoveMobj(bodyQue[bodyQueSlot % bodyQueSize]);
            }
            bodyQue[bodyQueSlot % bodyQueSize] = players[playernum].Mobj;
            bodyQueSlot++;

            // Spawn a teleport fog.
            var subsector = Geometry.PointInSubsector(x, y, world.Map);

            var angle = (Angle.Ang45.Data >> Trig.AngleToFineShift) *
                        ((int)Math.Round(mthing.Angle.ToDegree()) / 45);

            //
            // The code below to reproduce respawn fog bug in deathmath
            // is based on Chocolate Doom's implementation.
            //

            Fixed xa;
            Fixed ya;

            switch (angle)
            {
            case 4096:               // -4096:
                xa = Trig.Tan(2048); // finecosine[-4096]
                ya = Trig.Tan(0);    // finesine[-4096]
                break;

            case 5120:               // -3072:
                xa = Trig.Tan(3072); // finecosine[-3072]
                ya = Trig.Tan(1024); // finesine[-3072]
                break;

            case 6144:               // -2048:
                xa = Trig.Sin(0);    // finecosine[-2048]
                ya = Trig.Tan(2048); // finesine[-2048]
                break;

            case 7168:               // -1024:
                xa = Trig.Sin(1024); // finecosine[-1024]
                ya = Trig.Tan(3072); // finesine[-1024]
                break;

            case 0:
            case 1024:
            case 2048:
            case 3072:
                xa = Trig.Cos((int)angle);
                ya = Trig.Sin((int)angle);
                break;

            default:
                throw new Exception("Unexpected angle: " + angle);
            }

            var mo = SpawnMobj(
                x + 20 * xa, y + 20 * ya,
                subsector.Sector.FloorHeight,
                MobjType.Tfog);

            if (!world.FirstTicIsNotYetDone)
            {
                // Don't start sound on first frame.
                world.StartSound(mo, Sfx.TELEPT, SfxType.Misc);
            }

            return(true);
        }