Add SaveLoadSavePrototype test (#18859)

This commit is contained in:
Leon Friedrich
2023-08-08 19:27:16 +12:00
committed by GitHub
parent b345625de9
commit 6982f238e8
16 changed files with 272 additions and 117 deletions

View File

@@ -30,10 +30,8 @@ namespace Content.IntegrationTests.Tests.Atmos
var prototypeManager = server.ResolveDependency<IPrototypeManager>(); var prototypeManager = server.ResolveDependency<IPrototypeManager>();
AtmosAlarmThreshold threshold = default!; AtmosAlarmThreshold threshold = default!;
await server.WaitPost(() => var proto = prototypeManager.Index<AtmosAlarmThresholdPrototype>("AlarmThresholdTestDummy");
{ threshold = new(proto);
threshold = prototypeManager.Index<AtmosAlarmThreshold>("AlarmThresholdTestDummy");
});
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {

View File

@@ -232,7 +232,7 @@ public sealed class PrototypeSaveTest
SerializationHookContext hookCtx, SerializationHookContext hookCtx,
ISerializationContext? context, ISerializationManager.InstantiationDelegate<EntityUid>? instanceProvider) ISerializationContext? context, ISerializationManager.InstantiationDelegate<EntityUid>? instanceProvider)
{ {
return EntityUid.Invalid; return EntityUid.Parse(node.Value);
} }
} }
} }

View File

@@ -1,5 +1,8 @@
#nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation; using Robust.Shared.Serialization.Markdown.Validation;
using Robust.UnitTesting; using Robust.UnitTesting;
@@ -59,4 +62,126 @@ public sealed class PrototypeTests
} }
}); });
} }
/// <summary>
/// This test writes all known prototypes as yaml files, reads them again, then serializes them again.
/// </summary>
[Test]
public async Task ServerPrototypeSaveLoadSaveTest()
{
await using var pairTracker = await PoolManager.GetServerClient();
var context = new PrototypeSaveTest.TestEntityUidContext();
await SaveLoadSavePrototype(pairTracker.Pair.Server, context);
await pairTracker.CleanReturnAsync();
}
/// <summary>
/// This test writes all known prototypes as yaml files, reads them again, then serializes them again.
/// </summary>
[Test]
public async Task ClientPrototypeSaveLoadSaveTest()
{
await using var pairTracker = await PoolManager.GetServerClient();
var context = new PrototypeSaveTest.TestEntityUidContext();
await SaveLoadSavePrototype(pairTracker.Pair.Client, context);
await pairTracker.CleanReturnAsync();
}
private async Task SaveLoadSavePrototype(
RobustIntegrationTest.IntegrationInstance instance,
PrototypeSaveTest.TestEntityUidContext ctx)
{
var protoMan = instance.ResolveDependency<IPrototypeManager>();
var seriMan = instance.ResolveDependency<ISerializationManager>();
await instance.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
foreach (var kind in protoMan.EnumeratePrototypeKinds())
{
foreach (var proto in protoMan.EnumeratePrototypes(kind))
{
var noException = TrySaveLoadSavePrototype(
seriMan,
protoMan,
kind,
proto,
ctx);
// This will probably throw an exception for each prototype of this kind.
// We want to avoid having tests crash because they run out of time.
if (!noException)
break;
}
}
});
});
}
/// <returns>False if an exception was caught</returns>
private bool TrySaveLoadSavePrototype(
ISerializationManager seriMan,
IPrototypeManager protoMan,
Type kind,
IPrototype proto,
PrototypeSaveTest.TestEntityUidContext ctx)
{
DataNode first;
DataNode second;
try
{
first = seriMan.WriteValue(kind, proto, alwaysWrite: true, context:ctx);
}
catch (Exception e)
{
protoMan.TryGetMapping(kind, proto.ID, out var mapping);
Assert.Fail($"Caught exception while writing {kind.Name} prototype {proto.ID}. Exception:\n{e}");
return false;
}
object? obj;
try
{
obj = seriMan.Read(kind, first, context:ctx);
}
catch (Exception e)
{
protoMan.TryGetMapping(kind, proto.ID, out var mapping);
Assert.Fail($"Caught exception while re-reading {kind.Name} prototype {proto.ID}." +
$"\nException:\n{e}" +
$"\n\nOriginal yaml:\n{mapping}" +
$"\n\nWritten yaml:\n{first}");
return false;
}
Assert.That(obj?.GetType(), Is.EqualTo(proto.GetType()));
var deserialized = (IPrototype) obj!;
try
{
second = seriMan.WriteValue(kind, deserialized, alwaysWrite: true, context:ctx);
}
catch (Exception e)
{
protoMan.TryGetMapping(kind, proto.ID, out var mapping);
Assert.Fail($"Caught exception while re-writing {kind.Name} prototype {proto.ID}." +
$"\nException:\n{e}" +
$"\n\nOriginal yaml:\n{mapping}" +
$"\n\nWritten yaml:\n{first}");
return false;
}
var diff = first.Except(second);
if (diff == null || diff.IsEmpty)
return true;
protoMan.TryGetMapping(kind, proto.ID, out var orig);
Assert.Fail($"Re-written {kind.Name} prototype {proto.ID} differs." +
$"\nYaml diff:\n{diff}" +
$"\n\nOriginal yaml:\n{orig}" +
$"\n\nWritten yaml:\n{first}" +
$"\n\nRe-written Yaml:\n{second}");
return true;
}
} }

