(965c31410a) Unstable v0.10.4.0
This commit is contained in:
@@ -0,0 +1,662 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class EditorNode
|
||||
{
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public Vector2 Size { get; set; }
|
||||
|
||||
public int ID;
|
||||
|
||||
private const int HeaderSize = 32;
|
||||
|
||||
public Rectangle HeaderRectangle => new Rectangle(Position.ToPoint(), new Point((int) Size.X, HeaderSize));
|
||||
public Rectangle Rectangle => new Rectangle(new Point((int) Position.X, (int) Position.Y + HeaderSize), Size.ToPoint());
|
||||
public string Name { get; protected set; }
|
||||
|
||||
public bool CanAddConnections { get; set; }
|
||||
|
||||
public readonly List<NodeConnection> Connections = new List<NodeConnection>();
|
||||
|
||||
public readonly List<NodeConnectionType> RemovableTypes = new List<NodeConnectionType>();
|
||||
|
||||
public bool IsHighlighted;
|
||||
|
||||
public bool IsSelected;
|
||||
|
||||
protected EditorNode(string name)
|
||||
{
|
||||
Name = name;
|
||||
Position = Vector2.Zero;
|
||||
}
|
||||
|
||||
public virtual XElement Save()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public XElement SaveConnections()
|
||||
{
|
||||
XElement allConnections = new XElement("Connections", new XAttribute("i", ID));
|
||||
foreach (NodeConnection connection in Connections)
|
||||
{
|
||||
XElement connectionElement = new XElement("Connection");
|
||||
connectionElement.Add(new XAttribute("i", connection.ID));
|
||||
connectionElement.Add(new XAttribute("type", connection.Type.Label));
|
||||
|
||||
if (connection.EndConversation)
|
||||
{
|
||||
connectionElement.Add(new XAttribute("endconversation", connection.EndConversation));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(connection.OptionText))
|
||||
{
|
||||
connectionElement.Add(new XAttribute("optiontext", connection.OptionText));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(connection.OverrideValue?.ToString()))
|
||||
{
|
||||
connectionElement.Add(new XAttribute("overridevalue", connection.OverrideValue?.ToString()));
|
||||
connectionElement.Add(new XAttribute("valuetype", connection.OverrideValue?.GetType().ToString()));
|
||||
}
|
||||
|
||||
foreach (var nodeConnection in connection.ConnectedTo)
|
||||
{
|
||||
XElement connectedTo = new XElement("ConnectedTo",
|
||||
new XAttribute("i", nodeConnection.ID),
|
||||
new XAttribute("node", nodeConnection.Parent.ID));
|
||||
connectionElement.Add(connectedTo);
|
||||
}
|
||||
|
||||
allConnections.Add(connectionElement);
|
||||
}
|
||||
|
||||
return allConnections;
|
||||
}
|
||||
|
||||
public void LoadConnections(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
int id = subElement.GetAttributeInt("i", -1);
|
||||
string? connectionType = subElement.GetAttributeString("type", null);
|
||||
bool endConversation = subElement.GetAttributeBool("endconversation", false);
|
||||
|
||||
if (id < 0) { continue; }
|
||||
|
||||
NodeConnection? connection = Connections.Find(c => c.ID == id);
|
||||
if (connection == null)
|
||||
{
|
||||
if (string.Equals(connectionType, NodeConnectionType.Option.Label, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
connection = new NodeConnection(this, NodeConnectionType.Option) { ID = id, EndConversation = endConversation };
|
||||
Connections.Add(connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
string? optionText = subElement.GetAttributeString("optiontext", null);
|
||||
string? overrideValue = subElement.GetAttributeString("overridevalue", null);
|
||||
string? valueType = subElement.GetAttributeString("valuetype", null);
|
||||
|
||||
if (optionText != null) { connection.OptionText = optionText; }
|
||||
|
||||
if (overrideValue != null && valueType != null)
|
||||
{
|
||||
Type? type = Type.GetType(valueType);
|
||||
if (type != null)
|
||||
{
|
||||
if (type.IsEnum)
|
||||
{
|
||||
Array enums = Enum.GetValues(type);
|
||||
foreach (object? @enum in enums)
|
||||
{
|
||||
if (string.Equals(@enum?.ToString(), overrideValue, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
connection.OverrideValue = @enum;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.OverrideValue = Convert.ChangeType(overrideValue, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement connectedTo in subElement.Elements())
|
||||
{
|
||||
int id2 = connectedTo.GetAttributeInt("i", -1);
|
||||
int node = connectedTo.GetAttributeInt("node", -1);
|
||||
if (id2 < 0 || node < 0) { continue; }
|
||||
|
||||
EditorNode? otherNode = EventEditorScreen.nodeList.Find(editorNode => editorNode.ID == node);
|
||||
NodeConnection? otherConnection = otherNode?.Connections.Find(c => c.ID == id2);
|
||||
if (otherConnection != null)
|
||||
{
|
||||
connection.ConnectedTo.Add(otherConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static EditorNode? Load(XElement element)
|
||||
{
|
||||
return element.Name.ToString().ToLowerInvariant() switch
|
||||
{
|
||||
"eventnode" => EventNode.LoadEventNode(element),
|
||||
"valuenode" => ValueNode.LoadValueNode(element),
|
||||
"customnode" => CustomNode.LoadCustomNode(element),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public virtual XElement? ToXML()
|
||||
{
|
||||
XElement newElement = new XElement(Name);
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
if (connection.Type == NodeConnectionType.Value)
|
||||
{
|
||||
if (connection.GetValue() != null)
|
||||
{
|
||||
newElement.Add(new XAttribute(connection.Attribute?.ToLowerInvariant(), connection.GetValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newElement.Add(new XAttribute("_npos", XMLExtensions.Vector2ToString(Position)));
|
||||
|
||||
return newElement;
|
||||
}
|
||||
|
||||
public void Connect(EditorNode otherNode, NodeConnectionType type)
|
||||
{
|
||||
NodeConnection? conn = Connections.Find(connection => connection.Type == type && !connection.ConnectedTo.Any());
|
||||
NodeConnection? found = otherNode.Connections.Find(connection => connection.Type == NodeConnectionType.Activate);
|
||||
if (found != null)
|
||||
{
|
||||
conn?.ConnectedTo.Add(found);
|
||||
}
|
||||
}
|
||||
|
||||
public void Connect(NodeConnection connection, NodeConnection ownConnection)
|
||||
{
|
||||
connection.ConnectedTo.Add(ownConnection);
|
||||
}
|
||||
|
||||
public void Disconnect(NodeConnection conn)
|
||||
{
|
||||
foreach (var connection in EventEditorScreen.nodeList.SelectMany(editorNode => editorNode.Connections.Where(connection => connection.ConnectedTo.Contains(conn))))
|
||||
{
|
||||
connection.ConnectedTo.Remove(conn);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearConnections()
|
||||
{
|
||||
foreach (NodeConnection conn in Connections)
|
||||
{
|
||||
conn.ClearConnections();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Rectangle GetDrawRectangle()
|
||||
{
|
||||
return Rectangle;
|
||||
}
|
||||
|
||||
public NodeConnection? GetConnectionOnMouse(Vector2 mousePos)
|
||||
{
|
||||
return Connections.FirstOrDefault(eventNodeConnection => eventNodeConnection.DrawRectangle.Contains(mousePos));
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
DrawBack(spriteBatch);
|
||||
DrawFront(spriteBatch);
|
||||
}
|
||||
|
||||
protected virtual void DrawFront(SpriteBatch spriteBatch) { }
|
||||
|
||||
protected virtual Color BackgroundColor => new Color(150, 150, 150);
|
||||
|
||||
private void DrawBack(SpriteBatch spriteBatch)
|
||||
{
|
||||
Color outlineColor = Color.White * 0.8f;
|
||||
Color fontColor = Color.White;
|
||||
Color headerColor = IsHighlighted ? new Color(100, 100, 100) : new Color(120, 120, 120);
|
||||
if (IsSelected)
|
||||
{
|
||||
headerColor = new Color(80, 80, 80);
|
||||
}
|
||||
|
||||
float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f;
|
||||
|
||||
Rectangle bodyRect = GetDrawRectangle();
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, HeaderRectangle, headerColor, isFilled: true, depth: 1.0f);
|
||||
GUI.DrawRectangle(spriteBatch, bodyRect, BackgroundColor, isFilled: true, depth: 1.0f);
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, HeaderRectangle, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom));
|
||||
GUI.DrawRectangle(spriteBatch, bodyRect, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom));
|
||||
|
||||
int x = 0, y = 0;
|
||||
foreach (NodeConnection connection in Connections)
|
||||
{
|
||||
switch (connection.Type.NodeSide)
|
||||
{
|
||||
case NodeConnectionType.Side.Left:
|
||||
connection.Draw(spriteBatch, Rectangle, y);
|
||||
y++;
|
||||
break;
|
||||
case NodeConnectionType.Side.Right:
|
||||
connection.Draw(spriteBatch, Rectangle, x);
|
||||
x++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 headerSize = GUI.SubHeadingFont.MeasureString(Name);
|
||||
GUI.SubHeadingFont.DrawString(spriteBatch, Name, HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2), fontColor);
|
||||
}
|
||||
|
||||
public virtual void AddOption()
|
||||
{
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Option));
|
||||
}
|
||||
|
||||
public void RemoveOption(NodeConnection connection)
|
||||
{
|
||||
int index = Connections.IndexOf(connection);
|
||||
foreach (var nodeConnection in Connections.Skip(index))
|
||||
{
|
||||
nodeConnection.ID--;
|
||||
}
|
||||
|
||||
Connections.Remove(connection);
|
||||
}
|
||||
|
||||
public EditorNode? GetNext()
|
||||
{
|
||||
var nextNode = Connections.Find(connection => connection.Type == NodeConnectionType.Next);
|
||||
return nextNode?.ConnectedTo.FirstOrDefault()?.Parent;
|
||||
}
|
||||
|
||||
public EditorNode? GetNext(NodeConnectionType type)
|
||||
{
|
||||
var nextNode = Connections.Find(connection => connection.Type == type);
|
||||
return nextNode?.ConnectedTo.FirstOrDefault()?.Parent;
|
||||
}
|
||||
|
||||
public static bool IsInstanceOf(Type type1, Type type2)
|
||||
{
|
||||
return type1.IsAssignableFrom(type2) || type1.IsSubclassOf(type2);
|
||||
}
|
||||
|
||||
public EditorNode? GetParent()
|
||||
{
|
||||
var myNode = Connections.Find(connection => connection.Type == NodeConnectionType.Activate);
|
||||
if (myNode == null) { return null; }
|
||||
|
||||
foreach (EditorNode editorNode in EventEditorScreen.nodeList)
|
||||
{
|
||||
List<NodeConnection> childConnection = editorNode.Connections.Where(connection => connection.Type == NodeConnectionType.Next ||
|
||||
connection.Type == NodeConnectionType.Option ||
|
||||
connection.Type == NodeConnectionType.Failure ||
|
||||
connection.Type == NodeConnectionType.Success ||
|
||||
connection.Type == NodeConnectionType.Add).ToList();
|
||||
if (childConnection.Any(connection => connection != null && connection.ConnectedTo.Contains(myNode)))
|
||||
{
|
||||
return editorNode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class EventNode : EditorNode
|
||||
{
|
||||
private readonly Type type;
|
||||
|
||||
public EventNode(Type type, string name) : base(name)
|
||||
{
|
||||
this.type = type;
|
||||
Size = new Vector2(256, 256);
|
||||
PropertyInfo[] properties = type.GetProperties().Where(info => info.CustomAttributes.Any(data => data.AttributeType == typeof(Serialize))).ToArray();
|
||||
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Activate));
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Next));
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Value, property.Name, property.PropertyType, property));
|
||||
}
|
||||
|
||||
if (IsInstanceOf(type, typeof(BinaryOptionAction)))
|
||||
{
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Success));
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Failure));
|
||||
}
|
||||
|
||||
if (IsInstanceOf(type, typeof(ConversationAction)))
|
||||
{
|
||||
CanAddConnections = true;
|
||||
RemovableTypes.Add(NodeConnectionType.Option);
|
||||
}
|
||||
|
||||
if (IsInstanceOf(type, typeof(StatusEffectAction)) || IsInstanceOf(type, typeof(MissionAction)))
|
||||
{
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Add));
|
||||
}
|
||||
}
|
||||
|
||||
public override XElement Save()
|
||||
{
|
||||
XElement newElement = new XElement(nameof(EventNode),
|
||||
new XAttribute("i", ID),
|
||||
new XAttribute("type", type.ToString()),
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("xpos", Position.X),
|
||||
new XAttribute("ypos", Position.Y));
|
||||
|
||||
return newElement;
|
||||
}
|
||||
|
||||
public static EditorNode? LoadEventNode(XElement element)
|
||||
{
|
||||
if (!string.Equals(element.Name.ToString(), nameof(EventNode), StringComparison.InvariantCultureIgnoreCase)) { return null; }
|
||||
|
||||
Type? t = Type.GetType(element.GetAttributeString("type", string.Empty));
|
||||
if (t == null) { return null; }
|
||||
|
||||
EventNode newNode = new EventNode(t, element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) };
|
||||
float posX = element.GetAttributeFloat("xpos", 0f);
|
||||
float posY = element.GetAttributeFloat("ypos", 0f);
|
||||
newNode.Position = new Vector2(posX, posY);
|
||||
return newNode;
|
||||
}
|
||||
|
||||
public override Rectangle GetDrawRectangle()
|
||||
{
|
||||
return ScaleRectFromConnections(Connections, Rectangle);
|
||||
}
|
||||
|
||||
public static Rectangle ScaleRectFromConnections(List<NodeConnection> connections, Rectangle baseRect)
|
||||
{
|
||||
// determine how big this box should get based on how many input/output nodes the sides have
|
||||
int y = connections.Count(connection => connection.Type.NodeSide == NodeConnectionType.Side.Left),
|
||||
x = connections.Count(connection => connection.Type.NodeSide == NodeConnectionType.Side.Right);
|
||||
int maxHeight = Math.Max(x, y);
|
||||
|
||||
Rectangle bodyRect = baseRect;
|
||||
bodyRect.Height = bodyRect.Height / 8 * maxHeight;
|
||||
return bodyRect;
|
||||
}
|
||||
|
||||
public Tuple<EditorNode?, string?, bool>[] GetOptions()
|
||||
{
|
||||
IEnumerable<NodeConnection> myNode = Connections.Where(connection => connection.Type == NodeConnectionType.Option).ToArray();
|
||||
List<Tuple<EditorNode?, string?, bool>> list = new List<Tuple<EditorNode?, string?, bool>>();
|
||||
if (myNode != null)
|
||||
{
|
||||
foreach (NodeConnection connection in myNode)
|
||||
{
|
||||
if (connection.ConnectedTo.Any())
|
||||
{
|
||||
foreach (NodeConnection nodeConnection in connection.ConnectedTo)
|
||||
{
|
||||
list.Add(Tuple.Create((EditorNode?) nodeConnection.Parent, connection.OptionText, connection.EndConversation));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(Tuple.Create<EditorNode?, string?, bool>(null, connection.OptionText, connection.EndConversation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal class ValueNode : EditorNode
|
||||
{
|
||||
private object? nodeValue;
|
||||
|
||||
public object? Value
|
||||
{
|
||||
get => nodeValue;
|
||||
set
|
||||
{
|
||||
nodeValue = value;
|
||||
if (value is string str)
|
||||
{
|
||||
WrappedText = TextManager.Get(str, true) is { } translated ? translated : str;
|
||||
}
|
||||
else
|
||||
{
|
||||
WrappedText = value?.ToString() ?? string.Empty;
|
||||
}
|
||||
valueTextSize = GUI.SubHeadingFont.MeasureString(WrappedText);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 valueTextSize = Vector2.Zero;
|
||||
|
||||
public Type Type { get; }
|
||||
|
||||
public ValueNode(Type type, string name) : base(name)
|
||||
{
|
||||
Type = type;
|
||||
Value = type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
Size = new Vector2(256, 32);
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Out, "Output", Type));
|
||||
}
|
||||
|
||||
public override XElement Save()
|
||||
{
|
||||
XElement newElement = new XElement(nameof(ValueNode));
|
||||
newElement.Add(new XAttribute("i", ID));
|
||||
if (Value != null)
|
||||
{
|
||||
newElement.Add(new XAttribute("value", Value));
|
||||
}
|
||||
|
||||
newElement.Add(new XAttribute("type", Type.ToString()));
|
||||
newElement.Add(new XAttribute("name", Name));
|
||||
newElement.Add(new XAttribute("xpos", Position.X));
|
||||
newElement.Add(new XAttribute("ypos", Position.Y));
|
||||
return newElement;
|
||||
}
|
||||
|
||||
public override XElement? ToXML() { return null; }
|
||||
|
||||
public static EditorNode? LoadValueNode(XElement element)
|
||||
{
|
||||
if (!string.Equals(element.Name.ToString(), nameof(ValueNode), StringComparison.InvariantCultureIgnoreCase)) { return null; }
|
||||
|
||||
string? value = element.GetAttributeString("value", null);
|
||||
Type? type = Type.GetType(element.GetAttributeString("type", string.Empty));
|
||||
if (type != null)
|
||||
{
|
||||
ValueNode newNode = new ValueNode(type, element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) };
|
||||
float posX = element.GetAttributeFloat("xpos", 0f);
|
||||
float posY = element.GetAttributeFloat("ypos", 0f);
|
||||
newNode.Position = new Vector2(posX, posY);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
if (type.IsEnum)
|
||||
{
|
||||
Array enums = Enum.GetValues(type);
|
||||
foreach (object? @enum in enums)
|
||||
{
|
||||
if (string.Equals(@enum?.ToString(), value, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
newNode.Value = @enum;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newNode.Value = Convert.ChangeType(value, type);
|
||||
}
|
||||
}
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override Color BackgroundColor => new Color(50, 50, 50);
|
||||
|
||||
private string? wrappedText;
|
||||
|
||||
private string? WrappedText
|
||||
{
|
||||
get => wrappedText;
|
||||
set
|
||||
{
|
||||
string valueText = value ?? "null";
|
||||
int width = Rectangle.Width;
|
||||
if (width == 0)
|
||||
{
|
||||
wrappedText = valueText;
|
||||
return;
|
||||
}
|
||||
|
||||
if (width > 16)
|
||||
{
|
||||
width -= 16;
|
||||
}
|
||||
|
||||
valueText = ToolBox.WrapText(valueText, width, GUI.SubHeadingFont);
|
||||
wrappedText = valueText;
|
||||
}
|
||||
}
|
||||
|
||||
public override Rectangle GetDrawRectangle()
|
||||
{
|
||||
Rectangle drawRectangle = Rectangle;
|
||||
Vector2 size = GUI.SubHeadingFont.MeasureString(WrappedText);
|
||||
drawRectangle.Height = (int) Math.Max(size.Y + 16, drawRectangle.Height);
|
||||
return drawRectangle;
|
||||
}
|
||||
|
||||
protected override void DrawFront(SpriteBatch spriteBatch)
|
||||
{
|
||||
base.DrawFront(spriteBatch);
|
||||
Vector2 pos = GetDrawRectangle().Location.ToVector2() + (GetDrawRectangle().Size.ToVector2() / 2) - (valueTextSize / 2);
|
||||
Rectangle drawRect = Rectangle;
|
||||
drawRect.Inflate(-1, -1);
|
||||
GUI.DrawString(spriteBatch, pos, WrappedText, NodeConnection.GetPropertyColor(Type), font: GUI.SubHeadingFont);
|
||||
}
|
||||
}
|
||||
|
||||
class SpecialNode : EditorNode
|
||||
{
|
||||
public SpecialNode(string name) : base(name)
|
||||
{
|
||||
Size = new Vector2(256, 256);
|
||||
}
|
||||
|
||||
public override Rectangle GetDrawRectangle()
|
||||
{
|
||||
return EventNode.ScaleRectFromConnections(Connections, Rectangle);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomNode : SpecialNode
|
||||
{
|
||||
public CustomNode(string name) : base(name)
|
||||
{
|
||||
CanAddConnections = true;
|
||||
RemovableTypes.Add(NodeConnectionType.Value);
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Activate));
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Next));
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Add));
|
||||
}
|
||||
|
||||
public CustomNode() : this("Custom")
|
||||
{
|
||||
Prompt(s =>
|
||||
{
|
||||
Name = s;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public override void AddOption()
|
||||
{
|
||||
Prompt(s =>
|
||||
{
|
||||
Connections.Add(new NodeConnection(this, NodeConnectionType.Value, s, typeof(string)));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public override XElement Save()
|
||||
{
|
||||
XElement newElement = new XElement(nameof(CustomNode));
|
||||
newElement.Add(new XAttribute("i", ID));
|
||||
newElement.Add(new XAttribute("name", Name));
|
||||
newElement.Add(new XAttribute("xpos", Position.X));
|
||||
newElement.Add(new XAttribute("ypos", Position.Y));
|
||||
foreach (NodeConnection connection in Connections.FindAll(connection => connection.Type == NodeConnectionType.Value))
|
||||
{
|
||||
newElement.Add(new XElement("Value", new XAttribute("name", connection.Attribute)));
|
||||
}
|
||||
return newElement;
|
||||
}
|
||||
|
||||
public static EditorNode? LoadCustomNode(XElement element)
|
||||
{
|
||||
if (!string.Equals(element.Name.ToString(), nameof(CustomNode), StringComparison.OrdinalIgnoreCase)) { return null; }
|
||||
|
||||
CustomNode newNode = new CustomNode(element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) };
|
||||
float posX = element.GetAttributeFloat("xpos", 0f);
|
||||
float posY = element.GetAttributeFloat("ypos", 0f);
|
||||
newNode.Position = new Vector2(posX, posY);
|
||||
foreach (XElement valueElement in element.Elements())
|
||||
{
|
||||
newNode.Connections.Add(new NodeConnection(newNode, NodeConnectionType.Value, valueElement.GetAttributeString("name", string.Empty), typeof(string)));
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
private static void Prompt(Func<string, bool> OnAccepted)
|
||||
{
|
||||
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));
|
||||
var layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal: true);
|
||||
GUITextBox nameInput = new GUITextBox(new RectTransform(Vector2.One, layout.RectTransform));
|
||||
|
||||
msgBox.Buttons[1].OnClicked = delegate
|
||||
{
|
||||
msgBox.Close();
|
||||
return true;
|
||||
};
|
||||
|
||||
msgBox.Buttons[0].OnClicked = delegate
|
||||
{
|
||||
OnAccepted.Invoke(nameInput.Text);
|
||||
msgBox.Close();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,351 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class NodeConnectionType
|
||||
{
|
||||
public static readonly NodeConnectionType Activate = new NodeConnectionType(Side.Left, "Activate");
|
||||
public static readonly NodeConnectionType Value = new NodeConnectionType(Side.Left, "Value");
|
||||
public static readonly NodeConnectionType Option = new NodeConnectionType(Side.Right, "Option", new[] { Activate });
|
||||
public static readonly NodeConnectionType Add = new NodeConnectionType(Side.Right, "Add", new[] { Activate });
|
||||
public static readonly NodeConnectionType Success = new NodeConnectionType(Side.Right, "Success", new[] { Activate });
|
||||
public static readonly NodeConnectionType Failure = new NodeConnectionType(Side.Right, "Failure", new[] { Activate });
|
||||
public static readonly NodeConnectionType Next = new NodeConnectionType(Side.Right, "Next", new[] { Activate });
|
||||
public static readonly NodeConnectionType Out = new NodeConnectionType(Side.Right, "Out", new[] { Value });
|
||||
|
||||
public enum Side
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
public Side NodeSide { get; }
|
||||
|
||||
public string Label { get; }
|
||||
|
||||
public NodeConnectionType[]? AllowedConnections { get; }
|
||||
|
||||
private NodeConnectionType(Side side, string name, NodeConnectionType[]? allowedConnections = null)
|
||||
{
|
||||
NodeSide = side;
|
||||
Label = name;
|
||||
AllowedConnections = allowedConnections;
|
||||
}
|
||||
}
|
||||
|
||||
internal class NodeConnection
|
||||
{
|
||||
public string Attribute { get; }
|
||||
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool EndConversation { get; set; }
|
||||
|
||||
private string? optionText;
|
||||
|
||||
public string? OptionText
|
||||
{
|
||||
get => optionText;
|
||||
set
|
||||
{
|
||||
optionText = value;
|
||||
actualValue = WrappedValue = TextManager.Get(value, true) is { } translated ? translated : value;
|
||||
}
|
||||
}
|
||||
|
||||
public NodeConnectionType Type { get; }
|
||||
|
||||
public Type? ValueType { get; }
|
||||
|
||||
private object? overrideValue;
|
||||
private object? actualValue;
|
||||
|
||||
public object? OverrideValue
|
||||
{
|
||||
get => overrideValue;
|
||||
set
|
||||
{
|
||||
overrideValue = value;
|
||||
if (value is string str)
|
||||
{
|
||||
actualValue = WrappedValue = TextManager.Get(str, true) is { } translated ? translated : str;
|
||||
}
|
||||
else
|
||||
{
|
||||
actualValue = WrappedValue = value?.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string? wrappedValue;
|
||||
|
||||
private string? WrappedValue
|
||||
{
|
||||
get => wrappedValue;
|
||||
set
|
||||
{
|
||||
string valueText = value ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(valueText))
|
||||
{
|
||||
wrappedValue = null;
|
||||
return;
|
||||
}
|
||||
Vector2 textSize = GUI.SmallFont.MeasureString(valueText);
|
||||
bool wasWrapped = false;
|
||||
while (textSize.X > 96)
|
||||
{
|
||||
wasWrapped = true;
|
||||
valueText = $"{valueText}...".Substring(0, valueText.Length - 4);
|
||||
textSize = GUI.SmallFont.MeasureString($"{valueText}...");
|
||||
}
|
||||
|
||||
if (wasWrapped)
|
||||
{
|
||||
valueText = valueText.TrimEnd(' ') + "...";
|
||||
}
|
||||
|
||||
|
||||
wrappedValue = valueText;
|
||||
}
|
||||
}
|
||||
|
||||
public PropertyInfo? PropertyInfo { get; }
|
||||
|
||||
public Rectangle DrawRectangle = Rectangle.Empty;
|
||||
|
||||
public readonly EditorNode Parent;
|
||||
|
||||
public readonly List<NodeConnection> ConnectedTo = new List<NodeConnection>();
|
||||
|
||||
private readonly Color bgColor = Color.DarkGray * 0.8f;
|
||||
|
||||
private readonly Color outlineColor = Color.White * 0.8f;
|
||||
|
||||
public object? GetValue()
|
||||
{
|
||||
if (OverrideValue != null)
|
||||
{
|
||||
return OverrideValue;
|
||||
}
|
||||
|
||||
foreach (EditorNode editorNode in EventEditorScreen.nodeList)
|
||||
{
|
||||
var outNode = editorNode.Connections.Find(connection => connection.Type == NodeConnectionType.Out);
|
||||
if (outNode != null && outNode.ConnectedTo.Contains(this))
|
||||
{
|
||||
return (outNode.Parent as ValueNode)?.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ClearConnections()
|
||||
{
|
||||
foreach (var connection in EventEditorScreen.nodeList.SelectMany(editorNode => editorNode.Connections.Where(connection => connection.ConnectedTo.Contains(this))))
|
||||
{
|
||||
connection.ConnectedTo.Remove(this);
|
||||
}
|
||||
|
||||
ConnectedTo.Clear();
|
||||
}
|
||||
|
||||
public NodeConnection(EditorNode parent, NodeConnectionType type, string attribute = "", Type? valueType = null, PropertyInfo? propertyInfo = null)
|
||||
{
|
||||
Type = type;
|
||||
ValueType = valueType;
|
||||
Attribute = attribute;
|
||||
PropertyInfo = propertyInfo;
|
||||
Parent = parent;
|
||||
ID = parent.Connections.Count;
|
||||
}
|
||||
|
||||
private Point GetRenderPos(Rectangle parentRectangle, int yOffset)
|
||||
{
|
||||
int x = Type.NodeSide == NodeConnectionType.Side.Left ? parentRectangle.Left - 15 : parentRectangle.Right - 1;
|
||||
return new Point(x, parentRectangle.Y + 8 + parentRectangle.Height / 8 * yOffset);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, Rectangle parentRectangle, int yOffset)
|
||||
{
|
||||
float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f;
|
||||
Point pos = GetRenderPos(parentRectangle, yOffset);
|
||||
DrawRectangle = new Rectangle(pos, new Point(16, 16));
|
||||
GUI.DrawRectangle(spriteBatch, DrawRectangle, bgColor, isFilled: true);
|
||||
GUI.DrawRectangle(spriteBatch, DrawRectangle, EndConversation ? GUI.Style.Red : outlineColor, isFilled: false, thickness: (int)Math.Max(1, 1.25f / camZoom));
|
||||
|
||||
string label = string.IsNullOrWhiteSpace(Attribute) ? Type.Label : Attribute;
|
||||
float xPos = parentRectangle.Center.X > pos.X ? 24 : -8 - GUI.SmallFont.MeasureString(label).X;
|
||||
|
||||
if (Type != NodeConnectionType.Out)
|
||||
{
|
||||
Vector2 size = GUI.SmallFont.MeasureString(label);
|
||||
Vector2 positon = new Vector2(pos.X + xPos, pos.Y);
|
||||
Rectangle bgRect = new Rectangle(positon.ToPoint(), size.ToPoint());
|
||||
bgRect.Inflate(4, 4);
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, bgRect, Color.Black * 0.6f, isFilled: true);
|
||||
GUI.DrawString(spriteBatch, positon, label, GetPropertyColor(ValueType), font: GUI.SmallFont);
|
||||
|
||||
Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
mousePos.Y = -mousePos.Y;
|
||||
if (bgRect.Contains(mousePos))
|
||||
{
|
||||
CustomAttributeData? attribute = PropertyInfo?.CustomAttributes.FirstOrDefault();
|
||||
if (attribute?.AttributeType == typeof(Serialize))
|
||||
{
|
||||
if (attribute.ConstructorArguments.Count > 2)
|
||||
{
|
||||
string? description = attribute.ConstructorArguments[2].Value as string;
|
||||
if (!string.IsNullOrWhiteSpace(description))
|
||||
{
|
||||
EventEditorScreen.DrawnTooltip = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (OverrideValue != null)
|
||||
{
|
||||
DrawLabel(spriteBatch, new Vector2(DrawRectangle.Center.X - 96, pos.Y + (DrawRectangle.Height / 2) - (20 / 2)), WrappedValue ?? "null", actualValue?.ToString() ?? string.Empty);
|
||||
}
|
||||
|
||||
if (OptionText != null)
|
||||
{
|
||||
DrawLabel(spriteBatch, new Vector2(DrawRectangle.Center.X, pos.Y + (DrawRectangle.Height / 2) - (20 / 2)), WrappedValue ?? "null", actualValue?.ToString() ?? string.Empty);
|
||||
}
|
||||
|
||||
if (Parent.IsHighlighted)
|
||||
{
|
||||
DrawConnections(spriteBatch, yOffset, Math.Max(8.0f, 8.0f / camZoom), Color.Red);
|
||||
}
|
||||
|
||||
DrawConnections(spriteBatch, yOffset, width: Math.Max(2.0f, 2.0f / camZoom));
|
||||
|
||||
if (EventEditorScreen.DraggedConnection == this)
|
||||
{
|
||||
DrawSquareLine(spriteBatch, EventEditorScreen.DraggingPosition, yOffset, width: Math.Max(2.0f, 2.0f / camZoom));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawConnections(SpriteBatch spriteBatch, int yOffset, float width = 2, Color? overrideColor = null)
|
||||
{
|
||||
foreach (NodeConnection? eventNodeConnection in ConnectedTo)
|
||||
{
|
||||
if (eventNodeConnection != null)
|
||||
{
|
||||
DrawSquareLine(spriteBatch, new Vector2(eventNodeConnection.DrawRectangle.Left + 1, eventNodeConnection.DrawRectangle.Center.Y), yOffset, width, overrideColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLabel(SpriteBatch spriteBatch, Vector2 pos, string text, string fullText)
|
||||
{
|
||||
float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f;
|
||||
Rectangle valueRect = new Rectangle((int)pos.X, (int)pos.Y, 96, 20);
|
||||
Vector2 textSize = GUI.SmallFont.MeasureString(text);
|
||||
Vector2 position = valueRect.Location.ToVector2() + valueRect.Size.ToVector2() / 2 - textSize / 2;
|
||||
Rectangle drawRect = valueRect;
|
||||
drawRect.Inflate(4, 4);
|
||||
GUI.DrawRectangle(spriteBatch, drawRect, new Color(50, 50, 50), isFilled: true);
|
||||
GUI.DrawRectangle(spriteBatch, drawRect, EndConversation ? GUI.Style.Red : outlineColor, isFilled: false, thickness: (int)Math.Max(1, 1.25f / camZoom));
|
||||
GUI.DrawString(spriteBatch, position, text, GetPropertyColor(ValueType), font: GUI.SmallFont);
|
||||
DrawRectangle = Rectangle.Union(DrawRectangle, drawRect);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(fullText))
|
||||
{
|
||||
Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
mousePos.Y = -mousePos.Y;
|
||||
if (DrawRectangle.Contains(mousePos))
|
||||
{
|
||||
EventEditorScreen.DrawnTooltip = fullText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSquareLine(SpriteBatch spriteBatch, Vector2 position, int yOffset, float width = 2, Color? overrideColor = null)
|
||||
{
|
||||
// draw a line between 2 nodes using points
|
||||
// the order of this array is messed up, I know
|
||||
// order of points is from start node to end node: 0, 4, 1, 2, 5, 3
|
||||
Vector2[] points = new Vector2[6];
|
||||
int xOffset = 24 * (yOffset + 1);
|
||||
points[0] = new Vector2(DrawRectangle.Right, DrawRectangle.Center.Y);
|
||||
points[3] = position;
|
||||
points[1] = points[0];
|
||||
points[2] = points[3];
|
||||
|
||||
points[4] = points[1];
|
||||
points[5] = points[2];
|
||||
|
||||
points[1].X += (points[2].X - points[1].X) / 2;
|
||||
points[1].X = Math.Max(points[1].X, points[0].X + xOffset);
|
||||
points[2].X = points[1].X;
|
||||
|
||||
// if the node is "behind" us do some magic to make the line curve to prevent overlapping
|
||||
if (points[1].X <= points[0].X + xOffset)
|
||||
{
|
||||
points[4].X += xOffset;
|
||||
points[1].X = points[4].X;
|
||||
points[1].Y += (points[2].Y - points[1].Y) / 2;
|
||||
}
|
||||
|
||||
if (points[2].X >= points[3].X - xOffset)
|
||||
{
|
||||
points[5].X -= xOffset;
|
||||
points[2].X = points[5].X;
|
||||
points[2].Y -= points[2].Y - points[1].Y;
|
||||
}
|
||||
|
||||
Color drawColor = Parent is ValueNode ? GetPropertyColor(ValueType) : GUI.Style.Red;
|
||||
|
||||
if (overrideColor != null)
|
||||
{
|
||||
drawColor = overrideColor.Value;
|
||||
}
|
||||
|
||||
GUI.DrawLine(spriteBatch, points[0], points[4], drawColor, width: (int)width);
|
||||
GUI.DrawLine(spriteBatch, points[4], points[1], drawColor, width: (int)width);
|
||||
GUI.DrawLine(spriteBatch, points[1], points[2], drawColor, width: (int)width);
|
||||
GUI.DrawLine(spriteBatch, points[2], points[5], drawColor, width: (int)width);
|
||||
GUI.DrawLine(spriteBatch, points[5], points[3], drawColor, width: (int)width);
|
||||
}
|
||||
|
||||
private static readonly Color defaultColor = new Color(139, 233, 253);
|
||||
private static readonly Color yellowColor = new Color(241, 250, 140);
|
||||
private static readonly Color pinkColor = new Color(255, 121, 198);
|
||||
private static readonly Color purpleColor = new Color(189, 147, 249);
|
||||
|
||||
public static Color GetPropertyColor(Type? valueType)
|
||||
{
|
||||
Color color = defaultColor;
|
||||
if (valueType == typeof(bool))
|
||||
color = pinkColor;
|
||||
else if (valueType == typeof(string))
|
||||
color = yellowColor;
|
||||
else if (valueType == typeof(int) ||
|
||||
valueType == typeof(float) ||
|
||||
valueType == typeof(double))
|
||||
color = purpleColor;
|
||||
else if (valueType == null) color = Color.White;
|
||||
return color;
|
||||
}
|
||||
|
||||
public bool CanConnect(NodeConnection otherNode)
|
||||
{
|
||||
if (otherNode.OverrideValue != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Type.AllowedConnections == null || Type.AllowedConnections.Contains(otherNode.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user