sandpypi/Sand.Core/ParticleLibraryLoader.cs

256 lines
9.5 KiB
C#

using System.Text.Json.Nodes;
namespace Sand.Core;
public static class ParticleLibraryLoader
{
private static readonly HashSet<string> StaticTypes =
[
"wall",
"stone",
"rock",
"iron",
"gold",
"copper",
"wood",
"brass",
"glass",
];
public static ParticleLibrary LoadFromDirectory(string partRootPath)
{
var directories = new[]
{
Path.Combine(partRootPath, "coreparts"),
Path.Combine(partRootPath, "mods"),
};
var rawDefinitions = new Dictionary<string, JsonObject>(StringComparer.OrdinalIgnoreCase);
foreach (var directory in directories)
{
if (!Directory.Exists(directory))
{
continue;
}
foreach (var filePath in Directory.EnumerateFiles(directory, "*.json", SearchOption.AllDirectories))
{
var rootNode = JsonNode.Parse(File.ReadAllText(filePath)) as JsonObject;
if (rootNode is null)
{
continue;
}
foreach (var pair in rootNode)
{
if (pair.Value is JsonObject obj)
{
rawDefinitions[pair.Key.ToLowerInvariant()] = obj;
}
}
}
}
var orderedIds = rawDefinitions.Keys.OrderBy(static id => id, StringComparer.Ordinal).ToArray();
var definitions = new List<ParticleDef>(orderedIds.Length);
foreach (var id in orderedIds)
{
definitions.Add(ParseDefinition(id, rawDefinitions[id]));
}
var knownIds = new HashSet<string>(orderedIds, StringComparer.OrdinalIgnoreCase);
foreach (var definition in definitions)
{
ValidateTarget(knownIds, definition.Id, definition.Melt, nameof(definition.Melt));
ValidateTarget(knownIds, definition.Id, definition.Evaporate, nameof(definition.Evaporate));
ValidateTarget(knownIds, definition.Id, definition.Solidify, nameof(definition.Solidify));
ValidateTarget(knownIds, definition.Id, definition.Freeze, nameof(definition.Freeze));
ValidateTarget(knownIds, definition.Id, definition.Broken, nameof(definition.Broken));
ValidateTarget(knownIds, definition.Id, definition.Produces, nameof(definition.Produces));
ValidateTarget(knownIds, definition.Id, definition.ProducesOnDeath, nameof(definition.ProducesOnDeath));
}
return new ParticleLibrary(definitions);
}
private static ParticleDef ParseDefinition(string id, JsonObject source)
{
var isGas = GetBool(source, "is_gas");
var isLiquid = GetBool(source, "liquid");
var isSolid = GetBool(source, "solid", true);
var isSpecial = !isGas && !isLiquid && !isSolid;
return new ParticleDef
{
Id = id,
Name = GetString(source, "name") ?? id,
Kind = isGas ? ParticleKind.Gas : isLiquid ? ParticleKind.Liquid : ParticleKind.Solid,
IsStatic = StaticTypes.Contains(id) || GetBool(source, "static"),
IsSpecial = isSpecial,
Mass = GetFloat(source, "mass", 1f),
Hardness = GetFloat(source, "hardness", 0.5f),
Velocity = GetFloat(source, "velocity", 0f),
Conductivity = GetFloat(source, "conductivity", 0f),
Conductive = GetBool(source, "conductive"),
Flamability = GetFloat(source, "flamability", 0f),
Durability = GetFloat(source, "durability", 100f),
HeatCapacity = GetFloat(source, "heat_capacity", 1f),
Friction = GetFloat(source, "friction", 0f),
Viscosity = GetFloat(source, "viscosity", 0f),
Pressure = GetFloat(source, "pressure", 0f),
Temperature = GetFloat(source, "temperature", 22f),
Melt = GetLowerString(source, "melt"),
MeltTemperature = GetNullableFloat(source, "melt_temperature"),
Evaporate = GetLowerString(source, "evaporate"),
EvaporateTemperature = GetNullableFloat(source, "evaporate_temperature"),
Solidify = GetLowerString(source, "solidify"),
SolidifyTemperature = GetNullableFloat(source, "solidify_temperature"),
Freeze = GetLowerString(source, "freeze"),
FreezeTemperature = GetNullableFloat(source, "freeze_temperature"),
BurnDuration = GetFloat(source, "burn_duration", 0f),
BurnTemperature = GetFloat(source, "burn_temperature", 0f),
BurnRate = GetFloat(source, "burn_rate", 1f),
Burning = GetBool(source, "burning"),
Lifetime = GetNullableFloat(source, "lifetime"),
Explosive = GetBool(source, "explosive"),
ExplosionRadius = GetInt(source, "explosion_radius", 0),
ExplosionColor = GetOptionalColor(source, "explosion_color"),
ExplosionForce = GetFloat(source, "explosion_force", 6f),
ExplosionDuration = GetInt(source, "explosion_duration", 1),
PressureResistance = GetFloat(source, "pressure_resistance", 0f),
PressureTolerance = GetFloat(source, "pressure_tolerance", 0f),
PressureThreshold = GetFloat(source, "pressure_threshold", 0f),
PressureThresholdDuration = GetInt(source, "pressure_threshold_duration", 0),
Broken = GetLowerString(source, "Broken") ?? GetLowerString(source, "broken"),
Produces = GetLowerString(source, "produces"),
ProducesOnDeath = GetLowerString(source, "produces_on_death"),
HeatEmission = GetFloat(source, "heat_emission", 0f),
EnergyTransfer = GetFloat(source, "energy_transfer", 0f),
Radius = GetFloat(source, "radius", 0f),
ForceFalloff = GetFloat(source, "force_falloff", 1f),
Turbulence = GetFloat(source, "turbulence", 0f),
Affects = GetStringArray(source, "affects"),
IsWind = GetBool(source, "is_wind"),
WindStrength = GetFloat(source, "wind_strength", 0f),
WindDirection = GetFloatArray(source, "wind_direction"),
IsGravity = GetBool(source, "is_gravity"),
GravityStrength = GetFloat(source, "gravity_strength", 0f),
PullDirection = GetLowerString(source, "pull_direction"),
IsRepulsor = GetBool(source, "is_repulsor"),
RepulsionStrength = GetFloat(source, "repulsion_strength", 0f),
PushDirection = GetLowerString(source, "push_direction"),
Color = GetColor(source),
};
}
private static void ValidateTarget(HashSet<string> knownIds, string id, string? target, string propertyName)
{
if (target is not null && !knownIds.Contains(target))
{
throw new InvalidDataException($"Particle '{id}' references unknown {propertyName} target '{target}'.");
}
}
private static string? GetString(JsonObject source, string key) => source[key]?.GetValue<string>();
private static string? GetLowerString(JsonObject source, string key)
{
var value = GetString(source, key);
return value?.ToLowerInvariant();
}
private static bool GetBool(JsonObject source, string key, bool defaultValue = false)
{
if (source[key] is null)
{
return defaultValue;
}
return source[key]!.GetValue<bool>();
}
private static float GetFloat(JsonObject source, string key, float defaultValue)
{
if (source[key] is null)
{
return defaultValue;
}
return source[key]!.GetValue<float>();
}
private static int GetInt(JsonObject source, string key, int defaultValue)
{
if (source[key] is null)
{
return defaultValue;
}
return source[key]!.GetValue<int>();
}
private static float? GetNullableFloat(JsonObject source, string key)
{
if (source[key] is null)
{
return null;
}
return source[key]!.GetValue<float>();
}
private static Rgb24 GetColor(JsonObject source)
{
if (source["color"] is not JsonArray array || array.Count < 3)
{
return new Rgb24(255, 255, 255);
}
return new Rgb24(
(byte)array[0]!.GetValue<int>(),
(byte)array[1]!.GetValue<int>(),
(byte)array[2]!.GetValue<int>());
}
private static Rgb24? GetOptionalColor(JsonObject source, string key)
{
if (source[key] is not JsonArray array || array.Count < 3)
{
return null;
}
return new Rgb24(
(byte)array[0]!.GetValue<int>(),
(byte)array[1]!.GetValue<int>(),
(byte)array[2]!.GetValue<int>());
}
private static string[] GetStringArray(JsonObject source, string key)
{
if (source[key] is not JsonArray array)
{
return [];
}
return array
.Where(static item => item is not null)
.Select(static item => item!.GetValue<string>().ToLowerInvariant())
.ToArray();
}
private static float[]? GetFloatArray(JsonObject source, string key)
{
if (source[key] is not JsonArray array || array.Count == 0)
{
return null;
}
return array
.Where(static item => item is not null)
.Select(static item => item!.GetValue<float>())
.ToArray();
}
}