using System; using Microsoft.Xna.Framework.Graphics; namespace FreedomFighter; // Entity classes ported 1:1 from GameU.pas, including quirks // (e.g. TEnemy moves inside Draw, missiles can target stray sprites). public class Player : Sprite { public int Health; public int FireRate; public int Power; public Weapon Weapon; public Player(SpriteManager m) : base(m) { } public override void Move(float moveCount) { var input = G.Input; if (input.Button5Clicked) { G.Music.Songs[6].Play(); Weapon = Weapon.Reg; } if (input.Button6Clicked) { G.Music.Songs[6].Play(); Weapon = Weapon.Super; } if (input.Button7Clicked) { G.Music.Songs[6].Play(); Weapon = Weapon.Multi; } if (FireRate > 1) FireRate--; if (input.Button4 && FireRate == 1 && Weapon == Weapon.Reg) Bullet.Spawn(X + Width / 2 - 16, Y - 34, 0, 3, 0); else if (input.Button4 && FireRate == 1 && Weapon == Weapon.Super) { Bullet.Spawn(X + Width / 2 - 16 - 12, Y - 34, 0, 3, 0); Bullet.Spawn(X + Width / 2 - 16 + 12, Y - 34, 0, 3, 0); } if (input.Button4 && FireRate == 1 && Weapon == Weapon.Multi) { Bullet.Spawn(X + Width / 2 - 16, Y - 34, 2, 3, -35); Bullet.Spawn(X + Width / 2 - 16, Y - 34, 0, 3, 0); Bullet.Spawn(X + Width / 2 - 16, Y - 34, -2, 3, 35); } if (input.Button8 && FireRate == 1) { G.Music.Songs[13].Play(); Missile.Spawn(X + Width / 2, Y, 2); FireRate = 20; } if (input.Left && input.Up) { X -= 3; Y -= 3; } else if (input.Left && input.Down) { X -= 3; Y += 3; } else if (input.Right && input.Up) { X += 3; Y -= 3; } else if (input.Right && input.Down) { X += 3; Y += 3; } else if (input.Left) X -= 3; else if (input.Right) X += 3; else if (input.Up) Y -= 3; else if (input.Down) Y += 3; if (X + Width > 800) X = 800 - Width; if (X < 0) X = 0; if (Y + Height > 600) Y = 600 - Height; if (Y < 0) Y = 0; } } public class DemoShip : Sprite { public DemoShip(SpriteManager m) : base(m) { } } public class Enemy : Sprite { public float YVel; public int Health; public string MoveDir = ""; public AI AI; public bool Charging; public int HitTimer; public int FireRate; public int ExplodeTime; public int AnimTicks; public Enemy(SpriteManager m) : base(m) { } public override void Move(float moveCount) { if (HitTimer < 255) { HitTimer += 15; SetBlue(HitTimer); SetGreen(HitTimer); } switch (AI) { case AI.Normal: case AI.Hunt: if (G.Random(1000) < 5) EnemyBullet.Spawn(X, Y, 3, 0); break; case AI.Tactical: Y += YVel; if (!Charging && Y > 200) { YVel = 1; G.TFCounter++; if (G.TFCounter > 16) { G.TFCounter = 0; if (ImageIndex < 4) ImageIndex++; else { Charging = true; YVel = -2; } } } if (Charging && Y < -100) { ImageIndex = 0; YVel = 5; } break; case AI.Mine: if (AnimTicks > ExplodeTime) { Kill(); Explosion.Spawn(X, Y, false, false); for (int angle = 360; angle >= 0; angle -= 45) EnemyBullet.Spawn(X, Y, 2, angle); } break; case AI.Turret: if (FireRate < 0) { EnemyBullet.Spawn(X + 16, Y + 16, 3, Rotation); FireRate = 40; } break; } if (Y > G.ScreenHeight) Kill(); } public override void OnCollision(Sprite sprite, int colX, int colY) { if (sprite is Player) { G.Music.Songs[11].Play(); Kill(); G.Player.Health--; Explosion.Spawn(X, Y, false, false); } if (sprite is Bullet) { Health--; sprite.Kill(); if (Health < 1) { Explosion.Spawn(X, Y, false, false); G.Music.Songs[11].Play(); Kill(); G.ShipsKilled++; if (G.Player.Power < 18) G.Player.Power++; G.Score += 10; } else { HitTimer = 0; G.Music.Songs[10].Volume = 60; G.Music.Songs[10].Play(); } } if (sprite is Missile) { Health -= 5; sprite.Kill(); G.Music.Songs[10].Volume = 60; G.Music.Songs[10].Play(); if (Health < 1) { Explosion.Spawn(X, Y, false, false); G.Music.Songs[11].Play(); Kill(); G.ShipsKilled++; if (G.Player.Power < 18) G.Player.Power++; G.Score += 10; } else HitTimer = 0; } } // The original moved enemies inside Draw; preserved for identical behavior. public override void Draw(SpriteBatch sb) { base.Draw(sb); switch (AI) { case AI.Normal: case AI.None: Y += YVel; break; case AI.Hunt: Y += YVel; if (Y > 200) { Y += YVel; // double speed if (G.Player.X > X) X++; if (G.Player.X < X) X--; } break; case AI.Mine: Y += 1.5f; Rotation += 0.5f; if (Rotation > 359) Rotation = 0; AnimTicks++; break; case AI.Turret: Rotation = (float)(180 / Math.PI * Math.Atan2(Y - G.Player.Y, X - G.Player.X)) + 90; if (Rotation > 360) Rotation -= 360; else if (Rotation < 0) Rotation += 360; FireRate--; Y += YVel; break; } if (Y > G.ScreenHeight) Kill(); } } public class Boss : Sprite { public float YVel; public int Health; public string MoveDir = ""; public int HitTimer; public AI AI; public int Timer; public int RandX; public Boss(SpriteManager m) : base(m) { } public override void Move(float moveCount) { if (HitTimer < 255) { HitTimer += 15; SetBlue(HitTimer); SetGreen(HitTimer); } if (AI == AI.Boss) { if (G.Random(1000) < 10) { EnemyBullet.Spawn(X, Y + 40, 3, 0); EnemyBullet.Spawn(X + 32, Y + 40, 3, 0); EnemyBullet.Spawn(X + 64, Y + 40, 3, 0); } Y += YVel; if (Y > 300) { Y = 300; if (MoveDir == "right") X++; if (MoveDir == "left") X--; if (X > G.ScreenWidth - 98 && MoveDir == "right") { X = G.ScreenWidth - 98; MoveDir = "left"; } if (X < 0 && MoveDir == "left") { MoveDir = "right"; X = 0; } } } else if (AI == AI.Boss2) { Timer++; if (Timer > 200) { RandX = G.Random(10); Timer = 0; } if (MoveDir == "down" && Timer % 25 == 0) { EnemyBullet.Spawn(X + 40, Y + Height, 3, 0); EnemyBullet.Spawn(X + 50, Y + Height, 3, 0); EnemyBullet.Spawn(X + 60, Y + Height, 3, 0); EnemyBullet.Spawn(X + 70, Y + Height, 3, 0); } if (RandX > 7) X++; else if (RandX > 2) { /* hold */ } else X--; if (MoveDir == "down") Y++; if (MoveDir == "up") Y -= 2; if (Y > 300 && MoveDir == "down") { Y = 300; MoveDir = "up"; } if (Y < -100 && MoveDir == "up") { MoveDir = "down"; Y = -100; } if (X > 800 - Width) { X = 800 - Width; RandX = 0; } if (X < 0) { X = 0; RandX = 8; } } } public override void OnCollision(Sprite sprite, int colX, int colY) { if (sprite is Bullet) { sprite.Kill(); Health--; HitTimer = 0; if (Health == 0) { Kill(); Explosion.Spawn(X, Y, true, true); G.Score += 1000; } } if (sprite is Missile) { sprite.Kill(); Health -= 5; HitTimer = 0; if (Health < 1) { Kill(); Explosion.Spawn(X, Y, true, true); G.Score += 1000; } } } } public class Bullet : Sprite { public float XVel, YVel; public Bullet(SpriteManager m) : base(m) { } public static void Spawn(float x, float y, float xVel, float yVel, float rotation) { var b = new Bullet(G.Sprites) { Width = 34, Height = 34, X = x, Y = y, XVel = xVel, YVel = yVel, Rotation = rotation, Image = G.Images.IL1[2], ImageIndex = 0, CenterX = 0.5f, CenterY = 0.5f, DoCollision = true, DoPixelCheck = true, }; G.Player.FireRate = 20; } public override void Move(float moveCount) { X -= XVel; Y -= YVel; if (Y > 600 || Y < 0 || X > 800 || X < 0) Kill(); } } public class EnemyBullet : Sprite { public float XVel, YVel; public EnemyBullet(SpriteManager m) : base(m) { } public static void Spawn(float x, float y, float vel, float rotation) { double rad = Math.PI / 180 * (rotation + 90); _ = new EnemyBullet(G.Sprites) { Name = "EnemyBullet", X = x, Y = y, Rotation = rotation, CenterX = 0.5f, CenterY = 0.5f, XVel = vel * (float)Math.Cos(rad), YVel = vel * (float)Math.Sin(rad), Width = 32, Height = 32, Image = G.Images.IL1[8], ImageIndex = 0, DoCollision = true, DoPixelCheck = true, }; } public override void Move(float moveCount) { X += XVel; Y += YVel; if (X > G.ScreenWidth || X < 0 || Y > G.ScreenHeight || Y < 0) Kill(); } public override void OnCollision(Sprite sprite, int colX, int colY) { if (sprite is Player) { Kill(); G.Player.Health--; } } } public class Missile : Sprite { public float XVel, YVel; public Sprite? Target; public Missile(SpriteManager m) : base(m) { } public static void Spawn(float x, float y, float yVel) { var missile = new Missile(G.Sprites) { Name = "Missile", X = x, Y = y, Rotation = 180, YVel = yVel, Width = 16, Height = 16, Image = G.Images.IL1[19], ImageIndex = 0, DoCollision = true, DoPixelCheck = true, }; missile.Target = missile.GetTarget(); } public Sprite? GetTarget() { Sprite? target = null; float prevDist = 800; foreach (var s in G.Sprites.Items) { float dx = X - s.X; float dy = Y - s.Y; float distance = (float)Math.Sqrt(dx * dx + dy * dy); // faithful to the original: filters by Name only, so unnamed sprites // (fighters, but also player bullets and explosions) are all fair game if (distance < prevDist && s.Name != "Player" && s.Name != "EnemyBullet" && s.Name != "Missile") { target = s; prevDist = distance; } } return target; } public override void Move(float moveCount) { if (Target != null) { if (Y > Target.Y) { Rotation = (float)(180 / Math.PI * Math.Atan2(Y - Target.Y, X - Target.X)) + 90; if (Rotation > 360) Rotation -= 360; else if (Rotation < 0) Rotation += 360; } double rad = Math.PI / 180 * (Rotation + 90); X += (float)Math.Cos(rad); Y += YVel * (float)Math.Sin(rad); } else { double rad = Math.PI / 180 * (Rotation + 90); Y += YVel * (float)Math.Sin(rad); X += (float)Math.Cos(rad); Target = GetTarget(); } if (Y < 0) Kill(); if (X < 0 || X > G.ScreenWidth) Kill(); } } public class Explosion : Sprite { public int MaxFrames; public int ECounter; public bool IsBoss; public Explosion(SpriteManager m) : base(m) { } public static void Spawn(float x, float y, bool big, bool boss) { int frames = big ? 6 : 5; int imageIdx = big ? 11 : 3; int tileWidth = big ? 65 : 32; int tileHeight = big ? 65 : 34; _ = new Explosion(G.Sprites) { X = x, Y = y, Width = tileWidth, Height = tileHeight, Image = G.Images.IL1[imageIdx], ImageIndex = 0, DoCollision = false, MaxFrames = frames, ECounter = 0, IsBoss = boss, }; } public override void Move(float moveCount) { Y += 1; ECounter++; if (ECounter > 6) { ImageIndex++; ECounter = 0; } if (ImageIndex > MaxFrames) { if (IsBoss) G.Done = true; Kill(); } } }