View File

@@ -1,7 +1,7 @@
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
namespace Content.Server.Atmos.Monitor.Components; namespace Content.Server.Atmos.Monitor.Components;
@@ -19,19 +19,19 @@ public sealed class AtmosMonitorComponent : Component
// Note that this cancels every single network // Note that this cancels every single network
// event, including ones that may not be // event, including ones that may not be
// related to atmos monitor events. // related to atmos monitor events.
[ViewVariables] [DataField("netEnabled")]
public bool NetEnabled = true; public bool NetEnabled = true;
[DataField("temperatureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))] [DataField("temperatureThresholdId", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThresholdPrototype>)))]
public readonly string? TemperatureThresholdId; public readonly string? TemperatureThresholdId;
[ViewVariables] [DataField("temperatureThreshold")]
public AtmosAlarmThreshold? TemperatureThreshold; public AtmosAlarmThreshold? TemperatureThreshold;
[DataField("pressureThreshold", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThreshold>)))] [DataField("pressureThresholdId", customTypeSerializer: (typeof(PrototypeIdSerializer<AtmosAlarmThresholdPrototype>)))]
public readonly string? PressureThresholdId; public readonly string? PressureThresholdId;
[ViewVariables] [DataField("pressureThreshold")]
public AtmosAlarmThreshold? PressureThreshold; public AtmosAlarmThreshold? PressureThreshold;
// monitor fire - much different from temperature // monitor fire - much different from temperature
@@ -41,14 +41,11 @@ public sealed class AtmosMonitorComponent : Component
[DataField("monitorFire")] [DataField("monitorFire")]
public bool MonitorFire = false; public bool MonitorFire = false;
// really messy but this is parsed at runtime after [DataField("gasThresholdPrototypes",
// prototypes are initialized, there's no customTypeSerializer:typeof(PrototypeIdValueDictionarySerializer<Gas, AtmosAlarmThresholdPrototype>))]
// way without implementing a new public Dictionary<Gas, string>? GasThresholdPrototypes;
// type serializer
[DataField("gasThresholds")]
public Dictionary<Gas, string>? GasThresholdIds;
[ViewVariables] [DataField("gasThresholds")]
public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds; public Dictionary<Gas, AtmosAlarmThreshold>? GasThresholds;
// Stores a reference to the gas on the tile this is on. // Stores a reference to the gas on the tile this is on.
@@ -56,14 +53,16 @@ public sealed class AtmosMonitorComponent : Component
public GasMixture? TileGas; public GasMixture? TileGas;
// Stores the last alarm state of this alarm. // Stores the last alarm state of this alarm.
[ViewVariables] [DataField("lastAlarmState")]
public AtmosAlarmType LastAlarmState = AtmosAlarmType.Normal; public AtmosAlarmType LastAlarmState = AtmosAlarmType.Normal;
[ViewVariables] public HashSet<AtmosMonitorThresholdType> TrippedThresholds = new(); [DataField("trippedThresholds")]
public HashSet<AtmosMonitorThresholdType> TrippedThresholds = new();
/// <summary> /// <summary>
/// Registered devices in this atmos monitor. Alerts will be sent directly /// Registered devices in this atmos monitor. Alerts will be sent directly
/// to these devices. /// to these devices.
/// </summary> /// </summary>
[ViewVariables] public HashSet<string> RegisteredDevices = new(); [DataField("registeredDevices")]
public HashSet<string> RegisteredDevices = new();
} }

View File

