#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 EventEditorNodeConnection { 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; if (value is string) { actualValue = WrappedValue = TextManager.Get(value).Fallback(value).Value; } else { actualValue = WrappedValue = 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).Fallback(str).Value; } 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 = GUIStyle.SmallFont.MeasureString(valueText); bool wasWrapped = false; while (textSize.X > 96) { wasWrapped = true; valueText = $"{valueText}...".Substring(0, valueText.Length - 4); textSize = GUIStyle.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 ConnectedTo = new List(); 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 EventEditorNodeConnection(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 ? GUIStyle.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 - GUIStyle.SmallFont.MeasureString(label).X; if (Type != NodeConnectionType.Out) { Vector2 size = GUIStyle.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: GUIStyle.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 (EventEditorNodeConnection? 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 = GUIStyle.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 ? GUIStyle.Red : outlineColor, isFilled: false, thickness: (int)Math.Max(1, 1.25f / camZoom)); GUI.DrawString(spriteBatch, position, text, GetPropertyColor(ValueType), font: GUIStyle.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) { float knobLength = 24 * (yOffset + 1); Vector2 start = new Vector2(DrawRectangle.Right, DrawRectangle.Center.Y); var (points, _) = ToolBox.GetSquareLineBetweenPoints(start, position, knobLength); Color drawColor = Parent is ValueNode ? GetPropertyColor(ValueType) : GUIStyle.Red; if (overrideColor != null) { drawColor = overrideColor.Value; } GUI.DrawLine(spriteBatch, points[0], points[1], drawColor, width: (int)width); GUI.DrawLine(spriteBatch, points[1], points[2], drawColor, width: (int)width); GUI.DrawLine(spriteBatch, points[2], points[3], drawColor, width: (int)width); GUI.DrawLine(spriteBatch, points[3], points[4], drawColor, width: (int)width); GUI.DrawLine(spriteBatch, points[4], points[5], 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(EventEditorNodeConnection otherNode) { if (otherNode.OverrideValue != null) { return false; } return Type.AllowedConnections == null || Type.AllowedConnections.Contains(otherNode.Type); } } }