using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace FreedomFighter; /// /// Equivalent of a TOmegaImageList item: a texture subdivided into equally-sized /// patterns (frames), drawn by pattern index. Also builds alpha masks for /// pixel-perfect collision (Omega's DoPixelCheck). /// public class OmegaImage { public string Name = ""; public Texture2D Texture = null!; public int TileWidth; public int TileHeight; public int Columns; public int PatternCount; public bool[] OpaqueMask = null!; // per-pixel alpha>0, whole texture, row-major public static OmegaImage Load(GraphicsDevice gd, string file, int tileW, int tileH, string name) { using var fs = File.OpenRead(file); var tex = Texture2D.FromStream(gd, fs); int tw = tileW > 0 ? tileW : tex.Width; int th = tileH > 0 ? tileH : tex.Height; var pixels = new Color[tex.Width * tex.Height]; tex.GetData(pixels); var mask = new bool[pixels.Length]; for (int i = 0; i < pixels.Length; i++) mask[i] = pixels[i].A > 0; return new OmegaImage { Name = name, Texture = tex, TileWidth = tw, TileHeight = th, Columns = Math.Max(1, tex.Width / tw), PatternCount = Math.Max(1, tex.Width / tw) * Math.Max(1, tex.Height / th), OpaqueMask = mask, }; } public Rectangle PatternRect(int pattern) { if (pattern < 0 || pattern >= PatternCount) pattern = 0; return new Rectangle(pattern % Columns * TileWidth, pattern / Columns * TileHeight, TileWidth, TileHeight); } /// Is the pixel at (px,py) within the given pattern opaque? Coordinates are pattern-local. public bool IsOpaque(int pattern, int px, int py) { if (px < 0 || py < 0 || px >= TileWidth || py >= TileHeight) return false; var r = PatternRect(pattern); int x = r.X + px, y = r.Y + py; if (x >= Texture.Width || y >= Texture.Height) return false; return OpaqueMask[y * Texture.Width + x]; } /// /// Full Omega draw. X,Y is the top-left of the pattern; rotation (degrees, clockwise) /// pivots around (centerX, centerY) as a fraction of the pattern size. /// public void Draw(SpriteBatch sb, float x, float y, float rotation = 0, float centerX = 0, float centerY = 0, float scaleX = 1, float scaleY = 1, int red = 255, int green = 255, int blue = 255, int alpha = 255, int pattern = 0) { var src = PatternRect(pattern); var origin = new Vector2(centerX * TileWidth, centerY * TileHeight); var pos = new Vector2(x + origin.X * scaleX, y + origin.Y * scaleY); var color = new Color(red, green, blue, alpha); sb.Draw(Texture, pos, src, color, MathHelper.ToRadians(rotation), origin, new Vector2(scaleX, scaleY), SpriteEffects.None, 0); } } /// Loads the image lists extracted from GameU.dfm via gfx/manifest.json. public class ImageLists { readonly Dictionary> _lists = new(); public List IL1 => _lists["OmegaImageList1"]; public List IL2 => _lists["OmegaImageList2"]; public List IL3 => _lists["OmegaImageList3"]; public List IL4 => _lists["OmegaImageList4"]; public List IL5 => _lists["OmegaImageList5"]; record ManifestItem(int Index, string Name, string File, int TileWidth, int TileHeight); public ImageLists(GraphicsDevice gd, string contentDir) { string gfxDir = Path.Combine(contentDir, "gfx"); var manifest = JsonSerializer.Deserialize>>( File.ReadAllText(Path.Combine(gfxDir, "manifest.json")))!; foreach (var (listName, items) in manifest) { var list = new List(); foreach (var item in items) list.Add(OmegaImage.Load(gd, Path.Combine(gfxDir, item.File), item.TileWidth, item.TileHeight, item.Name)); _lists[listName] = list; } } }