Client LuaCsForBarotrauma
EventEditorNodeConnection.cs
1 #nullable enable
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Reflection;
6 using Microsoft.Xna.Framework;
7 using Microsoft.Xna.Framework.Graphics;
8 
9 namespace Barotrauma
10 {
11  internal class NodeConnectionType
12  {
13  public static readonly NodeConnectionType Activate = new NodeConnectionType(Side.Left, "Activate");
14  public static readonly NodeConnectionType Value = new NodeConnectionType(Side.Left, "Value");
15  public static readonly NodeConnectionType Option = new NodeConnectionType(Side.Right, "Option", new[] { Activate });
16  public static readonly NodeConnectionType Add = new NodeConnectionType(Side.Right, "Add", new[] { Activate });
17  public static readonly NodeConnectionType Success = new NodeConnectionType(Side.Right, "Success", new[] { Activate });
18  public static readonly NodeConnectionType Failure = new NodeConnectionType(Side.Right, "Failure", new[] { Activate });
19  public static readonly NodeConnectionType Next = new NodeConnectionType(Side.Right, "Next", new[] { Activate });
20  public static readonly NodeConnectionType Out = new NodeConnectionType(Side.Right, "Out", new[] { Value });
21 
22  public enum Side
23  {
24  Left,
25  Right
26  }
27 
28  public Side NodeSide { get; }
29 
30  public string Label { get; }
31 
32  public NodeConnectionType[]? AllowedConnections { get; }
33 
34  private NodeConnectionType(Side side, string name, NodeConnectionType[]? allowedConnections = null)
35  {
36  NodeSide = side;
37  Label = name;
38  AllowedConnections = allowedConnections;
39  }
40  }
41 
42  internal class EventEditorNodeConnection
43  {
44  public string Attribute { get; }
45 
46  public int ID { get; set; }
47 
48  public bool EndConversation { get; set; }
49 
50  private string? optionText;
51 
52  public string? OptionText
53  {
54  get => optionText;
55  set
56  {
57  optionText = value;
58  if (value is string)
59  {
60  actualValue = WrappedValue = TextManager.Get(value).Fallback(value).Value;
61  }
62  else
63  {
64  actualValue = WrappedValue = value;
65  }
66  }
67  }
68 
69  public NodeConnectionType Type { get; }
70 
71  public Type? ValueType { get; }
72 
73  private object? overrideValue;
74  private object? actualValue;
75 
76  public object? OverrideValue
77  {
78  get => overrideValue;
79  set
80  {
81  overrideValue = value;
82  if (value is string str)
83  {
84  actualValue = WrappedValue = TextManager.Get(str).Fallback(str).Value;
85  }
86  else
87  {
88  actualValue = WrappedValue = value?.ToString() ?? string.Empty;
89  }
90  }
91  }
92 
93  private string? wrappedValue;
94 
95  private string? WrappedValue
96  {
97  get => wrappedValue;
98  set
99  {
100  string valueText = value ?? string.Empty;
101  if (string.IsNullOrWhiteSpace(valueText))
102  {
103  wrappedValue = null;
104  return;
105  }
106  Vector2 textSize = GUIStyle.SmallFont.MeasureString(valueText);
107  bool wasWrapped = false;
108  while (textSize.X > 96)
109  {
110  wasWrapped = true;
111  valueText = $"{valueText}...".Substring(0, valueText.Length - 4);
112  textSize = GUIStyle.SmallFont.MeasureString($"{valueText}...");
113  }
114 
115  if (wasWrapped)
116  {
117  valueText = valueText.TrimEnd(' ') + "...";
118  }
119 
120 
121  wrappedValue = valueText;
122  }
123  }
124 
125  public PropertyInfo? PropertyInfo { get; }
126 
127  public Rectangle DrawRectangle = Rectangle.Empty;
128 
129  public readonly EditorNode Parent;
130 
131  public readonly List<EventEditorNodeConnection> ConnectedTo = new List<EventEditorNodeConnection>();
132 
133  private readonly Color bgColor = Color.DarkGray * 0.8f;
134 
135  private readonly Color outlineColor = Color.White * 0.8f;
136 
137  public object? GetValue()
138  {
139  if (OverrideValue != null)
140  {
141  return OverrideValue;
142  }
143 
144  foreach (EditorNode editorNode in EventEditorScreen.nodeList)
145  {
146  var outNode = editorNode.Connections.Find(connection => connection.Type == NodeConnectionType.Out);
147  if (outNode != null && outNode.ConnectedTo.Contains(this))
148  {
149  return (outNode.Parent as ValueNode)?.Value;
150  }
151  }
152 
153  return null;
154  }
155 
156  public void ClearConnections()
157  {
158  foreach (var connection in EventEditorScreen.nodeList.SelectMany(editorNode => editorNode.Connections.Where(connection => connection.ConnectedTo.Contains(this))))
159  {
160  connection.ConnectedTo.Remove(this);
161  }
162 
163  ConnectedTo.Clear();
164  }
165 
166  public EventEditorNodeConnection(EditorNode parent, NodeConnectionType type, string attribute = "", Type? valueType = null, PropertyInfo? propertyInfo = null)
167  {
168  Type = type;
169  ValueType = valueType;
170  Attribute = attribute;
171  PropertyInfo = propertyInfo;
172  Parent = parent;
173  ID = parent.Connections.Count;
174  }
175 
176  private Point GetRenderPos(Rectangle parentRectangle, int yOffset)
177  {
178  int x = Type.NodeSide == NodeConnectionType.Side.Left ? parentRectangle.Left - 15 : parentRectangle.Right - 1;
179  return new Point(x, parentRectangle.Y + 8 + parentRectangle.Height / 8 * yOffset);
180  }
181 
182  public void Draw(SpriteBatch spriteBatch, Rectangle parentRectangle, int yOffset)
183  {
184  float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f;
185  Point pos = GetRenderPos(parentRectangle, yOffset);
186  DrawRectangle = new Rectangle(pos, new Point(16, 16));
187  GUI.DrawRectangle(spriteBatch, DrawRectangle, bgColor, isFilled: true);
188  GUI.DrawRectangle(spriteBatch, DrawRectangle, EndConversation ? GUIStyle.Red : outlineColor, isFilled: false, thickness: (int)Math.Max(1, 1.25f / camZoom));
189 
190  string label = string.IsNullOrWhiteSpace(Attribute) ? Type.Label : Attribute;
191  float xPos = parentRectangle.Center.X > pos.X ? 24 : -8 - GUIStyle.SmallFont.MeasureString(label).X;
192 
193  if (Type != NodeConnectionType.Out)
194  {
195  Vector2 size = GUIStyle.SmallFont.MeasureString(label);
196  Vector2 positon = new Vector2(pos.X + xPos, pos.Y);
197  Rectangle bgRect = new Rectangle(positon.ToPoint(), size.ToPoint());
198  bgRect.Inflate(4, 4);
199 
200  GUI.DrawRectangle(spriteBatch, bgRect, Color.Black * 0.6f, isFilled: true);
201  GUI.DrawString(spriteBatch, positon, label, GetPropertyColor(ValueType), font: GUIStyle.SmallFont);
202 
203  Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
204  mousePos.Y = -mousePos.Y;
205  if (bgRect.Contains(mousePos))
206  {
207  CustomAttributeData? attribute = PropertyInfo?.CustomAttributes.FirstOrDefault();
208  if (attribute?.AttributeType == typeof(Serialize))
209  {
210  if (attribute.ConstructorArguments.Count > 2)
211  {
212  string? description = attribute.ConstructorArguments[2].Value as string;
213  if (!string.IsNullOrWhiteSpace(description))
214  {
215  EventEditorScreen.DrawnTooltip = description;
216  }
217  }
218  }
219  }
220  }
221 
222  if (OverrideValue != null)
223  {
224  DrawLabel(spriteBatch, new Vector2(DrawRectangle.Center.X - 96, pos.Y + (DrawRectangle.Height / 2) - (20 / 2)), WrappedValue ?? "null", actualValue?.ToString() ?? string.Empty);
225  }
226 
227  if (OptionText != null)
228  {
229  DrawLabel(spriteBatch, new Vector2(DrawRectangle.Center.X, pos.Y + (DrawRectangle.Height / 2) - (20 / 2)), WrappedValue ?? "null", actualValue?.ToString() ?? string.Empty);
230  }
231 
232  if (Parent.IsHighlighted)
233  {
234  DrawConnections(spriteBatch, yOffset, Math.Max(8.0f, 8.0f / camZoom), Color.Red);
235  }
236 
237  DrawConnections(spriteBatch, yOffset, width: Math.Max(2.0f, 2.0f / camZoom));
238 
239  if (EventEditorScreen.DraggedConnection == this)
240  {
241  DrawSquareLine(spriteBatch, EventEditorScreen.DraggingPosition, yOffset, width: Math.Max(2.0f, 2.0f / camZoom));
242  }
243  }
244 
245  private void DrawConnections(SpriteBatch spriteBatch, int yOffset, float width = 2, Color? overrideColor = null)
246  {
247  foreach (EventEditorNodeConnection? eventNodeConnection in ConnectedTo)
248  {
249  if (eventNodeConnection != null)
250  {
251  DrawSquareLine(spriteBatch, new Vector2(eventNodeConnection.DrawRectangle.Left + 1, eventNodeConnection.DrawRectangle.Center.Y), yOffset, width, overrideColor);
252  }
253  }
254  }
255 
256  private void DrawLabel(SpriteBatch spriteBatch, Vector2 pos, string text, string fullText)
257  {
258  float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f;
259  Rectangle valueRect = new Rectangle((int)pos.X, (int)pos.Y, 96, 20);
260  Vector2 textSize = GUIStyle.SmallFont.MeasureString(text);
261  Vector2 position = valueRect.Location.ToVector2() + valueRect.Size.ToVector2() / 2 - textSize / 2;
262  Rectangle drawRect = valueRect;
263  drawRect.Inflate(4, 4);
264  GUI.DrawRectangle(spriteBatch, drawRect, new Color(50, 50, 50), isFilled: true);
265  GUI.DrawRectangle(spriteBatch, drawRect, EndConversation ? GUIStyle.Red : outlineColor, isFilled: false, thickness: (int)Math.Max(1, 1.25f / camZoom));
266  GUI.DrawString(spriteBatch, position, text, GetPropertyColor(ValueType), font: GUIStyle.SmallFont);
267  DrawRectangle = Rectangle.Union(DrawRectangle, drawRect);
268 
269  if (!string.IsNullOrWhiteSpace(fullText))
270  {
271  Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
272  mousePos.Y = -mousePos.Y;
273  if (DrawRectangle.Contains(mousePos))
274  {
275  EventEditorScreen.DrawnTooltip = fullText;
276  }
277  }
278  }
279 
280  private void DrawSquareLine(SpriteBatch spriteBatch, Vector2 position, int yOffset, float width = 2, Color? overrideColor = null)
281  {
282  float knobLength = 24 * (yOffset + 1);
283  Vector2 start = new Vector2(DrawRectangle.Right, DrawRectangle.Center.Y);
284  var (points, _) = ToolBox.GetSquareLineBetweenPoints(start, position, knobLength);
285 
286  Color drawColor = Parent is ValueNode ? GetPropertyColor(ValueType) : GUIStyle.Red;
287 
288  if (overrideColor != null)
289  {
290  drawColor = overrideColor.Value;
291  }
292 
293  GUI.DrawLine(spriteBatch, points[0], points[1], drawColor, width: (int)width);
294  GUI.DrawLine(spriteBatch, points[1], points[2], drawColor, width: (int)width);
295  GUI.DrawLine(spriteBatch, points[2], points[3], drawColor, width: (int)width);
296  GUI.DrawLine(spriteBatch, points[3], points[4], drawColor, width: (int)width);
297  GUI.DrawLine(spriteBatch, points[4], points[5], drawColor, width: (int)width);
298  }
299 
300  private static readonly Color defaultColor = new Color(139, 233, 253);
301  private static readonly Color yellowColor = new Color(241, 250, 140);
302  private static readonly Color pinkColor = new Color(255, 121, 198);
303  private static readonly Color purpleColor = new Color(189, 147, 249);
304 
305  public static Color GetPropertyColor(Type? valueType)
306  {
307  Color color = defaultColor;
308  if (valueType == typeof(bool))
309  color = pinkColor;
310  else if (valueType == typeof(string))
311  color = yellowColor;
312  else if (valueType == typeof(int) ||
313  valueType == typeof(float) ||
314  valueType == typeof(double))
315  color = purpleColor;
316  else if (valueType == null) color = Color.White;
317  return color;
318  }
319 
320  public bool CanConnect(EventEditorNodeConnection otherNode)
321  {
322  if (otherNode.OverrideValue != null)
323  {
324  return false;
325  }
326 
327  return Type.AllowedConnections == null || Type.AllowedConnections.Contains(otherNode.Type);
328  }
329  }
330 }