using System; using System.Runtime.InteropServices; using JetBrains.Annotations; using UnityEngine.Networking; namespace RoR2.Stats; public struct StatField : IComparable { [StructLayout(LayoutKind.Explicit)] private struct ValueUnion { [FieldOffset(0)] public readonly ulong ulongValue; [FieldOffset(0)] public readonly double doubleValue; public static explicit operator ulong(ValueUnion v) { return v.ulongValue; } public static explicit operator double(ValueUnion v) { return v.doubleValue; } public static implicit operator ValueUnion(ulong ulongValue) { return new ValueUnion(ulongValue); } public static implicit operator ValueUnion(double doubleValue) { return new ValueUnion(doubleValue); } private ValueUnion(ulong ulongValue) { this = default(ValueUnion); this.ulongValue = ulongValue; } private ValueUnion(double doubleValue) { this = default(ValueUnion); this.doubleValue = doubleValue; } } public StatDef statDef; public bool hasChanged; public bool nonDefaultValue; private ValueUnion value; public string name => statDef.name; public StatRecordType recordType => statDef.recordType; public StatDataType dataType => statDef.dataType; private ulong ulongValue { get { return value.ulongValue; } set { this.value = value; } } private double doubleValue { get { return value.doubleValue; } set { this.value = value; } } public override string ToString() { return dataType switch { StatDataType.ULong => TextSerialization.ToStringInvariant(value.ulongValue), StatDataType.Double => TextSerialization.ToStringInvariant(value.doubleValue), _ => throw new ArgumentOutOfRangeException(), }; } public string ToLocalNumeric() { return dataType switch { StatDataType.ULong => TextSerialization.ToStringNumeric(value.ulongValue), StatDataType.Double => TextSerialization.ToStringNumeric(value.doubleValue), _ => throw new ArgumentOutOfRangeException(), }; } public ulong CalculatePointValue() { throw new NotImplementedException(); } [Pure] public static StatField GetDelta(ref StatField newerValue, ref StatField olderValue) { StatField result; if (newerValue.recordType != 0) { if (newerValue.dataType == StatDataType.ULong && newerValue.ulongValue == olderValue.ulongValue) { result = default(StatField); result.statDef = newerValue.statDef; result.hasChanged = false; result.ulongValue = newerValue.ulongValue; return result; } if (newerValue.dataType == StatDataType.Double && newerValue.doubleValue == olderValue.doubleValue) { result = default(StatField); result.statDef = newerValue.statDef; result.hasChanged = false; result.doubleValue = newerValue.doubleValue; return result; } } result = default(StatField); result.statDef = newerValue.statDef; result.hasChanged = false; StatField result2 = result; switch (newerValue.dataType) { case StatDataType.ULong: switch (newerValue.recordType) { case StatRecordType.Sum: result2.ulongValue = newerValue.ulongValue - olderValue.ulongValue; result2.hasChanged = result2.ulongValue != 0; break; case StatRecordType.Max: result2.ulongValue = Math.Max(newerValue.ulongValue, olderValue.ulongValue); result2.hasChanged = result2.ulongValue != olderValue.ulongValue; break; case StatRecordType.Newest: result2.ulongValue = newerValue.ulongValue; result2.hasChanged = result2.ulongValue != olderValue.ulongValue; break; } break; case StatDataType.Double: switch (newerValue.recordType) { case StatRecordType.Sum: result2.doubleValue = newerValue.doubleValue - olderValue.doubleValue; result2.hasChanged = result2.doubleValue != 0.0; break; case StatRecordType.Max: result2.doubleValue = Math.Max(newerValue.doubleValue, olderValue.doubleValue); result2.hasChanged = result2.doubleValue != olderValue.doubleValue; break; case StatRecordType.Newest: result2.doubleValue = newerValue.doubleValue; result2.hasChanged = result2.doubleValue != olderValue.doubleValue; break; } break; } return result2; } public void PushDelta(ref StatField deltaField) { switch (dataType) { case StatDataType.ULong: PushStatValue(deltaField.ulongValue); break; case StatDataType.Double: PushStatValue(deltaField.doubleValue); break; default: throw new ArgumentOutOfRangeException(); } } public void Write(NetworkWriter writer) { switch (dataType) { case StatDataType.ULong: writer.WritePackedUInt64(ulongValue); break; case StatDataType.Double: writer.Write(doubleValue); break; default: throw new ArgumentOutOfRangeException(); } } public void Read(NetworkReader reader) { switch (dataType) { case StatDataType.ULong: { ulong num2 = ulongValue; ulongValue = reader.ReadPackedUInt64(); hasChanged = ulongValue != num2; break; } case StatDataType.Double: { double num = doubleValue; doubleValue = reader.ReadDouble(); hasChanged = num != doubleValue; break; } default: throw new ArgumentOutOfRangeException(); } } private void EnforceDataType(StatDataType otherDataType) { if (dataType != otherDataType) { throw new InvalidOperationException($"Expected data type {dataType}, got data type {otherDataType}."); } } public void PushStatValue(ulong incomingValue) { EnforceDataType(StatDataType.ULong); switch (recordType) { case StatRecordType.Sum: ulongValue += incomingValue; break; case StatRecordType.Max: ulongValue = Math.Max(incomingValue, ulongValue); break; case StatRecordType.Newest: ulongValue = incomingValue; break; } hasChanged = true; nonDefaultValue = ulongValue != 0; } public void PushStatValue(double incomingValue) { EnforceDataType(StatDataType.Double); switch (recordType) { case StatRecordType.Sum: doubleValue += incomingValue; break; case StatRecordType.Max: doubleValue = Math.Max(incomingValue, doubleValue); break; case StatRecordType.Newest: doubleValue = incomingValue; break; } hasChanged = true; nonDefaultValue = doubleValue != 0.0; } public void SetFromString(string valueString) { switch (dataType) { case StatDataType.ULong: { TextSerialization.TryParseInvariant(valueString, out ulong result2); value = result2; nonDefaultValue = ulongValue != 0; hasChanged = true; break; } case StatDataType.Double: { TextSerialization.TryParseInvariant(valueString, out double result); value = result; nonDefaultValue = doubleValue != 0.0; hasChanged = true; break; } default: throw new ArgumentOutOfRangeException(); } } public ulong GetULongValue() { EnforceDataType(StatDataType.ULong); return ulongValue; } public double GetDoubleValue() { EnforceDataType(StatDataType.Double); return doubleValue; } public double GetValueAsDouble() { return dataType switch { StatDataType.ULong => ulongValue, StatDataType.Double => doubleValue, _ => throw new ArgumentOutOfRangeException(), }; } [Obsolete] public decimal GetDecimalValue() { return dataType switch { StatDataType.ULong => ulongValue, StatDataType.Double => (decimal)doubleValue, _ => throw new ArgumentOutOfRangeException(), }; } public bool IsDefault() { return dataType switch { StatDataType.ULong => ulongValue == 0, StatDataType.Double => doubleValue == 0.0, _ => true, }; } public void SetDefault() { switch (dataType) { case StatDataType.ULong: ulongValue = 0uL; break; case StatDataType.Double: doubleValue = 0.0; break; default: throw new NotImplementedException(); } nonDefaultValue = false; hasChanged = false; } public ulong GetPointValue(double pointValue) { return dataType switch { StatDataType.ULong => (ulong)((double)ulongValue * pointValue), StatDataType.Double => (ulong)(doubleValue * pointValue), _ => throw new ArgumentOutOfRangeException(), }; } public int CompareTo(StatField other) { return CompareTo(in other); } public int CompareTo(in StatField other) { EnforceDataType(other.dataType); return dataType switch { StatDataType.ULong => ulongValue.CompareTo(other.ulongValue), StatDataType.Double => doubleValue.CompareTo(other.doubleValue), _ => throw new ArgumentOutOfRangeException(), }; } public void SetToMaxValue() { switch (dataType) { case StatDataType.ULong: ulongValue = ulong.MaxValue; break; case StatDataType.Double: doubleValue = double.MaxValue; break; default: throw new ArgumentOutOfRangeException(); } nonDefaultValue = true; hasChanged = true; } }