3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
5 using Microsoft.Xna.Framework.Input;
7 using System.Collections.Generic;
9 using System.Reflection;
10 using System.Xml.Linq;
11 using Directory = System.IO.Directory;
15 internal class EventEditorScreen : EditorScreen
17 private GUIFrame GuiFrame =
null!;
19 public override Camera Cam {
get; }
20 public static string? DrawnTooltip {
get;
set; }
22 public static readonly List<EditorNode> nodeList =
new List<EditorNode>();
24 private readonly List<EditorNode> selectedNodes =
new List<EditorNode>();
26 public static Vector2 DraggingPosition = Vector2.Zero;
27 public static EventEditorNodeConnection? DraggedConnection;
29 private EditorNode? draggedNode;
30 private Vector2 dragOffset;
32 private readonly Dictionary<EditorNode, Vector2> markedNodes =
new Dictionary<EditorNode, Vector2>();
34 private static string projectName =
string.Empty;
36 private OutpostGenerationParams? lastTestParam;
37 private LocationType? lastTestType;
39 private GUITickBox? isTraitorEventBox;
41 private static int CreateID()
43 int maxId = nodeList.Any() ? nodeList.Max(node => node.ID) : 0;
47 private Point screenResolution;
49 public EventEditorScreen()
56 private void CreateGUI()
58 GuiFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.2f, 0.4f), GUI.Canvas) { MinSize = new Point(300, 420) });
59 GUILayoutGroup layoutGroup =
new GUILayoutGroup(RectTransform(0.9f, 0.9f, GuiFrame,
Anchor.Center)) { Stretch =
true, AbsoluteSpacing = GUI.IntScale(5) };
62 GUILayoutGroup buttonLayout =
new GUILayoutGroup(RectTransform(1.0f, 0.50f, layoutGroup)) { RelativeSpacing = 0.04f };
63 GUIButton newProjectButton =
new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get(
"EventEditor.NewProject"));
64 GUIButton saveProjectButton =
new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get(
"EventEditor.SaveProject"));
65 GUIButton loadProjectButton =
new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get(
"EventEditor.LoadProject"));
66 GUIButton exportProjectButton =
new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get(
"EventEditor.Export"));
71 GUILayoutGroup loadEventLayout =
new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup));
72 new GUITextBlock(RectTransform(1.0f, 0.5f, loadEventLayout), TextManager.Get(
"EventEditor.LoadEvent"), font: GUIStyle.SubHeadingFont);
74 GUILayoutGroup loadDropdownLayout =
new GUILayoutGroup(RectTransform(1.0f, 0.5f, loadEventLayout), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
75 GUIDropDown loadDropdown =
new GUIDropDown(RectTransform(0.8f, 1.0f, loadDropdownLayout), elementCount: 10);
76 GUIButton loadButton =
new GUIButton(RectTransform(0.2f, 1.0f, loadDropdownLayout), TextManager.Get(
"Load"));
80 GUILayoutGroup addActionLayout =
new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup));
81 new GUITextBlock(RectTransform(1.0f, 0.5f, addActionLayout), TextManager.Get(
"EventEditor.AddAction"), font: GUIStyle.SubHeadingFont);
83 GUILayoutGroup addActionDropdownLayout =
new GUILayoutGroup(RectTransform(1.0f, 0.5f, addActionLayout), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
84 GUIDropDown addActionDropdown =
new GUIDropDown(RectTransform(0.8f, 1.0f, addActionDropdownLayout), elementCount: 10);
85 GUIButton addActionButton =
new GUIButton(RectTransform(0.2f, 1.0f, addActionDropdownLayout), TextManager.Get(
"EventEditor.Add"));
88 GUILayoutGroup addValueLayout =
new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup));
89 new GUITextBlock(RectTransform(1.0f, 0.5f, addValueLayout), TextManager.Get(
"EventEditor.AddValue"), font: GUIStyle.SubHeadingFont);
91 GUILayoutGroup addValueDropdownLayout =
new GUILayoutGroup(RectTransform(1.0f, 0.5f, addValueLayout), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
92 GUIDropDown addValueDropdown =
new GUIDropDown(RectTransform(0.8f, 1.0f, addValueDropdownLayout), elementCount: 7);
93 GUIButton addValueButton =
new GUIButton(RectTransform(0.2f, 1.0f, addValueDropdownLayout), TextManager.Get(
"EventEditor.Add"));
96 GUILayoutGroup addSpecialLayout =
new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup));
97 new GUITextBlock(RectTransform(1.0f, 0.5f, addSpecialLayout), TextManager.Get(
"EventEditor.AddSpecial"), font: GUIStyle.SubHeadingFont);
98 GUILayoutGroup addSpecialDropdownLayout =
new GUILayoutGroup(RectTransform(1.0f, 0.5f, addSpecialLayout), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
99 GUIDropDown addSpecialDropdown =
new GUIDropDown(RectTransform(0.8f, 1.0f, addSpecialDropdownLayout), elementCount: 1);
100 GUIButton addSpecialButton =
new GUIButton(RectTransform(0.2f, 1.0f, addSpecialDropdownLayout), TextManager.Get(
"EventEditor.Add"));
103 foreach (EventPrefab eventPrefab
in EventSet.GetAllEventPrefabs().Where(p => !p.Identifier.IsEmpty).Distinct().OrderBy(p => p.Identifier))
105 if (!typeof(ScriptedEvent).IsAssignableFrom(eventPrefab.EventType)) {
continue; }
106 var textBlock = loadDropdown.AddItem(eventPrefab.Identifier.Value!, eventPrefab) as GUITextBlock;
107 if (eventPrefab is TraitorEventPrefab && textBlock !=
null)
109 textBlock.TextColor = Color.MediumPurple;
114 foreach (Type type
in Assembly.GetExecutingAssembly().GetTypes().Where(type => type.IsSubclassOf(typeof(EventAction))).OrderBy(t => t.Name))
116 addActionDropdown.AddItem(type.Name, type);
119 addSpecialDropdown.AddItem(
"Custom", typeof(CustomNode));
121 addValueDropdown.AddItem(nameof(Single), typeof(
float));
122 addValueDropdown.AddItem(nameof(Boolean), typeof(
bool));
123 addValueDropdown.AddItem(nameof(String), typeof(
string));
126 addValueDropdown.AddItem(nameof(ReputationAction.ReputationType), typeof(ReputationAction.ReputationType));
127 addValueDropdown.AddItem(nameof(SpawnAction.SpawnLocationType), typeof(SpawnAction.SpawnLocationType));
130 loadButton.OnClicked += (button, o) => Load(loadDropdown.SelectedData as EventPrefab);
131 addActionButton.OnClicked += (button, o) => AddAction(addActionDropdown.SelectedData as Type);
132 addValueButton.OnClicked += (button, o) => AddValue(addValueDropdown.SelectedData as Type);
133 addSpecialButton.OnClicked += (button, o) => AddSpecial(addSpecialDropdown.SelectedData as Type);
134 exportProjectButton.OnClicked += ExportEventToFile;
135 saveProjectButton.OnClicked += SaveProjectToFile;
136 newProjectButton.OnClicked += TryCreateNewProject;
137 loadProjectButton.OnClicked += (button, o) =>
139 FileSelection.OnFileSelected = (file) =>
141 XDocument? document = XMLExtensions.TryLoadXml(file);
142 if (document?.Root !=
null)
148 string directory = Path.GetFullPath(
"EventProjects");
149 if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); }
151 FileSelection.ClearFileTypeFilters();
152 FileSelection.AddFileTypeFilter(
"Scripted Event",
"*.sevproj");
153 FileSelection.SelectFileTypeFilter(
"*.sevproj");
154 FileSelection.CurrentDirectory = directory;
155 FileSelection.Open =
true;
159 isTraitorEventBox =
new GUITickBox(RectTransform(1.0f, 0.125f, layoutGroup),
"Traitor event");
161 screenResolution =
new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
164 private bool ExportEventToFile(GUIButton button,
object o)
166 XElement? save = ExportXML();
171 string directory = Path.GetFullPath(
"EventProjects");
172 if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); }
174 string exportPath = Path.Combine(directory,
"Exported");
175 if (!Directory.Exists(exportPath)) { Directory.CreateDirectory(exportPath); }
177 var msgBox =
new GUIMessageBox(TextManager.Get(
"EventEditor.ExportProjectPrompt"),
"",
new[] { TextManager.Get(
"Cancel"), TextManager.Get(
"EventEditor.Export") },
new Vector2(0.2f, 0.175f), minSize:
new Point(300, 175));
178 var layout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal:
true);
179 GUITextBox nameInput =
new GUITextBox(
new RectTransform(Vector2.One, layout.RectTransform)) { Text = projectName };
182 msgBox.Buttons[0].OnClicked = delegate
189 msgBox.Buttons[1].OnClicked = delegate
191 foreach (var illegalChar
in Path.GetInvalidFileNameCharsCrossPlatform())
193 if (!nameInput.Text.Contains(illegalChar)) {
continue; }
195 GUI.AddMessage(TextManager.GetWithVariable(
"SubNameIllegalCharsWarning",
"[illegalchar]", illegalChar.ToString()), GUIStyle.Red);
200 string path = Path.Combine(exportPath, $
"{nameInput.Text}.xml");
201 File.WriteAllText(path, save.ToString(), catchUnauthorizedAccessExceptions:
false);
202 AskForConfirmation(TextManager.Get(
"EventEditor.OpenTextHeader"), TextManager.Get(
"EventEditor.OpenTextBody"), () =>
204 ToolBox.OpenFileWithShell(path);
207 GUI.AddMessage($
"XML exported to {path}", GUIStyle.Green);
213 DebugConsole.ThrowError(
"Failed to export event", e);
218 GUI.AddMessage(
"Unable to export because the project contains errors", GUIStyle.Red);
224 private bool TryCreateNewProject(GUIButton button,
object o)
226 AskForConfirmation(TextManager.Get(
"EventEditor.NewProject"), TextManager.Get(
"EventEditor.NewProjectPrompt"), () =>
230 selectedNodes.Clear();
231 projectName = TextManager.Get(
"EventEditor.Unnamed").Value;
237 public static GUIMessageBox AskForConfirmation(LocalizedString header, LocalizedString body, Func<bool> onConfirm,
GUISoundType? overrideConfirmButtonSound =
null)
239 LocalizedString[] buttons = { TextManager.Get(
"Ok"), TextManager.Get(
"Cancel") };
240 GUIMessageBox msgBox =
new GUIMessageBox(header, body, buttons);
243 msgBox.Buttons[1].OnClicked = delegate
250 msgBox.Buttons[0].OnClicked = delegate
256 if (overrideConfirmButtonSound.HasValue)
258 msgBox.Buttons[0].ClickSound = overrideConfirmButtonSound.Value;
263 private bool SaveProjectToFile(GUIButton button,
object o)
265 string directory = Path.GetFullPath(
"EventProjects");
267 if (!Directory.Exists(directory))
269 Directory.CreateDirectory(directory);
272 var msgBox =
new GUIMessageBox(TextManager.Get(
"EventEditor.NameFilePrompt"),
"",
new[] { TextManager.Get(
"Cancel"), TextManager.Get(
"Save") },
new Vector2(0.2f, 0.175f), minSize:
new Point(300, 175));
273 var layout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal:
true);
274 GUITextBox nameInput =
new GUITextBox(
new RectTransform(Vector2.One, layout.RectTransform)) { Text = projectName };
277 msgBox.Buttons[0].OnClicked = delegate
284 msgBox.Buttons[1].OnClicked = delegate
286 foreach (var illegalChar
in Path.GetInvalidFileNameCharsCrossPlatform())
288 if (!nameInput.Text.Contains(illegalChar)) {
continue; }
290 GUI.AddMessage(TextManager.GetWithVariable(
"SubNameIllegalCharsWarning",
"[illegalchar]", illegalChar.ToString()), GUIStyle.Red);
295 projectName = nameInput.Text;
296 XElement save = SaveEvent(projectName);
297 string filePath = System.IO.Path.Combine(directory, $
"{projectName}.sevproj");
298 File.WriteAllText(Path.Combine(directory, $
"{projectName}.sevproj"), save.ToString());
299 GUI.AddMessage($
"Project saved to {filePath}", GUIStyle.Green);
301 AskForConfirmation(TextManager.Get(
"EventEditor.TestPromptHeader"), TextManager.Get(
"EventEditor.TestPromptBody"), CreateTestSetupMenu);
307 private bool Load(EventPrefab? prefab)
309 if (prefab ==
null) {
return false; }
311 AskForConfirmation(TextManager.Get(
"EventEditor.NewProject"), TextManager.Get(
"EventEditor.NewProjectPrompt"), () =>
314 selectedNodes.Clear();
316 if (isTraitorEventBox != null)
318 isTraitorEventBox.Selected = prefab is TraitorEventPrefab;
320 bool hadNodes =
true;
321 CreateNodes(prefab.ConfigElement, ref hadNodes);
324 GUI.NotifyPrompt(TextManager.Get(
"EventEditor.RandomGenerationHeader"), TextManager.Get(
"EventEditor.RandomGenerationBody"));
331 private bool AddAction(Type? type)
333 if (type ==
null) {
return false; }
335 Vector2 spawnPos = Cam.WorldViewCenter;
336 spawnPos.Y = -spawnPos.Y;
337 EventNode newNode =
new EventNode(type, type.Name) { ID = CreateID() };
338 newNode.Position = spawnPos - newNode.Size / 2;
339 nodeList.Add(newNode);
343 private bool AddValue(Type? type)
345 if (type ==
null) {
return false; }
347 Vector2 spawnPos = Cam.WorldViewCenter;
348 spawnPos.Y = -spawnPos.Y;
349 ValueNode newValue =
new ValueNode(type, type.Name) { ID = CreateID() };
350 newValue.Position = spawnPos - newValue.Size / 2;
351 nodeList.Add(newValue);
355 private bool AddSpecial(Type? type)
357 if (type ==
null) {
return false; }
358 Vector2 spawnPos = Cam.WorldViewCenter;
359 spawnPos.Y = -spawnPos.Y;
361 ConstructorInfo? constructor = type.GetConstructor(Array.Empty<Type>());
362 SpecialNode? newNode =
null;
363 if (constructor !=
null)
365 newNode = constructor.Invoke(Array.Empty<
object>()) as SpecialNode;
369 newNode.ID = CreateID();
370 newNode.Position = spawnPos - newNode.Size / 2;
371 nodeList.Add(newNode);
377 private void CreateNodes(ContentXElement element, ref
bool hadNodes, EditorNode? parent =
null,
int ident = 0)
379 EditorNode? lastNode =
null;
380 foreach (var subElement
in element.Elements())
383 switch (subElement.Name.ToString().ToLowerInvariant())
388 CreateNodes(subElement, ref hadNodes, parent, ident);
397 Vector2 defaultNodePos =
new Vector2(-16000, -16000);
399 Type? t = Type.GetType($
"Barotrauma.{subElement.Name}");
400 if (t !=
null && EditorNode.IsInstanceOf(t, typeof(EventAction)))
402 newNode =
new EventNode(t, subElement.Name.ToString()) { Position =
new Vector2(ident, 0), ID = CreateID() };
406 newNode =
new CustomNode(subElement.Name.ToString()) { Position =
new Vector2(ident, 0), ID = CreateID() };
407 foreach (XAttribute attribute
in subElement.Attributes().Where(attribute => !attribute.ToString().StartsWith(
"_")))
409 newNode.Connections.Add(
new EventEditorNodeConnection(newNode, NodeConnectionType.Value, attribute.Name.ToString(), typeof(
string)));
413 Vector2 npos = subElement.GetAttributeVector2(
"_npos", defaultNodePos);
414 if (npos != defaultNodePos)
416 newNode.Position = npos;
423 var parentElement = subElement.Parent;
424 foreach (var xElement
in subElement.Elements())
426 switch (xElement.Name.ToString().ToLowerInvariant())
429 EventEditorNodeConnection optionConnection =
new EventEditorNodeConnection(newNode, NodeConnectionType.Option)
431 OptionText = xElement.GetAttributeString(
"text",
string.Empty),
432 EndConversation = xElement.GetAttributeBool(
"endconversation",
false)
434 newNode.Connections.Add(optionConnection);
439 foreach (EventEditorNodeConnection connection
in newNode.Connections)
441 if (connection.Type == NodeConnectionType.Value)
443 foreach (XAttribute attribute
in subElement.Attributes())
445 if (
string.Equals(connection.Attribute, attribute.Name.ToString(), StringComparison.InvariantCultureIgnoreCase) && connection.ValueType !=
null)
447 if (connection.ValueType.IsEnum)
449 Array values = Enum.GetValues(connection.ValueType);
450 foreach (
object? @
enum in values)
452 if (
string.Equals(@
enum?.ToString(), attribute.Value, StringComparison.InvariantCultureIgnoreCase))
454 connection.OverrideValue = @
enum;
462 connection.OverrideValue = ChangeType(attribute.Value, connection.ValueType);
466 DebugConsole.ThrowError($
"Failed to convert the value {attribute.Value} of the attribute {attribute.Name} to {connection.ValueType}.");
474 if (npos == defaultNodePos)
477 bool Predicate(EditorNode node) =>
Rectangle.Union(node.GetDrawRectangle(), node.HeaderRectangle).Intersects(
Rectangle.Union(newNode.GetDrawRectangle(), newNode.HeaderRectangle));
479 while (nodeList.Any(Predicate))
481 EditorNode? otherNode = nodeList.Find(Predicate);
482 if (otherNode !=
null)
484 newNode.Position +=
new Vector2(128, otherNode.GetDrawRectangle().Height + otherNode.HeaderRectangle.Height +
new Random().Next(128, 256));
489 if (subElement.Name.ToString().ToLowerInvariant() is
"text" or
"conditional")
491 parent?.AddConnection(NodeConnectionType.Add);
492 parent?.Connect(newNode, NodeConnectionType.Add);
496 if (parentElement?.FirstElement() == subElement)
498 switch (parentElement?.Name.ToString().ToLowerInvariant())
501 parent?.Connect(newNode, NodeConnectionType.Failure);
504 parent?.Connect(newNode, NodeConnectionType.Success);
506 case "onroundendaction":
507 parent?.Connect(newNode, NodeConnectionType.Next);
512 EventEditorNodeConnection? activateConnection = newNode.Connections.Find(connection => connection.Type == NodeConnectionType.Activate);
513 EventEditorNodeConnection? optionConnection = parent.Connections.FirstOrDefault(connection =>
514 connection.Type == NodeConnectionType.Option &&
string.Equals(connection.OptionText, parentElement.GetAttributeString(
"text",
string.Empty), StringComparison.Ordinal));
516 if (activateConnection !=
null)
518 optionConnection?.ConnectedTo.Add(activateConnection);
523 parent?.Connect(newNode, NodeConnectionType.Add);
529 lastNode?.Connect(newNode, NodeConnectionType.Next);
534 nodeList.Add(newNode);
536 CreateNodes(subElement, ref hadNodes, newNode, ident);
545 private static RectTransform RectTransform(
float x,
float y, GUIComponent parent,
Anchor anchor =
Anchor.TopRight)
547 return new RectTransform(
new Vector2(x, y), parent.RectTransform, anchor);
550 public override void Select()
552 GUI.PreventPauseMenuToggle =
false;
553 projectName = TextManager.Get(
"EventEditor.Unnamed").Value;
557 public override void AddToGUIUpdateList()
559 GuiFrame.AddToGUIUpdateList();
562 public static object? ChangeType(
string value, Type type)
564 if (type == typeof(Identifier))
566 return value.ToIdentifier();
570 return Convert.ChangeType(value, type);
574 private XElement? ExportXML()
576 XElement mainElement =
new XElement(
577 isTraitorEventBox is {
Selected:
true } ? nameof(TraitorEvent) : nameof(ScriptedEvent),
578 new XAttribute(
"identifier", projectName.RemoveWhitespace().ToLowerInvariant()));
579 EditorNode? startNode =
null;
580 foreach (EditorNode eventNode
in nodeList.Where(node => node is EventNode || node is SpecialNode))
582 if (eventNode.GetParent() ==
null)
584 if (startNode !=
null)
586 DebugConsole.ThrowError(
"You have more than one start node, only one will be picked while the others will get ignored.");
588 startNode ??= eventNode;
592 if (startNode ==
null) {
return null; }
594 ExportChildNodes(startNode, mainElement);
599 private void ExportChildNodes(EditorNode startNode, XElement parent)
601 XElement? newElement = startNode.ToXML();
602 if (newElement ==
null) {
return; }
603 parent.Add(newElement);
605 EditorNode? success = startNode.GetNext(NodeConnectionType.Success);
606 EditorNode? failure = startNode.GetNext(NodeConnectionType.Failure);
607 EditorNode? add = startNode.GetNext(NodeConnectionType.Add);
608 Tuple<EditorNode?, string?, bool>[] options = startNode is EventNode eNode ? eNode.GetOptions() :
new Tuple<EditorNode?, string?, bool>[0];
612 XElement successElement =
new XElement(
"Success");
613 ExportChildNodes(success, successElement);
614 newElement.Add(successElement);
619 XElement failureElement =
new XElement(
"Failure");
620 ExportChildNodes(failure, failureElement);
621 newElement.Add(failureElement);
624 if (add is CustomNode custom)
626 ExportChildNodes(custom, newElement);
629 foreach (var (node, text, end) in options)
631 XElement optionElement =
new XElement(
"Option");
632 optionElement.Add(
new XAttribute(
"text", text ??
""));
633 if (end) { optionElement.Add(
new XAttribute(
"endconversation",
true)); }
635 if (node is EventNode eventNode)
637 ExportChildNodes(eventNode, optionElement);
640 newElement.Add(optionElement);
643 EditorNode? next = startNode.GetNext();
646 ExportChildNodes(next, parent);
650 private static XElement SaveEvent(
string name)
652 XElement mainElement =
new XElement(
"SavedEvent",
new XAttribute(
"name", name));
653 XElement nodes =
new XElement(
"Nodes");
654 foreach (var editorNode
in nodeList)
656 nodes.Add(editorNode.Save());
659 mainElement.Add(nodes);
661 XElement connections =
new XElement(
"AllConnections");
662 foreach (var editorNode
in nodeList)
664 connections.Add(editorNode.SaveConnections());
667 mainElement.Add(connections);
671 private static void Load(XElement saveElement)
674 projectName = saveElement.GetAttributeString(
"name", TextManager.Get(
"EventEditor.Unnamed").Value);
675 foreach (XElement element
in saveElement.Elements())
677 switch (element.Name.ToString().ToLowerInvariant())
681 foreach (var subElement
in element.Elements())
683 EditorNode? node = EditorNode.Load(subElement);
692 case "allconnections":
694 foreach (var subElement
in element.Elements())
696 int id = subElement.GetAttributeInt(
"i", -1);
697 EditorNode? node = nodeList.Find(editorNode => editorNode.ID ==
id);
698 node?.LoadConnections(subElement);
707 private static void CreateContextMenu(EditorNode node, EventEditorNodeConnection? connection =
null)
709 if (GUIContextMenu.CurrentContextMenu !=
null) {
return; }
711 GUIContextMenu.CreateContextMenu(
712 new ContextMenuOption(
"EventEditor.Edit", isEnabled: node is ValueNode || connection?.Type == NodeConnectionType.Value || connection?.Type == NodeConnectionType.Option, onSelected: delegate
714 CreateEditMenu(node as ValueNode, connection);
716 new ContextMenuOption(
"EventEditor.MarkEnding", isEnabled: connection !=
null && connection.Type == NodeConnectionType.Option, onSelected: delegate
718 if (connection == null) { return; }
720 connection.EndConversation = !connection.EndConversation;
722 new ContextMenuOption(
"EventEditor.RemoveConnection", isEnabled: connection !=
null, onSelected: delegate
724 if (connection ==
null) {
return; }
726 connection.ClearConnections();
727 connection.OverrideValue =
null;
728 connection.OptionText = connection.OptionText;
730 new ContextMenuOption(
"EventEditor.AddOption", isEnabled: node.CanAddConnections, onSelected: node.AddOption),
731 new ContextMenuOption(
"EventEditor.RemoveOption", isEnabled: connection !=
null && node.RemovableTypes.Contains(connection.Type), onSelected: delegate
733 connection?.Parent.RemoveOption(connection);
735 new ContextMenuOption(
"EventEditor.Delete", isEnabled:
true, onSelected: delegate
737 nodeList.Remove(node);
738 node.ClearConnections();
742 private bool CreateTestSetupMenu()
744 var msgBox =
new GUIMessageBox(TextManager.Get(
"EventEditor.TestPromptHeader"),
"",
new[] { TextManager.Get(
"Cancel"), TextManager.Get(
"OK") },
745 relativeSize:
new Vector2(0.2f, 0.3f), minSize:
new Point(300, 175));
747 var layout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.5f), msgBox.Content.RectTransform));
749 new GUITextBlock(
new RectTransform(
new Vector2(1, 0.25f), layout.RectTransform), TextManager.Get(
"EventEditor.OutpostGenParams"), font: GUIStyle.SubHeadingFont);
750 GUIDropDown paramInput =
new GUIDropDown(
new RectTransform(
new Vector2(1, 0.25f), layout.RectTransform),
string.Empty, OutpostGenerationParams.OutpostParams.Count());
751 foreach (OutpostGenerationParams param
in OutpostGenerationParams.OutpostParams)
753 paramInput.AddItem(param.Identifier.Value!, param);
755 paramInput.OnSelected = (_, param) =>
757 lastTestParam = param as OutpostGenerationParams;
760 paramInput.SelectItem(lastTestParam ?? OutpostGenerationParams.OutpostParams.FirstOrDefault());
762 new GUITextBlock(
new RectTransform(
new Vector2(1, 0.25f), layout.RectTransform), TextManager.Get(
"EventEditor.LocationType"), font: GUIStyle.SubHeadingFont);
763 GUIDropDown typeInput =
new GUIDropDown(
new RectTransform(
new Vector2(1, 0.25f), layout.RectTransform),
string.Empty, LocationType.Prefabs.Count());
764 foreach (LocationType type
in LocationType.Prefabs)
766 typeInput.AddItem(type.Identifier.Value!, type);
768 typeInput.OnSelected = (_, type) =>
770 lastTestType = type as LocationType;
773 typeInput.SelectItem(lastTestType ?? LocationType.Prefabs.FirstOrDefault());
776 msgBox.Buttons[0].OnClicked = (button, o) =>
783 msgBox.Buttons[1].OnClicked = (button, o) =>
785 TestEvent(lastTestParam, lastTestType);
793 private static void CreateEditMenu(ValueNode? node, EventEditorNodeConnection? connection =
null)
799 newValue = node.Value;
802 else if (connection !=
null)
804 newValue = connection.OverrideValue;
805 type = connection.ValueType;
812 if (connection?.Type == NodeConnectionType.Option)
814 newValue = connection.OptionText;
815 type = typeof(
string);
818 if (type ==
null) {
return; }
820 Vector2 size = type == typeof(
string) ?
new Vector2(0.2f, 0.3f) : new Vector2(0.2f, 0.175f);
821 var msgBox =
new GUIMessageBox(TextManager.Get(
"EventEditor.Edit"),
"",
new[] { TextManager.Get(
"Cancel"), TextManager.Get(
"OK") }, size, minSize:
new Point(300, 175));
823 Vector2 layoutSize = type == typeof(
string) ?
new Vector2(1f, 0.5f) : new Vector2(1f, 0.25f);
824 var layout =
new GUILayoutGroup(
new RectTransform(layoutSize, msgBox.Content.RectTransform), isHorizontal:
true);
828 Array enums = Enum.GetValues(type);
829 GUIDropDown valueInput =
new GUIDropDown(
new RectTransform(Vector2.One, layout.RectTransform), newValue?.ToString() ??
"", enums.Length);
830 foreach (
object? @
enum in enums) { valueInput.AddItem(@
enum?.ToString() ??
"", @
enum); }
832 valueInput.OnSelected += (component, o) =>
840 if (type == typeof(
string))
842 GUIListBox listBox =
new GUIListBox(
new RectTransform(Vector2.One, layout.RectTransform)) { CanBeFocused =
false };
843 GUITextBox valueInput =
new GUITextBox(
new RectTransform(Vector2.One, listBox.Content.RectTransform,
Anchor.TopRight), wrap:
true, style:
"GUITextBoxNoBorder");
844 valueInput.OnTextChanged += (component, o) =>
846 Vector2 textSize = valueInput.Font.MeasureString(valueInput.WrappedText);
847 valueInput.RectTransform.NonScaledSize =
new Point(valueInput.RectTransform.NonScaledSize.X, (
int)textSize.Y + 10);
848 listBox.UpdateScrollBarSize();
849 listBox.BarScroll = 1.0f;
853 valueInput.Text = newValue?.ToString() ??
"<type here>";
855 else if (type == typeof(Identifier))
857 GUITextBox valueInput =
new GUITextBox(
new RectTransform(Vector2.One, layout.RectTransform), newValue?.ToString() ??
string.Empty);
858 valueInput.OnTextChanged += (component, o) =>
860 newValue =
new Identifier(o);
864 else if (type == typeof(
float))
866 GUINumberInput valueInput =
new GUINumberInput(
new RectTransform(Vector2.One, layout.RectTransform),
NumberType.Float);
867 if (newValue is
float floatVal)
869 valueInput.FloatValue = floatVal;
871 valueInput.OnValueChanged += component => { newValue = component.FloatValue; };
873 else if (type == typeof(
int))
875 GUINumberInput valueInput =
new GUINumberInput(
new RectTransform(Vector2.One, layout.RectTransform),
NumberType.Int);
876 if (newValue is
int intVal)
878 valueInput.IntValue = intVal;
880 valueInput.OnValueChanged += component => { newValue = component.IntValue; };
882 else if (type == typeof(
bool))
884 GUITickBox valueInput =
new GUITickBox(
new RectTransform(Vector2.One, layout.RectTransform),
"Value");
885 if (newValue is
bool val)
887 valueInput.Selected = val;
889 valueInput.OnSelected += component =>
891 newValue = component.Selected;
898 msgBox.Buttons[0].OnClicked = (button, o) =>
905 msgBox.Buttons[1].OnClicked = (button, o) =>
909 node.Value = newValue;
911 else if (connection !=
null)
913 if (connection.Type == NodeConnectionType.Option)
915 connection.OptionText = newValue?.ToString();
919 connection.ClearConnections();
920 connection.OverrideValue = newValue;
929 private bool TestEvent(OutpostGenerationParams? param, LocationType? type)
931 SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.HasTag(
SubmarineTag.Shuttle)) ??
throw new NullReferenceException(
"Could not test event: There are no shuttles available.");
933 XElement? eventXml = ExportXML();
935 if (eventXml !=
null)
937 prefab = EventPrefab.Create(eventXml.FromPackage(
null), file:
null);
941 GUI.AddMessage(
"Unable to open test enviroment because the event contains errors.", GUIStyle.Red);
945 GameSession gameSession =
new GameSession(subInfo, Option.None, CampaignDataPath.Empty, GameModePreset.TestMode, CampaignSettings.Empty,
null);
946 TestGameMode gameMode = ((TestGameMode?)gameSession.GameMode) ??
throw new InvalidCastException();
948 gameMode.SpawnOutpost =
true;
949 gameMode.OutpostParams = param;
950 gameMode.OutpostType = type;
951 gameMode.TriggeredEvent = prefab;
952 gameMode.OnRoundEnd = () =>
955 GameMain.EventEditorScreen.Select();
958 GameMain.GameScreen.Select();
959 gameSession.StartRound(
null,
false);
963 public override void Draw(
double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
965 DrawnTooltip =
string.Empty;
966 Cam.UpdateTransform();
969 spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: Cam.Transform);
970 graphics.Clear(
new Color(0.2f, 0.2f, 0.2f, 1.0f));
972 foreach (EditorNode node
in nodeList.Where(node => node is SpecialNode))
974 node.Draw(spriteBatch);
978 foreach (EditorNode node
in nodeList.Where(node => node is ValueNode))
980 node.Draw(spriteBatch);
983 foreach (EditorNode node
in nodeList.Where(node => node is EventNode))
985 node.Draw(spriteBatch);
988 draggedNode?.Draw(spriteBatch);
989 foreach (var (node, _) in markedNodes)
991 node.Draw(spriteBatch);
997 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
998 GUI.Draw(Cam, spriteBatch);
1000 if (!
string.IsNullOrWhiteSpace(DrawnTooltip) && GUIStyle.SmallFont.Value !=
null)
1002 string tooltip = ToolBox.WrapText(DrawnTooltip, 256.0f, GUIStyle.SmallFont.Value);
1003 GUI.DrawString(spriteBatch, PlayerInput.MousePosition +
new Vector2(32, 32), tooltip, Color.White, Color.Black * 0.8f, 4, GUIStyle.SmallFont);
1009 public override void Update(
double deltaTime)
1011 if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y)
1016 Cam.MoveCamera((
float) deltaTime, allowMove:
true, allowZoom: GUI.MouseOn ==
null);
1017 Vector2 mousePos = Cam.ScreenToWorld(PlayerInput.MousePosition);
1018 mousePos.Y = -mousePos.Y;
1020 foreach (EditorNode node
in nodeList)
1022 if (PlayerInput.PrimaryMouseButtonDown())
1024 EventEditorNodeConnection? connection = node.GetConnectionOnMouse(mousePos);
1025 if (connection !=
null && connection.Type.NodeSide == NodeConnectionType.Side.Right)
1027 if (connection.Type != NodeConnectionType.Out)
1029 if (connection.ConnectedTo.Any()) {
return; }
1032 DraggedConnection = connection;
1037 if (node.IsHighlighted = node.HeaderRectangle.Contains(mousePos))
1039 if (PlayerInput.PrimaryMouseButtonDown())
1042 if (PlayerInput.IsCtrlDown())
1044 if (selectedNodes.Contains(node))
1046 selectedNodes.Remove(node);
1050 selectedNodes.Add(node);
1053 node.IsSelected = selectedNodes.Contains(node);
1058 dragOffset = draggedNode.Position - mousePos;
1059 foreach (EditorNode selectedNode
in selectedNodes)
1061 if (!markedNodes.ContainsKey(selectedNode))
1063 markedNodes.Add(selectedNode, selectedNode.Position - mousePos);
1069 if (PlayerInput.SecondaryMouseButtonClicked())
1071 EventEditorNodeConnection? connection = node.GetConnectionOnMouse(mousePos);
1072 if (node.GetDrawRectangle().Contains(mousePos) || connection !=
null)
1074 CreateContextMenu(node, node.GetConnectionOnMouse(mousePos));
1080 if (PlayerInput.SecondaryMouseButtonClicked())
1082 foreach (var selectedNode
in selectedNodes)
1084 selectedNode.IsSelected =
false;
1087 selectedNodes.Clear();
1090 if (draggedNode !=
null)
1092 if (!PlayerInput.PrimaryMouseButtonHeld())
1095 markedNodes.Clear();
1099 Vector2 offsetChange = Vector2.Zero;
1100 draggedNode.IsHighlighted =
true;
1101 draggedNode.Position = mousePos + dragOffset;
1103 if (PlayerInput.KeyHit(Keys.Up)) { offsetChange.Y--; }
1105 if (PlayerInput.KeyHit(Keys.Down)) { offsetChange.Y++; }
1107 if (PlayerInput.KeyHit(Keys.Left)) { offsetChange.X--; }
1109 if (PlayerInput.KeyHit(Keys.Right)) { offsetChange.X++; }
1111 dragOffset += offsetChange;
1113 foreach (var (editorNode, offset) in markedNodes.Where(pair => pair.Key != draggedNode))
1115 editorNode.Position = mousePos + offset;
1118 if (offsetChange != Vector2.Zero)
1120 foreach (var (key, value) in markedNodes.ToList())
1122 markedNodes[key] = value + offsetChange;
1128 if (DraggedConnection !=
null)
1130 if (!PlayerInput.PrimaryMouseButtonHeld())
1132 foreach (EditorNode node
in nodeList)
1134 var nodeOnMouse = node.GetConnectionOnMouse(mousePos);
1135 if (nodeOnMouse !=
null && nodeOnMouse != DraggedConnection && nodeOnMouse.Type.NodeSide == NodeConnectionType.Side.Left)
1137 if (!DraggedConnection.CanConnect(nodeOnMouse)) {
continue; }
1139 nodeOnMouse.ClearConnections();
1140 EditorNode.Connect(DraggedConnection, nodeOnMouse);
1145 DraggedConnection =
null;
1149 DraggingPosition = mousePos;
1154 DraggingPosition = Vector2.Zero;
1157 if (PlayerInput.MidButtonHeld())
1159 Vector2 moveSpeed = PlayerInput.MouseSpeed * (float) deltaTime * 60.0f / Cam.Zoom;
1160 moveSpeed.X = -moveSpeed.X;
1161 Cam.Position += moveSpeed;
1164 base.Update(deltaTime);