81 lines
3.7 KiB
C#
81 lines
3.7 KiB
C#
|
|
using System.Text.Json.Serialization;
|
||
|
|
|
||
|
|
namespace Theriapolis.Core.Data;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 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.
|
||
|
|
/// </summary>
|
||
|
|
public sealed record BiomeDef
|
||
|
|
{
|
||
|
|
[JsonPropertyName("id")]
|
||
|
|
public string Id { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("display_name")]
|
||
|
|
public string DisplayName { get; init; } = "";
|
||
|
|
|
||
|
|
/// <summary>Single capital letter used in placeholder tile rendering.</summary>
|
||
|
|
[JsonPropertyName("letter")]
|
||
|
|
public char Letter { get; init; } = '?';
|
||
|
|
|
||
|
|
/// <summary>Hex color string (#RRGGBB) for the placeholder tile background.</summary>
|
||
|
|
[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;
|
||
|
|
|
||
|
|
/// <summary>Priority — higher-priority biomes win when multiple match.</summary>
|
||
|
|
[JsonPropertyName("priority")]
|
||
|
|
public int Priority { get; init; } = 0;
|
||
|
|
|
||
|
|
/// <summary>True if this is a transition/mixed biome (not assignable from base rules).</summary>
|
||
|
|
[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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>How well this biome matches the given (e, m, t) values. Returns 0 if outside range.</summary>
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|