diff --git a/BodySim.Tests/BodyTests.cs b/BodySim.Tests/BodyTests.cs new file mode 100644 index 0000000..27bea66 --- /dev/null +++ b/BodySim.Tests/BodyTests.cs @@ -0,0 +1,32 @@ +using System.Text.Json; + +namespace BodySim.Tests; + +public class BodyTests +{ + [Fact] + public void Constructor_InitializesRespiratorySystem() + { + var body = new Body(); + + Assert.NotNull(body.GetSystem(BodySystemType.Respiratory)); + } + + [Fact] + public void ExportForGodotJson_ContainsSystemsAndResources() + { + var body = new Body(); + + using var document = JsonDocument.Parse(body.ExportForGodotJson()); + var root = document.RootElement; + + Assert.True(root.GetProperty("resources").TryGetProperty(BodyResourceType.Blood.ToString(), out _)); + + var systems = root.GetProperty("systems"); + Assert.True(systems.TryGetProperty(BodySystemType.Skeletal.ToString(), out _)); + Assert.True(systems.TryGetProperty(BodySystemType.Circulatory.ToString(), out _)); + Assert.True(systems.TryGetProperty(BodySystemType.Respiratory.ToString(), out var respiratory)); + Assert.True(respiratory.TryGetProperty(BodyPartType.Chest.ToString(), out var chest)); + Assert.True(chest.GetProperty("components").TryGetProperty(BodyComponentType.AirFlow.ToString(), out _)); + } +} diff --git a/BodySim/Body.cs b/BodySim/Body.cs index ace44e4..a58114d 100644 --- a/BodySim/Body.cs +++ b/BodySim/Body.cs @@ -1,3 +1,5 @@ +using System.Text.Json; + namespace BodySim; public class Body @@ -61,4 +63,33 @@ public void Clot(BodyPartType bodyPart) { return Systems.TryGetValue(systemType, out BodySystemBase? system) ? system : null; } + + public string ExportForGodotJson(bool indented = false) + { + var payload = new + { + resources = ResourcePool.GetResources().ToDictionary(resource => resource.Key.ToString(), resource => resource.Value), + systems = Systems.ToDictionary( + system => system.Key.ToString(), + system => system.Value.GetNodes().ToDictionary( + node => node.Key.ToString(), + node => new + { + status = node.Value.Status.ToString(), + components = node.Value.Components.ToDictionary( + nodeComponent => nodeComponent.ComponentType.ToString(), + nodeComponent => new + { + current = nodeComponent.Current, + max = nodeComponent.Max, + regenRate = nodeComponent.RegenRate, + }), + })) + }; + + return JsonSerializer.Serialize(payload, new JsonSerializerOptions + { + WriteIndented = indented + }); + } } diff --git a/BodySim/BodySystemBase.cs b/BodySim/BodySystemBase.cs index 3ec5a93..78f8392 100644 --- a/BodySim/BodySystemBase.cs +++ b/BodySim/BodySystemBase.cs @@ -37,6 +37,8 @@ public void SetNodeStatus(BodyPartType bodyPartType, SystemNodeStatus status) } return null; } + + public IReadOnlyDictionary GetNodes() => Statuses; public virtual void MetabolicUpdate() { diff --git a/BodySim/RespiratorySystem.cs b/BodySim/RespiratorySystem.cs new file mode 100644 index 0000000..a85fd4a --- /dev/null +++ b/BodySim/RespiratorySystem.cs @@ -0,0 +1,60 @@ +namespace BodySim; + +public class RespiratorySystem : BodySystemBase +{ + private const float NormalAirFlow = 100f; + + public RespiratorySystem(BodyResourcePool pool, EventHub eventHub) + : base(BodySystemType.Respiratory, pool, eventHub) + { + InitSystem(); + eventHub.RegisterListener(this); + eventHub.RegisterListener(this); + } + + public override void HandleMessage(IEvent evt) + { + switch (evt) + { + case SuffocateEvent suffocateEvent: + SetAirwayState(suffocateEvent.BodyPartType, blocked: true); + break; + case ClearAirwayEvent clearAirwayEvent: + SetAirwayState(clearAirwayEvent.BodyPartType, blocked: false); + break; + } + } + + public override void InitSystem() + { + foreach (BodyPartType partType in Enum.GetValues()) + { + Statuses[partType] = new RespiratoryNode(partType); + } + } + + private void SetAirwayState(BodyPartType bodyPartType, bool blocked) + { + if (!Statuses.TryGetValue(bodyPartType, out var node)) return; + node.Status = blocked ? SystemNodeStatus.Disabled : SystemNodeStatus.Healthy; + var airflow = node.GetComponent(BodyComponentType.AirFlow); + if (airflow != null) + { + airflow.Current = blocked ? 0 : NormalAirFlow; + } + } +} + +public class RespiratoryNode(BodyPartType bodyPartType) : BodyPartNodeBase(bodyPartType, CreateComponents()) +{ + private const float DefaultComponentValue = 100f; + private const float HealthRegenRate = 0.1f; + private const float NoRegenRate = 0f; + + private static List CreateComponents() => + [ + new BodyComponentBase(DefaultComponentValue, DefaultComponentValue, HealthRegenRate, BodyComponentType.Health), + new BodyComponentBase(DefaultComponentValue, DefaultComponentValue, NoRegenRate, BodyComponentType.LungCapacity), + new BodyComponentBase(DefaultComponentValue, DefaultComponentValue, NoRegenRate, BodyComponentType.AirFlow), + ]; +}