3 using System.Collections.Generic;
5 using System.Reflection;
7 using Microsoft.Xna.Framework;
8 using Microsoft.Xna.Framework.Graphics;
12 internal class EditorNode
14 public Vector2 Position {
get;
set; }
16 public Vector2 Size {
get;
set; }
20 private const int HeaderSize = 32;
22 public Rectangle HeaderRectangle =>
new Rectangle(Position.ToPoint(),
new Point((
int) Size.X, HeaderSize));
24 public string Name {
get;
protected set; }
26 public bool CanAddConnections {
get;
set; }
28 public readonly List<EventEditorNodeConnection> Connections =
new List<EventEditorNodeConnection>();
30 public readonly List<NodeConnectionType> RemovableTypes =
new List<NodeConnectionType>();
32 public bool IsHighlighted;
34 public bool IsSelected;
36 protected EditorNode(
string name)
39 Position = Vector2.Zero;
42 public virtual XElement Save()
44 throw new NotImplementedException();
47 public XElement SaveConnections()
49 XElement allConnections =
new XElement(
"Connections",
new XAttribute(
"i", ID));
50 foreach (EventEditorNodeConnection connection
in Connections)
52 XElement connectionElement =
new XElement(
"Connection");
53 connectionElement.Add(
new XAttribute(
"i", connection.ID));
54 connectionElement.Add(
new XAttribute(
"type", connection.Type.Label));
56 if (connection.EndConversation)
58 connectionElement.Add(
new XAttribute(
"endconversation", connection.EndConversation));
61 if (!
string.IsNullOrWhiteSpace(connection.OptionText))
63 connectionElement.Add(
new XAttribute(
"optiontext", connection.OptionText));
66 if (connection.OverrideValue is { } overrideValue && !
string.IsNullOrWhiteSpace(connection.OverrideValue?.ToString()))
68 connectionElement.Add(
new XAttribute(
"overridevalue", overrideValue.ToString() ??
string.Empty));
69 connectionElement.Add(
new XAttribute(
"valuetype", overrideValue.GetType().ToString()));
72 foreach (var nodeConnection
in connection.ConnectedTo)
74 XElement connectedTo =
new XElement(
"ConnectedTo",
75 new XAttribute(
"i", nodeConnection.ID),
76 new XAttribute(
"node", nodeConnection.Parent.ID));
77 connectionElement.Add(connectedTo);
80 allConnections.Add(connectionElement);
83 return allConnections;
86 public void LoadConnections(XElement element)
88 foreach (var subElement
in element.Elements())
90 int id = subElement.GetAttributeInt(
"i", -1);
91 string? connectionType = subElement.GetAttributeString(
"type",
null);
92 bool endConversation = subElement.GetAttributeBool(
"endconversation",
false);
94 if (
id < 0) {
continue; }
96 EventEditorNodeConnection? connection = Connections.Find(c => c.ID ==
id);
97 if (connection ==
null)
99 if (
string.Equals(connectionType, NodeConnectionType.Option.Label, StringComparison.InvariantCultureIgnoreCase))
101 connection =
new EventEditorNodeConnection(
this, NodeConnectionType.Option) { ID = id, EndConversation = endConversation };
102 Connections.Add(connection);
110 string? optionText = subElement.GetAttributeString(
"optiontext",
null);
111 string? overrideValue = subElement.GetAttributeString(
"overridevalue",
null);
112 string? valueType = subElement.GetAttributeString(
"valuetype",
null);
114 if (optionText !=
null) { connection.OptionText = optionText; }
116 if (overrideValue !=
null && valueType !=
null)
118 Type? type = Type.GetType(valueType);
123 Array enums = Enum.GetValues(type);
124 foreach (
object? @
enum in enums)
126 if (
string.Equals(@
enum?.ToString(), overrideValue, StringComparison.InvariantCultureIgnoreCase))
128 connection.OverrideValue = @
enum;
134 connection.OverrideValue = EventEditorScreen.ChangeType(overrideValue, type);
139 foreach (XElement connectedTo
in subElement.Elements())
141 int id2 = connectedTo.GetAttributeInt(
"i", -1);
142 int node = connectedTo.GetAttributeInt(
"node", -1);
143 if (id2 < 0 || node < 0) {
continue; }
145 EditorNode? otherNode = EventEditorScreen.nodeList.Find(editorNode => editorNode.ID == node);
146 EventEditorNodeConnection? otherConnection = otherNode?.Connections.Find(c => c.ID == id2);
147 if (otherConnection !=
null)
149 connection.ConnectedTo.Add(otherConnection);
155 public static EditorNode? Load(XElement element)
157 return element.Name.ToString().ToLowerInvariant()
switch
159 "eventnode" => EventNode.LoadEventNode(element),
160 "valuenode" => ValueNode.LoadValueNode(element),
161 "customnode" => CustomNode.LoadCustomNode(element),
166 public virtual XElement? ToXML()
168 XElement newElement =
new XElement(Name);
169 foreach (var connection
in Connections)
171 if (connection.Type == NodeConnectionType.Value)
173 if (connection.GetValue() is { } connValue)
175 newElement.Add(
new XAttribute(connection.Attribute.ToLowerInvariant(), connValue));
180 newElement.Add(
new XAttribute(
"_npos", XMLExtensions.Vector2ToString(Position)));
185 public void Connect(EditorNode otherNode, NodeConnectionType type)
187 EventEditorNodeConnection? conn = Connections.Find(connection => connection.Type == type && !connection.ConnectedTo.Any());
188 EventEditorNodeConnection? found = otherNode.Connections.Find(connection => connection.Type == NodeConnectionType.Activate);
191 conn?.ConnectedTo.Add(found);
195 public static void Connect(EventEditorNodeConnection connection, EventEditorNodeConnection ownConnection)
197 connection.ConnectedTo.Add(ownConnection);
200 public static void Disconnect(EventEditorNodeConnection conn)
202 foreach (var connection
in EventEditorScreen.nodeList.SelectMany(editorNode => editorNode.Connections.Where(connection => connection.ConnectedTo.Contains(conn))))
204 connection.ConnectedTo.Remove(conn);
208 public void ClearConnections()
210 foreach (EventEditorNodeConnection conn
in Connections)
212 conn.ClearConnections();
216 public virtual Rectangle GetDrawRectangle()
221 public EventEditorNodeConnection? GetConnectionOnMouse(Vector2 mousePos)
223 return Connections.FirstOrDefault(eventNodeConnection => eventNodeConnection.DrawRectangle.Contains(mousePos));
226 public void Draw(SpriteBatch spriteBatch)
228 DrawBack(spriteBatch);
229 DrawFront(spriteBatch);
232 protected virtual void DrawFront(SpriteBatch spriteBatch) { }
234 protected virtual Color BackgroundColor =>
new Color(150, 150, 150);
236 private void DrawBack(SpriteBatch spriteBatch)
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);
243 headerColor =
new Color(80, 80, 80);
246 float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f;
250 GUI.DrawRectangle(spriteBatch, HeaderRectangle, headerColor, isFilled:
true, depth: 1.0f);
251 GUI.DrawRectangle(spriteBatch, bodyRect, BackgroundColor, isFilled:
true, depth: 1.0f);
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));
257 foreach (EventEditorNodeConnection connection
in Connections)
259 switch (connection.Type.NodeSide)
261 case NodeConnectionType.Side.Left:
262 connection.Draw(spriteBatch, Rectangle, y);
265 case NodeConnectionType.Side.Right:
266 connection.Draw(spriteBatch, Rectangle, x);
272 Vector2 headerSize = GUIStyle.SubHeadingFont.MeasureString(Name);
273 GUIStyle.SubHeadingFont.DrawString(spriteBatch, Name, HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2), fontColor);
276 public void AddConnection(NodeConnectionType connectionType)
278 Connections.Add(
new EventEditorNodeConnection(
this, connectionType));
281 public virtual void AddOption()
283 Connections.Add(
new EventEditorNodeConnection(
this, NodeConnectionType.Option));
286 public void RemoveOption(EventEditorNodeConnection connection)
288 int index = Connections.IndexOf(connection);
289 foreach (var nodeConnection
in Connections.Skip(index))
294 Connections.Remove(connection);
297 public EditorNode? GetNext()
299 var nextNode = Connections.Find(connection => connection.Type == NodeConnectionType.Next);
300 return nextNode?.ConnectedTo.FirstOrDefault()?.Parent;
303 public EditorNode? GetNext(NodeConnectionType type)
305 var nextNode = Connections.Find(connection => connection.Type == type);
306 return nextNode?.ConnectedTo.FirstOrDefault()?.Parent;
309 public static bool IsInstanceOf(Type type1, Type type2)
311 return type1.IsAssignableFrom(type2) || type1.IsSubclassOf(type2);
314 public EditorNode? GetParent()
316 var myNode = Connections.Find(connection => connection.Type == NodeConnectionType.Activate);
317 if (myNode ==
null) {
return null; }
319 foreach (EditorNode editorNode
in EventEditorScreen.nodeList)
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)))
336 internal class EventNode : EditorNode
338 private readonly Type type;
340 public EventNode(Type type,
string name) : base(name)
343 Size =
new Vector2(256, 256);
344 PropertyInfo[] properties = type.GetProperties().Where(info => info.CustomAttributes.Any(data => data.AttributeType == typeof(Serialize))).ToArray();
346 Connections.Add(
new EventEditorNodeConnection(
this, NodeConnectionType.Activate));
347 Connections.Add(
new EventEditorNodeConnection(
this, NodeConnectionType.Next));
349 foreach (PropertyInfo property
in properties)
351 Connections.Add(
new EventEditorNodeConnection(
this, NodeConnectionType.Value, property.Name, property.PropertyType, property));
354 if (IsInstanceOf(type, typeof(BinaryOptionAction)))
356 Connections.Add(
new EventEditorNodeConnection(
this, NodeConnectionType.Success));
357 Connections.Add(
new EventEditorNodeConnection(
this, NodeConnectionType.Failure));
360 if (IsInstanceOf(type, typeof(ConversationAction)))
362 CanAddConnections =
true;
363 RemovableTypes.Add(NodeConnectionType.Option);
366 if (IsInstanceOf(type, typeof(StatusEffectAction)) || IsInstanceOf(type, typeof(MissionAction)))
368 Connections.Add(
new EventEditorNodeConnection(
this, NodeConnectionType.Add));
372 public override XElement Save()
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));
384 public static EditorNode? LoadEventNode(XElement element)
386 if (!
string.Equals(element.Name.ToString(), nameof(EventNode), StringComparison.InvariantCultureIgnoreCase)) {
return null; }
388 Type? t = Type.GetType(element.GetAttributeString(
"type",
string.Empty));
389 if (t ==
null) {
return null; }
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);
398 public override Rectangle GetDrawRectangle()
400 return ScaleRectFromConnections(Connections, Rectangle);
403 public static Rectangle ScaleRectFromConnections(List<EventEditorNodeConnection> connections, Rectangle baseRect)
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);
411 bodyRect.Height = bodyRect.Height / 8 * maxHeight;
415 public Tuple<EditorNode?, string?, bool>[] GetOptions()
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>>();
421 foreach (EventEditorNodeConnection connection
in myNode)
423 if (connection.ConnectedTo.Any())
425 foreach (EventEditorNodeConnection nodeConnection
in connection.ConnectedTo)
427 list.Add(Tuple.Create((EditorNode?) nodeConnection.Parent, connection.OptionText, connection.EndConversation));
432 list.Add(Tuple.Create<EditorNode?,
string?,
bool>(
null, connection.OptionText, connection.EndConversation));
437 return list.ToArray();
441 internal class ValueNode : EditorNode
443 private object? nodeValue;
451 if (value is
string str)
453 WrappedText = TextManager.Get(str) is { Loaded:
true } translated ? translated.Value : str;
457 WrappedText = value?.ToString() ??
string.Empty;
459 valueTextSize = GUIStyle.SubHeadingFont.MeasureString(WrappedText);
463 private Vector2 valueTextSize = Vector2.Zero;
465 public Type Type {
get; }
467 public ValueNode(Type type,
string name) : base(name)
470 Value = type.IsValueType ? Activator.CreateInstance(type) :
null;
471 Size =
new Vector2(256, 32);
472 Connections.Add(
new EventEditorNodeConnection(
this, NodeConnectionType.Out,
"Output", Type));
475 public override XElement Save()
477 XElement newElement =
new XElement(nameof(ValueNode));
478 newElement.Add(
new XAttribute(
"i", ID));
481 newElement.Add(
new XAttribute(
"value", Value));
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));
491 public override XElement? ToXML() {
return null; }
493 public static EditorNode? LoadValueNode(XElement element)
495 if (!
string.Equals(element.Name.ToString(), nameof(ValueNode), StringComparison.InvariantCultureIgnoreCase)) {
return null; }
497 string? value = element.GetAttributeString(
"value",
null);
498 Type? type = Type.GetType(element.GetAttributeString(
"type",
string.Empty));
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);
510 Array enums = Enum.GetValues(type);
511 foreach (
object? @
enum in enums)
513 if (
string.Equals(@
enum?.ToString(), value, StringComparison.InvariantCultureIgnoreCase))
515 newNode.Value = @
enum;
521 newNode.Value = EventEditorScreen.ChangeType(value, type);
531 protected override Color BackgroundColor =>
new Color(50, 50, 50);
533 private string? wrappedText;
535 private string? WrappedText
540 string valueText = value ??
"null";
544 wrappedText = valueText;
553 if (GUIStyle.SubHeadingFont.Value !=
null)
555 valueText = ToolBox.WrapText(valueText, width, GUIStyle.SubHeadingFont.Value);
557 wrappedText = valueText;
561 public override Rectangle GetDrawRectangle()
564 Vector2 size = GUIStyle.SubHeadingFont.MeasureString(WrappedText ??
"");
565 drawRectangle.Height = (int) Math.Max(size.Y + 16, drawRectangle.Height);
566 return drawRectangle;
569 protected override void DrawFront(SpriteBatch spriteBatch)
571 base.DrawFront(spriteBatch);
572 Vector2 pos = GetDrawRectangle().Location.ToVector2() + (GetDrawRectangle().Size.ToVector2() / 2) - (valueTextSize / 2);
574 drawRect.Inflate(-1, -1);
575 GUI.DrawString(spriteBatch, pos, WrappedText, EventEditorNodeConnection.GetPropertyColor(Type), font: GUIStyle.SubHeadingFont);
583 Size =
new Vector2(256, 256);
588 return EventNode.ScaleRectFromConnections(Connections,
Rectangle);
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));
616 Connections.Add(
new EventEditorNodeConnection(
this, NodeConnectionType.Value, s, typeof(
string)));
621 public override XElement
Save()
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))
630 newElement.Add(
new XElement(
"Value",
new XAttribute(
"name", connection.Attribute)));
637 if (!
string.Equals(element.Name.ToString(), nameof(
CustomNode), StringComparison.OrdinalIgnoreCase)) {
return null; }
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())
645 newNode.Connections.Add(
new EventEditorNodeConnection(newNode, NodeConnectionType.Value, valueElement.GetAttributeString(
"name",
string.Empty), typeof(
string)));
650 private static void Prompt(Func<string, bool> OnAccepted)
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));
656 msgBox.Buttons[1].OnClicked = delegate
662 msgBox.Buttons[0].OnClicked = delegate
664 OnAccepted.Invoke(nameInput.
Text);
override void AddOption()
static ? EditorNode LoadCustomNode(XElement element)
override Rectangle GetDrawRectangle()