using System.Text.Json.Serialization; namespace Theriapolis.Core.Data; /// /// Immutable biome definition loaded from biomes.json. /// Defines the biome's identity, visual representation, and the (e,m,t) ranges /// that can produce it during BiomeAssign. /// public sealed record BiomeDef { [JsonPropertyName("id")] public string Id { get; init; } = ""; [JsonPropertyName("display_name")] public string DisplayName { get; init; } = ""; /// Single capital letter used in placeholder tile rendering. [JsonPropertyName("letter")] public char Letter { get; init; } = '?'; /// Hex color string (#RRGGBB) for the placeholder tile background. [JsonPropertyName("color")] public string Color { get; init; } = "#888888"; [JsonPropertyName("placeholder_sprite")] public string PlaceholderSprite { get; init; } = ""; // ── Assignment thresholds ──────────────────────────────────────────────── // These are used only for "natural" biome assignment. // Ocean is handled separately (elevation < sea_level). [JsonPropertyName("elevation_min")] public float ElevationMin { get; init; } = 0f; [JsonPropertyName("elevation_max")] public float ElevationMax { get; init; } = 1f; [JsonPropertyName("moisture_min")] public float MoistureMin { get; init; } = 0f; [JsonPropertyName("moisture_max")] public float MoistureMax { get; init; } = 1f; [JsonPropertyName("temp_min")] public float TempMin { get; init; } = 0f; [JsonPropertyName("temp_max")] public float TempMax { get; init; } = 1f; /// Priority — higher-priority biomes win when multiple match. [JsonPropertyName("priority")] public int Priority { get; init; } = 0; /// True if this is a transition/mixed biome (not assignable from base rules). [JsonPropertyName("is_transition")] public bool IsTransition { get; init; } = false; // ── Parsed color cache ─────────────────────────────────────────────────── private (byte R, byte G, byte B)? _parsedColor; public (byte R, byte G, byte B) ParsedColor() { if (_parsedColor.HasValue) return _parsedColor.Value; string hex = Color.TrimStart('#'); byte r = Convert.ToByte(hex[..2], 16); byte g = Convert.ToByte(hex[2..4], 16); byte b = Convert.ToByte(hex[4..6], 16); _parsedColor = (r, g, b); return _parsedColor.Value; } /// How well this biome matches the given (e, m, t) values. Returns 0 if outside range. public float Score(float e, float m, float t) { if (e < ElevationMin || e > ElevationMax) return 0f; if (m < MoistureMin || m > MoistureMax) return 0f; if (t < TempMin || t > TempMax) return 0f; // Score = how close the values are to the center of the range (prefer tighter fits) float eMid = (ElevationMin + ElevationMax) * 0.5f; float mMid = (MoistureMin + MoistureMax) * 0.5f; float tMid = (TempMin + TempMax) * 0.5f; float eHalf = (ElevationMax - ElevationMin) * 0.5f + 0.001f; float mHalf = (MoistureMax - MoistureMin) * 0.5f + 0.001f; float tHalf = (TempMax - TempMin) * 0.5f + 0.001f; float closeness = 1f - (MathF.Abs(e - eMid)/eHalf + MathF.Abs(m - mMid)/mHalf + MathF.Abs(t - tMid)/tHalf) / 3f; return closeness + Priority * 0.5f; } }