@@ -37,8 +37,8 @@ public sealed class AtmosMonitorSystem : EntitySystem
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<AtmosMonitorComponent, ComponentInit>(OnAtmosMonitorInit);
SubscribeLocalEvent<AtmosMonitorComponent, ComponentStartup>(OnAtmosMonitorStartup); SubscribeLocalEvent<AtmosMonitorComponent, ComponentStartup>(OnAtmosMonitorStartup);
SubscribeLocalEvent<AtmosMonitorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate); SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
SubscribeLocalEvent<AtmosMonitorComponent, TileFireEvent>(OnFireEvent); SubscribeLocalEvent<AtmosMonitorComponent, TileFireEvent>(OnFireEvent);
SubscribeLocalEvent<AtmosMonitorComponent, PowerChangedEvent>(OnPowerChangedEvent); SubscribeLocalEvent<AtmosMonitorComponent, PowerChangedEvent>(OnPowerChangedEvent);
@@ -57,23 +57,28 @@ public sealed class AtmosMonitorSystem : EntitySystem
{ {
atmosMonitor.TileGas = _atmosphereSystem.GetContainingMixture(uid, true); atmosMonitor.TileGas = _atmosphereSystem.GetContainingMixture(uid, true);
} }
private void OnMapInit(EntityUid uid, AtmosMonitorComponent component, MapInitEvent args)
private void OnAtmosMonitorInit(EntityUid uid, AtmosMonitorComponent component, ComponentInit args)
{ {
if (component.TemperatureThresholdId != null) if (component.TemperatureThresholdId != null)
component.TemperatureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.TemperatureThresholdId)); {
var proto = _prototypeManager.Index<AtmosAlarmThresholdPrototype>(component.TemperatureThresholdId);
component.TemperatureThreshold ??= new(proto);
}
if (component.PressureThresholdId != null) if (component.PressureThresholdId != null)
component.PressureThreshold = new(_prototypeManager.Index<AtmosAlarmThreshold>(component.PressureThresholdId));
if (component.GasThresholdIds != null)
{ {
component.GasThresholds = new(); var proto = _prototypeManager.Index<AtmosAlarmThresholdPrototype>(component.PressureThresholdId);
foreach (var (gas, id) in component.GasThresholdIds) component.PressureThreshold ??= new(proto);
{ }
if (_prototypeManager.TryIndex<AtmosAlarmThreshold>(id, out var gasThreshold))
component.GasThresholds.Add(gas, new(gasThreshold)); if (component.GasThresholdPrototypes == null)
} return;
component.GasThresholds ??= new();
foreach (var (gas, id) in component.GasThresholdPrototypes)
{
var proto = _prototypeManager.Index<AtmosAlarmThresholdPrototype>(id);
component.GasThresholds.TryAdd(gas, new(proto));
} }
} }

View File

@@ -6,7 +6,7 @@ namespace Content.Server.DeviceLinking.Components;
[RegisterComponent] [RegisterComponent]
public sealed class AutoLinkReceiverComponent : Component public sealed class AutoLinkReceiverComponent : Component
{ {
[DataField("channel", required: true, readOnly: true)] [DataField("channel", required: true)]
public string AutoLinkChannel = default!; public string AutoLinkChannel = default!;
} }

View File

@@ -6,7 +6,7 @@ namespace Content.Server.DeviceLinking.Components;
[RegisterComponent] [RegisterComponent]
public sealed class AutoLinkTransmitterComponent : Component public sealed class AutoLinkTransmitterComponent : Component
{ {
[DataField("channel", required: true, readOnly: true)] [DataField("channel", required: true)]
public string AutoLinkChannel = default!; public string AutoLinkChannel = default!;
} }

View File

@@ -25,7 +25,7 @@ namespace Content.Server.UserInterface
[DataField("adminOnly")] [DataField("adminOnly")]
public bool AdminOnly { get; set; } = false; public bool AdminOnly { get; set; } = false;
[DataField("key", readOnly: true, required: true)] [DataField("key", required: true)]
private string _keyRaw = default!; private string _keyRaw = default!;
[DataField("verbText")] [DataField("verbText")]

View File

