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, XMLExtensions.ParseStringArray(value));
218  break;
219  case "identifierarray":
220  PropertyInfo.SetValue(parentObject, XMLExtensions.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  public bool TrySetValue(object parentObject, object value)
233  {
234  if (value == null || parentObject == null || PropertyInfo == null) return false;
235 
236  try
237  {
238  if (!supportedTypes.TryGetValue(PropertyType, out string typeName))
239  {
240  if (PropertyType.IsEnum)
241  {
242  object enumVal;
243  try
244  {
245  enumVal = Enum.Parse(PropertyInfo.PropertyType, value.ToString(), true);
246  }
247  catch (Exception e)
248  {
249  DebugConsole.ThrowError(
250  $"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value} (not a valid {PropertyInfo.PropertyType})", e);
251  return false;
252  }
253  PropertyInfo.SetValue(parentObject, enumVal);
254  return true;
255  }
256  else
257  {
258  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value} (Type \"{PropertyType.Name}\" not supported)");
259 
260  return false;
261  }
262  }
263 
264  try
265  {
266  if (value.GetType() == typeof(string))
267  {
268  switch (typeName)
269  {
270  case "string":
271  PropertyInfo.SetValue(parentObject, value, null);
272  return true;
273  case "point":
274  PropertyInfo.SetValue(parentObject, XMLExtensions.ParsePoint((string)value));
275  return true;
276  case "vector2":
277  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseVector2((string)value));
278  return true;
279  case "vector3":
280  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseVector3((string)value));
281  return true;
282  case "vector4":
283  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseVector4((string)value));
284  return true;
285  case "color":
286  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseColor((string)value));
287  return true;
288  case "rectangle":
289  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseRect((string)value, false));
290  return true;
291  case "identifier":
292  PropertyInfo.SetValue(parentObject, new Identifier((string)value));
293  return true;
294  case "languageidentifier":
295  PropertyInfo.SetValue(parentObject, ((string)value).ToLanguageIdentifier());
296  return true;
297  case "localizedstring":
298  PropertyInfo.SetValue(parentObject, new RawLString((string)value));
299  return true;
300  case "stringarray":
301  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseStringArray((string)value));
302  return true;
303  case "identifierarray":
304  PropertyInfo.SetValue(parentObject, XMLExtensions.ParseIdentifierArray((string)value));
305  return true;
306  default:
307  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value}");
308  DebugConsole.ThrowError($"(Cannot convert a string to a {PropertyType})");
309  return false;
310  }
311  }
312  else if (PropertyType != value.GetType())
313  {
314  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value}");
315  DebugConsole.ThrowError("(Non-matching type, should be " + PropertyType + " instead of " + value.GetType() + ")");
316  return false;
317  }
318 
319  PropertyInfo.SetValue(parentObject, value, null);
320  }
321 
322  catch (Exception e)
323  {
324  DebugConsole.ThrowError($"Failed to set the value of the property \"{Name}\" of \"{parentObject}\" to {value}", e);
325  return false;
326  }
327 
328  return true;
329  }
330  catch (Exception e)
331  {
332  DebugConsole.ThrowError($"Error in SerializableProperty.TrySetValue (Property: {PropertyInfo.Name})", e);
333  return false;
334  }
335  }
336 
337  public bool TrySetValue(object parentObject, float value)
338  {
339  try
340  {
341  if (TrySetFloatValueWithoutReflection(parentObject, value)) { return true; }
342  PropertyInfo.SetValue(parentObject, value, null);
343  }
344  catch (TargetInvocationException e)
345  {
346  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.TrySetValue", e.InnerException);
347  return false;
348  }
349  catch (Exception e)
350  {
351  DebugConsole.ThrowError($"Error in SerializableProperty.TrySetValue (Property: {PropertyInfo.Name})", e);
352  return false;
353  }
354 
355  return true;
356  }
357 
358  public bool TrySetValue(object parentObject, bool value)
359  {
360  try
361  {
362  if (TrySetBoolValueWithoutReflection(parentObject, value)) { return true; }
363  PropertyInfo.SetValue(parentObject, value, null);
364  }
365  catch (TargetInvocationException e)
366  {
367  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.TrySetValue", e.InnerException);
368  return false;
369  }
370  catch (Exception e)
371  {
372  DebugConsole.ThrowError($"Error in SerializableProperty.TrySetValue (Property: {PropertyInfo.Name})", e);
373  return false;
374  }
375  return true;
376  }
377 
378  public bool TrySetValue(object parentObject, int value)
379  {
380  try
381  {
382  if (TrySetFloatValueWithoutReflection(parentObject, value)) { return true; }
383  PropertyInfo.SetValue(parentObject, value, null);
384  }
385  catch (TargetInvocationException e)
386  {
387  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.TrySetValue", e.InnerException);
388  return false;
389  }
390  catch (Exception e)
391  {
392  DebugConsole.ThrowError($"Error in SerializableProperty.TrySetValue (Property: {PropertyInfo.Name})", e);
393  return false;
394  }
395  return true;
396  }
397 
398  public object GetValue(object parentObject)
399  {
400  if (parentObject == null || PropertyInfo == null) { return false; }
401 
402  var value = TryGetValueWithoutReflection(parentObject);
403  if (value != null) { return value; }
404 
405  try
406  {
407  return PropertyInfo.GetValue(parentObject, null);
408  }
409  catch (TargetInvocationException e)
410  {
411  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.GetValue", e.InnerException);
412  return false;
413  }
414  catch (Exception e)
415  {
416  DebugConsole.ThrowError("Error in SerializableProperty.GetValue", e);
417  return false;
418  }
419  }
420 
421  public float GetFloatValue(object parentObject)
422  {
423  if (parentObject == null || PropertyInfo == null) { return 0.0f; }
424 
425  if (TryGetFloatValueWithoutReflection(parentObject, out float value))
426  {
427  return value;
428  }
429 
430  try
431  {
432  if (PropertyType == typeof(int))
433  {
434  return (int)PropertyInfo.GetValue(parentObject, null);
435  }
436  else
437  {
438  return (float)PropertyInfo.GetValue(parentObject, null);
439  }
440  }
441  catch (TargetInvocationException e)
442  {
443  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.GetValue", e.InnerException);
444  return 0.0f;
445  }
446  catch (Exception e)
447  {
448  DebugConsole.ThrowError("Error in SerializableProperty.GetValue", e);
449  return 0.0f;
450  }
451  }
452 
453  public bool GetBoolValue(object parentObject)
454  {
455  if (parentObject == null || PropertyInfo == null) { return false; }
456 
457  if (TryGetBoolValueWithoutReflection(parentObject, out bool value))
458  {
459  return value;
460  }
461 
462  try
463  {
464  return (bool)PropertyInfo.GetValue(parentObject, null);
465  }
466  catch (TargetInvocationException e)
467  {
468  DebugConsole.ThrowError("Exception thrown by the target of SerializableProperty.GetValue", e.InnerException);
469  return false;
470  }
471  catch (Exception e)
472  {
473  DebugConsole.ThrowError("Error in SerializableProperty.GetValue", e);
474  return false;
475  }
476  }
477 
478  public static string GetSupportedTypeName(Type type)
479  {
480  if (type.IsEnum) { return "Enum"; }
481  if (!supportedTypes.TryGetValue(type, out string typeName))
482  {
483  return null;
484  }
485  return typeName;
486  }
487 
488  private readonly ImmutableDictionary<Identifier, Func<object, object>> valueGetters =
489  new Dictionary<Identifier, Func<object, object>>()
490  {
491  {"Voltage".ToIdentifier(), (obj) => obj is Powered p ? p.Voltage : (object) null},
492  {"Charge".ToIdentifier(), (obj) => obj is PowerContainer p ? p.Charge : (object) null},
493  {"Overload".ToIdentifier(), (obj) => obj is PowerTransfer p ? p.Overload : (object) null},
494  {"AvailableFuel".ToIdentifier(), (obj) => obj is Reactor r ? r.AvailableFuel : (object) null},
495  {"FissionRate".ToIdentifier(), (obj) => obj is Reactor r ? r.FissionRate : (object) null},
496  {"OxygenFlow".ToIdentifier(), (obj) => obj is Vent v ? v.OxygenFlow : (object) null},
497  {
498  "CurrFlow".ToIdentifier(),
499  (obj) => obj is Pump p ? (object) p.CurrFlow :
500  obj is OxygenGenerator o ? (object)o.CurrFlow :
501  null
502  },
503  {"CurrentVolume".ToIdentifier(), (obj) => obj is Engine e ? e.CurrentVolume : (object)null},
504  {"MotionDetected".ToIdentifier(), (obj) => obj is MotionSensor m ? m.MotionDetected : (object)null},
505  {"Oxygen".ToIdentifier(), (obj) => obj is Character c ? c.Oxygen : (object)null},
506  {"Health".ToIdentifier(), (obj) => obj is Character c ? c.Health : (object)null},
507  {"OxygenAvailable".ToIdentifier(), (obj) => obj is Character c ? c.OxygenAvailable : (object)null},
508  {"PressureProtection".ToIdentifier(), (obj) => obj is Character c ? c.PressureProtection : (object)null},
509  {"IsDead".ToIdentifier(), (obj) => obj is Character c ? c.IsDead : (object)null},
510  {"IsHuman".ToIdentifier(), (obj) => obj is Character c ? c.IsHuman : (object)null},
511  {"IsOn".ToIdentifier(), (obj) => obj is LightComponent l ? l.IsOn : (object)null},
512  {"Condition".ToIdentifier(), (obj) => obj is Item i ? i.Condition : (object)null},
513  {"ContainerIdentifier".ToIdentifier(), (obj) => obj is Item i ? i.ContainerIdentifier : (object)null},
514  {"PhysicsBodyActive".ToIdentifier(), (obj) => obj is Item i ? i.PhysicsBodyActive : (object)null},
515  }.ToImmutableDictionary();
516 
520  private object TryGetValueWithoutReflection(object parentObject)
521  {
522  if (PropertyType == typeof(float))
523  {
524  if (TryGetFloatValueWithoutReflection(parentObject, out float value)) { return value; }
525  }
526  else if (PropertyType == typeof(bool))
527  {
528  if (TryGetBoolValueWithoutReflection(parentObject, out bool value)) { return value; }
529  }
530  else if (PropertyType == typeof(string))
531  {
532  if (TryGetStringValueWithoutReflection(parentObject, out string value)) { return value; }
533  }
534  return null;
535  }
536 
540  private bool TryGetFloatValueWithoutReflection(object parentObject, out float value)
541  {
542  value = 0.0f;
543  switch (Name)
544  {
545  case nameof(Powered.Voltage):
546  {
547  if (parentObject is Powered powered) { value = powered.Voltage; return true; }
548  }
549  break;
550  case nameof(Powered.RelativeVoltage):
551  {
552  if (parentObject is Powered powered) { value = powered.RelativeVoltage; return true; }
553  }
554  break;
555  case nameof(Powered.CurrPowerConsumption):
556  {
557  if (parentObject is Powered powered) { value = powered.CurrPowerConsumption; return true; }
558  }
559  break;
560  case nameof(PowerContainer.Charge):
561  {
562  if (parentObject is PowerContainer powerContainer) { value = powerContainer.Charge; return true; }
563  }
564  break;
565  case nameof(PowerContainer.ChargePercentage):
566  {
567  if (parentObject is PowerContainer powerContainer) { value = powerContainer.ChargePercentage; return true; }
568  }
569  break;
570  case nameof(PowerContainer.RechargeRatio):
571  {
572  if (parentObject is PowerContainer powerContainer) { value = powerContainer.RechargeRatio; return true; }
573  }
574  break;
575  case nameof(Reactor.AvailableFuel):
576  { if (parentObject is Reactor reactor) { value = reactor.AvailableFuel; return true; } }
577  break;
578  case nameof(Reactor.FissionRate):
579  { if (parentObject is Reactor reactor) { value = reactor.FissionRate; return true; } }
580  break;
581  case nameof(Reactor.Temperature):
582  { if (parentObject is Reactor reactor) { value = reactor.Temperature; return true; } }
583  break;
584  case nameof(Vent.OxygenFlow):
585  if (parentObject is Vent vent) { value = vent.OxygenFlow; return true; }
586  break;
587  case nameof(Pump.CurrFlow):
588  { if (parentObject is Pump pump) { value = pump.CurrFlow; return true; } }
589  if (parentObject is OxygenGenerator oxygenGenerator) { value = oxygenGenerator.CurrFlow; return true; }
590  break;
591  case nameof(Engine.CurrentBrokenVolume):
592  { if (parentObject is Engine engine) { value = engine.CurrentBrokenVolume; return true; } }
593  { if (parentObject is Pump pump) { value = pump.CurrentBrokenVolume; return true; } }
594  break;
595  case nameof(Engine.CurrentVolume):
596  { if (parentObject is Engine engine) { value = engine.CurrentVolume; return true; } }
597  break;
598  case nameof(Character.Oxygen):
599  { if (parentObject is Character character) { value = character.Oxygen; return true; } }
600  { if (parentObject is Hull hull) { value = hull.Oxygen; return true; } }
601  break;
602  case nameof(Character.Health):
603  { if (parentObject is Character character) { value = character.Health; return true; } }
604  break;
605  case nameof(Character.OxygenAvailable):
606  { if (parentObject is Character character) { value = character.OxygenAvailable; return true; } }
607  break;
608  case nameof(Character.PressureProtection):
609  { if (parentObject is Character character) { value = character.PressureProtection; return true; } }
610  break;
611  case nameof(Item.Condition):
612  { if (parentObject is Item item) { value = item.Condition; return true; } }
613  break;
614  case nameof(Character.SpeedMultiplier):
615  { if (parentObject is Character character) { value = character.SpeedMultiplier; return true; } }
616  break;
617  case nameof(Character.PropulsionSpeedMultiplier):
618  { if (parentObject is Character character) { value = character.PropulsionSpeedMultiplier; return true; } }
619  break;
620  case nameof(Character.LowPassMultiplier):
621  { if (parentObject is Character character) { value = character.LowPassMultiplier; return true; } }
622  break;
623  case nameof(Character.HullOxygenPercentage):
624  {
625  if (parentObject is Character character)
626  {
627  value = character.HullOxygenPercentage;
628  return true;
629  }
630  else if (parentObject is Item item)
631  {
632  value = item.HullOxygenPercentage;
633  return true;
634  }
635  }
636  break;
637  case nameof(Door.Stuck):
638  { if (parentObject is Door door) { value = door.Stuck; return true; } }
639  break;
640  }
641  return false;
642  }
643 
647  private bool TryGetBoolValueWithoutReflection(object parentObject, out bool value)
648  {
649  value = false;
650  switch (Name)
651  {
652  case nameof(ItemComponent.IsActive):
653  if (parentObject is ItemComponent ic) { value = ic.IsActive; return true; }
654  break;
655  case nameof(PowerTransfer.Overload):
656  if (parentObject is PowerTransfer powerTransfer) { value = powerTransfer.Overload; return true; }
657  break;
658  case nameof(MotionSensor.MotionDetected):
659  if (parentObject is MotionSensor motionSensor) { value = motionSensor.MotionDetected; return true; }
660  break;
661  case nameof(Character.IsDead):
662  { if (parentObject is Character character) { value = character.IsDead; return true; } }
663  break;
664  case nameof(Character.IsHuman):
665  { if (parentObject is Character character) { value = character.IsHuman; return true; } }
666  break;
667  case nameof(LightComponent.IsOn):
668  { if (parentObject is LightComponent lightComponent) { value = lightComponent.IsOn; return true; } }
669  break;
670  case nameof(Item.PhysicsBodyActive):
671  {
672  if (parentObject is Item item) { value = item.PhysicsBodyActive; return true; }
673  }
674  break;
675  case nameof(DockingPort.Docked):
676  if (parentObject is DockingPort dockingPort) { value = dockingPort.Docked; return true; }
677  break;
678  case nameof(Reactor.TemperatureCritical):
679  if (parentObject is Reactor reactor) { value = reactor.TemperatureCritical; return true; }
680  break;
681  case nameof(TriggerComponent.TriggerActive):
682  if (parentObject is TriggerComponent trigger) { value = trigger.TriggerActive; return true; }
683  break;
684  case nameof(Controller.State):
685  if (parentObject is Controller controller) { value = controller.State; return true; }
686  break;
687  case nameof(Character.InWater):
688  {
689  if (parentObject is Character character)
690  {
691  value = character.InWater;
692  return true;
693  }
694  else if (parentObject is Item item)
695  {
696  value = item.InWater;
697  return true;
698  }
699  }
700  break;
701  case nameof(Rope.Snapped):
702  if (parentObject is Rope rope) { value = rope.Snapped; return true; }
703  break;
704  }
705  return false;
706  }
707 
711  private bool TryGetStringValueWithoutReflection(object parentObject, out string value)
712  {
713  value = null;
714  switch (Name)
715  {
716  case nameof(Item.ContainerIdentifier):
717  {
718  if (parentObject is Item item) { value = item.ContainerIdentifier.Value; return true; }
719  }
720  break;
721  }
722  return false;
723  }
724 
728  private bool TrySetFloatValueWithoutReflection(object parentObject, float value)
729  {
730  switch (Name)
731  {
732  case nameof(Item.Condition):
733  { if (parentObject is Item item) { item.Condition = value; return true; } }
734  break;
735  case nameof(Powered.Voltage):
736  if (parentObject is Powered powered) { powered.Voltage = value; return true; }
737  break;
738  case nameof(PowerContainer.Charge):
739  if (parentObject is PowerContainer powerContainer) { powerContainer.Charge = value; return true; }
740  break;
741  case nameof(Reactor.AvailableFuel):
742  if (parentObject is Reactor reactor) { reactor.AvailableFuel = value; return true; }
743  break;
744  case nameof(Character.Oxygen):
745  { if (parentObject is Character character) { character.Oxygen = value; return true; } }
746  break;
747  case nameof(Character.OxygenAvailable):
748  { if (parentObject is Character character) { character.OxygenAvailable = value; return true; } }
749  break;
750  case nameof(Character.PressureProtection):
751  { if (parentObject is Character character) { character.PressureProtection = value; return true; } }
752  break;
753  case nameof(Character.LowPassMultiplier):
754  { if (parentObject is Character character) { character.LowPassMultiplier = value; return true; } }
755  break;
756  case nameof(Character.SpeedMultiplier):
757  { if (parentObject is Character character) { character.StackSpeedMultiplier(value); return true; } }
758  break;
759  case nameof(Character.HealthMultiplier):
760  { if (parentObject is Character character) { character.StackHealthMultiplier(value); return true; } }
761  break;
762  case nameof(Character.PropulsionSpeedMultiplier):
763  { if (parentObject is Character character) { character.PropulsionSpeedMultiplier = value; return true; } }
764  break;
765  case nameof(Character.ObstructVisionAmount):
766  { if (parentObject is Character character) { character.ObstructVisionAmount = value; return true; } }
767  break;
768  case nameof(Item.Scale):
769  { if (parentObject is Item item) { item.Scale = value; return true; } }
770  break;
771  }
772  return false;
773  }
777  private bool TrySetBoolValueWithoutReflection(object parentObject, bool value)
778  {
779  switch (Name)
780  {
781  case nameof(Character.ObstructVision):
782  { if (parentObject is Character character) { character.ObstructVision = value; return true; } }
783  break;
784  case nameof(Character.HideFace):
785  { if (parentObject is Character character) { character.HideFace = value; return true; } }
786  break;
787  case nameof(Character.UseHullOxygen):
788  { if (parentObject is Character character) { character.UseHullOxygen = value; return true; } }
789  break;
790  case nameof(LightComponent.IsOn):
791  { if (parentObject is LightComponent lightComponent) { lightComponent.IsOn = value; return true; } }
792  break;
793  case nameof(ItemComponent.IsActive):
794  { if (parentObject is ItemComponent ic) { ic.IsActive = value; return true; } }
795  break;
796  }
797  return false;
798  }
799 
800  public static List<SerializableProperty> GetProperties<T>(ISerializableEntity obj)
801  {
802  List<SerializableProperty> editableProperties = new List<SerializableProperty>();
803  foreach (var property in obj.SerializableProperties.Values)
804  {
805  if (property.Attributes.OfType<T>().Any()) editableProperties.Add(property);
806  }
807 
808  return editableProperties;
809  }
810 
811  public static Dictionary<Identifier, SerializableProperty> GetProperties(object obj)
812  {
813  Type objType = obj.GetType();
814  if (cachedProperties.ContainsKey(objType))
815  {
816  return cachedProperties[objType];
817  }
818 
819  var properties = TypeDescriptor.GetProperties(obj.GetType()).Cast<PropertyDescriptor>();
820  Dictionary<Identifier, SerializableProperty> dictionary = new Dictionary<Identifier, SerializableProperty>();
821  foreach (var property in properties)
822  {
823  var serializableProperty = new SerializableProperty(property);
824  dictionary.Add(serializableProperty.Name.ToIdentifier(), serializableProperty);
825  }
826 
827  cachedProperties[objType] = dictionary;
828 
829  return dictionary;
830  }
831 
832  public static Dictionary<Identifier, SerializableProperty> DeserializeProperties(object obj, XElement element = null)
833  {
834  Dictionary<Identifier, SerializableProperty> dictionary = GetProperties(obj);
835 
836  foreach (var property in dictionary.Values)
837  {
838  //set the value of the property to the default value if there is one
839  foreach (var ini in property.Attributes.OfType<Serialize>())
840  {
841  property.TrySetValue(obj, ini.DefaultValue);
842  break;
843  }
844  }
845 
846  if (element != null)
847  {
848  //go through all the attributes in the xml element
849  //and set the value of the matching property if it is initializable
850  foreach (XAttribute attribute in element.Attributes())
851  {
852  if (!dictionary.TryGetValue(attribute.NameAsIdentifier(), out SerializableProperty property)) { continue; }
853  if (!property.Attributes.OfType<Serialize>().Any()) { continue; }
854  property.TrySetValue(obj, attribute.Value);
855  }
856  }
857 
858  return dictionary;
859  }
860 
861  public static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault = false, bool ignoreEditable = false)
862  {
863  var saveProperties = GetProperties<Serialize>(obj);
864  foreach (var property in saveProperties)
865  {
866  object value = property.GetValue(obj);
867  if (value == null) continue;
868 
869  if (!saveIfDefault)
870  {
871  //only save
872  // - if the attribute is saveable and it's different from the default value
873  // - or can be changed in-game or in the editor
874  bool save = false;
875  foreach (var attribute in property.Attributes.OfType<Serialize>())
876  {
877  if ((attribute.IsSaveable == IsPropertySaveable.Yes && !attribute.DefaultValue.Equals(value)) ||
878  (!ignoreEditable && property.Attributes.OfType<Editable>().Any()))
879  {
880  save = true;
881  break;
882  }
883  }
884 
885  if (!save) continue;
886  }
887 
888  string stringValue;
889  if (!supportedTypes.TryGetValue(value.GetType(), out string typeName))
890  {
891  if (property.PropertyType.IsEnum)
892  {
893  stringValue = value.ToString();
894  }
895  else
896  {
897  DebugConsole.ThrowError("Failed to serialize the property \"" + property.Name + "\" of \"" + obj + "\" (type " + property.PropertyType + " not supported)");
898  continue;
899  }
900  }
901  else
902  {
903  switch (typeName)
904  {
905  case "float":
906  //make sure the decimal point isn't converted to a comma or anything else
907  stringValue = ((float)value).ToString("G", CultureInfo.InvariantCulture);
908  break;
909  case "point":
910  stringValue = XMLExtensions.PointToString((Point)value);
911  break;
912  case "vector2":
913  stringValue = XMLExtensions.Vector2ToString((Vector2)value);
914  break;
915  case "vector3":
916  stringValue = XMLExtensions.Vector3ToString((Vector3)value);
917  break;
918  case "vector4":
919  stringValue = XMLExtensions.Vector4ToString((Vector4)value);
920  break;
921  case "color":
922  stringValue = XMLExtensions.ColorToString((Color)value);
923  break;
924  case "rectangle":
925  stringValue = XMLExtensions.RectToString((Rectangle)value);
926  break;
927  case "stringarray":
928  string[] stringArray = (string[])value;
929  stringValue = stringArray != null ? string.Join(';', stringArray) : "";
930  break;
931  case "identifierarray":
932  Identifier[] identifierArray = (Identifier[])value;
933  stringValue = identifierArray != null ? string.Join(';', identifierArray) : "";
934  break;
935  default:
936  stringValue = value.ToString();
937  break;
938  }
939  }
940  element.GetAttribute(property.Name)?.Remove();
941  element.SetAttributeValue(property.Name, stringValue);
942  }
943  }
944 
952  public static void UpgradeGameVersion(ISerializableEntity entity, ContentXElement configElement, Version savedVersion)
953  {
954  foreach (var subElement in configElement.Elements())
955  {
956  if (!subElement.Name.ToString().Equals("upgrade", StringComparison.OrdinalIgnoreCase)) { continue; }
957  var upgradeVersion = new Version(subElement.GetAttributeString("gameversion", "0.0.0.0"));
958  if (subElement.GetAttributeBool("campaignsaveonly", false))
959  {
960  if ((GameMain.GameSession?.LastSaveVersion ?? GameMain.Version) >= upgradeVersion) { continue; }
961  }
962  else
963  {
964  if (savedVersion >= upgradeVersion) { continue; }
965  }
966  foreach (XAttribute attribute in subElement.Attributes())
967  {
968  var attributeName = attribute.NameAsIdentifier();
969  if (attributeName == "gameversion" || attributeName == "campaignsaveonly") { continue; }
970 
971  if (attributeName == "refreshrect")
972  {
973  if (entity is Structure structure)
974  {
975  if (!structure.ResizeHorizontal)
976  {
977  structure.Rect = structure.DefaultRect = new Rectangle(structure.Rect.X, structure.Rect.Y,
978  (int)structure.Prefab.ScaledSize.X,
979  structure.Rect.Height);
980  }
981  if (!structure.ResizeVertical)
982  {
983  structure.Rect = structure.DefaultRect = new Rectangle(structure.Rect.X, structure.Rect.Y,
984  structure.Rect.Width,
985  (int)structure.Prefab.ScaledSize.Y);
986  }
987  }
988  else if (entity is Item item)
989  {
990  if (!item.ResizeHorizontal)
991  {
992  item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
993  (int)(item.Prefab.Size.X * item.Prefab.Scale),
994  item.Rect.Height);
995  }
996  if (!item.ResizeVertical)
997  {
998  item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
999  item.Rect.Width,
1000  (int)(item.Prefab.Size.Y * item.Prefab.Scale));
1001  }
1002  }
1003  }
1004 
1005  if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property))
1006  {
1007  FixValue(property, entity, attribute);
1008  if (property.Name == nameof(ItemComponent.Msg) && entity is ItemComponent component)
1009  {
1010  component.ParseMsg();
1011  }
1012  }
1013  else if (entity is Item item1)
1014  {
1015  foreach (ISerializableEntity component in item1.AllPropertyObjects)
1016  {
1017  if (component.SerializableProperties.TryGetValue(attributeName, out SerializableProperty componentProperty))
1018  {
1019  FixValue(componentProperty, component, attribute);
1020  if (componentProperty.Name == nameof(ItemComponent.Msg))
1021  {
1022  ((ItemComponent)component).ParseMsg();
1023  }
1024  }
1025  }
1026  }
1027  }
1028 
1029  static void FixValue(SerializableProperty property, object parentObject, XAttribute attribute)
1030  {
1031  if (attribute.Value.Length > 0 && attribute.Value[0] == '*')
1032  {
1033  float.TryParse(attribute.Value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture, out float multiplier);
1034 
1035  if (property.PropertyType == typeof(int))
1036  {
1037  property.TrySetValue(parentObject, (int)(((int)property.GetValue(parentObject)) * multiplier));
1038  }
1039  else if (property.PropertyType == typeof(float))
1040  {
1041  property.TrySetValue(parentObject, (float)property.GetValue(parentObject) * multiplier);
1042  }
1043  else if (property.PropertyType == typeof(Vector2))
1044  {
1045  property.TrySetValue(parentObject, (Vector2)property.GetValue(parentObject) * multiplier);
1046  }
1047  else if (property.PropertyType == typeof(Point))
1048  {
1049  property.TrySetValue(parentObject, ((Point)property.GetValue(parentObject)).Multiply(multiplier));
1050  }
1051  }
1052  else if (attribute.Value.Length > 0 && attribute.Value[0] == '+')
1053  {
1054  if (property.PropertyType == typeof(int))
1055  {
1056  float.TryParse(attribute.Value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture, out float addition);
1057  property.TrySetValue(parentObject, (int)(((int)property.GetValue(parentObject)) + addition));
1058  }
1059  else if (property.PropertyType == typeof(float))
1060  {
1061  float.TryParse(attribute.Value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture, out float addition);
1062  property.TrySetValue(parentObject, (float)property.GetValue(parentObject) + addition);
1063  }
1064  else if (property.PropertyType == typeof(Vector2))
1065  {
1066  var addition = XMLExtensions.ParseVector2(attribute.Value.Substring(1));
1067  property.TrySetValue(parentObject, (Vector2)property.GetValue(parentObject) + addition);
1068  }
1069  else if (property.PropertyType == typeof(Point))
1070  {
1071  var addition = XMLExtensions.ParsePoint(attribute.Value.Substring(1));
1072  property.TrySetValue(parentObject, ((Point)property.GetValue(parentObject)) + addition);
1073  }
1074  }
1075  else
1076  {
1077  property.TrySetValue(parentObject, attribute.Value);
1078  }
1079  }
1080 
1081  if (entity is Item item2)
1082  {
1083  var componentElement = subElement.FirstElement();
1084  if (componentElement == null) { continue; }
1085  ItemComponent itemComponent = item2.Components.First(c => c.Name == componentElement.Name.ToString());
1086  if (itemComponent == null) { continue; }
1087  foreach (XAttribute attribute in componentElement.Attributes())
1088  {
1089  var attributeName = attribute.NameAsIdentifier();
1090  if (itemComponent.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property))
1091  {
1092  FixValue(property, itemComponent, attribute);
1093  }
1094  }
1095  foreach (var element in componentElement.Elements())
1096  {
1097  switch (element.Name.ToString().ToLowerInvariant())
1098  {
1099  case "requireditem":
1100  case "requireditems":
1101  itemComponent.RequiredItems.Clear();
1102  itemComponent.DisabledRequiredItems.Clear();
1103 
1104  itemComponent.SetRequiredItems(element, allowEmpty: true);
1105  break;
1106  }
1107  }
1108  if (itemComponent is ItemContainer itemContainer &&
1109  (componentElement.GetChildElement("containable") != null || componentElement.GetChildElement("subcontainer") != null))
1110  {
1111  itemContainer.ReloadContainableRestrictions(componentElement);
1112  }
1113  }
1114  }
1115  }
1116  }
1117 }
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