Client LuaCsForBarotrauma
EditorNode.cs
1 #nullable enable
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Reflection;
6 using System.Xml.Linq;
7 using Microsoft.Xna.Framework;
8 using Microsoft.Xna.Framework.Graphics;
9 
10 namespace Barotrauma
11 {
12  internal class EditorNode
13  {
14  public Vector2 Position { get; set; }
15 
16  public Vector2 Size { get; set; }
17 
18  public int ID;
19 
20  private const int HeaderSize = 32;
21 
22  public Rectangle HeaderRectangle => new Rectangle(Position.ToPoint(), new Point((int) Size.X, HeaderSize));
23  public Rectangle Rectangle => new Rectangle(new Point((int) Position.X, (int) Position.Y + HeaderSize), Size.ToPoint());
24  public string Name { get; protected set; }
25 
26  public bool CanAddConnections { get; set; }
27 
28  public readonly List<EventEditorNodeConnection> Connections = new List<EventEditorNodeConnection>();
29 
30  public readonly List<NodeConnectionType> RemovableTypes = new List<NodeConnectionType>();
31 
32  public bool IsHighlighted;
33 
34  public bool IsSelected;
35 
36  protected EditorNode(string name)
37  {
38  Name = name;
39  Position = Vector2.Zero;
40  }
41 
42  public virtual XElement Save()
43  {
44  throw new NotImplementedException();
45  }
46 
47  public XElement SaveConnections()
48  {
49  XElement allConnections = new XElement("Connections", new XAttribute("i", ID));
50  foreach (EventEditorNodeConnection connection in Connections)
51  {
52  XElement connectionElement = new XElement("Connection");
53  connectionElement.Add(new XAttribute("i", connection.ID));
54  connectionElement.Add(new XAttribute("type", connection.Type.Label));
55 
56  if (connection.EndConversation)
57  {
58  connectionElement.Add(new XAttribute("endconversation", connection.EndConversation));
59  }
60 
61  if (!string.IsNullOrWhiteSpace(connection.OptionText))
62  {
63  connectionElement.Add(new XAttribute("optiontext", connection.OptionText));
64  }
65 
66  if (connection.OverrideValue is { } overrideValue && !string.IsNullOrWhiteSpace(connection.OverrideValue?.ToString()))
67  {
68  connectionElement.Add(new XAttribute("overridevalue", overrideValue.ToString() ?? string.Empty));
69  connectionElement.Add(new XAttribute("valuetype", overrideValue.GetType().ToString()));
70  }
71 
72  foreach (var nodeConnection in connection.ConnectedTo)
73  {
74  XElement connectedTo = new XElement("ConnectedTo",
75  new XAttribute("i", nodeConnection.ID),
76  new XAttribute("node", nodeConnection.Parent.ID));
77  connectionElement.Add(connectedTo);
78  }
79 
80  allConnections.Add(connectionElement);
81  }
82 
83  return allConnections;
84  }
85 
86  public void LoadConnections(XElement element)
87  {
88  foreach (var subElement in element.Elements())
89  {
90  int id = subElement.GetAttributeInt("i", -1);
91  string? connectionType = subElement.GetAttributeString("type", null);
92  bool endConversation = subElement.GetAttributeBool("endconversation", false);
93 
94  if (id < 0) { continue; }
95 
96  EventEditorNodeConnection? connection = Connections.Find(c => c.ID == id);
97  if (connection == null)
98  {
99  if (string.Equals(connectionType, NodeConnectionType.Option.Label, StringComparison.InvariantCultureIgnoreCase))
100  {
101  connection = new EventEditorNodeConnection(this, NodeConnectionType.Option) { ID = id, EndConversation = endConversation };
102  Connections.Add(connection);
103  }
104  else
105  {
106  continue;
107  }
108  }
109 
110  string? optionText = subElement.GetAttributeString("optiontext", null);
111  string? overrideValue = subElement.GetAttributeString("overridevalue", null);
112  string? valueType = subElement.GetAttributeString("valuetype", null);
113 
114  if (optionText != null) { connection.OptionText = optionText; }
115 
116  if (overrideValue != null && valueType != null)
117  {
118  Type? type = Type.GetType(valueType);
119  if (type != null)
120  {
121  if (type.IsEnum)
122  {
123  Array enums = Enum.GetValues(type);
124  foreach (object? @enum in enums)
125  {
126  if (string.Equals(@enum?.ToString(), overrideValue, StringComparison.InvariantCultureIgnoreCase))
127  {
128  connection.OverrideValue = @enum;
129  }
130  }
131  }
132  else
133  {
134  connection.OverrideValue = EventEditorScreen.ChangeType(overrideValue, type);
135  }
136  }
137  }
138 
139  foreach (XElement connectedTo in subElement.Elements())
140  {
141  int id2 = connectedTo.GetAttributeInt("i", -1);
142  int node = connectedTo.GetAttributeInt("node", -1);
143  if (id2 < 0 || node < 0) { continue; }
144 
145  EditorNode? otherNode = EventEditorScreen.nodeList.Find(editorNode => editorNode.ID == node);
146  EventEditorNodeConnection? otherConnection = otherNode?.Connections.Find(c => c.ID == id2);
147  if (otherConnection != null)
148  {
149  connection.ConnectedTo.Add(otherConnection);
150  }
151  }
152  }
153  }
154 
155  public static EditorNode? Load(XElement element)
156  {
157  return element.Name.ToString().ToLowerInvariant() switch
158  {
159  "eventnode" => EventNode.LoadEventNode(element),
160  "valuenode" => ValueNode.LoadValueNode(element),
161  "customnode" => CustomNode.LoadCustomNode(element),
162  _ => null
163  };
164  }
165 
166  public virtual XElement? ToXML()
167  {
168  XElement newElement = new XElement(Name);
169  foreach (var connection in Connections)
170  {
171  if (connection.Type == NodeConnectionType.Value)
172  {
173  if (connection.GetValue() is { } connValue)
174  {
175  newElement.Add(new XAttribute(connection.Attribute.ToLowerInvariant(), connValue));
176  }
177  }
178  }
179 
180  newElement.Add(new XAttribute("_npos", XMLExtensions.Vector2ToString(Position)));
181 
182  return newElement;
183  }
184 
185  public void Connect(EditorNode otherNode, NodeConnectionType type)
186  {
187  EventEditorNodeConnection? conn = Connections.Find(connection => connection.Type == type && !connection.ConnectedTo.Any());
188  EventEditorNodeConnection? found = otherNode.Connections.Find(connection => connection.Type == NodeConnectionType.Activate);
189  if (found != null)
190  {
191  conn?.ConnectedTo.Add(found);
192  }
193  }
194 
195  public static void Connect(EventEditorNodeConnection connection, EventEditorNodeConnection ownConnection)
196  {
197  connection.ConnectedTo.Add(ownConnection);
198  }
199 
200  public static void Disconnect(EventEditorNodeConnection conn)
201  {
202  foreach (var connection in EventEditorScreen.nodeList.SelectMany(editorNode => editorNode.Connections.Where(connection => connection.ConnectedTo.Contains(conn))))
203  {
204  connection.ConnectedTo.Remove(conn);
205  }
206  }
207 
208  public void ClearConnections()
209  {
210  foreach (EventEditorNodeConnection conn in Connections)
211  {
212  conn.ClearConnections();
213  }
214  }
215 
216  public virtual Rectangle GetDrawRectangle()
217  {
218  return Rectangle;
219  }
220 
221  public EventEditorNodeConnection? GetConnectionOnMouse(Vector2 mousePos)
222  {
223  return Connections.FirstOrDefault(eventNodeConnection => eventNodeConnection.DrawRectangle.Contains(mousePos));
224  }
225 
226  public void Draw(SpriteBatch spriteBatch)
227  {
228  DrawBack(spriteBatch);
229  DrawFront(spriteBatch);
230  }
231 
232  protected virtual void DrawFront(SpriteBatch spriteBatch) { }
233 
234  protected virtual Color BackgroundColor => new Color(150, 150, 150);
235 
236  private void DrawBack(SpriteBatch spriteBatch)
237  {
238  Color outlineColor = Color.White * 0.8f;
239  Color fontColor = Color.White;
240  Color headerColor = IsHighlighted ? new Color(100, 100, 100) : new Color(120, 120, 120);
241  if (IsSelected)
242  {
243  headerColor = new Color(80, 80, 80);
244  }
245 
246  float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f;
247 
248  Rectangle bodyRect = GetDrawRectangle();
249 
250  GUI.DrawRectangle(spriteBatch, HeaderRectangle, headerColor, isFilled: true, depth: 1.0f);
251  GUI.DrawRectangle(spriteBatch, bodyRect, BackgroundColor, isFilled: true, depth: 1.0f);
252 
253  GUI.DrawRectangle(spriteBatch, HeaderRectangle, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom));
254  GUI.DrawRectangle(spriteBatch, bodyRect, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom));
255 
256  int x = 0, y = 0;
257  foreach (EventEditorNodeConnection connection in Connections)
258  {
259  switch (connection.Type.NodeSide)
260  {
261  case NodeConnectionType.Side.Left:
262  connection.Draw(spriteBatch, Rectangle, y);
263  y++;
264  break;
265  case NodeConnectionType.Side.Right:
266  connection.Draw(spriteBatch, Rectangle, x);
267  x++;
268  break;
269  }
270  }
271 
272  Vector2 headerSize = GUIStyle.SubHeadingFont.MeasureString(Name);
273  GUIStyle.SubHeadingFont.DrawString(spriteBatch, Name, HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2), fontColor);
274  }
275 
276  public void AddConnection(NodeConnectionType connectionType)
277  {
278  Connections.Add(new EventEditorNodeConnection(this, connectionType));
279  }
280 
281  public virtual void AddOption()
282  {
283  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Option));
284  }
285 
286  public void RemoveOption(EventEditorNodeConnection connection)
287  {
288  int index = Connections.IndexOf(connection);
289  foreach (var nodeConnection in Connections.Skip(index))
290  {
291  nodeConnection.ID--;
292  }
293 
294  Connections.Remove(connection);
295  }
296 
297  public EditorNode? GetNext()
298  {
299  var nextNode = Connections.Find(connection => connection.Type == NodeConnectionType.Next);
300  return nextNode?.ConnectedTo.FirstOrDefault()?.Parent;
301  }
302 
303  public EditorNode? GetNext(NodeConnectionType type)
304  {
305  var nextNode = Connections.Find(connection => connection.Type == type);
306  return nextNode?.ConnectedTo.FirstOrDefault()?.Parent;
307  }
308 
309  public static bool IsInstanceOf(Type type1, Type type2)
310  {
311  return type1.IsAssignableFrom(type2) || type1.IsSubclassOf(type2);
312  }
313 
314  public EditorNode? GetParent()
315  {
316  var myNode = Connections.Find(connection => connection.Type == NodeConnectionType.Activate);
317  if (myNode == null) { return null; }
318 
319  foreach (EditorNode editorNode in EventEditorScreen.nodeList)
320  {
321  List<EventEditorNodeConnection> childConnection = editorNode.Connections.Where(connection => connection.Type == NodeConnectionType.Next ||
322  connection.Type == NodeConnectionType.Option ||
323  connection.Type == NodeConnectionType.Failure ||
324  connection.Type == NodeConnectionType.Success ||
325  connection.Type == NodeConnectionType.Add).ToList();
326  if (childConnection.Any(connection => connection != null && connection.ConnectedTo.Contains(myNode)))
327  {
328  return editorNode;
329  }
330  }
331 
332  return null;
333  }
334  }
335 
336  internal class EventNode : EditorNode
337  {
338  private readonly Type type;
339 
340  public EventNode(Type type, string name) : base(name)
341  {
342  this.type = type;
343  Size = new Vector2(256, 256);
344  PropertyInfo[] properties = type.GetProperties().Where(info => info.CustomAttributes.Any(data => data.AttributeType == typeof(Serialize))).ToArray();
345 
346  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Activate));
347  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Next));
348 
349  foreach (PropertyInfo property in properties)
350  {
351  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Value, property.Name, property.PropertyType, property));
352  }
353 
354  if (IsInstanceOf(type, typeof(BinaryOptionAction)))
355  {
356  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Success));
357  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Failure));
358  }
359 
360  if (IsInstanceOf(type, typeof(ConversationAction)))
361  {
362  CanAddConnections = true;
363  RemovableTypes.Add(NodeConnectionType.Option);
364  }
365 
366  if (IsInstanceOf(type, typeof(StatusEffectAction)) || IsInstanceOf(type, typeof(MissionAction)))
367  {
368  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Add));
369  }
370  }
371 
372  public override XElement Save()
373  {
374  XElement newElement = new XElement(nameof(EventNode),
375  new XAttribute("i", ID),
376  new XAttribute("type", type.ToString()),
377  new XAttribute("name", Name),
378  new XAttribute("xpos", Position.X),
379  new XAttribute("ypos", Position.Y));
380 
381  return newElement;
382  }
383 
384  public static EditorNode? LoadEventNode(XElement element)
385  {
386  if (!string.Equals(element.Name.ToString(), nameof(EventNode), StringComparison.InvariantCultureIgnoreCase)) { return null; }
387 
388  Type? t = Type.GetType(element.GetAttributeString("type", string.Empty));
389  if (t == null) { return null; }
390 
391  EventNode newNode = new EventNode(t, element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) };
392  float posX = element.GetAttributeFloat("xpos", 0f);
393  float posY = element.GetAttributeFloat("ypos", 0f);
394  newNode.Position = new Vector2(posX, posY);
395  return newNode;
396  }
397 
398  public override Rectangle GetDrawRectangle()
399  {
400  return ScaleRectFromConnections(Connections, Rectangle);
401  }
402 
403  public static Rectangle ScaleRectFromConnections(List<EventEditorNodeConnection> connections, Rectangle baseRect)
404  {
405  // determine how big this box should get based on how many input/output nodes the sides have
406  int y = connections.Count(connection => connection.Type.NodeSide == NodeConnectionType.Side.Left),
407  x = connections.Count(connection => connection.Type.NodeSide == NodeConnectionType.Side.Right);
408  int maxHeight = Math.Max(x, y);
409 
410  Rectangle bodyRect = baseRect;
411  bodyRect.Height = bodyRect.Height / 8 * maxHeight;
412  return bodyRect;
413  }
414 
415  public Tuple<EditorNode?, string?, bool>[] GetOptions()
416  {
417  IEnumerable<EventEditorNodeConnection> myNode = Connections.Where(connection => connection.Type == NodeConnectionType.Option).ToArray();
418  List<Tuple<EditorNode?, string?, bool>> list = new List<Tuple<EditorNode?, string?, bool>>();
419  if (myNode != null)
420  {
421  foreach (EventEditorNodeConnection connection in myNode)
422  {
423  if (connection.ConnectedTo.Any())
424  {
425  foreach (EventEditorNodeConnection nodeConnection in connection.ConnectedTo)
426  {
427  list.Add(Tuple.Create((EditorNode?) nodeConnection.Parent, connection.OptionText, connection.EndConversation));
428  }
429  }
430  else
431  {
432  list.Add(Tuple.Create<EditorNode?, string?, bool>(null, connection.OptionText, connection.EndConversation));
433  }
434  }
435  }
436 
437  return list.ToArray();
438  }
439  }
440 
441  internal class ValueNode : EditorNode
442  {
443  private object? nodeValue;
444 
445  public object? Value
446  {
447  get => nodeValue;
448  set
449  {
450  nodeValue = value;
451  if (value is string str)
452  {
453  WrappedText = TextManager.Get(str) is { Loaded:true } translated ? translated.Value : str;
454  }
455  else
456  {
457  WrappedText = value?.ToString() ?? string.Empty;
458  }
459  valueTextSize = GUIStyle.SubHeadingFont.MeasureString(WrappedText);
460  }
461  }
462 
463  private Vector2 valueTextSize = Vector2.Zero;
464 
465  public Type Type { get; }
466 
467  public ValueNode(Type type, string name) : base(name)
468  {
469  Type = type;
470  Value = type.IsValueType ? Activator.CreateInstance(type) : null;
471  Size = new Vector2(256, 32);
472  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Out, "Output", Type));
473  }
474 
475  public override XElement Save()
476  {
477  XElement newElement = new XElement(nameof(ValueNode));
478  newElement.Add(new XAttribute("i", ID));
479  if (Value != null)
480  {
481  newElement.Add(new XAttribute("value", Value));
482  }
483 
484  newElement.Add(new XAttribute("type", Type.ToString()));
485  newElement.Add(new XAttribute("name", Name));
486  newElement.Add(new XAttribute("xpos", Position.X));
487  newElement.Add(new XAttribute("ypos", Position.Y));
488  return newElement;
489  }
490 
491  public override XElement? ToXML() { return null; }
492 
493  public static EditorNode? LoadValueNode(XElement element)
494  {
495  if (!string.Equals(element.Name.ToString(), nameof(ValueNode), StringComparison.InvariantCultureIgnoreCase)) { return null; }
496 
497  string? value = element.GetAttributeString("value", null);
498  Type? type = Type.GetType(element.GetAttributeString("type", string.Empty));
499  if (type != null)
500  {
501  ValueNode newNode = new ValueNode(type, element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) };
502  float posX = element.GetAttributeFloat("xpos", 0f);
503  float posY = element.GetAttributeFloat("ypos", 0f);
504  newNode.Position = new Vector2(posX, posY);
505 
506  if (value != null)
507  {
508  if (type.IsEnum)
509  {
510  Array enums = Enum.GetValues(type);
511  foreach (object? @enum in enums)
512  {
513  if (string.Equals(@enum?.ToString(), value, StringComparison.InvariantCultureIgnoreCase))
514  {
515  newNode.Value = @enum;
516  }
517  }
518  }
519  else
520  {
521  newNode.Value = EventEditorScreen.ChangeType(value, type);
522  }
523  }
524 
525  return newNode;
526  }
527 
528  return null;
529  }
530 
531  protected override Color BackgroundColor => new Color(50, 50, 50);
532 
533  private string? wrappedText;
534 
535  private string? WrappedText
536  {
537  get => wrappedText;
538  set
539  {
540  string valueText = value ?? "null";
541  int width = Rectangle.Width;
542  if (width == 0)
543  {
544  wrappedText = valueText;
545  return;
546  }
547 
548  if (width > 16)
549  {
550  width -= 16;
551  }
552 
553  if (GUIStyle.SubHeadingFont.Value != null)
554  {
555  valueText = ToolBox.WrapText(valueText, width, GUIStyle.SubHeadingFont.Value);
556  }
557  wrappedText = valueText;
558  }
559  }
560 
561  public override Rectangle GetDrawRectangle()
562  {
563  Rectangle drawRectangle = Rectangle;
564  Vector2 size = GUIStyle.SubHeadingFont.MeasureString(WrappedText ?? "");
565  drawRectangle.Height = (int) Math.Max(size.Y + 16, drawRectangle.Height);
566  return drawRectangle;
567  }
568 
569  protected override void DrawFront(SpriteBatch spriteBatch)
570  {
571  base.DrawFront(spriteBatch);
572  Vector2 pos = GetDrawRectangle().Location.ToVector2() + (GetDrawRectangle().Size.ToVector2() / 2) - (valueTextSize / 2);
573  Rectangle drawRect = Rectangle;
574  drawRect.Inflate(-1, -1);
575  GUI.DrawString(spriteBatch, pos, WrappedText, EventEditorNodeConnection.GetPropertyColor(Type), font: GUIStyle.SubHeadingFont);
576  }
577  }
578 
579  class SpecialNode : EditorNode
580  {
581  public SpecialNode(string name) : base(name)
582  {
583  Size = new Vector2(256, 256);
584  }
585 
586  public override Rectangle GetDrawRectangle()
587  {
588  return EventNode.ScaleRectFromConnections(Connections, Rectangle);
589  }
590  }
591 
593  {
594  public CustomNode(string name) : base(name)
595  {
596  CanAddConnections = true;
597  RemovableTypes.Add(NodeConnectionType.Value);
598  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Activate));
599  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Next));
600  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Add));
601  }
602 
603  public CustomNode() : this("Custom")
604  {
605  Prompt(s =>
606  {
607  Name = s;
608  return true;
609  });
610  }
611 
612  public override void AddOption()
613  {
614  Prompt(s =>
615  {
616  Connections.Add(new EventEditorNodeConnection(this, NodeConnectionType.Value, s, typeof(string)));
617  return true;
618  });
619  }
620 
621  public override XElement Save()
622  {
623  XElement newElement = new XElement(nameof(CustomNode));
624  newElement.Add(new XAttribute("i", ID));
625  newElement.Add(new XAttribute("name", Name));
626  newElement.Add(new XAttribute("xpos", Position.X));
627  newElement.Add(new XAttribute("ypos", Position.Y));
628  foreach (EventEditorNodeConnection connection in Connections.FindAll(connection => connection.Type == NodeConnectionType.Value))
629  {
630  newElement.Add(new XElement("Value", new XAttribute("name", connection.Attribute)));
631  }
632  return newElement;
633  }
634 
635  public static EditorNode? LoadCustomNode(XElement element)
636  {
637  if (!string.Equals(element.Name.ToString(), nameof(CustomNode), StringComparison.OrdinalIgnoreCase)) { return null; }
638 
639  CustomNode newNode = new CustomNode(element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) };
640  float posX = element.GetAttributeFloat("xpos", 0f);
641  float posY = element.GetAttributeFloat("ypos", 0f);
642  newNode.Position = new Vector2(posX, posY);
643  foreach (XElement valueElement in element.Elements())
644  {
645  newNode.Connections.Add(new EventEditorNodeConnection(newNode, NodeConnectionType.Value, valueElement.GetAttributeString("name", string.Empty), typeof(string)));
646  }
647  return newNode;
648  }
649 
650  private static void Prompt(Func<string, bool> OnAccepted)
651  {
652  var msgBox = new GUIMessageBox(TextManager.Get("Name"), "", new[] { TextManager.Get("Ok"), TextManager.Get("Cancel") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
653  var layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal: true);
654  GUITextBox nameInput = new GUITextBox(new RectTransform(Vector2.One, layout.RectTransform));
655 
656  msgBox.Buttons[1].OnClicked = delegate
657  {
658  msgBox.Close();
659  return true;
660  };
661 
662  msgBox.Buttons[0].OnClicked = delegate
663  {
664  OnAccepted.Invoke(nameInput.Text);
665  msgBox.Close();
666  return true;
667  };
668  }
669  }
670 }
override XElement Save()
Definition: EditorNode.cs:621
CustomNode(string name)
Definition: EditorNode.cs:594
override void AddOption()
Definition: EditorNode.cs:612
static ? EditorNode LoadCustomNode(XElement element)
Definition: EditorNode.cs:635
SpecialNode(string name)
Definition: EditorNode.cs:581
override Rectangle GetDrawRectangle()
Definition: EditorNode.cs:586