@@ -27,12 +27,12 @@ public sealed class IntrinsicUIComponent : Component, ISerializationHooks
[DataDefinition] [DataDefinition]
public struct IntrinsicUIEntry public struct IntrinsicUIEntry
{ {
[ViewVariables] public Enum? Key { get; set; } = null; [ViewVariables] public Enum? Key { get; private set; } = null;
/// <summary> /// <summary>
/// The BUI key that this intrinsic UI should open. /// The BUI key that this intrinsic UI should open.
/// </summary> /// </summary>
[DataField("key", readOnly: true, required: true)] [DataField("key", required: true)]
private string _keyRaw = default!; private string _keyRaw = default!;
/// <summary> /// <summary>

View File

@@ -7,9 +7,9 @@ namespace Content.Server.UserInterface;
public sealed class OpenUiActionEvent : InstantActionEvent, ISerializationHooks public sealed class OpenUiActionEvent : InstantActionEvent, ISerializationHooks
{ {
[ViewVariables] [ViewVariables]
public Enum? Key { get; set; } public Enum? Key { get; private set; }
[DataField("key", readOnly: true, required: true)] [DataField("key", required: true)]
private string _keyRaw = default!; private string _keyRaw = default!;
void ISerializationHooks.AfterDeserialization() void ISerializationHooks.AfterDeserialization()

View File

@@ -3,77 +3,91 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.Monitor; namespace Content.Shared.Atmos.Monitor;
// mostly based around floats and percentages, no literals
// except for the range boundaries
[Prototype("alarmThreshold")] [Prototype("alarmThreshold")]
[Serializable, NetSerializable] public sealed class AtmosAlarmThresholdPrototype : IPrototype
public sealed class AtmosAlarmThreshold : IPrototype, ISerializationHooks
{ {
[IdDataField] [IdDataField]
public string ID { get; } = default!; public string ID { get; } = default!;
[DataField("ignore")]
public readonly bool Ignore;
[DataField("upperBound")]
public readonly AlarmThresholdSetting UpperBound = AlarmThresholdSetting.Disabled;
[DataField("lowerBound")]
public readonly AlarmThresholdSetting LowerBound = AlarmThresholdSetting.Disabled;
[DataField("upperWarnAround")]
public readonly AlarmThresholdSetting UpperWarningPercentage = AlarmThresholdSetting.Disabled;
[DataField("lowerWarnAround")]
public readonly AlarmThresholdSetting LowerWarningPercentage = AlarmThresholdSetting.Disabled;
}
[Serializable, NetSerializable, DataDefinition]
public sealed class AtmosAlarmThreshold
{
[DataField("ignore")] [DataField("ignore")]
public bool Ignore; public bool Ignore;
[DataField("upperBound")] [DataField("upperBound")]
private AlarmThresholdSetting _UpperBound; private AlarmThresholdSetting _upperBound = AlarmThresholdSetting.Disabled;
public AlarmThresholdSetting UpperBound { get { return _UpperBound; } private set [DataField("lowerBound")]
private AlarmThresholdSetting _lowerBound = AlarmThresholdSetting.Disabled;
[DataField("upperWarnAround")]
public AlarmThresholdSetting UpperWarningPercentage = AlarmThresholdSetting.Disabled;
[DataField("lowerWarnAround")]
public AlarmThresholdSetting LowerWarningPercentage = AlarmThresholdSetting.Disabled;
public AlarmThresholdSetting UpperBound
{
get => _upperBound;
set
{ {
// Because the warnings are stored as percentages of the bounds, // Because the warnings are stored as percentages of the bounds,
// Make a copy of the calculated bounds, so that the real warning amount // Make a copy of the calculated bounds, so that the real warning amount
// doesn't change value when user changes the bounds // doesn't change value when user changes the bounds
var oldWarning = UpperWarningBound; var oldWarning = UpperWarningBound;
_UpperBound = value; _upperBound = value;
UpperWarningBound = oldWarning; UpperWarningBound = oldWarning;
} }
} }
[DataField("lowerBound")] public AlarmThresholdSetting LowerBound
public AlarmThresholdSetting _LowerBound; {
get => _lowerBound;
public AlarmThresholdSetting LowerBound { get { return _LowerBound; } private set set
{ {
// Because the warnings are stored as percentages of the bounds, // Because the warnings are stored as percentages of the bounds,
// Make a copy of the calculated bounds, so that the real warning amount // Make a copy of the calculated bounds, so that the real warning amount
// doesn't change value when user changes the bounds // doesn't change value when user changes the bounds
var oldWarning = LowerWarningBound; var oldWarning = LowerWarningBound;
_LowerBound = value; _lowerBound = value;
LowerWarningBound = oldWarning; LowerWarningBound = oldWarning;
} }
} }
// upper warning percentage
// must always cause UpperWarningBound
// to be smaller
[DataField("upperWarnAround")]
public AlarmThresholdSetting UpperWarningPercentage { get; private set; }
// lower warning percentage
// must always cause LowerWarningBound
// to be larger
[DataField("lowerWarnAround")]
public AlarmThresholdSetting LowerWarningPercentage { get; private set; }
[ViewVariables] [ViewVariables]
public AlarmThresholdSetting UpperWarningBound public AlarmThresholdSetting UpperWarningBound
{ {
get { return CalculateWarningBound(AtmosMonitorThresholdBound.Upper); } get => CalculateWarningBound(AtmosMonitorThresholdBound.Upper);
set { UpperWarningPercentage = CalculateWarningPercentage(AtmosMonitorThresholdBound.Upper, value); } set => UpperWarningPercentage = CalculateWarningPercentage(AtmosMonitorThresholdBound.Upper, value);
} }
[ViewVariables] [ViewVariables]
public AlarmThresholdSetting LowerWarningBound public AlarmThresholdSetting LowerWarningBound
{ {
get { return CalculateWarningBound(AtmosMonitorThresholdBound.Lower); } get => CalculateWarningBound(AtmosMonitorThresholdBound.Lower);
set { LowerWarningPercentage = CalculateWarningPercentage(AtmosMonitorThresholdBound.Lower, value); } set => LowerWarningPercentage = CalculateWarningPercentage(AtmosMonitorThresholdBound.Lower, value);
} }
public AtmosAlarmThreshold() public AtmosAlarmThreshold()
{ {
UpperBound = new AlarmThresholdSetting();
LowerBound = new AlarmThresholdSetting();
UpperWarningPercentage = new AlarmThresholdSetting();
LowerWarningPercentage = new AlarmThresholdSetting();
} }
public AtmosAlarmThreshold(AtmosAlarmThreshold other) public AtmosAlarmThreshold(AtmosAlarmThreshold other)
@@ -85,12 +99,13 @@ public sealed class AtmosAlarmThreshold : IPrototype, ISerializationHooks
LowerWarningPercentage = other.LowerWarningPercentage; LowerWarningPercentage = other.LowerWarningPercentage;
} }
void ISerializationHooks.AfterDeserialization() public AtmosAlarmThreshold(AtmosAlarmThresholdPrototype proto)
{ {
UpperBound = new AlarmThresholdSetting{ Enabled = UpperBound.Value != 0, Value = UpperBound.Value }; Ignore = proto.Ignore;
LowerBound = new AlarmThresholdSetting{ Enabled = LowerBound.Value != 0, Value = LowerBound.Value }; UpperBound = proto.UpperBound;
UpperWarningPercentage = new AlarmThresholdSetting{ Enabled = UpperWarningPercentage.Value != 0, Value = UpperWarningPercentage.Value }; LowerBound = proto.LowerBound;
LowerWarningPercentage = new AlarmThresholdSetting{ Enabled = LowerWarningPercentage.Value != 0, Value = LowerWarningPercentage.Value }; UpperWarningPercentage = proto.UpperWarningPercentage;
LowerWarningPercentage = proto.LowerWarningPercentage;
} }
// utility function to check a threshold against some calculated value // utility function to check a threshold against some calculated value
@@ -238,38 +253,41 @@ public sealed class AtmosAlarmThreshold : IPrototype, ISerializationHooks
break; break;
} }
} }
}
[DataDefinition, Serializable] [DataDefinition, Serializable]
public struct AlarmThresholdSetting public readonly struct AlarmThresholdSetting
{
[DataField("enabled")]
public bool Enabled { get; init; } = true;
[DataField("threshold")]
public float Value { get; init; } = 1;
public static AlarmThresholdSetting Disabled = new() {Enabled = false, Value = 0};
public AlarmThresholdSetting()
{ {
[DataField("enabled")] }
public bool Enabled { get; set; } = false;
[DataField("threshold")]
public float Value { get; set; } = 0;
public AlarmThresholdSetting() public static bool operator <=(float a, AlarmThresholdSetting b)
{ {
} return b.Enabled && a <= b.Value;
}
public static bool operator <=(float a, AlarmThresholdSetting b) public static bool operator >=(float a, AlarmThresholdSetting b)
{ {
return b.Enabled && a <= b.Value; return b.Enabled && a >= b.Value;
} }
public static bool operator >=(float a, AlarmThresholdSetting b) public AlarmThresholdSetting WithThreshold(float threshold)
{ {
return b.Enabled && a >= b.Value; return this with {Value = threshold};
} }
public AlarmThresholdSetting WithThreshold(float threshold) public AlarmThresholdSetting WithEnabled(bool enabled)
{ {
return new AlarmThresholdSetting{ Enabled = Enabled, Value = threshold }; return this with {Enabled = enabled};
}
public AlarmThresholdSetting WithEnabled(bool enabled)
{
return new AlarmThresholdSetting{ Enabled = enabled, Value = Value };
}
} }
} }

