using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Theriapolis.Core.Data; using Theriapolis.Core.World; namespace Theriapolis.Game.Rendering; /// /// Manages the biome tile textures used by the world-map renderer. /// For Phase 0/1, all tiles are generated at runtime as flat-colour 32×32 squares /// with a 1px darker border and a single centered letter. /// Final art will replace these by swapping file contents; no code changes needed. /// public sealed class TileAtlas : IDisposable { private readonly GraphicsDevice _gd; private readonly Dictionary _textures = new(); private readonly Dictionary _settlementIcons = new(); // keyed by tier private SpriteFont? _font; private bool _disposed; public GraphicsDevice GraphicsDevice => _gd; // Fallback solid-color texture for any biome that has no dedicated entry private Texture2D? _fallback; public TileAtlas(GraphicsDevice gd) { _gd = gd; } /// /// Generate all placeholder textures from the loaded BiomeDef array. /// Call once after content is loaded. /// public void GeneratePlaceholders(BiomeDef[] biomes, SpriteFont? font = null) { _font = font; foreach (var def in biomes) { var biomeId = BiomeAssignHelper.ParseBiomeId(def.Id); if (_textures.ContainsKey(biomeId)) continue; _textures[biomeId] = MakeTile(def); } _fallback = MakeSolidColor(Color.HotPink); // obvious "missing art" colour GenerateSettlementIcons(); } /// Returns the settlement icon texture for the given tier (1–5). public Texture2D GetSettlementIcon(int tier) { if (_settlementIcons.TryGetValue(tier, out var tex)) return tex; return _fallback ?? _textures.Values.First(); } private void GenerateSettlementIcons() { // Tier 1: large gold diamond (capital) _settlementIcons[1] = MakeSettlementIcon(20, new Color(255, 215, 0), diamond: true); // Tier 2: white square (city) _settlementIcons[2] = MakeSettlementIcon(14, new Color(230, 230, 230), diamond: false); // Tier 3: light-blue circle (town) _settlementIcons[3] = MakeSettlementIcon(10, new Color(150, 200, 255), diamond: false); // Tier 4: pale dot (village) _settlementIcons[4] = MakeSettlementIcon(6, new Color(200, 200, 200), diamond: false); // Tier 5 (PoI): small red circle _settlementIcons[5] = MakeSettlementIcon(5, new Color(200, 60, 60), diamond: false); } private Texture2D MakeSettlementIcon(int size, Color fill, bool diamond) { var tex = new Texture2D(_gd, size, size); var pixels = new Color[size * size]; float cx = (size - 1) * 0.5f; float cy = (size - 1) * 0.5f; for (int py = 0; py < size; py++) for (int px = 0; px < size; px++) { float nx = px - cx, ny = py - cy; bool inside = diamond ? (MathF.Abs(nx) + MathF.Abs(ny)) <= size * 0.5f : (nx * nx + ny * ny) <= (cx * cx); pixels[py * size + px] = inside ? fill : Color.Transparent; } tex.SetData(pixels); return tex; } /// Returns the texture for the given biome, falling back to the error texture. public Texture2D GetTile(BiomeId biome) { if (_textures.TryGetValue(biome, out var tex)) return tex; return _fallback ?? _textures.Values.First(); } // ── Texture generation helpers ──────────────────────────────────────────── private Texture2D MakeTile(BiomeDef def) { int size = Theriapolis.Core.C.WORLD_TILE_PIXELS; var (r, g, b) = def.ParsedColor(); var fillColor = new Color(r, g, b); var tex = new Texture2D(_gd, size, size); var pixels = new Color[size * size]; Array.Fill(pixels, fillColor); tex.SetData(pixels); return tex; } private Texture2D MakeSolidColor(Color color) { int size = Theriapolis.Core.C.WORLD_TILE_PIXELS; var tex = new Texture2D(_gd, size, size); var pixels = new Color[size * size]; Array.Fill(pixels, color); tex.SetData(pixels); return tex; } public void Dispose() { if (_disposed) return; _disposed = true; foreach (var tex in _textures.Values) tex.Dispose(); foreach (var tex in _settlementIcons.Values) tex.Dispose(); _fallback?.Dispose(); } } /// Internal helper to avoid exposing the private static method on BiomeAssignStage. internal static class BiomeAssignHelper { public static BiomeId ParseBiomeId(string id) => Theriapolis.Core.World.Generation.Stages.BiomeAssignStage.ParseBiomeId(id); }