Client LuaCsForBarotrauma
SerializableProperty.cs
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.Linq;
10 using System.Reflection;
11 using System.Xml.Linq;
12 
13 namespace Barotrauma
14 {
15  public enum IsPropertySaveable
16  {
17  Yes,
18  No
19  }
20 
21  [AttributeUsage(AttributeTargets.Property)]
22  public sealed class Serialize : Attribute
23  {
24  public readonly object DefaultValue;
25  public readonly IsPropertySaveable IsSaveable;
26  public readonly Identifier TranslationTextTag;
27 
32 
33  public string Description;
34 
43  public Serialize(object defaultValue, IsPropertySaveable isSaveable, string description = "", string translationTextTag = "", bool alwaysUseInstanceValues = false)
44  {
45  DefaultValue = defaultValue;
46  IsSaveable = isSaveable;
47  TranslationTextTag = translationTextTag.ToIdentifier();
48  Description = description;
49  AlwaysUseInstanceValues = alwaysUseInstanceValues;
50  }
51  }
52 
53  [AttributeUsage(AttributeTargets.Property)]
54  public sealed class Header : Attribute
55  {
56  public readonly LocalizedString Text;
57 
58  public Header(string text = "", string localizedTextTag = null)
59  {
60  Text = localizedTextTag != null ? TextManager.Get(localizedTextTag) : text;
61  }
62  }
63 
64  public sealed class SerializableProperty
65  {
66  private static readonly ImmutableDictionary<Type, string> supportedTypes = new Dictionary<Type, string>
67  {
68  { typeof(bool), "bool" },
69  { typeof(int), "int" },
70  { typeof(float), "float" },
71  { typeof(string), "string" },
72  { typeof(Identifier), "identifier" },
73  { typeof(LanguageIdentifier), "languageidentifier" },
74  { typeof(LocalizedString), "localizedstring" },
75  { typeof(Point), "point" },
76  { typeof(Vector2), "vector2" },
77  { typeof(Vector3), "vector3" },
78  { typeof(Vector4), "vector4" },
79  { typeof(Rectangle), "rectangle" },
80  { typeof(Color), "color" },
81  { typeof(string[]), "stringarray" },
82  { typeof(Identifier[]), "identifierarray" }
83  }.ToImmutableDictionary();
84 
85  private static readonly Dictionary<Type, Dictionary<Identifier, SerializableProperty>> cachedProperties =
86  new Dictionary<Type, Dictionary<Identifier, SerializableProperty>>();
87  public readonly string Name;
88  public readonly AttributeCollection Attributes;
89  public readonly Type PropertyType;
90 
91  public readonly bool OverridePrefabValues;
92 
93  public readonly PropertyInfo PropertyInfo;
94 
95  public SerializableProperty(PropertyDescriptor property)
96  {
97  Name = property.Name;
98  PropertyInfo = property.ComponentType.GetProperty(property.Name);
99  PropertyType = property.PropertyType;
100  Attributes = property.Attributes;
101  OverridePrefabValues = GetAttribute<Serialize>()?.AlwaysUseInstanceValues ?? false;
102  }
103 
104  public T GetAttribute<T>() where T : Attribute
105  {
106  foreach (Attribute a in Attributes)
107  {
108  if (a is T) return (T)a;
109  }
110 
111  return default;
112  }
113 
114  public void SetValue(object parentObject, object val)
115  {
116  PropertyInfo.SetValue(parentObject, val);
117  }
118 
119  public bool TrySetValue(object parentObject, string value)
120  {
121  if (value == null) { return false; }
122 
123  if (!supportedTypes.TryGetValue(PropertyType, out string typeName))
124  {
125  if (PropertyType.IsEnum)
126  {
127  object enumVal;
128  try
129  {
130  enumVal = Enum.Parse(PropertyInfo.PropertyType, value, true);
131  }
132  catch (Exception e)
133  {
134  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value} (not a valid {PropertyInfo.PropertyType})", e);
135  return false;
136  }
137  try
138  {
139  PropertyInfo.SetValue(parentObject, enumVal);
140  }
141  catch (Exception e)
142  {
143  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value}", e);
144  return false;
145  }
146  }
147  else
148  {
149  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value} (Type \"{PropertyType.Name}\" not supported)");
150 
151  return false;
152  }
153  }
154 
155  try
156  {
157  switch (typeName)
158  {
159  case "bool":
160  bool boolValue = value.ToIdentifier() == "true";
161  if (TrySetBoolValueWithoutReflection(parentObject, boolValue)) { return true; }
162  PropertyInfo.SetValue(parentObject, boolValue, null);
163  break;
164  case "int":
165  if (int.TryParse(value, out int intVal))
166  {
167  if (TrySetFloatValueWithoutReflection(parentObject, intVal)) { return true; }
168  PropertyInfo.SetValue(parentObject, intVal, null);
169  }
170  else
171  {
172  return false;
173  }
174  break;
175  case "float":
176  if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float floatVal))
177  {
178  if (TrySetFloatValueWithoutReflection(parentObject, floatVal)) { return true; }
179  PropertyInfo.SetValue(parentObject, floatVal, null);
180  }
181  else
182  {
183  return false;
184  }
185  break;
186  case "string":
187  PropertyInfo.SetValue(parentObject, value, null);
188  break;
189  case "point":
190  PropertyInfo.SetValue(parentObject, XMLExtensions.ParsePoint(value));
191  break;
192  case "vector2":
193  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseVector2(value));
194  break;
195  case "vector3":
196  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseVector3(value));
197  break;
198  case "vector4":
199  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseVector4(value));
200  break;
201  case "color":
202  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseColor(value));
203  break;
204  case "rectangle":
205  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseRect(value, true));
206  break;
207  case "identifier":
208  PropertyInfo.SetValue(parentObject, value.ToIdentifier());
209  break;
210  case "languageidentifier":
211  PropertyInfo.SetValue(parentObject, value.ToLanguageIdentifier());
212  break;
213  case "localizedstring":
214  PropertyInfo.SetValue(parentObject, new RawLString(value));
215  break;
216  case "stringarray":
217  PropertyInfo.SetValue(parentObject, ParseStringArray(value));
218  break;
219  case "identifierarray":
220  PropertyInfo.SetValue(parentObject, ParseIdentifierArray(value));
221  break;
222  }
223  }
224  catch (Exception e)
225  {
226  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value}", e);
227  return false;
228  }
229  return true;
230  }
231 
232 
233  private static string[] ParseStringArray(string stringArrayValues)
234  {
235  return string.IsNullOrEmpty(stringArrayValues) ? Array.Empty<string>() : stringArrayValues.Split(';');
236  }
237 
238  private static Identifier[] ParseIdentifierArray(string stringArrayValues)
239  {
240  return ParseStringArray(stringArrayValues).ToIdentifiers();
241  }
242 
243  public bool TrySetValue(object parentObject, object value)
244  {
245  if (value == null || parentObject == null || PropertyInfo == null) return false;
246 
247  try
248  {
249  if (!supportedTypes.TryGetValue(PropertyType, out string typeName))
250  {
251  if (PropertyType.IsEnum)
252  {
253  object enumVal;
254  try
255  {
256  enumVal = Enum.Parse(PropertyInfo.PropertyType, value.ToString(), true);
257  }
258  catch (Exception e)
259  {
260  DebugConsole.ThrowError(
261  $"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value} (not a valid {PropertyInfo.PropertyType})", e);
262  return false;
263  }
264  PropertyInfo.SetValue(parentObject, enumVal);
265  return true;
266  }
267  else
268  {
269  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value} (Type \"{PropertyType.Name}\" not supported)");
270 
271  return false;
272  }
273  }
274 
275  try
276  {
277  if (value.GetType() == typeof(string))
278  {
279  switch (typeName)
280  {
281  case "string":
282  PropertyInfo.SetValue(parentObject, value, null);
283  return true;
284  case "point":
285  PropertyInfo.SetValue(parentObject, XMLExtensions.ParsePoint((string)value));
286  return true;
287  case "vector2":
288  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseVector2((string)value));
289  return true;
290  case "vector3":
291  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseVector3((string)value));
292  return true;
293  case "vector4":
294  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseVector4((string)value));
295  return true;
296  case "color":
297  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseColor((string)value));
298  return true;
299  case "rectangle":
300  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseRect((string)value, false));
301  return true;
302  case "identifier":
303  PropertyInfo.SetValue(parentObject, new Identifier((string)value));
304  return true;
305  case "languageidentifier":
306  PropertyInfo.SetValue(parentObject, ((string)value).ToLanguageIdentifier());
307  return true;
308  case "localizedstring":
309  PropertyInfo.SetValue(parentObject, new RawLString((string)value));
310  return true;
311  case "stringarray":
312  PropertyInfo.SetValue(parentObject, ParseStringArray((string)value));
313  return true;
314  case "identifierarray":
315  PropertyInfo.SetValue(parentObject, ParseIdentifierArray((string)value));
316  return true;
317  default:
318  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value}");
319  DebugConsole.ThrowError($"(Cannot convert a string to a {PropertyType})");
320  return false;
321  }
322  }
323  else if (PropertyType != value.GetType())
324  {
325  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value}");
326  DebugConsole.ThrowError("(Non-matching type, should be " + PropertyType + " instead of " + value.GetType() + ")");
327  return false;
328  }
329 
330  PropertyInfo.SetValue(parentObject, value, null);
331  }
332 
333  catch (Exception e)
334  {
335  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value}", e);
336  return false;
337  }
338 
339  return true;
340  }
341  catch (Exception e)
342  {
343  DebugConsole.ThrowError($"Error in SerializableProperty.TrySetValue (Property: {PropertyInfo.Name})", e);
344  return false;
345  }
346  }
347 
348  public bool TrySetValue(object parentObject, float value)
349  {
350  try
351  {
352  if (TrySetFloatValueWithoutReflection(parentObject, value)) { return true; }
353  PropertyInfo.SetValue(parentObject, value, null);
354  }
355  catch (TargetInvocationException e)
356  {
357  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.TrySetValue", e.InnerException);
358  return false;
359  }
360  catch (Exception e)
361  {
362  DebugConsole.ThrowError($"Error in SerializableProperty.TrySetValue (Property: {PropertyInfo.Name})", e);
363  return false;
364  }
365 
366  return true;
367  }
368 
369  public bool TrySetValue(object parentObject, bool value)
370  {
371  try
372  {
373  if (TrySetBoolValueWithoutReflection(parentObject, value)) { return true; }
374  PropertyInfo.SetValue(parentObject, value, null);
375  }
376  catch (TargetInvocationException e)
377  {
378  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.TrySetValue", e.InnerException);
379  return false;
380  }
381  catch (Exception e)
382  {
383  DebugConsole.ThrowError($"Error in SerializableProperty.TrySetValue (Property: {PropertyInfo.Name})", e);
384  return false;
385  }
386  return true;
387  }
388 
389  public bool TrySetValue(object parentObject, int value)
390  {
391  try
392  {
393  if (TrySetFloatValueWithoutReflection(parentObject, value)) { return true; }
394  PropertyInfo.SetValue(parentObject, value, null);
395  }
396  catch (TargetInvocationException e)
397  {
398  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.TrySetValue", e.InnerException);
399  return false;
400  }
401  catch (Exception e)
402  {
403  DebugConsole.ThrowError($"Error in SerializableProperty.TrySetValue (Property: {PropertyInfo.Name})", e);
404  return false;
405  }
406  return true;
407  }
408 
409  public object GetValue(object parentObject)
410  {
411  if (parentObject == null || PropertyInfo == null) { return false; }
412 
413  var value = TryGetValueWithoutReflection(parentObject);
414  if (value != null) { return value; }
415 
416  try
417  {
418  return PropertyInfo.GetValue(parentObject, null);
419  }
420  catch (TargetInvocationException e)
421  {
422  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.GetValue", e.InnerException);
423  return false;
424  }
425  catch (Exception e)
426  {
427  DebugConsole.ThrowError("Error in SerializableProperty.GetValue", e);
428  return false;
429  }
430  }
431 
432  public float GetFloatValue(object parentObject)
433  {
434  if (parentObject == null || PropertyInfo == null) { return 0.0f; }
435 
436  if (TryGetFloatValueWithoutReflection(parentObject, out float value))
437  {
438  return value;
439  }
440 
441  try
442  {
443  if (PropertyType == typeof(int))
444  {
445  return (int)PropertyInfo.GetValue(parentObject, null);
446  }
447  else
448  {
449  return (float)PropertyInfo.GetValue(parentObject, null);
450  }
451  }
452  catch (TargetInvocationException e)
453  {
454  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.GetValue", e.InnerException);
455  return 0.0f;
456  }
457  catch (Exception e)
458  {
459  DebugConsole.ThrowError("Error in SerializableProperty.GetValue", e);
460  return 0.0f;
461  }
462  }
463 
464  public bool GetBoolValue(object parentObject)
465  {
466  if (parentObject == null || PropertyInfo == null) { return false; }
467 
468  if (TryGetBoolValueWithoutReflection(parentObject, out bool value))
469  {
470  return value;
471  }
472 
473  try
474  {
475  return (bool)PropertyInfo.GetValue(parentObject, null);
476  }
477  catch (TargetInvocationException e)
478  {
479  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.GetValue", e.InnerException);
480  return false;
481  }
482  catch (Exception e)
483  {
484  DebugConsole.ThrowError("Error in SerializableProperty.GetValue", e);
485  return false;
486  }
487  }
488 
489  public static string GetSupportedTypeName(Type type)
490  {
491  if (type.IsEnum) { return "Enum"; }
492  if (!supportedTypes.TryGetValue(type, out string typeName))
493  {
494  return null;
495  }
496  return typeName;
497  }
498 
499  private readonly ImmutableDictionary<Identifier, Func<object, object>> valueGetters =
500  new Dictionary<Identifier, Func<object, object>>()
501  {
502  {"Voltage".ToIdentifier(), (obj) => obj is Powered p ? p.Voltage : (object) null},
503  {"Charge".ToIdentifier(), (obj) => obj is PowerContainer p ? p.Charge : (object) null},
504  {"Overload".ToIdentifier(), (obj) => obj is PowerTransfer p ? p.Overload : (object) null},
505  {"AvailableFuel".ToIdentifier(), (obj) => obj is Reactor r ? r.AvailableFuel : (object) null},
506  {"FissionRate".ToIdentifier(), (obj) => obj is Reactor r ? r.FissionRate : (object) null},
507  {"OxygenFlow".ToIdentifier(), (obj) => obj is Vent v ? v.OxygenFlow : (object) null},
508  {
509  "CurrFlow".ToIdentifier(),
510  (obj) => obj is Pump p ? (object) p.CurrFlow :
511  obj is OxygenGenerator o ? (object)o.CurrFlow :
512  null
513  },
514  {"CurrentVolume".ToIdentifier(), (obj) => obj is Engine e ? e.CurrentVolume : (object)null},
515  {"MotionDetected".ToIdentifier(), (obj) => obj is MotionSensor m ? m.MotionDetected : (object)null},
516  {"Oxygen".ToIdentifier(), (obj) => obj is Character c ? c.Oxygen : (object)null},
517  {"Health".ToIdentifier(), (obj) => obj is Character c ? c.Health : (object)null},
518  {"OxygenAvailable".ToIdentifier(), (obj) => obj is Character c ? c.OxygenAvailable : (object)null},
519  {"PressureProtection".ToIdentifier(), (obj) => obj is Character c ? c.PressureProtection : (object)null},
520  {"IsDead".ToIdentifier(), (obj) => obj is Character c ? c.IsDead : (object)null},
521  {"IsHuman".ToIdentifier(), (obj) => obj is Character c ? c.IsHuman : (object)null},
522  {"IsOn".ToIdentifier(), (obj) => obj is LightComponent l ? l.IsOn : (object)null},
523  {"Condition".ToIdentifier(), (obj) => obj is Item i ? i.Condition : (object)null},
524  {"ContainerIdentifier".ToIdentifier(), (obj) => obj is Item i ? i.ContainerIdentifier : (object)null},
525  {"PhysicsBodyActive".ToIdentifier(), (obj) => obj is Item i ? i.PhysicsBodyActive : (object)null},
526  }.ToImmutableDictionary();
527 
531  private object TryGetValueWithoutReflection(object parentObject)
532  {
533  if (PropertyType == typeof(float))
534  {
535  if (TryGetFloatValueWithoutReflection(parentObject, out float value)) { return value; }
536  }
537  else if (PropertyType == typeof(bool))
538  {
539  if (TryGetBoolValueWithoutReflection(parentObject, out bool value)) { return value; }
540  }
541  else if (PropertyType == typeof(string))
542  {
543  if (TryGetStringValueWithoutReflection(parentObject, out string value)) { return value; }
544  }
545  return null;
546  }
547 
551  private bool TryGetFloatValueWithoutReflection(object parentObject, out float value)
552  {
553  value = 0.0f;
554  switch (Name)
555  {
556  case nameof(Powered.Voltage):
557  {
558  if (parentObject is Powered powered) { value = powered.Voltage; return true; }
559  }
560  break;
561  case nameof(Powered.RelativeVoltage):
562  {
563  if (parentObject is Powered powered) { value = powered.RelativeVoltage; return true; }
564  }
565  break;
566  case nameof(Powered.CurrPowerConsumption):
567  {
568  if (parentObject is Powered powered) { value = powered.CurrPowerConsumption; return true; }
569  }
570  break;
571  case nameof(PowerContainer.Charge):
572  {
573  if (parentObject is PowerContainer powerContainer) { value = powerContainer.Charge; return true; }
574  }
575  break;
576  case nameof(PowerContainer.ChargePercentage):
577  {
578  if (parentObject is PowerContainer powerContainer) { value = powerContainer.ChargePercentage; return true; }
579  }
580  break;
581  case nameof(PowerContainer.RechargeRatio):
582  {
583  if (parentObject is PowerContainer powerContainer) { value = powerContainer.RechargeRatio; return true; }
584  }
585  break;
586  case nameof(Reactor.AvailableFuel):
587  { if (parentObject is Reactor reactor) { value = reactor.AvailableFuel; return true; } }
588  break;
589  case nameof(Reactor.FissionRate):
590  { if (parentObject is Reactor reactor) { value = reactor.FissionRate; return true; } }
591  break;
592  case nameof(Reactor.Temperature):
593  { if (parentObject is Reactor reactor) { value = reactor.Temperature; return true; } }
594  break;
595  case nameof(Vent.OxygenFlow):
596  if (parentObject is Vent vent) { value = vent.OxygenFlow; return true; }
597  break;
598  case nameof(Pump.CurrFlow):
599  { if (parentObject is Pump pump) { value = pump.CurrFlow; return true; } }
600  if (parentObject is OxygenGenerator oxygenGenerator) { value = oxygenGenerator.CurrFlow; return true; }
601  break;
602  case nameof(Engine.CurrentBrokenVolume):
603  { if (parentObject is Engine engine) { value = engine.CurrentBrokenVolume; return true; } }
604  { if (parentObject is Pump pump) { value = pump.CurrentBrokenVolume; return true; } }
605  break;
606  case nameof(Engine.CurrentVolume):
607  { if (parentObject is Engine engine) { value = engine.CurrentVolume; return true; } }
608  break;
609  case nameof(Character.Oxygen):
610  { if (parentObject is Character character) { value = character.Oxygen; return true; } }
611  { if (parentObject is Hull hull) { value = hull.Oxygen; return true; } }
612  break;
613  case nameof(Character.Health):
614  { if (parentObject is Character character) { value = character.Health; return true; } }
615  break;
616  case nameof(Character.OxygenAvailable):
617  { if (parentObject is Character character) { value = character.OxygenAvailable; return true; } }
618  break;
619  case nameof(Character.PressureProtection):
620  { if (parentObject is Character character) { value = character.PressureProtection; return true; } }
621  break;
622  case nameof(Item.Condition):
623  { if (parentObject is Item item) { value = item.Condition; return true; } }
624  break;
625  case nameof(Character.SpeedMultiplier):
626  { if (parentObject is Character character) { value = character.SpeedMultiplier; return true; } }
627  break;
628  case nameof(Character.PropulsionSpeedMultiplier):
629  { if (parentObject is Character character) { value = character.PropulsionSpeedMultiplier; return true; } }
630  break;
631  case nameof(Character.LowPassMultiplier):
632  { if (parentObject is Character character) { value = character.LowPassMultiplier; return true; } }
633  break;
634  case nameof(Character.HullOxygenPercentage):
635  {
636  if (parentObject is Character character)
637  {
638  value = character.HullOxygenPercentage;
639  return true;
640  }
641  else if (parentObject is Item item)
642  {
643  value = item.HullOxygenPercentage;
644  return true;
645  }
646  }
647  break;
648  case nameof(Door.Stuck):
649  { if (parentObject is Door door) { value = door.Stuck; return true; } }
650  break;
651  }
652  return false;
653  }
654 
658  private bool TryGetBoolValueWithoutReflection(object parentObject, out bool value)
659  {
660  value = false;
661  switch (Name)
662  {
663  case nameof(ItemComponent.IsActive):
664  if (parentObject is ItemComponent ic) { value = ic.IsActive; return true; }
665  break;
666  case nameof(PowerTransfer.Overload):
667  if (parentObject is PowerTransfer powerTransfer) { value = powerTransfer.Overload; return true; }
668  break;
669  case nameof(MotionSensor.MotionDetected):
670  if (parentObject is MotionSensor motionSensor) { value = motionSensor.MotionDetected; return true; }
671  break;
672  case nameof(Character.IsDead):
673  { if (parentObject is Character character) { value = character.IsDead; return true; } }
674  break;
675  case nameof(Character.IsHuman):
676  { if (parentObject is Character character) { value = character.IsHuman; return true; } }
677  break;
678  case nameof(LightComponent.IsOn):
679  { if (parentObject is LightComponent lightComponent) { value = lightComponent.IsOn; return true; } }
680  break;
681  case nameof(Item.PhysicsBodyActive):
682  {
683  if (parentObject is Item item) { value = item.PhysicsBodyActive; return true; }
684  }
685  break;
686  case nameof(DockingPort.Docked):
687  if (parentObject is DockingPort dockingPort) { value = dockingPort.Docked; return true; }
688  break;
689  case nameof(Reactor.TemperatureCritical):
690  if (parentObject is Reactor reactor) { value = reactor.TemperatureCritical; return true; }
691  break;
692  case nameof(TriggerComponent.TriggerActive):
693  if (parentObject is TriggerComponent trigger) { value = trigger.TriggerActive; return true; }
694  break;
695  case nameof(Controller.State):
696  if (parentObject is Controller controller) { value = controller.State; return true; }
697  break;
698  case nameof(Character.InWater):
699  {
700  if (parentObject is Character character)
701  {
702  value = character.InWater;
703  return true;
704  }
705  else if (parentObject is Item item)
706  {
707  value = item.InWater;
708  return true;
709  }
710  }
711  break;
712  case nameof(Rope.Snapped):
713  if (parentObject is Rope rope) { value = rope.Snapped; return true; }
714  break;
715  }
716  return false;
717  }
718 
722  private bool TryGetStringValueWithoutReflection(object parentObject, out string value)
723  {
724  value = null;
725  switch (Name)
726  {
727  case nameof(Item.ContainerIdentifier):
728  {
729  if (parentObject is Item item) { value = item.ContainerIdentifier.Value; return true; }
730  }
731  break;
732  }
733  return false;
734  }
735 
739  private bool TrySetFloatValueWithoutReflection(object parentObject, float value)
740  {
741  switch (Name)
742  {
743  case nameof(Item.Condition):
744  { if (parentObject is Item item) { item.Condition = value; return true; } }
745  break;
746  case nameof(Powered.Voltage):
747  if (parentObject is Powered powered) { powered.Voltage = value; return true; }
748  break;
749  case nameof(PowerContainer.Charge):
750  if (parentObject is PowerContainer powerContainer) { powerContainer.Charge = value; return true; }
751  break;
752  case nameof(Reactor.AvailableFuel):
753  if (parentObject is Reactor reactor) { reactor.AvailableFuel = value; return true; }
754  break;
755  case nameof(Character.Oxygen):
756  { if (parentObject is Character character) { character.Oxygen = value; return true; } }
757  break;
758  case nameof(Character.OxygenAvailable):
759  { if (parentObject is Character character) { character.OxygenAvailable = value; return true; } }
760  break;
761  case nameof(Character.PressureProtection):
762  { if (parentObject is Character character) { character.PressureProtection = value; return true; } }
763  break;
764  case nameof(Character.LowPassMultiplier):
765  { if (parentObject is Character character) { character.LowPassMultiplier = value; return true; } }
766  break;
767  case nameof(Character.SpeedMultiplier):
768  { if (parentObject is Character character) { character.StackSpeedMultiplier(value); return true; } }
769  break;
770  case nameof(Character.HealthMultiplier):
771  { if (parentObject is Character character) { character.StackHealthMultiplier(value); return true; } }
772  break;
773  case nameof(Character.PropulsionSpeedMultiplier):
774  { if (parentObject is Character character) { character.PropulsionSpeedMultiplier = value; return true; } }
775  break;
776  case nameof(Character.ObstructVisionAmount):
777  { if (parentObject is Character character) { character.ObstructVisionAmount = value; return true; } }
778  break;
779  case nameof(Item.Scale):
780  { if (parentObject is Item item) { item.Scale = value; return true; } }
781  break;
782  }
783  return false;
784  }
788  private bool TrySetBoolValueWithoutReflection(object parentObject, bool value)
789  {
790  switch (Name)
791  {
792  case nameof(Character.ObstructVision):
793  { if (parentObject is Character character) { character.ObstructVision = value; return true; } }
794  break;
795  case nameof(Character.HideFace):
796  { if (parentObject is Character character) { character.HideFace = value; return true; } }
797  break;
798  case nameof(Character.UseHullOxygen):
799  { if (parentObject is Character character) { character.UseHullOxygen = value; return true; } }
800  break;
801  case nameof(LightComponent.IsOn):
802  { if (parentObject is LightComponent lightComponent) { lightComponent.IsOn = value; return true; } }
803  break;
804  case nameof(ItemComponent.IsActive):
805  { if (parentObject is ItemComponent ic) { ic.IsActive = value; return true; } }
806  break;
807  }
808  return false;
809  }
810 
811  public static List<SerializableProperty> GetProperties<T>(ISerializableEntity obj)
812  {
813  List<SerializableProperty> editableProperties = new List<SerializableProperty>();
814  foreach (var property in obj.SerializableProperties.Values)
815  {
816  if (property.Attributes.OfType<T>().Any()) editableProperties.Add(property);
817  }
818 
819  return editableProperties;
820  }
821 
822  public static Dictionary<Identifier, SerializableProperty> GetProperties(object obj)
823  {
824  Type objType = obj.GetType();
825  if (cachedProperties.ContainsKey(objType))
826  {
827  return cachedProperties[objType];
828  }
829 
830  var properties = TypeDescriptor.GetProperties(obj.GetType()).Cast<PropertyDescriptor>();
831  Dictionary<Identifier, SerializableProperty> dictionary = new Dictionary<Identifier, SerializableProperty>();
832  foreach (var property in properties)
833  {
834  var serializableProperty = new SerializableProperty(property);
835  dictionary.Add(serializableProperty.Name.ToIdentifier(), serializableProperty);
836  }
837 
838  cachedProperties[objType] = dictionary;
839 
840  return dictionary;
841  }
842 
843  public static Dictionary<Identifier, SerializableProperty> DeserializeProperties(object obj, XElement element = null)
844  {
845  Dictionary<Identifier, SerializableProperty> dictionary = GetProperties(obj);
846 
847  foreach (var property in dictionary.Values)
848  {
849  //set the value of the property to the default value if there is one
850  foreach (var ini in property.Attributes.OfType<Serialize>())
851  {
852  property.TrySetValue(obj, ini.DefaultValue);
853  break;
854  }
855  }
856 
857  if (element != null)
858  {
859  //go through all the attributes in the xml element
860  //and set the value of the matching property if it is initializable
861  foreach (XAttribute attribute in element.Attributes())
862  {
863  if (!dictionary.TryGetValue(attribute.NameAsIdentifier(), out SerializableProperty property)) { continue; }
864  if (!property.Attributes.OfType<Serialize>().Any()) { continue; }
865  property.TrySetValue(obj, attribute.Value);
866  }
867  }
868 
869  return dictionary;
870  }
871 
872  public static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault = false, bool ignoreEditable = false)
873  {
874  var saveProperties = GetProperties<Serialize>(obj);
875  foreach (var property in saveProperties)
876  {
877  object value = property.GetValue(obj);
878  if (value == null) continue;
879 
880  if (!saveIfDefault)
881  {
882  //only save
883  // - if the attribute is saveable and it's different from the default value
884  // - or can be changed in-game or in the editor
885  bool save = false;
886  foreach (var attribute in property.Attributes.OfType<Serialize>())
887  {
888  if ((attribute.IsSaveable == IsPropertySaveable.Yes && !attribute.DefaultValue.Equals(value)) ||
889  (!ignoreEditable && property.Attributes.OfType<Editable>().Any()))
890  {
891  save = true;
892  break;
893  }
894  }
895 
896  if (!save) continue;
897  }
898 
899  string stringValue;
900  if (!supportedTypes.TryGetValue(value.GetType(), out string typeName))
901  {
902  if (property.PropertyType.IsEnum)
903  {
904  stringValue = value.ToString();
905  }
906  else
907  {
908  DebugConsole.ThrowError("Failed to serialize the property \"" + property.Name + "\" of \"" + obj + "\" (type " + property.PropertyType + " not supported)");
909  continue;
910  }
911  }
912  else
913  {
914  switch (typeName)
915  {
916  case "float":
917  //make sure the decimal point isn't converted to a comma or anything else
918  stringValue = ((float)value).ToString("G", CultureInfo.InvariantCulture);
919  break;
920  case "point":
921  stringValue = XMLExtensions.PointToString((Point)value);
922  break;
923  case "vector2":
924  stringValue = XMLExtensions.Vector2ToString((Vector2)value);
925  break;
926  case "vector3":
927  stringValue = XMLExtensions.Vector3ToString((Vector3)value);
928  break;
929  case "vector4":
930  stringValue = XMLExtensions.Vector4ToString((Vector4)value);
931  break;
932  case "color":
933  stringValue = XMLExtensions.ColorToString((Color)value);
934  break;
935  case "rectangle":
936  stringValue = XMLExtensions.RectToString((Rectangle)value);
937  break;
938  case "stringarray":
939  string[] stringArray = (string[])value;
940  stringValue = stringArray != null ? string.Join(';', stringArray) : "";
941  break;
942  case "identifierarray":
943  Identifier[] identifierArray = (Identifier[])value;
944  stringValue = identifierArray != null ? string.Join(';', identifierArray) : "";
945  break;
946  default:
947  stringValue = value.ToString();
948  break;
949  }
950  }
951  element.GetAttribute(property.Name)?.Remove();
952  element.SetAttributeValue(property.Name, stringValue);
953  }
954  }
955 
963  public static void UpgradeGameVersion(ISerializableEntity entity, ContentXElement configElement, Version savedVersion)
964  {
965  foreach (var subElement in configElement.Elements())
966  {
967  if (!subElement.Name.ToString().Equals("upgrade", StringComparison.OrdinalIgnoreCase)) { continue; }
968  var upgradeVersion = new Version(subElement.GetAttributeString("gameversion", "0.0.0.0"));
969  if (subElement.GetAttributeBool("campaignsaveonly", false))
970  {
971  if ((GameMain.GameSession?.LastSaveVersion ?? GameMain.Version) >= upgradeVersion) { continue; }
972  }
973  else
974  {
975  if (savedVersion >= upgradeVersion) { continue; }
976  }
977  foreach (XAttribute attribute in subElement.Attributes())
978  {
979  var attributeName = attribute.NameAsIdentifier();
980  if (attributeName == "gameversion" || attributeName == "campaignsaveonly") { continue; }
981 
982  if (attributeName == "refreshrect")
983  {
984  if (entity is Structure structure)
985  {
986  if (!structure.ResizeHorizontal)
987  {
988  structure.Rect = structure.DefaultRect = new Rectangle(structure.Rect.X, structure.Rect.Y,
989  (int)structure.Prefab.ScaledSize.X,
990  structure.Rect.Height);
991  }
992  if (!structure.ResizeVertical)
993  {
994  structure.Rect = structure.DefaultRect = new Rectangle(structure.Rect.X, structure.Rect.Y,
995  structure.Rect.Width,
996  (int)structure.Prefab.ScaledSize.Y);
997  }
998  }
999  else if (entity is Item item)
1000  {
1001  if (!item.ResizeHorizontal)
1002  {
1003  item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
1004  (int)(item.Prefab.Size.X * item.Prefab.Scale),
1005  item.Rect.Height);
1006  }
1007  if (!item.ResizeVertical)
1008  {
1009  item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
1010  item.Rect.Width,
1011  (int)(item.Prefab.Size.Y * item.Prefab.Scale));
1012  }
1013  }
1014  }
1015 
1016  if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property))
1017  {
1018  FixValue(property, entity, attribute);
1019  if (property.Name == nameof(ItemComponent.Msg) && entity is ItemComponent component)
1020  {
1021  component.ParseMsg();
1022  }
1023  }
1024  else if (entity is Item item1)
1025  {
1026  foreach (ISerializableEntity component in item1.AllPropertyObjects)
1027  {
1028  if (component.SerializableProperties.TryGetValue(attributeName, out SerializableProperty componentProperty))
1029  {
1030  FixValue(componentProperty, component, attribute);
1031  if (componentProperty.Name == nameof(ItemComponent.Msg))
1032  {
1033  ((ItemComponent)component).ParseMsg();
1034  }
1035  }
1036  }
1037  }
1038  }
1039 
1040  static void FixValue(SerializableProperty property, object parentObject, XAttribute attribute)
1041  {
1042  if (attribute.Value.Length > 0 && attribute.Value[0] == '*')
1043  {
1044  float.TryParse(attribute.Value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture, out float multiplier);
1045 
1046  if (property.PropertyType == typeof(int))
1047  {
1048  property.TrySetValue(parentObject, (int)(((int)property.GetValue(parentObject)) * multiplier));
1049  }
1050  else if (property.PropertyType == typeof(float))
1051  {
1052  property.TrySetValue(parentObject, (float)property.GetValue(parentObject) * multiplier);
1053  }
1054  else if (property.PropertyType == typeof(Vector2))
1055  {
1056  property.TrySetValue(parentObject, (Vector2)property.GetValue(parentObject) * multiplier);
1057  }
1058  else if (property.PropertyType == typeof(Point))
1059  {
1060  property.TrySetValue(parentObject, ((Point)property.GetValue(parentObject)).Multiply(multiplier));
1061  }
1062  }
1063  else if (attribute.Value.Length > 0 && attribute.Value[0] == '+')
1064  {
1065  if (property.PropertyType == typeof(int))
1066  {
1067  float.TryParse(attribute.Value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture, out float addition);
1068  property.TrySetValue(parentObject, (int)(((int)property.GetValue(parentObject)) + addition));
1069  }
1070  else if (property.PropertyType == typeof(float))
1071  {
1072  float.TryParse(attribute.Value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture, out float addition);
1073  property.TrySetValue(parentObject, (float)property.GetValue(parentObject) + addition);
1074  }
1075  else if (property.PropertyType == typeof(Vector2))
1076  {
1077  var addition = XMLExtensions.ParseVector2(attribute.Value.Substring(1));
1078  property.TrySetValue(parentObject, (Vector2)property.GetValue(parentObject) + addition);
1079  }
1080  else if (property.PropertyType == typeof(Point))
1081  {
1082  var addition = XMLExtensions.ParsePoint(attribute.Value.Substring(1));
1083  property.TrySetValue(parentObject, ((Point)property.GetValue(parentObject)) + addition);
1084  }
1085  }
1086  else
1087  {
1088  property.TrySetValue(parentObject, attribute.Value);
1089  }
1090  }
1091 
1092  if (entity is Item item2)
1093  {
1094  var componentElement = subElement.FirstElement();
1095  if (componentElement == null) { continue; }
1096  ItemComponent itemComponent = item2.Components.FirstOrDefault(c => c.Name == componentElement.Name.ToString());
1097  if (itemComponent == null) { continue; }
1098  foreach (XAttribute attribute in componentElement.Attributes())
1099  {
1100  var attributeName = attribute.NameAsIdentifier();
1101  if (itemComponent.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property))
1102  {
1103  FixValue(property, itemComponent, attribute);
1104  }
1105  }
1106  foreach (var element in componentElement.Elements())
1107  {
1108  switch (element.Name.ToString().ToLowerInvariant())
1109  {
1110  case "requireditem":
1111  case "requireditems":
1112  itemComponent.RequiredItems.Clear();
1113  itemComponent.DisabledRequiredItems.Clear();
1114 
1115  itemComponent.SetRequiredItems(element, allowEmpty: true);
1116  break;
1117  }
1118  }
1119  if (itemComponent is ItemContainer itemContainer &&
1120  (componentElement.GetChildElement("containable") != null || componentElement.GetChildElement("subcontainer") != null))
1121  {
1122  itemContainer.ReloadContainableRestrictions(componentElement);
1123  }
1124  }
1125  }
1126  }
1127  }
1128 }
static GameSession?? GameSession
Definition: GameMain.cs:88
static readonly Version Version
Definition: GameMain.cs:46
readonly LocalizedString Text
Header(string text="", string localizedTextTag=null)
The base class for components holding the different functionalities of the item
float RelativeVoltage
Essentially Voltage / MinVoltage (= how much of the minimum required voltage has been satisfied),...
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
bool TrySetValue(object parentObject, string value)
readonly AttributeCollection Attributes
void SetValue(object parentObject, object val)
SerializableProperty(PropertyDescriptor property)
static string GetSupportedTypeName(Type type)
float GetFloatValue(object parentObject)
object GetValue(object parentObject)
bool TrySetValue(object parentObject, object value)
static void UpgradeGameVersion(ISerializableEntity entity, ContentXElement configElement, Version savedVersion)
Upgrade the properties of an entity saved with an older version of the game. Properties that should b...
static List< SerializableProperty > GetProperties< T >(ISerializableEntity obj)
bool TrySetValue(object parentObject, float value)
bool TrySetValue(object parentObject, int value)
bool GetBoolValue(object parentObject)
bool TrySetValue(object parentObject, bool value)
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault=false, bool ignoreEditable=false)
bool AlwaysUseInstanceValues
If set to true, the instance values saved in a submarine file will always override the prefab values,...
readonly IsPropertySaveable IsSaveable
readonly Identifier TranslationTextTag
Serialize(object defaultValue, IsPropertySaveable isSaveable, string description="", string translationTextTag="", bool alwaysUseInstanceValues=false)
Makes the property serializable to/from XML
readonly object DefaultValue
Dictionary< Identifier, SerializableProperty > SerializableProperties
@ Character
Characters only