View File

@@ -42,6 +42,6 @@ public sealed class CartridgeLoaderComponent : Component
[DataField("diskSpace")] [DataField("diskSpace")]
public int DiskSpace = 5; public int DiskSpace = 5;
[DataField("uiKey", readOnly: true, required: true, customTypeSerializer: typeof(EnumSerializer))] [DataField("uiKey", required: true, customTypeSerializer: typeof(EnumSerializer))]
public Enum UiKey = default!; public Enum UiKey = default!;
} }

View File

@@ -39,12 +39,12 @@ public sealed class DamageSpecifierDictionarySerializer : ITypeReader<Dictionary
{ {
var dict = instanceProvider != null ? instanceProvider() : new(); var dict = instanceProvider != null ? instanceProvider() : new();
// Add all the damage types by just copying the type dictionary (if it is not null). // Add all the damage types by just copying the type dictionary (if it is not null).
if (node.TryGet("types", out var typesNode)) if (node.TryGet<MappingDataNode>("types", out var typesNode))
{ {
serializationManager.Read(typesNode, instanceProvider: () => dict, notNullableOverride: true); serializationManager.Read(typesNode, instanceProvider: () => dict, notNullableOverride: true);
} }
if (!node.TryGet("groups", out var groupsNode)) if (!node.TryGet<MappingDataNode>("groups", out var groupsNode))
return dict; return dict;
// Then resolve damage groups and add them // Then resolve damage groups and add them

View File

@@ -248,7 +248,12 @@ namespace Content.Shared.FixedPoint
public void Deserialize(string value) public void Deserialize(string value)
{ {
Value = FromFloat(FloatFromString(value)); // TODO implement "lossless" serializer.
// I.e., dont use floats.
if (value == "MaxValue")
Value = int.MaxValue;
else
Value = FromFloat(FloatFromString(value));
} }
public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}"; public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}";
@@ -260,6 +265,11 @@ namespace Content.Shared.FixedPoint
public readonly string Serialize() public readonly string Serialize()
{ {
// TODO implement "lossless" serializer.
// I.e., dont use floats.
if (Value == int.MaxValue)
return "MaxValue";
return ToString(); return ToString();
} }

View File

@@ -38,9 +38,9 @@
- type: DeviceNetworkRequiresPower - type: DeviceNetworkRequiresPower
- type: AtmosDevice - type: AtmosDevice
- type: AtmosMonitor - type: AtmosMonitor
temperatureThreshold: stationTemperature temperatureThresholdId: stationTemperature
pressureThreshold: stationPressure pressureThresholdId: stationPressure
gasThresholds: gasThresholdPrototypes:
Oxygen: stationOxygen Oxygen: stationOxygen
Nitrogen: ignore Nitrogen: ignore
CarbonDioxide: stationCO2 CarbonDioxide: stationCO2
@@ -127,9 +127,9 @@
examinableAddress: true examinableAddress: true
- type: DeviceNetworkRequiresPower - type: DeviceNetworkRequiresPower
- type: AtmosMonitor - type: AtmosMonitor
temperatureThreshold: stationTemperature temperatureThresholdId: stationTemperature
pressureThreshold: stationPressure pressureThresholdId: stationPressure
gasThresholds: gasThresholdPrototypes:
Oxygen: stationOxygen Oxygen: stationOxygen
Nitrogen: ignore Nitrogen: ignore
CarbonDioxide: stationCO2 CarbonDioxide: stationCO2

View File

@@ -46,9 +46,9 @@
- type: DeviceNetworkRequiresPower - type: DeviceNetworkRequiresPower
- type: AtmosDevice - type: AtmosDevice
- type: AtmosMonitor - type: AtmosMonitor
temperatureThreshold: stationTemperature temperatureThresholdId: stationTemperature
pressureThreshold: stationPressure pressureThresholdId: stationPressure
gasThresholds: gasThresholdPrototypes:
Oxygen: stationOxygen Oxygen: stationOxygen
Nitrogen: ignore Nitrogen: ignore
CarbonDioxide: stationCO2 CarbonDioxide: stationCO2