Add project files.

This commit is contained in:
2026-09-06 18:01:31 -04:00
parent 0142942887
commit ebcfd9cd39
93 changed files with 3332 additions and 0 deletions

120
FreedomFighter/Sprites.cs Normal file
View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace FreedomFighter;
/// <summary>
/// Port of the Omega TSprite base class. Position is the pattern's top-left;
/// CenterX/CenterY only affect the rotation pivot. Width/Height define the
/// logical collision box, independent of the drawn pattern size.
/// </summary>
public abstract class Sprite
{
public string Name = "";
public float X, Y;
public int Width, Height;
public float Rotation; // degrees
public float CenterX, CenterY; // 0..1 fraction of pattern
public float ScaleX = 1, ScaleY = 1;
public int Red = 255, Green = 255, Blue = 255, Alpha = 255;
public OmegaImage? Image;
public int ImageIndex;
public bool DoCollision;
public bool DoPixelCheck;
public bool IsDead;
protected Sprite(SpriteManager manager) => manager.Add(this);
public void SetGreen(int v) => Green = Math.Clamp(v, 0, 255);
public void SetBlue(int v) => Blue = Math.Clamp(v, 0, 255);
/// <summary>Marks the sprite for removal (Omega's Dead).</summary>
public void Kill() => IsDead = true;
public virtual void Move(float moveCount) { }
public virtual void Draw(SpriteBatch sb)
{
Image?.Draw(sb, (int)Math.Round(X), (int)Math.Round(Y), Rotation,
CenterX, CenterY, ScaleX, ScaleY, Red, Green, Blue, Alpha, ImageIndex);
}
public virtual void OnCollision(Sprite sprite, int colX, int colY) { }
public Rectangle Bounds => new((int)X, (int)Y, Width, Height);
}
/// <summary>
/// Port of TOmegaSprite: owns all sprites, runs movement, collision
/// (bounding box + optional pixel check), removal, and drawing.
/// </summary>
public class SpriteManager
{
public readonly List<Sprite> Items = new();
public int Count => Items.Count;
public void Add(Sprite s) => Items.Add(s);
public void KillAll()
{
foreach (var s in Items) s.Kill();
}
public void Move(float moveCount)
{
// like the Pascal for-loop, sprites spawned during iteration are not moved this frame
int n = Items.Count;
for (int i = 0; i < n; i++)
Items[i].Move(moveCount);
}
public void Collision()
{
int n = Items.Count;
for (int i = 0; i < n; i++)
{
var a = Items[i];
if (!a.DoCollision || a.IsDead) continue;
for (int j = i + 1; j < n; j++)
{
var b = Items[j];
if (!b.DoCollision || b.IsDead) continue;
var overlap = Rectangle.Intersect(a.Bounds, b.Bounds);
if (overlap.IsEmpty) continue;
if ((a.DoPixelCheck || b.DoPixelCheck) && !PixelOverlap(a, b, overlap)) continue;
a.OnCollision(b, overlap.X, overlap.Y);
b.OnCollision(a, overlap.X, overlap.Y);
}
}
}
/// <summary>Pixel test on the unrotated pattern masks (rotation ignored, like a cheap approximation).</summary>
static bool PixelOverlap(Sprite a, Sprite b, Rectangle overlap)
{
if (a.Image == null || b.Image == null) return true;
for (int y = overlap.Top; y < overlap.Bottom; y++)
for (int x = overlap.Left; x < overlap.Right; x++)
{
bool pa = a.Image.IsOpaque(a.ImageIndex, x - (int)a.X, y - (int)a.Y);
bool pb = b.Image.IsOpaque(b.ImageIndex, x - (int)b.X, y - (int)b.Y);
if (pa && pb) return true;
}
return false;
}
public void RemoveDead() => Items.RemoveAll(s => s.IsDead);
public void Draw(SpriteBatch sb)
{
int n = Items.Count;
for (int i = 0; i < n; i++)
if (!Items[i].IsDead)
Items[i].Draw(sb);
}
}