Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs
1 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Xml.Linq;
6 using System.Globalization;
7 
9 {
11  {
12  private readonly struct EventData : IEventData
13  {
14  public readonly CustomInterfaceElement BtnElement;
15 
16  public EventData(CustomInterfaceElement btnElement)
17  {
18  BtnElement = btnElement;
19  }
20  }
21 
22  class CustomInterfaceElement : ISerializableEntity
23  {
24  public bool ContinuousSignal;
25  public bool State;
26  public string ConnectionName;
27  public Connection Connection;
28 
29  [Serialize("", IsPropertySaveable.No, translationTextTag: "Label.", description: "The text displayed on this button/tickbox."), Editable]
30  public string Label { get; set; }
31 
32  [Serialize("1", IsPropertySaveable.No, description: "The signal sent out when this button is pressed or this tickbox checked."), Editable]
33  public string Signal { get; set; }
34 
35  public Identifier PropertyName { get; }
36  public bool TargetOnlyParentProperty { get; }
37 
38  public string NumberInputMin { get; }
39  public string NumberInputMax { get; }
40  public string NumberInputStep { get; }
41  public int NumberInputDecimalPlaces { get; }
42 
43  public int MaxTextLength { get; }
44 
45  public const string DefaultNumberInputMin = "0", DefaultNumberInputMax = "99", DefaultNumberInputStep = "1";
46  public const int DefaultNumberInputDecimalPlaces = 0;
47  public bool IsNumberInput { get; }
48  public NumberType? NumberType { get; }
49  public bool HasPropertyName { get; }
50  public bool ShouldSetProperty { get; set; }
51 
52  public string Name => "CustomInterfaceElement";
53 
54  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; set; }
55 
56  public List<StatusEffect> StatusEffects = new List<StatusEffect>();
57 
62  public CustomInterfaceElement(Item item, ContentXElement element, CustomInterface parent)
63  {
64  Label = element.GetAttributeString("text", "");
65  ConnectionName = element.GetAttributeString("connection", "");
66  PropertyName = element.GetAttributeIdentifier("propertyname", "");
67  TargetOnlyParentProperty = element.GetAttributeBool("targetonlyparentproperty", false);
68  NumberInputMin = element.GetAttributeString("min", DefaultNumberInputMin);
69  NumberInputMax = element.GetAttributeString("max", DefaultNumberInputMax);
70  NumberInputStep = element.GetAttributeString("step", DefaultNumberInputStep);
71  NumberInputDecimalPlaces = element.GetAttributeInt("decimalplaces", DefaultNumberInputDecimalPlaces);
72  MaxTextLength = element.GetAttributeInt("maxtextlength", int.MaxValue);
73 
74  HasPropertyName = !PropertyName.IsEmpty;
75  if (HasPropertyName)
76  {
77  string elementName = element.Name.ToString().ToLowerInvariant();
78  IsNumberInput = elementName == "numberinput" || elementName == "integerinput"; // backwards compatibility
79  if (IsNumberInput)
80  {
81  string numberType = element.GetAttributeString("numbertype", string.Empty);
82  switch (numberType)
83  {
84  case "f":
85  case "float":
86  NumberType = Barotrauma.NumberType.Float;
87  break;
88  case "int":
89  case "integer":
90  default: // backwards compatibility
91  NumberType = Barotrauma.NumberType.Int;
92  break;
93  }
94  }
95  }
96 
97  if (element.GetAttribute("signal") is XAttribute attribute)
98  {
99  Signal = attribute.Value;
100  ShouldSetProperty = HasPropertyName;
101  }
102  else if (HasPropertyName && parent != null)
103  {
104  if (TargetOnlyParentProperty)
105  {
106  if (parent.SerializableProperties.ContainsKey(PropertyName))
107  {
108  Signal = parent.SerializableProperties[PropertyName].GetValue(parent) as string;
109  }
110  }
111  else
112  {
113  foreach (ISerializableEntity e in parent.item.AllPropertyObjects)
114  {
115  if (!e.SerializableProperties.ContainsKey(PropertyName)) { continue; }
116  Signal = e.SerializableProperties[PropertyName].GetValue(e) as string;
117  break;
118  }
119  }
120  }
121  else
122  {
123  Signal = "1";
124  }
125 
126  foreach (var subElement in element.Elements())
127  {
128  if (subElement.Name.ToString().Equals("statuseffect", System.StringComparison.OrdinalIgnoreCase))
129  {
130  StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName: "custom interface element (label " + Label + ")"));
131  }
132  }
133  }
134  }
135 
136  private string[] labels;
137  [Serialize("", IsPropertySaveable.Yes, description: "The texts displayed on the buttons/tickboxes, separated by commas.", alwaysUseInstanceValues: true)]
138  public string Labels
139  {
140  get { return string.Join(",", labels); }
141  set
142  {
143  if (value == null) { return; }
144  if (customInterfaceElementList.Count > 0)
145  {
146  string[] splitValues = value == "" ? Array.Empty<string>() : value.Split(',');
147  UpdateLabels(splitValues);
148  }
149  }
150  }
151 
152  private string[] signals;
153  [Serialize("", IsPropertySaveable.Yes, description: "The signals sent when the buttons are pressed or the tickboxes checked, separated by commas.", alwaysUseInstanceValues: true)]
154  public string Signals
155  {
156  //use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
157  //kind of hacky, we should probably add support for (string) arrays to SerializableEntityEditor so this wouldn't be needed
158  get { return signals == null ? string.Empty : string.Join(";", signals); }
159  set
160  {
161  if (value == null) { return; }
162  if (customInterfaceElementList.Count > 0)
163  {
164  string[] splitValues = value == "" ? Array.Empty<string>() : value.Split(';');
165  UpdateSignals(splitValues);
166  }
167  }
168  }
169 
170  private bool[] elementStates;
171  [Serialize("", IsPropertySaveable.Yes, description: "", alwaysUseInstanceValues: true)]
172  public string ElementStates
173  {
174  get { return elementStates == null ? string.Empty : string.Join(",", elementStates); }
175  set
176  {
177  if (value == null) { return; }
178  if (customInterfaceElementList.Count > 0)
179  {
180  string[] splitValues = value == "" ? Array.Empty<string>() : value.Split(',');
181  for (int i = 0; i < customInterfaceElementList.Count && i < splitValues.Length; i++)
182  {
183  if (!bool.TryParse(splitValues[i], out bool val)) { continue; }
184  customInterfaceElementList[i].State = val;
185 #if CLIENT
186  if (uiElements != null && i < uiElements.Count && uiElements[i] is GUITickBox tickBox)
187  {
188  tickBox.Selected = val;
189  }
190 #endif
191  }
192  }
193  }
194  }
195 
196  private readonly List<CustomInterfaceElement> customInterfaceElementList = new List<CustomInterfaceElement>();
197 
199  : base(item, element)
200  {
201  foreach (var subElement in element.Elements())
202  {
203  switch (subElement.Name.ToString().ToLowerInvariant())
204  {
205  case "button":
206  case "textbox":
207  case "integerinput": // backwards compatibility
208  case "numberinput":
209  var button = new CustomInterfaceElement(item, subElement, this)
210  {
211  ContinuousSignal = false
212  };
213  if (string.IsNullOrEmpty(button.Label))
214  {
215  button.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
216  }
217  customInterfaceElementList.Add(button);
218  break;
219  case "tickbox":
220  var tickBox = new CustomInterfaceElement(item, subElement, this)
221  {
222  ContinuousSignal = true
223  };
224  if (string.IsNullOrEmpty(tickBox.Label))
225  {
226  tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal);
227  }
228  customInterfaceElementList.Add(tickBox);
229  break;
230  }
231  }
232  IsActive = true;
233  InitProjSpecific();
234  //load these here to ensure the UI elements (created in InitProjSpecific) are up-to-date
235  Labels = element.GetAttributeString("labels", "");
236  Signals = element.GetAttributeString("signals", "");
237  ElementStates = element.GetAttributeString("elementstates", "");
238  }
239 
240  private void UpdateLabels(string[] newLabels)
241  {
242  labels = new string[customInterfaceElementList.Count];
243  for (int i = 0; i < labels.Length; i++)
244  {
245  labels[i] = i < newLabels.Length ? newLabels[i] : customInterfaceElementList[i].Label;
246  customInterfaceElementList[i].Label = labels[i];
247  }
248  UpdateLabelsProjSpecific();
249  }
250 
251  private void UpdateSignals(string[] newSignals)
252  {
253  signals = new string[customInterfaceElementList.Count];
254  for (int i = 0; i < customInterfaceElementList.Count; i++)
255  {
256  var element = customInterfaceElementList[i];
257  if (i < newSignals.Length)
258  {
259  var newSignal = newSignals[i];
260  signals[i] = newSignal;
261  element.ShouldSetProperty = element.Signal != newSignal;
262  element.Signal = newSignal;
263  }
264  else
265  {
266  signals[i] = element.Signal;
267  }
268 
269  if (element.HasPropertyName && element.ShouldSetProperty)
270  {
271  if (element.TargetOnlyParentProperty)
272  {
273  if (SerializableProperties.ContainsKey(element.PropertyName))
274  {
275  SerializableProperties[element.PropertyName].TrySetValue(this, element.Signal);
276  }
277  }
278  else
279  {
280  foreach (var po in item.AllPropertyObjects)
281  {
282  if (!po.SerializableProperties.ContainsKey(element.PropertyName)) { continue; }
283  po.SerializableProperties[element.PropertyName].TrySetValue(po, element.Signal);
284  }
285  }
286  customInterfaceElementList[i].ShouldSetProperty = false;
287  }
288  }
289  UpdateSignalsProjSpecific();
290  }
291 
292  public override void OnItemLoaded()
293  {
294  foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
295  {
296  ciElement.Connection = item.Connections?.FirstOrDefault(c => c.Name == ciElement.ConnectionName);
297  }
298 #if SERVER
299  //make sure the clients know about the states of the checkboxes and text fields
300  if (customInterfaceElementList.Any())
301  {
303  {
304  CoroutineManager.Invoke(() =>
305  {
306  if (!item.Removed) { item.CreateServerEvent(this); }
307  }, delay: 0.1f);
308  }
309  }
310 #endif
311  }
312 
313  partial void UpdateLabelsProjSpecific();
314 
315  partial void UpdateSignalsProjSpecific();
316 
317  partial void InitProjSpecific();
318 
319  private void ButtonClicked(CustomInterfaceElement btnElement)
320  {
321  if (btnElement == null) return;
322  if (btnElement.Connection != null)
323  {
324  item.SendSignal(new Signal(btnElement.Signal, 0, null, item), btnElement.Connection);
325  }
326  foreach (StatusEffect effect in btnElement.StatusEffects)
327  {
328  item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, character: item.ParentInventory?.Owner as Character);
329  }
330  }
331 
332  private void TickBoxToggled(CustomInterfaceElement tickBoxElement, bool state)
333  {
334  if (tickBoxElement == null) { return; }
335  tickBoxElement.State = state;
336  }
337 
338  private void TextChanged(CustomInterfaceElement textElement, string text)
339  {
340  if (textElement == null) { return; }
341  textElement.Signal = text;
342  if (!textElement.TargetOnlyParentProperty)
343  {
344  foreach (ISerializableEntity e in item.AllPropertyObjects)
345  {
346  if (!e.SerializableProperties.ContainsKey(textElement.PropertyName)) { continue; }
347  e.SerializableProperties[textElement.PropertyName].TrySetValue(e, text);
348  }
349  }
350  else if (SerializableProperties.ContainsKey(textElement.PropertyName))
351  {
352  SerializableProperties[textElement.PropertyName].TrySetValue(this, text);
353  }
354  }
355 
356  private void ValueChanged(CustomInterfaceElement numberInputElement, int value)
357  {
358  if (numberInputElement == null) { return; }
359  numberInputElement.Signal = value.ToString();
360  if (!numberInputElement.TargetOnlyParentProperty)
361  {
362  foreach (ISerializableEntity e in item.AllPropertyObjects)
363  {
364  if (!e.SerializableProperties.ContainsKey(numberInputElement.PropertyName)) { continue; }
365  e.SerializableProperties[numberInputElement.PropertyName].TrySetValue(e, value);
366  }
367  }
368  else if (SerializableProperties.ContainsKey(numberInputElement.PropertyName))
369  {
370  SerializableProperties[numberInputElement.PropertyName].TrySetValue(this, value);
371  }
372  }
373 
374  private void ValueChanged(CustomInterfaceElement numberInputElement, float value)
375  {
376  if (numberInputElement == null) { return; }
377  numberInputElement.Signal = value.ToString();
378  if (!numberInputElement.TargetOnlyParentProperty)
379  {
380  foreach (ISerializableEntity e in item.AllPropertyObjects)
381  {
382  if (!e.SerializableProperties.ContainsKey(numberInputElement.PropertyName)) { continue; }
383  e.SerializableProperties[numberInputElement.PropertyName].TrySetValue(e, value);
384  }
385  }
386  else if (SerializableProperties.ContainsKey(numberInputElement.PropertyName))
387  {
388  SerializableProperties[numberInputElement.PropertyName].TrySetValue(this, value);
389  }
390  }
391 
392  public override void Update(float deltaTime, Camera cam)
393  {
394  foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
395  {
396  if (!ciElement.ContinuousSignal) { continue; }
397  //TODO: allow changing output when a tickbox is not selected
398  if (!string.IsNullOrEmpty(ciElement.Signal) && ciElement.Connection != null)
399  {
400  item.SendSignal(new Signal(ciElement.State ? ciElement.Signal : "0", source: item), ciElement.Connection);
401  }
402 
403  foreach (StatusEffect effect in ciElement.StatusEffects)
404  {
405  item.ApplyStatusEffect(effect, ciElement.State ? ActionType.OnUse : ActionType.OnSecondaryUse, 1.0f, null, null, null, true, false);
406  }
407  }
408  }
409 
410  public override XElement Save(XElement parentElement)
411  {
412  labels = customInterfaceElementList.Select(ci => ci.Label).ToArray();
413  signals = customInterfaceElementList.Select(ci => ci.Signal).ToArray();
414  elementStates = customInterfaceElementList.Select(ci => ci.State).ToArray();
415  return base.Save(parentElement);
416  }
417 
418  private static bool TryParseFloatInvariantCulture(string s, out float f)
419  {
420  return float.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out f);
421  }
422  }
423 }
string? GetAttributeString(string key, string? def)
IEnumerable< ContentXElement > Elements()
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
XAttribute? GetAttribute(string name)
Identifier GetAttributeIdentifier(string key, string def)
void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character=null, Limb limb=null, Entity useTarget=null, bool isNetworkEvent=false, bool checkCondition=true, Vector2? worldPosition=null)
IReadOnlyList< ISerializableEntity > AllPropertyObjects
bool FullyInitialized
Has everything in the item been loaded/instantiated/initialized (basically, can be used to check if t...
void SendSignal(string signal, string connectionName)
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
The base class for components holding the different functionalities of the item
Defines a point in the event that GoTo actions can jump to.
Definition: Label.cs:7
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
static StatusEffect Load(ContentXElement element, string parentDebugName)
Dictionary< Identifier, SerializableProperty > SerializableProperties
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients
NumberType
Definition: Enums.cs:715
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19