5 using Microsoft.Xna.Framework;
6 using Microsoft.Xna.Framework.Graphics;
7 using Microsoft.Xna.Framework.Input;
9 using System.Collections.Generic;
10 using System.Collections.Immutable;
12 using System.Threading;
13 using System.Xml.Linq;
31 private readonly record
struct LayerData(bool IsVisible =
true,
bool IsGrouped =
false);
53 ShadowCastingLightCount,
55 LowOxygenOutputWarning,
62 private readonly Point defaultPreviewImageSize =
new Point(640, 368);
64 private readonly
Camera cam;
65 private Vector2 camTargetFocus = Vector2.Zero;
69 private readonly HashSet<ulong> publishedWorkshopItemIds =
new HashSet<ulong>();
71 private Point screenResolution;
73 private bool lightingEnabled;
75 private bool wasSelectedBefore;
79 private readonly List<GUITickBox> showEntitiesTickBoxes =
new List<GUITickBox>();
80 private readonly Dictionary<string, bool> hiddenSubCategories =
new Dictionary<string, bool>();
86 private bool entityMenuOpen =
true;
87 private float entityMenuOpenState = 1.0f;
88 private string lastFilter;
91 private GUIListBox categorizedEntityList, allEntityList;
96 private GUITickBox defaultModeTickBox, wiringModeTickBox;
102 private GUIButton selectedCategoryButton;
104 private readonly List<GUIButton> entityCategoryButtons =
new List<GUIButton>();
112 const int PreviouslyUsedCount = 10;
113 private GUIFrame previouslyUsedPanel;
119 private List<GUIButton> layerSpecificButtons =
new List<GUIButton>();
122 private GUIFrame undoBufferDisclaimer;
128 private static int MaxAutoSaves => GameSettings.CurrentConfig.MaxAutoSaves;
130 public static readonly
object ItemAddMutex =
new object(), ItemRemoveMutex =
new object();
136 private static object bulkItemBufferinUse;
140 get => bulkItemBufferinUse;
143 if (value != bulkItemBufferinUse && bulkItemBufferinUse !=
null)
145 CommitBulkItemBuffer();
148 bulkItemBufferinUse = value;
151 public static List<AddOrDeleteCommand>
BulkItemBuffer =
new List<AddOrDeleteCommand>();
171 private Item OpenedItem;
176 private Vector2 oldItemPosition;
182 public static readonly List<Command>
Commands =
new List<Command>();
183 private static int commandIndex;
187 private Option<DateTime> editorSelectedTime;
192 private const int submarineNameLimit = 30;
195 private const int submarineDescriptionLimit = 500;
196 private GUITextBlock submarineDescriptionCharacterCount;
200 private Vector2 MeasurePositionStart = Vector2.Zero;
203 private bool lockMode;
205 private static bool isAutoSaving;
214 private static readonly
string autoSavePath = Path.Combine(
"Submarines",
".AutoSaves");
215 private static readonly
string autoSaveInfoPath = Path.Combine(autoSavePath,
"autosaves.xml");
217 private static string GetSubDescription()
219 if (MainSub?.Info !=
null)
221 LocalizedString localizedDescription = TextManager.Get($
"submarine.description.{MainSub.Info.Name ?? ""}");
222 if (!localizedDescription.IsNullOrEmpty()) {
return localizedDescription.
Value; }
230 return $
"{TextManager.Get("TotalHullVolume
")}:\n{Hull.HullList.Sum(h => h.Volume)}";
233 private static LocalizedString GetSelectedHullVolume()
235 float buoyancyVol = 0.0f;
236 float selectedVol = 0.0f;
237 float neutralPercentage = SubmarineBody.NeutralBallastPercentage;
238 Hull.HullList.ForEach(h =>
240 buoyancyVol += h.Volume;
243 selectedVol += h.Volume;
246 buoyancyVol *= neutralPercentage;
247 string retVal = $
"{TextManager.Get("SelectedHullVolume
")}:\n{selectedVol}";
248 if (selectedVol > 0.0f && buoyancyVol > 0.0f)
250 if (buoyancyVol / selectedVol < 1.0f)
252 retVal += $
" ({TextManager.GetWithVariable("OptimalBallastLevel
", "[value]
", (buoyancyVol / selectedVol).ToString("0.0000
"))})";
256 retVal += $
" ({TextManager.Get("InsufficientBallast
")})";
264 private static readonly Dictionary<string, LayerData> Layers =
new Dictionary<string, LayerData>();
279 private void CreateUI()
281 TopPanel =
new GUIFrame(
new RectTransform(
new Vector2(GUI.Canvas.RelativeSize.X, 0.01f), GUI.Canvas) { MinSize = new Point(0, 35) },
"GUIFrameTop");
284 isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
286 RelativeSpacing = 0.005f
289 new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
"GUIButtonToggleLeft")
291 ToolTip = TextManager.Get(
"back"),
292 OnClicked = (b, d) =>
294 var msgBox =
new GUIMessageBox(
"", TextManager.Get(
"PauseMenuQuitVerificationEditor"),
new[] { TextManager.Get(
"Yes"), TextManager.Get(
"Cancel") })
296 UserData =
"verificationprompt"
298 msgBox.Buttons[0].OnClicked = (yesBtn, userdata) =>
300 GUIMessageBox.CloseAll();
301 GameMain.MainMenuScreen.Select();
304 msgBox.Buttons[0].OnClicked += msgBox.Close;
305 msgBox.Buttons[1].OnClicked = (_, userdata) =>
314 new GUIFrame(
new RectTransform(
new Vector2(0.01f, 0.9f), paddedTopPanel.
RectTransform), style:
"VerticalLine");
316 new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
"OpenButton")
318 ToolTip = TextManager.Get(
"OpenSubButton"),
319 OnClicked = (btn, data) =>
328 new GUIFrame(
new RectTransform(
new Vector2(0.01f, 0.9f), paddedTopPanel.
RectTransform), style:
"VerticalLine");
330 new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
"SaveButton")
332 ToolTip = RichString.Rich(TextManager.Get(
"SaveSubButton") +
"‖color:125,125,125‖\nCtrl + S‖color:end‖"),
333 OnClicked = (btn, data) =>
336 if (ContentPackageManager.EnabledPackages.All.Any(cp => cp != ContentPackageManager.VanillaCorePackage && cp.Files.Any(f => f is not BaseSubFile)))
338 var msgBox =
new GUIMessageBox(
"DEBUG-ONLY WARNING",
"You currently have some mods enabled. Are you sure you want to save the submarine? If the mods override any vanilla content, saving the submarine may cause unintended changes.",
339 new LocalizedString[] {
"Yes, I know what I'm doing",
"Cancel" });
340 msgBox.Buttons[0].OnClicked = (btn, data) =>
347 msgBox.Buttons[1].OnClicked += msgBox.Close;
357 new GUIFrame(
new RectTransform(
new Vector2(0.01f, 0.9f), paddedTopPanel.
RectTransform), style:
"VerticalLine");
359 new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
"TestButton")
361 ToolTip = TextManager.Get(
"TestSubButton"),
362 OnClicked = TestSubmarine
365 new GUIFrame(
new RectTransform(
new Vector2(0.01f, 0.9f), paddedTopPanel.
RectTransform), style:
"VerticalLine");
367 visibilityButton =
new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight),
"", style:
"SetupVisibilityButton")
369 ToolTip = TextManager.Get(
"SubEditorVisibilityButton") +
'\n' + TextManager.Get(
"SubEditorVisibilityToolTip"),
370 OnClicked = (btn, userData) =>
372 previouslyUsedPanel.
Visible =
false;
373 undoBufferPanel.
Visible =
false;
381 new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight),
"", style:
"EditorLayerButton")
383 ToolTip = TextManager.Get(
"editor.layer.button") +
'\n' + TextManager.Get(
"editor.layer.tooltip"),
384 OnClicked = (btn, userData) =>
386 previouslyUsedPanel.
Visible =
false;
388 undoBufferPanel.
Visible =
false;
395 var previouslyUsedButton =
new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight),
"", style:
"RecentlyUsedButton")
397 ToolTip = TextManager.Get(
"PreviouslyUsedLabel"),
398 OnClicked = (btn, userData) =>
401 undoBufferPanel.
Visible =
false;
409 var undoBufferButton =
new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight),
"", style:
"UndoHistoryButton")
411 ToolTip = TextManager.Get(
"Editor.UndoHistoryButton"),
412 OnClicked = (btn, userData) =>
415 previouslyUsedPanel.
Visible =
false;
423 new GUIFrame(
new RectTransform(
new Vector2(0.01f, 0.9f), paddedTopPanel.
RectTransform), style:
"VerticalLine");
425 subNameLabel =
new GUITextBlock(
new RectTransform(
new Vector2(0.3f, 0.9f), paddedTopPanel.
RectTransform,
Anchor.CenterLeft),
426 TextManager.Get(
"unspecifiedsubfilename"), font: GUIStyle.LargeFont, textAlignment: Alignment.CenterLeft);
428 linkedSubBox =
new GUIDropDown(
new RectTransform(
new Vector2(0.15f, 0.9f), paddedTopPanel.
RectTransform),
429 TextManager.Get(
"AddSubButton"), elementCount: 20)
431 ToolTip = TextManager.Get(
"AddSubToolTip")
434 List<(
string Name, SubmarineInfo Sub)> subs =
new List<(
string Name, SubmarineInfo Sub)>();
436 foreach (SubmarineInfo sub
in SubmarineInfo.SavedSubmarines)
439 subs.Add((sub.Name, sub));
442 foreach (var (name, sub) in subs.OrderBy(tuple => tuple.Name))
444 linkedSubBox.
AddItem(name, sub);
448 linkedSubBox.
OnDropped += (component, obj) =>
450 MapEntity.SelectedList.Clear();
454 var spacing =
new GUIFrame(
new RectTransform(
new Vector2(0.02f, 1.0f), paddedTopPanel.
RectTransform), style:
null);
455 new GUIFrame(
new RectTransform(
new Vector2(0.1f, 0.9f), spacing.RectTransform,
Anchor.Center), style:
"VerticalLine");
457 defaultModeTickBox =
new GUITickBox(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight),
"", style:
"EditSubButton")
459 ToolTip = RichString.Rich(TextManager.Get(
"SubEditorEditingMode") +
"‖color:125,125,125‖\nCtrl + 1‖color:end‖"),
473 wiringModeTickBox =
new GUITickBox(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight),
"", style:
"WiringModeButton")
475 ToolTip = RichString.Rich(TextManager.Get(
"WiringModeButton") +
'\n' + TextManager.Get(
"WiringModeToolTip") +
"‖color:125,125,125‖\nCtrl + 2‖color:end‖"),
488 spacing =
new GUIFrame(
new RectTransform(
new Vector2(0.02f, 1.0f), paddedTopPanel.
RectTransform), style:
null);
489 new GUIFrame(
new RectTransform(
new Vector2(0.1f, 0.9f), spacing.RectTransform,
Anchor.Center), style:
"VerticalLine");
491 new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.9f), paddedTopPanel.
RectTransform, scaleBasis:
ScaleBasis.BothHeight),
"", style:
"GenerateWaypointsButton")
493 ToolTip = TextManager.Get(
"GenerateWaypointsButton") +
'\n' + TextManager.Get(
"GenerateWaypointsToolTip"),
494 OnClicked = (btn, userdata) =>
496 if (WayPoint.WayPointList.Any())
498 var generateWaypointsVerification =
new GUIMessageBox(
"", TextManager.Get(
"generatewaypointsverification"),
new[] { TextManager.Get(
"ok"), TextManager.Get(
"cancel") });
499 generateWaypointsVerification.Buttons[0].OnClicked = delegate
501 if (GenerateWaypoints())
503 GUI.AddMessage(TextManager.Get(
"waypointsgeneratedsuccesfully"), GUIStyle.Green);
505 WayPoint.ShowWayPoints =
true;
506 var matchingTickBox = showEntitiesTickBoxes?.Find(tb => tb.UserData as
string ==
"waypoint");
507 if (matchingTickBox !=
null)
509 matchingTickBox.Selected =
true;
511 generateWaypointsVerification.Close();
514 generateWaypointsVerification.Buttons[1].OnClicked = generateWaypointsVerification.Close;
518 if (GenerateWaypoints())
520 GUI.AddMessage(TextManager.Get(
"waypointsgeneratedsuccesfully"), GUIStyle.Green);
522 WayPoint.ShowWayPoints =
true;
529 spacing =
new GUIFrame(
new RectTransform(
new Vector2(0.02f, 1.0f), paddedTopPanel.
RectTransform), style:
null);
531 var selectedLayerText =
new GUITextBlock(
new RectTransform(
new Vector2(0.15f, 1.0f), paddedTopPanel.
RectTransform),
532 string.Empty, textAlignment: Alignment.Center);
533 selectedLayerText.TextGetter = () =>
535 string selectedLayer = layerList.
SelectedData as string;
536 if (selectedLayer != prevSelectedLayer)
538 prevSelectedLayer = selectedLayer;
539 return selectedLayer.IsNullOrEmpty() ?
string.Empty : TextManager.GetWithVariable(
"editor.layer.editinglayer",
"[layer]", selectedLayer);
541 return selectedLayerText.Text;
549 previouslyUsedPanel =
new GUIFrame(
new RectTransform(
new Vector2(0.1f, 0.2f), GUI.Canvas) { MinSize = new Point(200, 200) })
553 previouslyUsedList =
new GUIListBox(
new RectTransform(
new Vector2(0.9f, 0.9f), previouslyUsedPanel.
RectTransform,
Anchor.Center))
555 PlaySoundOnSelect =
true,
556 ScrollBarVisible =
true,
557 OnSelected = SelectPrefab
562 layerPanel =
new GUIFrame(
new RectTransform(
new Vector2(0.25f, 0.4f), GUI.Canvas, minSize:
new Point(300, 320)))
567 GUILayoutGroup layerGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.9f), layerPanel.
RectTransform, anchor:
Anchor.Center));
569 layerList =
new GUIListBox(
new RectTransform(
new Vector2(1f, 0.8f), layerGroup.RectTransform))
571 ScrollBarVisible =
true,
572 AutoHideScrollBar =
false,
573 OnSelected = (component, userdata) =>
579 layerSpecificButtons.ForEach(btn => btn.Enabled =
false);
585 layerSpecificButtons.ForEach(btn => btn.Enabled =
true);
591 GUILayoutGroup layerButtonGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.2f), layerGroup.RectTransform));
592 GUILayoutGroup layerButtonTopGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.5f), layerButtonGroup.RectTransform), isHorizontal:
true);
593 GUILayoutGroup layerButtonBottomGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.5f), layerButtonGroup.RectTransform), isHorizontal:
true);
595 GUIButton layerAddButton =
new GUIButton(
new RectTransform(
new Vector2(0.5f, 1f), layerButtonTopGroup.RectTransform), text: TextManager.Get(
"editor.layer.newlayer"), style:
"GUIButtonFreeScale")
597 OnClicked = (button, o) =>
599 CreateNewLayer(
null, MapEntity.SelectedList.ToList());
604 GUIButton layerDeleteButton =
new GUIButton(
new RectTransform(
new Vector2(0.5f, 1f), layerButtonTopGroup.RectTransform), text: TextManager.Get(
"editor.layer.deletelayer"), style:
"GUIButtonFreeScale")
607 OnClicked = (button, o) =>
611 RenameLayer(layer,
null);
616 layerSpecificButtons.Add(layerDeleteButton);
618 GUIButton layerRenameButton =
new GUIButton(
new RectTransform(
new Vector2(0.5f, 1.0f), layerButtonBottomGroup.RectTransform), text: TextManager.Get(
"editor.layer.renamelayer"), style:
"GUIButtonFreeScale")
621 OnClicked = (button, o) =>
625 GUI.PromptTextInput(TextManager.Get(
"editor.layer.renamelayer"), layer, newName =>
627 RenameLayer(layer, newName);
633 layerSpecificButtons.Add(layerRenameButton);
635 GUIButton selectLayerButton =
new GUIButton(
new RectTransform(
new Vector2(0.5f, 1.0f), layerButtonBottomGroup.RectTransform), text: TextManager.Get(
"editor.layer.selectlayer"), style:
"GUIButtonFreeScale")
638 OnClicked = (button, o) =>
642 foreach (MapEntity entity
in MapEntity.MapEntityList.Where(me => !me.Removed && me.Layer == layer))
644 if (entity.IsSelected) {
continue; }
645 MapEntity.SelectedList.Add(entity);
651 layerSpecificButtons.Add(selectLayerButton);
653 GUITextBlock.AutoScaleAndNormalize(layerAddButton.TextBlock, layerDeleteButton.TextBlock, layerRenameButton.TextBlock, selectLayerButton.TextBlock);
655 Vector2 subPanelSize =
new Vector2(0.925f, 0.9f);
657 undoBufferPanel =
new GUIFrame(
new RectTransform(
new Vector2(0.15f, 0.2f), GUI.Canvas) { MinSize = new Point(200, 200) })
662 undoBufferList =
new GUIListBox(
new RectTransform(subPanelSize, undoBufferPanel.
RectTransform,
Anchor.Center))
664 PlaySoundOnSelect =
true,
665 ScrollBarVisible =
true,
666 OnSelected = (_, userData) =>
669 if (userData is Command command)
678 int diff = index- commandIndex;
679 int amount = Math.Abs(diff);
694 undoBufferDisclaimer =
new GUIFrame(
new RectTransform(subPanelSize, undoBufferPanel.
RectTransform,
Anchor.Center), style:
null)
699 new GUITextBlock(
new RectTransform(Vector2.One, undoBufferDisclaimer.
RectTransform,
Anchor.Center), text: TextManager.Get(
"editor.undounavailable"), textAlignment: Alignment.Center, wrap:
true, font: GUIStyle.SubHeadingFont)
701 TextColor = GUIStyle.Orange
708 showEntitiesPanel =
new GUIFrame(
new RectTransform(
new Vector2(0.15f, 0.5f), GUI.Canvas)
710 MinSize = new Point(190, 0)
721 var tickBox =
new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowLighting"))
723 UserData =
"lighting",
725 OnSelected = (GUITickBox obj) =>
731 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowWalls"))
735 OnSelected = (GUITickBox obj) => {
Structure.ShowWalls = obj.Selected;
return true; }
737 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowStructures"))
739 UserData =
"structure",
741 OnSelected = (GUITickBox obj) => {
Structure.ShowStructures = obj.Selected;
return true; }
743 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowItems"))
747 OnSelected = (GUITickBox obj) => {
Item.ShowItems = obj.Selected;
return true; }
749 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowWires"))
753 OnSelected = (GUITickBox obj) => {
Item.ShowWires = obj.Selected;
return true; }
755 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowWaypoints"))
757 UserData =
"waypoint",
759 OnSelected = (GUITickBox obj) => { WayPoint.ShowWayPoints = obj.
Selected;
return true; }
761 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowSpawnpoints"))
763 UserData =
"spawnpoint",
764 Selected = WayPoint.ShowSpawnPoints,
765 OnSelected = (GUITickBox obj) => { WayPoint.ShowSpawnPoints = obj.
Selected;
return true; }
767 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowLinks"))
771 OnSelected = (GUITickBox obj) => {
Item.ShowLinks = obj.Selected;
return true; }
773 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowHulls"))
777 OnSelected = (GUITickBox obj) => { Hull.ShowHulls = obj.
Selected;
return true; }
779 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"ShowGaps"))
783 OnSelected = (GUITickBox obj) => { Gap.ShowGaps = obj.
Selected;
return true; },
785 showEntitiesTickBoxes.AddRange(paddedShowEntitiesPanel.Children.Select(c => c as GUITickBox));
787 var subcategoryHeader =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedShowEntitiesPanel.RectTransform), TextManager.Get(
"subcategories"), font: GUIStyle.SubHeadingFont);
788 subcategoryHeader.RectTransform.MinSize =
new Point(0, (
int)(subcategoryHeader.Rect.Height * 1.5f));
790 var subcategoryList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedShowEntitiesPanel.RectTransform) { MinSize = new Point(0, showEntitiesPanel.Rect.Height / 3) });
791 List<string> availableSubcategories =
new List<string>();
792 foreach (var prefab
in MapEntityPrefab.List)
794 if (!
string.IsNullOrEmpty(prefab.Subcategory) && !availableSubcategories.Contains(prefab.Subcategory))
796 availableSubcategories.Add(prefab.Subcategory);
799 foreach (
string subcategory
in availableSubcategories)
801 var tb =
new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.15f), subcategoryList.Content.RectTransform),
802 TextManager.Get(
"subcategory." + subcategory).Fallback(subcategory), font: GUIStyle.SmallFont)
804 UserData = subcategory,
806 OnSelected = (GUITickBox obj) => { hiddenSubCategories[(string)obj.UserData] = !obj.Selected;
return true; },
808 tb.TextBlock.Wrap =
true;
811 GUITextBlock.AutoScaleAndNormalize(subcategoryList.Content.Children.Where(c => c is GUITickBox).Select(c => ((GUITickBox)c).TextBlock));
812 foreach (GUIComponent child
in subcategoryList.Content.Children)
814 if (child is GUITickBox tb && tb.TextBlock.TextSize.X > tb.TextBlock.Rect.Width * 1.25f)
816 tb.ToolTip = tb.Text;
817 tb.Text = ToolBox.LimitString(tb.Text.Value, tb.Font, (
int)(tb.TextBlock.Rect.Width * 1.25f));
823 (
int)Math.Max(
showEntitiesPanel.
RectTransform.
NonScaledSize.X, paddedShowEntitiesPanel.RectTransform.Children.Max(c => (
int)((c.GUIComponent as GUITickBox)?.TextBlock.TextSize.X ?? 0)) / paddedShowEntitiesPanel.RectTransform.RelativeSize.X),
824 (
int)(paddedShowEntitiesPanel.RectTransform.Children.Sum(c => c.MinSize.Y) / paddedShowEntitiesPanel.RectTransform.RelativeSize.Y));
825 GUITextBlock.AutoScaleAndNormalize(paddedShowEntitiesPanel.Children.Where(c => c is GUITickBox).Select(c => ((GUITickBox)c).TextBlock));
829 float longestTextWidth = GUIStyle.SmallFont.MeasureString(TextManager.Get(
"SubEditorShadowCastingLights")).X;
830 entityCountPanel =
new GUIFrame(
new RectTransform(
new Vector2(0.08f, 0.5f), GUI.Canvas)
832 MinSize = new Point(Math.Max(170, (int)(longestTextWidth * 1.5f)), 0),
833 AbsoluteOffset = new Point(0, TopPanel.Rect.Height)
836 GUILayoutGroup paddedEntityCountPanel =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f, 0.95f), entityCountPanel.
RectTransform,
Anchor.Center))
839 AbsoluteSpacing = (int)(GUI.Scale * 4)
842 var itemCountText =
new GUITextBlock(
new RectTransform(
new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get(
"Items"),
843 textAlignment: Alignment.CenterLeft, font: GUIStyle.SmallFont);
844 var itemCount =
new GUITextBlock(
new RectTransform(
new Vector2(0.33f, 1.0f), itemCountText.RectTransform,
Anchor.TopRight,
Pivot.TopLeft),
"", textAlignment: Alignment.CenterRight);
845 itemCount.TextGetter = () =>
847 int count =
Item.ItemList.Count;
848 if (dummyCharacter?.Inventory !=
null)
852 itemCount.TextColor = count >
MaxItems ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, count / (
float)
MaxItems);
853 return count.ToString();
856 var structureCountText =
new GUITextBlock(
new RectTransform(
new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get(
"Structures"),
857 textAlignment: Alignment.CenterLeft, font: GUIStyle.SmallFont);
858 var structureCount =
new GUITextBlock(
new RectTransform(
new Vector2(0.33f, 1.0f), structureCountText.RectTransform,
Anchor.TopRight,
Pivot.TopLeft),
"", textAlignment: Alignment.CenterRight);
859 structureCount.TextGetter = () =>
861 int count = MapEntity.MapEntityList.Count -
Item.ItemList.Count - Hull.HullList.Count - WayPoint.WayPointList.Count - Gap.GapList.Count;
862 structureCount.TextColor = count >
MaxStructures ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, count / (
float)
MaxStructures);
863 return count.ToString();
866 var wallCountText =
new GUITextBlock(
new RectTransform(
new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get(
"Walls"),
867 textAlignment: Alignment.CenterLeft, font: GUIStyle.SmallFont);
868 var wallCount =
new GUITextBlock(
new RectTransform(
new Vector2(0.33f, 1.0f), wallCountText.RectTransform,
Anchor.TopRight,
Pivot.TopLeft),
"", textAlignment: Alignment.CenterRight);
869 wallCount.TextGetter = () =>
872 return Structure.WallList.Count.ToString();
875 var lightCountLabel =
new GUITextBlock(
new RectTransform(
new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get(
"SubEditorLights"),
876 textAlignment: Alignment.CenterLeft, font: GUIStyle.SmallFont);
877 var lightCountText =
new GUITextBlock(
new RectTransform(
new Vector2(0.33f, 1.0f), lightCountLabel.RectTransform,
Anchor.TopRight,
Pivot.TopLeft),
"", textAlignment: Alignment.CenterRight);
878 lightCountText.TextGetter = () =>
881 foreach (Item item
in Item.ItemList)
883 if (item.ParentInventory !=
null) {
continue; }
886 lightCountText.TextColor = lightCount >
MaxLights ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, lightCount / (
float)
MaxLights);
887 return lightCount.ToString() +
"/" +
MaxLights;
889 var shadowCastingLightCountLabel =
new GUITextBlock(
new RectTransform(
new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get(
"SubEditorShadowCastingLights"),
890 textAlignment: Alignment.CenterLeft, font: GUIStyle.SmallFont, wrap:
true);
891 var shadowCastingLightCountText =
new GUITextBlock(
new RectTransform(
new Vector2(0.33f, 1.0f), shadowCastingLightCountLabel.RectTransform,
Anchor.TopRight,
Pivot.TopLeft),
"", textAlignment: Alignment.CenterRight);
892 shadowCastingLightCountText.TextGetter = () =>
895 foreach (Item item
in Item.ItemList)
897 if (item.ParentInventory !=
null) {
continue; }
898 lightCount += item.GetComponents<
LightComponent>().Count(l => l.CastShadows && !l.DrawBehindSubs);
905 (
int)(paddedEntityCountPanel.RectTransform.Children.Max(c => (
int)((GUITextBlock) c.GUIComponent).TextSize.X / 0.75f) / paddedEntityCountPanel.RectTransform.RelativeSize.X),
906 (
int)(paddedEntityCountPanel.RectTransform.Children.Sum(c => (
int)(c.NonScaledSize.Y * 1.5f) + paddedEntityCountPanel.AbsoluteSpacing) / paddedEntityCountPanel.RectTransform.RelativeSize.Y));
911 hullVolumeFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.15f, 2.0f),
TopPanel.
RectTransform,
Anchor.BottomLeft,
Pivot.TopLeft, minSize:
new Point(300, 85)) { AbsoluteOffset =
new Point(entityCountPanel.
Rect.Width, 0) },
"GUIToolTip")
915 GUITextBlock totalHullVolume =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), hullVolumeFrame.
RectTransform),
"", font: GUIStyle.SmallFont)
917 TextGetter = GetTotalHullVolume
919 GUITextBlock selectedHullVolume =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), hullVolumeFrame.
RectTransform) { RelativeOffset = new Vector2(0.0f, 0.5f) },
"", font: GUIStyle.SmallFont)
921 TextGetter = GetSelectedHullVolume
925 { MinSize = new Point((int)(250 * GUI.Scale), (int)(80 * GUI.Scale)), AbsoluteOffset = new Point((int)(10 * GUI.Scale), -entityCountPanel.Rect.Height - (int)(10 * GUI.Scale)) },
"InnerFrame")
929 var saveAssemblyButton =
new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.8f), saveAssemblyFrame.
RectTransform,
Anchor.Center), TextManager.Get(
"SaveItemAssembly"));
930 saveAssemblyButton.TextBlock.AutoScaleHorizontal =
true;
931 saveAssemblyButton.OnClicked += (btn, userdata) =>
933 CreateSaveAssemblyScreen();
936 saveAssemblyFrame.
RectTransform.
MinSize =
new Point(saveAssemblyFrame.
Rect.Width, (
int)(saveAssemblyButton.Rect.Height / saveAssemblyButton.RectTransform.RelativeSize.Y));
939 { MinSize = new Point((int)(250 * GUI.Scale), (int)(80 * GUI.Scale)), AbsoluteOffset = new Point((int)(10 * GUI.Scale), -saveAssemblyFrame.Rect.Height - entityCountPanel.Rect.Height - (int)(10 * GUI.Scale)) },
"InnerFrame")
943 var saveStampButton =
new GUIButton(
new RectTransform(
new Vector2(0.9f, 0.8f), snapToGridFrame.
RectTransform,
Anchor.Center), TextManager.Get(
"subeditor.snaptogrid",
"spriteeditor.snaptogrid"));
944 saveStampButton.TextBlock.AutoScaleHorizontal =
true;
945 saveStampButton.OnClicked += (btn, userdata) =>
950 snapToGridFrame.
RectTransform.
MinSize =
new Point(snapToGridFrame.
Rect.Width, (
int)(saveStampButton.Rect.Height / saveStampButton.RectTransform.RelativeSize.Y));
955 EntityMenu =
new GUIFrame(
new RectTransform(
new Point(GameMain.GraphicsWidth, (
int)(359 * GUI.Scale)), GUI.Canvas,
Anchor.BottomRight));
957 toggleEntityMenuButton =
new GUIButton(
new RectTransform(
new Vector2(0.15f, 0.08f),
EntityMenu.
RectTransform,
Anchor.TopCenter,
Pivot.BottomCenter) { MinSize = new Point(0, 15) },
958 style:
"UIToggleButtonVertical")
960 OnClicked = (btn, userdata) =>
962 entityMenuOpen = !entityMenuOpen;
964 foreach (GUIComponent child
in btn.Children)
966 child.SpriteEffects = entityMenuOpen ? SpriteEffects.None : SpriteEffects.FlipVertically;
974 RelativeSpacing = 0.04f,
978 var entityMenuTop =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f, 0.13f), paddedTab.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
983 selectedCategoryButton =
new GUIButton(
new RectTransform(
new Vector2(1.0f, 1.0f), entityMenuTop.RectTransform, scaleBasis:
ScaleBasis.BothHeight),
"", style:
"CategoryButton.All")
987 selectedCategoryText =
new GUITextBlock(
new RectTransform(
new Vector2(0.2f, 1.0f), entityMenuTop.RectTransform), TextManager.Get(
"MapEntityCategory.All"), font: GUIStyle.LargeFont);
989 var filterText =
new GUITextBlock(
new RectTransform(
new Vector2(0.1f, 1.0f), entityMenuTop.RectTransform), TextManager.Get(
"serverlog.filter"), font: GUIStyle.SubHeadingFont);
990 filterText.RectTransform.MaxSize =
new Point((
int)(filterText.TextSize.X * 1.5f),
int.MaxValue);
991 entityFilterBox =
new GUITextBox(
new RectTransform(
new Vector2(0.17f, 1.0f), entityMenuTop.RectTransform), font: GUIStyle.Font, createClearButton:
true);
994 if (text == lastFilter) {
return true; }
996 FilterEntities(text);
1001 new GUIFrame(
new RectTransform(
new Vector2(0.075f, 1.0f), entityMenuTop.RectTransform), style:
null);
1003 entityCategoryButtons.Clear();
1004 entityCategoryButtons.Add(
1005 new GUIButton(
new RectTransform(
new Vector2(1.0f, 1.0f), entityMenuTop.RectTransform, scaleBasis:
ScaleBasis.BothHeight),
"", style:
"CategoryButton.All")
1007 OnClicked = (btn, userdata) =>
1009 OpenEntityMenu(null);
1017 entityCategoryButtons.Add(
new GUIButton(
new RectTransform(
new Vector2(1.0f, 1.0f), entityMenuTop.RectTransform, scaleBasis:
ScaleBasis.BothHeight),
1018 "", style:
"CategoryButton." + category.ToString())
1020 UserData = category,
1021 ToolTip = TextManager.Get(
"MapEntityCategory." + category.ToString()),
1022 OnClicked = (btn, userdata) =>
1024 MapEntityCategory newCategory = (MapEntityCategory)userdata;
1025 OpenEntityMenu(newCategory);
1030 entityCategoryButtons.ForEach(b => b.RectTransform.MaxSize =
new Point(b.Rect.Height));
1032 new GUIFrame(
new RectTransform(
new Vector2(0.8f, 0.01f), paddedTab.RectTransform), style:
"HorizontalLine");
1034 var entityListContainer =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.9f), paddedTab.RectTransform), style:
null);
1035 categorizedEntityList =
new GUIListBox(
new RectTransform(Vector2.One, entityListContainer.RectTransform), useMouseDownToSelect:
true);
1036 allEntityList =
new GUIListBox(
new RectTransform(Vector2.One, entityListContainer.RectTransform), useMouseDownToSelect:
true)
1038 OnSelected = SelectPrefab,
1039 UseGridLayout =
true,
1040 CheckSelected = MapEntityPrefab.GetSelected,
1042 PlaySoundOnSelect =
true,
1045 paddedTab.Recalculate();
1047 screenResolution =
new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
1050 private bool TestSubmarine(GUIButton button,
object obj)
1052 List<LocalizedString> errorMsgs =
new List<LocalizedString>();
1054 if (!Hull.HullList.Any())
1056 errorMsgs.Add(TextManager.Get(
"NoHullsWarning"));
1059 if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType ==
SpawnType.Human))
1061 errorMsgs.Add(TextManager.Get(
"NoHumanSpawnpointWarning"));
1064 if (errorMsgs.Any())
1066 new GUIMessageBox(TextManager.Get(
"Error"), LocalizedString.Join(
"\n\n", errorMsgs),
new Vector2(0.25f, 0.0f),
new Point(400, 200));
1072 backedUpSubInfo =
new SubmarineInfo(MainSub);
1074 GameMain.GameScreen.Select();
1076 GameSession gameSession =
new GameSession(backedUpSubInfo,
"", GameModePreset.TestMode, CampaignSettings.Empty,
null);
1077 gameSession.StartRound(
null,
false);
1079 foreach ((
string layerName, LayerData layerData) in Layers)
1081 Identifier identifier = layerName.ToIdentifier();
1082 bool enabled = layerData.IsVisible;
1083 MainSub.SetLayerEnabled(identifier, enabled);
1086 if (gameSession.GameMode is TestGameMode testGameMode)
1088 testGameMode.OnRoundEnd = () =>
1091 GameMain.SubEditorScreen.Select();
1100 backedUpSubInfo =
null;
1103 private void UpdateEntityList()
1108 int maxTextWidth = (int)(GUIStyle.SubHeadingFont.MeasureString(TextManager.Get(
"mapentitycategory.misc")).X + GUI.IntScale(50));
1109 Dictionary<string, List<MapEntityPrefab>> entityLists =
new Dictionary<string, List<MapEntityPrefab>>();
1110 Dictionary<string, MapEntityCategory> categoryKeys =
new Dictionary<string, MapEntityCategory>();
1115 LocalizedString categoryName = TextManager.Get(
"MapEntityCategory." + category);
1116 maxTextWidth = (int)Math.Max(maxTextWidth, GUIStyle.SubHeadingFont.MeasureString(categoryName.Replace(
" ",
"\n")).X + GUI.IntScale(50));
1117 foreach (MapEntityPrefab ep
in MapEntityPrefab.List)
1119 if (!ep.Category.HasFlag(category)) {
continue; }
1121 if (!entityLists.ContainsKey(category + ep.Subcategory))
1123 entityLists[category + ep.Subcategory] =
new List<MapEntityPrefab>();
1125 entityLists[category + ep.Subcategory].Add(ep);
1126 categoryKeys[category + ep.Subcategory] = category;
1127 LocalizedString subcategoryName = TextManager.Get(
"subcategory." + ep.Subcategory).Fallback(ep.Subcategory);
1128 if (subcategoryName !=
null)
1130 maxTextWidth = (int)Math.Max(maxTextWidth, GUIStyle.SubHeadingFont.MeasureString(subcategoryName.Replace(
" ",
"\n")).X + GUI.IntScale(50));
1136 int entitiesPerRow = (int)Math.Ceiling(categorizedEntityList.
Content.
Rect.Width / Math.Max(125 * GUI.Scale, 60));
1137 foreach (
string categoryKey
in entityLists.Keys)
1139 var categoryFrame =
new GUIFrame(
new RectTransform(Vector2.One, categorizedEntityList.
Content.
RectTransform), style:
null)
1141 ClampMouseRectToParent =
true,
1142 UserData = categoryKeys[categoryKey]
1145 new GUIFrame(
new RectTransform(Vector2.One, categoryFrame.RectTransform), style:
"HorizontalLine");
1147 LocalizedString categoryName = TextManager.Get(
"MapEntityCategory." + entityLists[categoryKey].First().Category);
1148 LocalizedString subCategoryName = entityLists[categoryKey].First().Subcategory;
1149 if (subCategoryName.IsNullOrEmpty())
1151 new GUITextBlock(
new RectTransform(
new Point(maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform,
Anchor.TopLeft),
1152 categoryName, textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont, wrap:
true)
1154 Padding =
new Vector4(GUI.IntScale(10))
1160 subCategoryName = subCategoryName.IsNullOrEmpty() ?
1161 TextManager.Get(
"mapentitycategory.misc") :
1162 (TextManager.Get($
"subcategory.{subCategoryName}").Fallback(subCategoryName));
1163 var categoryTitle =
new GUITextBlock(
new RectTransform(
new Point(maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform,
Anchor.TopLeft),
1164 categoryName, textAlignment: Alignment.TopLeft, font: GUIStyle.Font, wrap:
true)
1166 Padding =
new Vector4(GUI.IntScale(10))
1168 new GUITextBlock(
new RectTransform(
new Point(maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform,
Anchor.TopLeft) { AbsoluteOffset = new Point(0, (int)(categoryTitle.TextSize.Y + GUI.IntScale(10))) },
1169 subCategoryName, textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont, wrap:
true)
1171 Padding =
new Vector4(GUI.IntScale(10))
1175 var entityListInner =
new GUIListBox(
new RectTransform(
new Point(categoryFrame.Rect.Width - maxTextWidth, categoryFrame.Rect.Height), categoryFrame.RectTransform,
Anchor.CenterRight),
1177 useMouseDownToSelect:
true)
1179 ScrollBarVisible =
false,
1180 AutoHideScrollBar =
false,
1181 OnSelected = SelectPrefab,
1182 UseGridLayout =
true,
1183 CheckSelected = MapEntityPrefab.GetSelected,
1184 ClampMouseRectToParent =
true,
1185 PlaySoundOnSelect =
true,
1187 entityListInner.ContentBackground.ClampMouseRectToParent =
true;
1188 entityListInner.Content.ClampMouseRectToParent =
true;
1190 foreach (MapEntityPrefab ep
in entityLists[categoryKey])
1193 if ((ep.HideInMenus || ep.HideInEditors) && !GameMain.DebugDraw) {
continue; }
1195 CreateEntityElement(ep, entitiesPerRow, entityListInner.Content);
1198 entityListInner.UpdateScrollBarSize();
1199 int contentHeight = (int)(entityListInner.TotalSize + entityListInner.Padding.Y + entityListInner.Padding.W);
1200 categoryFrame.RectTransform.NonScaledSize =
new Point(categoryFrame.Rect.Width, contentHeight);
1201 categoryFrame.RectTransform.MinSize =
new Point(0, contentHeight);
1202 entityListInner.RectTransform.NonScaledSize =
new Point(entityListInner.Rect.Width, contentHeight);
1203 entityListInner.RectTransform.MinSize =
new Point(0, contentHeight);
1205 entityListInner.Content.RectTransform.SortChildren((i1, i2) =>
1206 string.Compare(((MapEntityPrefab)i1.GUIComponent.UserData)?.Name.Value, (i2.GUIComponent.UserData as MapEntityPrefab)?.Name.Value, StringComparison.Ordinal));
1209 foreach (MapEntityPrefab ep
in MapEntityPrefab.List)
1212 if ((ep.HideInMenus || ep.HideInEditors) && !GameMain.DebugDraw) {
continue; }
1214 CreateEntityElement(ep, entitiesPerRow, allEntityList.
Content);
1217 string.Compare(((MapEntityPrefab)i1.GUIComponent.UserData)?.Name.Value, (i2.GUIComponent.UserData as MapEntityPrefab)?.Name.Value, StringComparison.Ordinal));
1221 private void CreateEntityElement(MapEntityPrefab ep,
int entitiesPerRow, GUIComponent parent)
1225 float relWidth = 1.0f / entitiesPerRow;
1226 GUIFrame frame =
new GUIFrame(
new RectTransform(
1227 new Vector2(relWidth, relWidth * ((
float)parent.Rect.Width / parent.Rect.Height)),
1228 parent.RectTransform)
1229 { MinSize = new Point(0, 50) },
1230 style:
"GUITextBox")
1233 ClampMouseRectToParent =
true
1235 frame.RectTransform.MinSize =
new Point(0, frame.Rect.Width);
1236 frame.RectTransform.MaxSize =
new Point(
int.MaxValue, frame.Rect.Width);
1238 LocalizedString name = legacy ? TextManager.GetWithVariable(
"legacyitemformat",
"[name]", ep.Name) : ep.Name;
1239 frame.ToolTip = ep.CreateTooltipText();
1243 frame.Color = Color.Magenta;
1246 if (ep.HideInMenus || ep.HideInEditors)
1248 frame.Color = Color.Red;
1249 name =
"[HIDDEN] " + name;
1251 frame.ToolTip = RichString.Rich(frame.ToolTip);
1253 GUILayoutGroup paddedFrame =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.8f, 0.8f), frame.RectTransform,
Anchor.Center), childAnchor:
Anchor.TopCenter)
1256 RelativeSpacing = 0.03f,
1257 CanBeFocused =
false
1260 Sprite icon = ep.Sprite;
1261 Color iconColor = Color.White;
1262 if (ep is ItemPrefab itemPrefab)
1264 if (itemPrefab.InventoryIcon !=
null)
1266 icon = itemPrefab.InventoryIcon;
1267 iconColor = itemPrefab.InventoryIconColor;
1271 iconColor = itemPrefab.SpriteColor;
1274 GUIImage img =
null;
1275 if (ep.Sprite !=
null)
1277 img =
new GUIImage(
new RectTransform(
new Vector2(1.0f, 0.8f),
1278 paddedFrame.RectTransform,
Anchor.TopCenter), icon)
1280 CanBeFocused =
false,
1281 LoadAsynchronously =
true,
1282 SpriteEffects = icon.effects,
1283 Color = legacy ? iconColor * 0.6f : iconColor
1287 if (ep is ItemAssemblyPrefab itemAssemblyPrefab)
1289 new GUICustomComponent(
new RectTransform(
new Vector2(1.0f, 0.75f),
1290 paddedFrame.RectTransform,
Anchor.TopCenter), onDraw: (sb, customComponent) =>
1292 if (GUIImage.LoadingTextures) { return; }
1293 itemAssemblyPrefab.DrawIcon(sb, customComponent);
1296 HideElementsOutsideFrame =
true,
1297 ToolTip = frame.ToolTip.SanitizedString
1301 GUITextBlock textBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedFrame.RectTransform,
Anchor.BottomCenter),
1302 text: name, textAlignment: Alignment.Center, font: GUIStyle.SmallFont)
1304 CanBeFocused =
false
1306 if (legacy) { textBlock.TextColor *= 0.6f; }
1307 if (name.IsNullOrEmpty())
1309 DebugConsole.AddWarning($
"Entity \"{ep.Identifier.Value}\" has no name!",
1310 contentPackage: ep.ContentPackage);
1311 textBlock.Text = frame.ToolTip = ep.Identifier.Value;
1312 textBlock.TextColor = GUIStyle.Red;
1314 textBlock.Text = ToolBox.LimitString(textBlock.Text, textBlock.Font, textBlock.Rect.Width);
1317 && ep.ContentPackage?.Files.Length == 1
1318 && ContentPackageManager.LocalPackages.Contains(ep.ContentPackage))
1320 var deleteButton =
new GUIButton(
new RectTransform(
new Vector2(1.0f, 0.2f), paddedFrame.RectTransform,
Anchor.BottomCenter) { MinSize = new Point(0, 20) },
1321 TextManager.Get(
"Delete"), style:
"GUIButtonSmall")
1324 OnClicked = (btn, userData) =>
1326 ItemAssemblyPrefab assemblyPrefab = (ItemAssemblyPrefab)userData;
1327 if (assemblyPrefab !=
null)
1329 var msgBox =
new GUIMessageBox(
1330 TextManager.Get(
"DeleteDialogLabel"),
1331 TextManager.GetWithVariable(
"DeleteDialogQuestion",
"[file]", assemblyPrefab.Name),
1332 new[] { TextManager.Get(
"Yes"), TextManager.Get(
"Cancel") });
1333 msgBox.Buttons[0].OnClicked += (deleteBtn, userData2) =>
1337 assemblyPrefab.Delete();
1342 DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable(
"DeleteFileError",
"[file]", assemblyPrefab.Name), e);
1346 msgBox.Buttons[0].OnClicked += msgBox.Close;
1347 msgBox.Buttons[1].OnClicked += msgBox.Close;
1354 paddedFrame.Recalculate();
1357 img.Scale = Math.Min(Math.Min(img.Rect.Width / img.Sprite.size.X, img.Rect.Height / img.Sprite.size.Y), 1.5f);
1358 img.RectTransform.NonScaledSize =
new Point((
int)(img.Sprite.size.X * img.Scale), img.Rect.Height);
1364 Select(enableAutoSave:
true);
1369 public void Select(
bool enableAutoSave =
true)
1374 $
"DeterminePublishedItemIds",
1375 SteamManager.Workshop.GetPublishedItems(),
1378 if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item> items)) { return; }
1380 publishedWorkshopItemIds.Clear();
1381 publishedWorkshopItemIds.UnionWith(items.Select(it => it.Id.Value));
1384 GUI.PreventPauseMenuToggle =
false;
1385 if (!Directory.Exists(autoSavePath))
1387 System.IO.DirectoryInfo e = Directory.CreateDirectory(autoSavePath);
1388 e.Attributes = System.IO.FileAttributes.Directory | System.IO.FileAttributes.Hidden;
1391 DebugConsole.ThrowError(
"Failed to create auto save directory!");
1395 if (!File.Exists(autoSaveInfoPath))
1399 AutoSaveInfo =
new XDocument(
new XElement(
"AutoSaves"));
1400 IO.SafeXML.SaveSafe(AutoSaveInfo, autoSaveInfoPath);
1404 DebugConsole.ThrowError(
"Saving auto save info to \"" + autoSaveInfoPath +
"\" failed!", e);
1409 AutoSaveInfo = XMLExtensions.TryLoadXml(autoSaveInfoPath);
1412 GameMain.LightManager.AmbientLight =
1413 Level.Loaded?.GenerationParams?.AmbientLightColor ??
1414 new Color(3, 3, 3, 3);
1416 isAutoSaving =
false;
1418 if (!wasSelectedBefore)
1420 OpenEntityMenu(
null);
1421 wasSelectedBefore =
true;
1425 OpenEntityMenu(selectedCategory);
1428 if (backedUpSubInfo !=
null)
1433 string name = (MainSub ==
null) ? TextManager.Get(
"unspecifiedsubfilename").Value : MainSub.Info.Name;
1434 if (backedUpSubInfo !=
null) { name = backedUpSubInfo.
Name; }
1435 subNameLabel.
Text = ToolBox.LimitString(name, subNameLabel.
Font, subNameLabel.
Rect.Width);
1437 editorSelectedTime = Option<DateTime>.Some(DateTime.Now);
1439 GUI.ForceMouseOn(
null);
1440 SetMode(Mode.Default);
1442 if (backedUpSubInfo !=
null)
1444 MainSub =
new Submarine(backedUpSubInfo);
1449 backedUpSubInfo =
null;
1451 else if (MainSub ==
null)
1453 var subInfo =
new SubmarineInfo();
1454 MainSub =
new Submarine(subInfo, showErrorMessages:
false);
1455 ReconstructLayers();
1458 MainSub.UpdateTransform(interpolate:
false);
1459 cam.Position = MainSub.Position + MainSub.HiddenSubPosition;
1461 GameMain.SoundManager.SetCategoryGainMultiplier(
"default", 0.0f);
1462 GameMain.SoundManager.SetCategoryGainMultiplier(
"waterambience", 0.0f);
1464 string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
1467 List<(
string Name, SubmarineInfo Sub)> subs =
new List<(
string Name, SubmarineInfo Sub)>();
1469 foreach (SubmarineInfo sub
in SubmarineInfo.SavedSubmarines)
1472 if (Path.GetDirectoryName(Path.GetFullPath(sub.FilePath)) == downloadFolder) {
continue; }
1473 subs.Add((sub.Name, sub));
1476 foreach (var (subName, sub) in subs.OrderBy(tuple => tuple.Name))
1478 linkedSubBox.
AddItem(subName, sub);
1481 cam.UpdateTransform();
1483 CreateDummyCharacter();
1485 if (GameSettings.CurrentConfig.EnableSubmarineAutoSave && enableAutoSave)
1487 CoroutineManager.StartCoroutine(AutoSaveCoroutine(),
"SubEditorAutoSave");
1490 ImageManager.OnEditorSelected();
1493 ReconstructLayers();
1505 DebugConsole.ThrowError($
"Could not drag and drop the file. File \"{filePath}\" is corrupted!");
1510 LocalizedString body = TextManager.GetWithVariable(
"SubEditor.LoadConfirmBody",
"[submarine]", info.
Name);
1511 GUI.AskForConfirmation(TextManager.Get(
"Load"), body, onConfirm: () => LoadSub(info), onDeny: () => info.
Dispose());
1515 string text = File.ReadAllText(filePath);
1517 Vector2 mousePos = Mouse.GetState().Position.ToVector2();
1518 PasteAssembly(text, cam.ScreenToWorld(mousePos));
1524 if (saveFrame ==
null) {
break; }
1526 Texture2D texture =
Sprite.LoadTexture(filePath, compress:
false);
1528 if (MainSub !=
null)
1530 MainSub.Info.PreviewImage = previewImage.
Sprite;
1536 DebugConsole.ThrowError($
"Could not drag and drop the file. \"{extension}\" is not a valid file extension! (expected .xml, .sub, .png or .jpg)");
1546 private static IEnumerable<CoroutineStatus> AutoSaveCoroutine()
1548 DateTime target = DateTime.Now.AddSeconds(GameSettings.CurrentConfig.AutoSaveIntervalSeconds);
1549 DateTime tempTarget = DateTime.Now;
1551 bool wasPaused =
false;
1558 tempTarget = DateTime.Now;
1562 if (!GameMain.Instance.Paused && wasPaused)
1565 target = target.AddSeconds((DateTime.Now - tempTarget).TotalSeconds);
1567 yield
return CoroutineStatus.Running;
1570 if (Selected is SubEditorScreen)
1573 CoroutineManager.StartCoroutine(AutoSaveCoroutine(),
"SubEditorAutoSave");
1575 yield
return CoroutineStatus.Success;
1583 autoSaveLabel =
null;
1585 if (editorSelectedTime.TryUnwrap(out DateTime selectedTime))
1587 TimeSpan timeInEditor = DateTime.Now - selectedTime;
1590 if (timeInEditor.TotalSeconds > Timing.TotalTime * 1.5)
1592 DebugConsole.ThrowErrorAndLogToGA(
1593 "SubEditorScreen.DeselectEditorSpecific:InvalidTimeInEditor",
1594 $
"Error in sub editor screen. Calculated time in editor {timeInEditor} was larger than the time the game has run ({Timing.TotalTime} s).");
1598 AchievementManager.IncrementStat(AchievementStat.HoursInEditor, (
float)timeInEditor.TotalHours);
1599 editorSelectedTime = Option<DateTime>.None();
1603 GUI.ForceMouseOn(
null);
1605 if (ImageManager.EditorMode) { GameSettings.SaveCurrentConfig(); }
1616 DebugConsole.DeactivateCheats();
1619 SetMode(
Mode.Default);
1621 SoundPlayer.OverrideMusicType = Identifier.Empty;
1622 GameMain.
SoundManager.SetCategoryGainMultiplier(
"default", GameSettings.CurrentConfig.Audio.SoundVolume);
1623 GameMain.
SoundManager.SetCategoryGainMultiplier(
"waterambience", GameSettings.CurrentConfig.Audio.SoundVolume);
1625 if (CoroutineManager.IsCoroutineRunning(
"SubEditorAutoSave"))
1627 CoroutineManager.StopCoroutines(
"SubEditorAutoSave");
1630 if (dummyCharacter !=
null)
1633 dummyCharacter =
null;
1639 if (component is
GUIMessageBox { Closed:
false, UserData:
"colorpicker" } msgBox)
1643 colorPicker.Dispose();
1653 private void CreateDummyCharacter()
1655 if (dummyCharacter !=
null) { RemoveDummyCharacter(); }
1657 dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero,
"",
id: Entity.DummyID, hasAi:
false);
1658 dummyCharacter.
Info.
Name =
"Galldren";
1663 if (CharacterInventory.PersonalSlots.HasFlag(dummyCharacter.
Inventory.
SlotTypes[i])) {
continue; }
1672 GameMain.World.ProcessChanges();
1680 private static void AutoSave()
1682 if (MapEntity.MapEntityList.Any() && GameSettings.CurrentConfig.EnableSubmarineAutoSave && !isAutoSaving)
1684 if (MainSub !=
null)
1686 isAutoSaving =
true;
1687 if (!Directory.Exists(autoSavePath)) {
return; }
1689 XDocument doc =
new XDocument(
new XElement(
"Submarine"));
1690 MainSub.SaveToXElement(doc.Root);
1691 Thread saveThread =
new Thread(start =>
1696 TimeSpan time = DateTime.UtcNow - DateTime.MinValue;
1697 string filePath = Path.Combine(autoSavePath, $
"AutoSave_{(ulong)time.TotalMilliseconds}.sub");
1698 SaveUtil.CompressStringToFile(filePath, doc.ToString());
1700 CrossThread.RequestExecutionOnMainThread(() =>
1702 if (AutoSaveInfo?.Root == null || MainSub?.Info == null) { return; }
1704 int saveCount = AutoSaveInfo.Root.Elements().Count();
1705 while (AutoSaveInfo.Root.Elements().Count() > MaxAutoSaves)
1707 XElement min = AutoSaveInfo.Root.Elements().OrderBy(element => element.GetAttributeUInt64(
"time", 0)).FirstOrDefault();
1708 #warning TODO: revise
1709 string path = min.GetAttributeStringUnrestricted(
"file",
"");
1710 if (string.IsNullOrWhiteSpace(path)) { continue; }
1712 if (IO.File.Exists(path)) { IO.File.Delete(path); }
1716 XElement newElement =
new XElement(
"AutoSave",
1717 new XAttribute(
"file", filePath),
1718 new XAttribute(
"name", MainSub.Info.Name),
1719 new XAttribute(
"time", (ulong)time.TotalSeconds));
1720 AutoSaveInfo.Root.Add(newElement);
1724 IO.SafeXML.SaveSafe(AutoSaveInfo, autoSaveInfoPath);
1728 DebugConsole.ThrowError(
"Saving auto save info to \"" + autoSaveInfoPath +
"\" failed!", e);
1733 CrossThread.RequestExecutionOnMainThread(DisplayAutoSavePrompt);
1737 CrossThread.RequestExecutionOnMainThread(() => DebugConsole.ThrowError(
"Auto saving submarine failed!", e));
1739 isAutoSaving =
false;
1740 }) { Name =
"Auto Save Thread" };
1746 private static void DisplayAutoSavePrompt()
1748 if (Selected != GameMain.SubEditorScreen) {
return; }
1751 LocalizedString label = TextManager.Get(
"AutoSaved");
1752 autoSaveLabel =
new GUILayoutGroup(
new RectTransform(
new Point(GUI.IntScale(150), GUI.IntScale(32)), GameMain.SubEditorScreen.EntityMenu.RectTransform,
Anchor.TopRight)
1754 ScreenSpaceOffset = new Point(-GUI.IntScale(16), -GUI.IntScale(48))
1755 }, isHorizontal:
true)
1757 CanBeFocused =
false
1760 GUIImage checkmark =
new GUIImage(
new RectTransform(
new Vector2(0.25f, 1f), autoSaveLabel.
RectTransform), style:
"MissionCompletedIcon", scaleToFit:
true);
1761 GUITextBlock labelComponent =
new GUITextBlock(
new RectTransform(
new Vector2(0.75f, 1f), autoSaveLabel.
RectTransform), label, font: GUIStyle.SubHeadingFont, color: GUIStyle.Green)
1763 Padding = Vector4.Zero,
1764 AutoScaleHorizontal =
true,
1765 AutoScaleVertical =
true
1768 labelComponent.FadeOut(0.5f,
true, 1f);
1769 checkmark.FadeOut(0.5f,
true, 1f);
1770 autoSaveLabel?.
FadeOut(0.5f,
true, 1f);
1773 private bool SaveSub(ContentPackage packageToSaveTo)
1775 void handleExceptions(Action action)
1783 DebugConsole.ThrowError($
"An error occurred while trying to save {nameBox.Text}", e, createMessageBox:
true);
1787 if (
string.IsNullOrWhiteSpace(nameBox.
Text))
1789 GUI.AddMessage(TextManager.Get(
"SubNameMissingWarning"), GUIStyle.Red);
1797 MainSub.Info.OutpostModuleInfo !=
null)
1799 MainSub.Info.PreviewImage =
null;
1804 var msgBox =
new GUIMessageBox(TextManager.Get(
"warning"), TextManager.Get(
"undefinedsubmarineclasswarning"),
new LocalizedString[] { TextManager.Get(
"yes"), TextManager.Get(
"no") });
1806 msgBox.Buttons[0].OnClicked = (bt, userdata) =>
1808 handleExceptions(() => SaveSubToFile(nameBox.
Text, packageToSaveTo));
1813 msgBox.Buttons[1].OnClicked = (bt, userdata) =>
1821 bool result =
false;
1822 handleExceptions(() => result = SaveSubToFile(nameBox.
Text, packageToSaveTo));
1827 private void ReloadModifiedPackage(ContentPackage p)
1829 if (p is
null) {
return; }
1830 p.ReloadSubsAndItemAssemblies();
1831 if (p.Files.Length == 0)
1833 Directory.Delete(p.Dir, recursive:
true);
1834 ContentPackageManager.LocalPackages.Refresh();
1835 ContentPackageManager.EnabledPackages.DisableRemovedMods();
1852 private bool SaveSubToFile(
string name,
ContentPackage packageToSaveTo)
1854 Type subFileType = DetermineSubFileType(MainSub?.Info.Type ??
SubmarineType.Player);
1856 static string getExistingFilePath(
ContentPackage package,
string fileName)
1859 if (package.
Files.Any(f => f.Path == MainSub.Info.FilePath && Path.GetFileName(f.Path.Value) == fileName))
1861 return MainSub.Info.FilePath;
1866 if (!GameMain.DebugDraw)
1868 if (
Submarine.GetLightCount() > MaxLights)
1870 new GUIMessageBox(TextManager.Get(
"error"), TextManager.GetWithVariable(
"subeditor.lightcounterror",
"[max]", MaxLights.ToString()));
1874 if (
Submarine.GetShadowCastingLightCount() > MaxShadowCastingLights)
1876 new GUIMessageBox(TextManager.Get(
"error"), TextManager.GetWithVariable(
"subeditor.shadowcastinglightcounterror",
"[max]", MaxShadowCastingLights.ToString()));
1881 if (
string.IsNullOrWhiteSpace(name))
1883 GUI.AddMessage(TextManager.Get(
"SubNameMissingWarning"), GUIStyle.Red);
1887 foreach (var illegalChar
in Path.GetInvalidFileNameCharsCrossPlatform())
1889 if (!name.Contains(illegalChar)) {
continue; }
1890 GUI.AddMessage(TextManager.GetWithVariable(
"SubNameIllegalCharsWarning",
"[illegalchar]", illegalChar.ToString()), GUIStyle.Red);
1896 string newLocalModDir = $
"{ContentPackage.LocalModsDir}/{name}";
1898 string savePath = $
"{name}.sub";
1899 string prevSavePath =
null;
1900 if (packageToSaveTo !=
null)
1902 var modProject =
new ModProject(packageToSaveTo);
1903 var fileListPath = packageToSaveTo.
Path;
1904 if (packageToSaveTo == ContentPackageManager.VanillaCorePackage)
1907 throw new InvalidOperationException(
"Cannot save to Vanilla package");
1910 getExistingFilePath(packageToSaveTo, savePath) ??
1911 string.Format((MainSub?.Info.Type ??
SubmarineType.Player)
switch
1913 SubmarineType.Player =>
"Content/Submarines/{0}",
1914 SubmarineType.Outpost =>
"Content/Map/Outposts/{0}",
1915 SubmarineType.Ruin =>
"Content/Submarines/{0}",
1916 SubmarineType.Wreck =>
"Content/Map/Wrecks/{0}",
1917 SubmarineType.BeaconStation =>
"Content/Map/BeaconStations/{0}",
1918 SubmarineType.EnemySubmarine =>
"Content/Map/EnemySubmarines/{0}",
1919 SubmarineType.OutpostModule => MainSub.Info.FilePath.Contains(
"RuinModules") ?
"Content/Map/RuinModules/{0}" :
"Content/Map/Outposts/{0}",
1920 _ => throw new InvalidOperationException()
1922 modProject.ModVersion =
"";
1926 string existingFilePath = getExistingFilePath(packageToSaveTo, savePath);
1928 if (existingFilePath !=
null)
1930 savePath = existingFilePath;
1935 savePath = Path.Combine(packageToSaveTo.
Dir, savePath);
1936 if (File.Exists(savePath))
1938 var verification =
new GUIMessageBox(TextManager.Get(
"warning"), TextManager.Get(
"subeditor.duplicatesubinpackage"),
1939 new LocalizedString[] { TextManager.Get(
"yes"), TextManager.Get(
"no") });
1940 verification.Buttons[0].OnClicked = (_, _) =>
1942 addSubAndSave(modProject, savePath, fileListPath);
1943 verification.Close();
1946 verification.Buttons[1].OnClicked = verification.Close;
1951 addSubAndSave(modProject, savePath, fileListPath);
1955 savePath = Path.Combine(newLocalModDir, savePath);
1956 if (File.Exists(savePath))
1958 new GUIMessageBox(TextManager.Get(
"warning"), TextManager.GetWithVariable(
"subeditor.packagealreadyexists",
"[name]", name));
1963 ModProject modProject =
new ModProject { Name = name };
1964 addSubAndSave(modProject, savePath, Path.Combine(Path.GetDirectoryName(savePath), ContentPackage.FileListFileName));
1968 void addSubAndSave(ModProject modProject,
string filePath,
string packagePath)
1970 filePath = filePath.CleanUpPath();
1971 packagePath = packagePath.CleanUpPath();
1972 string packageDir = Path.GetDirectoryName(packagePath).CleanUpPathCrossPlatform(correctFilenameCase:
false);
1973 if (filePath.StartsWith(packageDir))
1975 filePath = $
"{ContentPath.ModDirStr}/{filePath[packageDir.Length..]}";
1977 if (!modProject.Files.Any(f => f.Type == subFileType && f.Path == filePath))
1980 var matchingFile = modProject.Files.FirstOrDefault(f => f.Type == subFileType && filePath.CleanUpPath().Equals(f.Path.CleanUpPath(), StringComparison.OrdinalIgnoreCase));
1981 if (matchingFile !=
null)
1983 File.Delete(matchingFile.Path.Replace(ContentPath.ModDirStr, packageDir, StringComparison.OrdinalIgnoreCase));
1984 modProject.RemoveFile(matchingFile);
1986 var newFile = ModProject.File.FromPath(filePath, subFileType);
1987 modProject.AddFile(newFile);
1990 using var _ = Validation.SkipInDebugBuilds();
1991 modProject.DiscardHashAndInstallTime();
1992 modProject.Save(packagePath);
1994 savePath = savePath.CleanUpPathCrossPlatform(correctFilenameCase:
false);
1995 if (MainSub !=
null)
1998 if (previewImage?.Sprite?.Texture !=
null && !previewImage.
Sprite.
Texture.IsDisposed && MainSub.Info.Type !=
SubmarineType.OutpostModule)
2000 bool savePreviewImage =
true;
2001 using System.IO.MemoryStream imgStream =
new System.IO.MemoryStream();
2008 DebugConsole.ThrowError($
"Saving the preview image of the submarine \"{MainSub.Info.Name}\" failed.", e);
2009 savePreviewImage =
false;
2011 MainSub.TrySaveAs(savePath, savePreviewImage ? imgStream :
null);
2015 MainSub.TrySaveAs(savePath);
2019 MainSub.CheckForErrors();
2021 GUI.AddMessage(TextManager.GetWithVariable(
"SubSavedNotification",
"[filepath]", savePath), GUIStyle.Green);
2023 if (savePath.StartsWith(newLocalModDir))
2025 ContentPackageManager.LocalPackages.Refresh();
2026 var newPackage = ContentPackageManager.LocalPackages.FirstOrDefault(p => p.Path.StartsWith(newLocalModDir));
2027 if (newPackage is RegularPackage regular)
2029 ContentPackageManager.EnabledPackages.EnableRegular(regular);
2030 GameSettings.SaveCurrentConfig();
2033 if (packageToSaveTo !=
null) { ReloadModifiedPackage(packageToSaveTo); }
2034 SubmarineInfo.RefreshSavedSub(savePath);
2035 if (prevSavePath !=
null && prevSavePath != savePath) { SubmarineInfo.RefreshSavedSub(prevSavePath); }
2036 MainSub.Info.PreviewImage = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.FilePath == savePath)?.PreviewImage;
2038 string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
2040 foreach (SubmarineInfo sub
in SubmarineInfo.SavedSubmarines)
2043 if (Path.GetDirectoryName(Path.GetFullPath(sub.FilePath)) == downloadFolder) {
continue; }
2044 linkedSubBox.
AddItem(sub.Name, sub);
2046 subNameLabel.
Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.
Font, subNameLabel.
Rect.Width);
2053 private void CreateSaveScreen(
bool quickSave =
false)
2055 if (saveFrame !=
null) {
return; }
2060 SetMode(Mode.Default);
2063 saveFrame =
new GUIFrame(
new RectTransform(Vector2.One, GUI.Canvas,
Anchor.Center), style:
"GUIBackgroundBlocker");
2065 var innerFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.55f, 0.65f), saveFrame.
RectTransform,
Anchor.Center) { MinSize = new Point(750, 500) });
2066 var paddedSaveFrame =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f, 0.9f), innerFrame.RectTransform,
Anchor.Center)) { Stretch =
true, RelativeSpacing = 0.02f };
2068 var columnArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.9f), paddedSaveFrame.RectTransform), isHorizontal:
true) { RelativeSpacing = 0.02f, Stretch =
true };
2069 var leftColumn =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.55f, 1.0f), columnArea.RectTransform)) { RelativeSpacing = 0.01f, Stretch =
true };
2070 var rightColumn =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.42f, 1.0f), columnArea.RectTransform)) { RelativeSpacing = 0.02f, Stretch =
true };
2074 var nameHeaderGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(.975f, 0.03f), leftColumn.RectTransform),
true);
2075 var saveSubLabel =
new GUITextBlock(
new RectTransform(
new Vector2(.5f, 1f), nameHeaderGroup.RectTransform),
2076 TextManager.Get(
"SaveSubDialogName"), font: GUIStyle.SubHeadingFont);
2078 submarineNameCharacterCount =
new GUITextBlock(
new RectTransform(
new Vector2(.5f, 1f), nameHeaderGroup.RectTransform),
string.Empty, textAlignment: Alignment.TopRight);
2080 nameBox =
new GUITextBox(
new RectTransform(
new Vector2(1.0f, 0.05f), leftColumn.RectTransform))
2082 OnEnterPressed = ChangeSubName
2086 if (text.Length > submarineNameLimit)
2088 nameBox.
Text = text.Substring(0, submarineNameLimit);
2089 nameBox.
Flash(GUIStyle.Red);
2093 submarineNameCharacterCount.
Text = text.
Length +
" / " + submarineNameLimit;
2097 nameBox.
Text = MainSub?.Info.Name ??
"";
2099 submarineNameCharacterCount.
Text = nameBox.
Text.Length +
" / " + submarineNameLimit;
2101 var descriptionHeaderGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(.975f, 0.03f), leftColumn.RectTransform), isHorizontal:
true);
2103 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), descriptionHeaderGroup.RectTransform), TextManager.Get(
"SaveSubDialogDescription"), font: GUIStyle.SubHeadingFont);
2104 submarineDescriptionCharacterCount =
new GUITextBlock(
new RectTransform(
new Vector2(.5f, 1f), descriptionHeaderGroup.RectTransform),
string.Empty, textAlignment: Alignment.TopRight);
2106 var descriptionContainer =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 0.25f), leftColumn.RectTransform));
2107 descriptionBox =
new GUITextBox(
new RectTransform(Vector2.One, descriptionContainer.Content.RectTransform,
Anchor.Center),
2108 font: GUIStyle.SmallFont, style:
"GUITextBoxNoBorder", wrap:
true, textAlignment: Alignment.TopLeft)
2110 Padding =
new Vector4(10 * GUI.Scale)
2115 if (text.Length > submarineDescriptionLimit)
2117 descriptionBox.
Text = text.Substring(0, submarineDescriptionLimit);
2118 descriptionBox.
Flash(GUIStyle.Red);
2122 Vector2 textSize = textBox.Font.MeasureString(descriptionBox.
WrappedText);
2123 textBox.RectTransform.NonScaledSize =
new Point(textBox.RectTransform.NonScaledSize.X, Math.Max(descriptionContainer.Content.Rect.Height, (
int)textSize.Y + 10));
2124 descriptionContainer.UpdateScrollBarSize();
2125 descriptionContainer.BarScroll = 1.0f;
2126 ChangeSubDescription(textBox, text);
2130 descriptionBox.
Text = GetSubDescription();
2132 var subTypeContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.01f), leftColumn.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
2137 new GUITextBlock(
new RectTransform(
new Vector2(0.4f, 1f), subTypeContainer.RectTransform), TextManager.Get(
"submarinetype"));
2138 var subTypeDropdown =
new GUIDropDown(
new RectTransform(
new Vector2(0.6f, 1f), subTypeContainer.RectTransform));
2139 subTypeContainer.RectTransform.MinSize =
new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
2140 foreach (SubmarineType subType
in Enum.GetValues(typeof(SubmarineType)))
2143 string textTag =
"SubmarineType." + subType;
2144 if (subType ==
SubmarineType.EnemySubmarine && !TextManager.ContainsTag(textTag))
2146 textTag =
"MissionType.Pirate";
2148 subTypeDropdown.AddItem(TextManager.Get(textTag), subType);
2153 var layerVisibilityGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.01f), leftColumn.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
2154 var visibleLayers = Layers.Where(l => !MainSub.Info.LayersHiddenByDefault.Contains(l.Key.ToIdentifier()));
2155 LocalizedString visibleLayersString = LocalizedString.Join(
", ", visibleLayers.Select(l => TextManager.Capitalize(l.Key)) ?? ((LocalizedString)
"None").ToEnumerable());
2156 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), layerVisibilityGroup.RectTransform), TextManager.Get(
"editor.layer.visiblebydefault"), textAlignment: Alignment.CenterLeft);
2157 var layerVisibilityDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.5f, 1f), layerVisibilityGroup.RectTransform), text: visibleLayersString, selectMultiple:
true);
2158 foreach (var layer
in Layers)
2160 string layerName = layer.Key;
2161 layerVisibilityDropDown.AddItem(TextManager.Capitalize(layerName), layerName);
2162 if (visibleLayers.Contains(layer))
2164 layerVisibilityDropDown.SelectItem(layerName);
2167 layerVisibilityDropDown.OnSelected += (button, obj) =>
2169 string layerName = (string)obj;
2170 bool isVisible = layerVisibilityDropDown.SelectedDataMultiple.Contains(obj);
2173 MainSub.Info.LayersHiddenByDefault.Remove(layerName.ToIdentifier());
2177 MainSub.Info.LayersHiddenByDefault.Add(layerName.ToIdentifier());
2180 layerVisibilityDropDown.Text = ToolBox.LimitString(layerVisibilityDropDown.Text.Value, layerVisibilityDropDown.Font, layerVisibilityDropDown.Rect.Width);
2183 layerVisibilityGroup.RectTransform.MinSize = layerVisibilityDropDown.RectTransform.MinSize =
new Point(0, layerVisibilityDropDown.RectTransform.Children.Max(c => c.MinSize.Y));
2188 var subTypeDependentSettingFrame =
new GUIFrame(
new RectTransform((1.0f, 0.5f), leftColumn.RectTransform), style:
"InnerFrame");
2190 var outpostSettingsContainer =
new GUILayoutGroup(
new RectTransform(Vector2.One, subTypeDependentSettingFrame.RectTransform))
2192 CanBeFocused =
true,
2199 var outpostModuleGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
2201 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), outpostModuleGroup.RectTransform), TextManager.Get(
"outpostmoduletype"), textAlignment: Alignment.CenterLeft);
2202 HashSet<Identifier> availableFlags =
new HashSet<Identifier>();
2203 foreach (Identifier flag
in OutpostGenerationParams.OutpostParams.SelectMany(p => p.ModuleCounts.Select(m => m.Identifier))) { availableFlags.Add(flag); }
2204 foreach (Identifier flag
in RuinGeneration.RuinGenerationParams.RuinParams.SelectMany(p => p.ModuleCounts.Select(m => m.Identifier))) { availableFlags.Add(flag); }
2205 foreach (var sub
in SubmarineInfo.SavedSubmarines)
2207 if (sub.OutpostModuleInfo ==
null) {
continue; }
2208 foreach (Identifier flag
in sub.OutpostModuleInfo.ModuleFlags)
2210 if (flag ==
"none") {
continue; }
2211 availableFlags.Add(flag);
2215 var moduleTypeDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.5f, 1f), outpostModuleGroup.RectTransform),
2216 text: LocalizedString.Join(
", ", MainSub?.Info?.OutpostModuleInfo?.ModuleFlags.Select(s => TextManager.Capitalize(s.Value)) ?? ((LocalizedString)
"None").ToEnumerable()), selectMultiple:
true);
2217 foreach (Identifier flag
in availableFlags.OrderBy(f => f.Value, StringComparer.InvariantCultureIgnoreCase))
2219 moduleTypeDropDown.AddItem(TextManager.Capitalize(flag.Value), flag);
2220 if (MainSub?.Info?.OutpostModuleInfo ==
null) {
continue; }
2221 if (MainSub.Info.OutpostModuleInfo.ModuleFlags.Contains(flag))
2223 moduleTypeDropDown.SelectItem(flag);
2226 moduleTypeDropDown.OnSelected += (_, __) =>
2228 if (MainSub?.Info?.OutpostModuleInfo ==
null) {
return false; }
2229 MainSub.Info.OutpostModuleInfo.SetFlags(moduleTypeDropDown.SelectedDataMultiple.Cast<Identifier>());
2230 moduleTypeDropDown.Text = ToolBox.LimitString(
2231 MainSub.Info.OutpostModuleInfo.ModuleFlags.Any(f => f !=
"none") ? moduleTypeDropDown.Text :
"None",
2232 moduleTypeDropDown.Font, moduleTypeDropDown.Rect.Width);
2235 outpostModuleGroup.RectTransform.MinSize =
new Point(0, outpostModuleGroup.RectTransform.Children.Max(c => c.MinSize.Y));
2239 var allowAttachGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
2241 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), allowAttachGroup.RectTransform), TextManager.Get(
"outpostmoduleallowattachto"), textAlignment: Alignment.CenterLeft);
2243 var allowAttachDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.5f, 1f), allowAttachGroup.RectTransform),
2244 text: LocalizedString.Join(
", ", MainSub?.Info?.OutpostModuleInfo?.AllowAttachToModules.Select(s => TextManager.Capitalize(s.Value)) ?? ((LocalizedString)
"Any").ToEnumerable()), selectMultiple:
true);
2245 allowAttachDropDown.AddItem(TextManager.Capitalize(
"any"),
"any".ToIdentifier());
2246 if (MainSub.Info.OutpostModuleInfo ==
null ||
2247 !MainSub.Info.OutpostModuleInfo.AllowAttachToModules.Any() ||
2248 MainSub.Info.OutpostModuleInfo.AllowAttachToModules.All(s => s ==
"any"))
2250 allowAttachDropDown.SelectItem(
"any".ToIdentifier());
2252 foreach (Identifier flag
in availableFlags.OrderBy(f => f.Value, StringComparer.InvariantCultureIgnoreCase))
2254 if (flag ==
"any" || flag ==
"none") {
continue; }
2255 allowAttachDropDown.AddItem(TextManager.Capitalize(flag.Value), flag);
2256 if (MainSub?.Info?.OutpostModuleInfo ==
null) {
continue; }
2257 if (MainSub.Info.OutpostModuleInfo.AllowAttachToModules.Contains(flag))
2259 allowAttachDropDown.SelectItem(flag);
2262 allowAttachDropDown.OnSelected += (_, __) =>
2264 if (MainSub?.Info?.OutpostModuleInfo ==
null) {
return false; }
2265 MainSub.Info.OutpostModuleInfo.SetAllowAttachTo(allowAttachDropDown.SelectedDataMultiple.Cast<Identifier>());
2266 allowAttachDropDown.Text = ToolBox.LimitString(
2267 MainSub.Info.OutpostModuleInfo.ModuleFlags.Any(f => f !=
"none") ? allowAttachDropDown.Text.Value :
"None",
2268 allowAttachDropDown.Font, allowAttachDropDown.Rect.Width);
2271 allowAttachGroup.RectTransform.MinSize =
new Point(0, allowAttachGroup.RectTransform.Children.Max(c => c.MinSize.Y));
2275 var locationTypeGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
2277 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), TextManager.Get(
"outpostmoduleallowedlocationtypes"), textAlignment: Alignment.CenterLeft);
2278 HashSet<Identifier> availableLocationTypes =
new HashSet<Identifier>();
2279 foreach (LocationType locationType
in LocationType.Prefabs) { availableLocationTypes.Add(locationType.Identifier); }
2281 var locationTypeDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.5f, 1f), locationTypeGroup.RectTransform),
2282 text: LocalizedString.Join(
", ", MainSub?.Info?.OutpostModuleInfo?.AllowedLocationTypes.Select(lt => TextManager.Capitalize(lt.Value)) ?? ((LocalizedString)
"any").ToEnumerable()), selectMultiple:
true);
2283 locationTypeDropDown.AddItem(TextManager.Capitalize(
"any"),
"any".ToIdentifier());
2284 foreach (Identifier locationType
in availableLocationTypes.OrderBy(f => f.Value, StringComparer.InvariantCultureIgnoreCase))
2286 locationTypeDropDown.AddItem(TextManager.Capitalize(locationType.Value), locationType);
2287 if (MainSub?.Info?.OutpostModuleInfo ==
null) {
continue; }
2288 if (MainSub.Info.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType))
2290 locationTypeDropDown.SelectItem(locationType);
2293 if (!MainSub.Info?.OutpostModuleInfo?.AllowedLocationTypes?.Any() ??
true) { locationTypeDropDown.SelectItem(
"any".ToIdentifier()); }
2295 locationTypeDropDown.OnSelected += (_, __) =>
2297 MainSub?.Info?.OutpostModuleInfo?.SetAllowedLocationTypes(locationTypeDropDown.SelectedDataMultiple.Cast<Identifier>());
2298 locationTypeDropDown.Text = ToolBox.LimitString(locationTypeDropDown.Text.Value, locationTypeDropDown.Font, locationTypeDropDown.Rect.Width);
2301 locationTypeGroup.RectTransform.MinSize =
new Point(0, locationTypeGroup.RectTransform.Children.Max(c => c.MinSize.Y));
2305 var gapPositionGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
2306 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), gapPositionGroup.RectTransform), TextManager.Get(
"outpostmodulegappositions"), textAlignment: Alignment.CenterLeft);
2307 var gapPositionDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.5f, 1f), gapPositionGroup.RectTransform),
2308 text:
"", selectMultiple:
true);
2310 var outpostModuleInfo = MainSub.Info?.OutpostModuleInfo;
2311 if (outpostModuleInfo !=
null)
2313 if (outpostModuleInfo.GapPositions == OutpostModuleInfo.GapPosition.None)
2315 outpostModuleInfo.DetermineGapPositions(MainSub);
2317 foreach (OutpostModuleInfo.GapPosition gapPos in Enum.GetValues(typeof(OutpostModuleInfo.GapPosition)))
2319 if (gapPos == OutpostModuleInfo.GapPosition.None) {
continue; }
2320 gapPositionDropDown.AddItem(TextManager.Capitalize(gapPos.ToString()), gapPos);
2321 if (outpostModuleInfo.GapPositions.HasFlag(gapPos))
2323 gapPositionDropDown.SelectItem(gapPos);
2328 gapPositionDropDown.OnSelected += (_, __) =>
2330 if (MainSub.Info?.OutpostModuleInfo ==
null) {
return false; }
2331 MainSub.Info.OutpostModuleInfo.GapPositions = OutpostModuleInfo.GapPosition.None;
2332 if (gapPositionDropDown.SelectedDataMultiple.Any())
2334 List<LocalizedString> gapPosTexts =
new List<LocalizedString>();
2335 foreach (OutpostModuleInfo.GapPosition gapPos in gapPositionDropDown.SelectedDataMultiple)
2337 MainSub.Info.OutpostModuleInfo.GapPositions |= gapPos;
2338 gapPosTexts.Add(TextManager.Capitalize(gapPos.ToString()));
2340 gapPositionDropDown.Text = ToolBox.LimitString(
string.Join(
", ", gapPosTexts), gapPositionDropDown.Font, gapPositionDropDown.Rect.Width);
2344 gapPositionDropDown.Text = ToolBox.LimitString(
"None", gapPositionDropDown.Font, gapPositionDropDown.Rect.Width);
2348 gapPositionGroup.RectTransform.MinSize =
new Point(0, gapPositionGroup.RectTransform.Children.Max(c => c.MinSize.Y));
2350 var canAttachToPrevGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
2351 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1f), canAttachToPrevGroup.RectTransform), TextManager.Get(
"canattachtoprevious"), textAlignment: Alignment.CenterLeft)
2353 ToolTip = TextManager.Get(
"canattachtoprevious.tooltip")
2355 var canAttachToPrevDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.5f, 1f), canAttachToPrevGroup.RectTransform),
2356 text:
"", selectMultiple:
true);
2357 if (outpostModuleInfo !=
null)
2359 foreach (OutpostModuleInfo.GapPosition gapPos in Enum.GetValues(typeof(OutpostModuleInfo.GapPosition)))
2361 if (gapPos == OutpostModuleInfo.GapPosition.None) {
continue; }
2362 canAttachToPrevDropDown.AddItem(TextManager.Capitalize(gapPos.ToString()), gapPos);
2363 if (outpostModuleInfo.CanAttachToPrevious.HasFlag(gapPos))
2365 canAttachToPrevDropDown.SelectItem(gapPos);
2370 canAttachToPrevDropDown.OnSelected += (_, __) =>
2372 if (
Submarine.MainSub.Info?.OutpostModuleInfo ==
null) {
return false; }
2373 Submarine.MainSub.Info.OutpostModuleInfo.CanAttachToPrevious = OutpostModuleInfo.GapPosition.None;
2374 if (canAttachToPrevDropDown.SelectedDataMultiple.Any())
2376 List<string> gapPosTexts =
new List<string>();
2377 foreach (OutpostModuleInfo.GapPosition gapPos in canAttachToPrevDropDown.SelectedDataMultiple)
2379 Submarine.MainSub.Info.OutpostModuleInfo.CanAttachToPrevious |= gapPos;
2380 gapPosTexts.Add(TextManager.Capitalize(gapPos.ToString()).Value);
2382 canAttachToPrevDropDown.Text = ToolBox.LimitString(
string.Join(
", ", gapPosTexts), canAttachToPrevDropDown.Font, canAttachToPrevDropDown.Rect.Width);
2386 canAttachToPrevDropDown.Text = ToolBox.LimitString(
"None", canAttachToPrevDropDown.Font, canAttachToPrevDropDown.Rect.Width);
2390 canAttachToPrevGroup.RectTransform.MinSize =
new Point(0, gapPositionGroup.RectTransform.Children.Max(c => c.MinSize.Y));
2395 var maxModuleCountGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.5f), outpostSettingsContainer.RectTransform), isHorizontal:
true)
2399 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), maxModuleCountGroup.RectTransform),
2400 TextManager.Get(
"OutPostModuleMaxCount"), textAlignment: Alignment.CenterLeft, wrap:
true)
2402 ToolTip = TextManager.Get(
"OutPostModuleMaxCountToolTip")
2404 new GUINumberInput(
new RectTransform(
new Vector2(0.4f, 1.0f), maxModuleCountGroup.RectTransform),
NumberType.Int)
2406 ToolTip = TextManager.Get(
"OutPostModuleMaxCountToolTip"),
2407 IntValue = MainSub?.Info?.OutpostModuleInfo?.MaxCount ?? 1000,
2410 OnValueChanged = (numberInput) =>
2412 MainSub.Info.OutpostModuleInfo.MaxCount = numberInput.IntValue;
2416 var commonnessGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.5f), outpostSettingsContainer.RectTransform), isHorizontal:
true)
2420 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), commonnessGroup.RectTransform),
2421 TextManager.Get(
"subeditor.outpostcommonness"), textAlignment: Alignment.CenterLeft, wrap:
true);
2422 new GUINumberInput(
new RectTransform(
new Vector2(0.4f, 1.0f), commonnessGroup.RectTransform),
NumberType.Float)
2424 FloatValue = MainSub?.Info?.OutpostModuleInfo?.Commonness ?? 10,
2426 MaxValueFloat = 100,
2427 OnValueChanged = (numberInput) =>
2429 MainSub.Info.OutpostModuleInfo.Commonness = numberInput.FloatValue;
2432 outpostSettingsContainer.RectTransform.MinSize =
new Point(0, outpostSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0));
2436 var extraSettingsContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(1, 0.5f), subTypeDependentSettingFrame.RectTransform))
2438 CanBeFocused =
true,
2443 var minDifficultyGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.25f), extraSettingsContainer.RectTransform), isHorizontal:
true)
2447 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), minDifficultyGroup.RectTransform),
2448 TextManager.Get(
"minleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap:
true);
2449 var numInput =
new GUINumberInput(
new RectTransform(
new Vector2(0.4f, 1.0f), minDifficultyGroup.RectTransform),
NumberType.Int)
2451 IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MinLevelDifficulty ?? 0),
2454 OnValueChanged = (numberInput) =>
2456 MainSub.Info.GetExtraSubmarineInfo.MinLevelDifficulty = numberInput.IntValue;
2459 minDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
2460 var maxDifficultyGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.25f), extraSettingsContainer.RectTransform), isHorizontal:
true)
2464 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), maxDifficultyGroup.RectTransform),
2465 TextManager.Get(
"maxleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap:
true);
2466 numInput =
new GUINumberInput(
new RectTransform(
new Vector2(0.4f, 1.0f), maxDifficultyGroup.RectTransform),
NumberType.Int)
2468 IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MaxLevelDifficulty ?? 100),
2471 OnValueChanged = (numberInput) =>
2473 MainSub.Info.GetExtraSubmarineInfo.MaxLevelDifficulty = numberInput.IntValue;
2476 maxDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
2481 var beaconSettingsContainer =
new GUILayoutGroup(
new RectTransform(Vector2.One, extraSettingsContainer.RectTransform))
2483 CanBeFocused =
true,
2488 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get(
"allowdamagedwalls"))
2490 Selected = MainSub?.Info?.BeaconStationInfo?.AllowDamagedWalls ??
true,
2491 OnSelected = (tb) =>
2493 MainSub.Info.BeaconStationInfo.AllowDamagedWalls = tb.Selected;
2497 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get(
"allowdamageddevices"))
2499 Selected = MainSub?.Info?.BeaconStationInfo?.AllowDamagedDevices ??
true,
2500 OnSelected = (tb) =>
2502 MainSub.Info.BeaconStationInfo.AllowDamagedDevices = tb.Selected;
2506 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get(
"allowdisconnectedwires"))
2508 Selected = MainSub?.Info?.BeaconStationInfo?.AllowDisconnectedWires ??
true,
2509 OnSelected = (tb) =>
2511 MainSub.Info.BeaconStationInfo.AllowDisconnectedWires = tb.Selected;
2515 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get(
"beaconstationplacement"))
2517 Selected = MainSub.Info.BeaconStationInfo is { Placement: Level.PlacementType.Top },
2518 OnSelected = (tb) =>
2520 MainSub.Info.BeaconStationInfo.Placement = tb.Selected ?
2521 Level.PlacementType.Top :
2522 Level.PlacementType.Bottom;
2526 beaconSettingsContainer.RectTransform.MinSize =
new Point(0, beaconSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0));
2530 var subSettingsContainer =
new GUILayoutGroup(
new RectTransform(Vector2.One, subTypeDependentSettingFrame.RectTransform))
2535 var priceGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal:
true)
2539 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), priceGroup.RectTransform),
2540 TextManager.Get(
"subeditor.price"), textAlignment: Alignment.CenterLeft, wrap:
true);
2543 int basePrice = (GameMain.DebugDraw ? 0 : MainSub?.CalculateBasePrice()) ?? 1000;
2544 new GUINumberInput(
new RectTransform(
new Vector2(0.4f, 1.0f), priceGroup.RectTransform),
NumberType.Int, buttonVisibility: GUINumberInput.ButtonVisibility.ForceHidden)
2546 IntValue = Math.Max(MainSub?.Info?.Price ?? basePrice, basePrice),
2547 MinValueInt = basePrice,
2548 MaxValueInt = 999999,
2549 OnValueChanged = (numberInput) =>
2551 MainSub.Info.Price = numberInput.IntValue;
2554 if (MainSub?.Info !=
null)
2556 MainSub.Info.Price = Math.Max(MainSub.Info.Price, basePrice);
2559 var classGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
2563 var classText =
new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), classGroup.RectTransform),
2564 TextManager.Get(
"submarineclass"), textAlignment: Alignment.CenterLeft, wrap:
true)
2566 ToolTip = TextManager.Get(
"submarineclass.description")
2568 GUIDropDown classDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.4f, 1.0f), classGroup.RectTransform));
2569 classDropDown.RectTransform.MinSize =
new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
2570 foreach (SubmarineClass subClass
in Enum.GetValues(typeof(SubmarineClass)))
2572 classDropDown.AddItem(TextManager.Get($
"{nameof(SubmarineClass)}.{subClass}"), subClass, toolTip: TextManager.Get($
"submarineclass.{subClass}.description"));
2575 classDropDown.OnSelected += (selected, userdata) =>
2581 MainSub.Info.SubmarineClass = submarineClass;
2590 classDropDown.SelectItem(!MainSub.Info.HasTag(
SubmarineTag.Shuttle) ? MainSub.Info.SubmarineClass : (object)
SubmarineTag.Shuttle);
2592 var tierGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal:
true)
2596 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), tierGroup.RectTransform),
2597 TextManager.Get(
"subeditor.tier"), textAlignment: Alignment.CenterLeft, wrap:
true)
2599 ToolTip = TextManager.Get(
"submarinetier.description")
2602 new GUINumberInput(
new RectTransform(
new Vector2(0.4f, 1.0f), tierGroup.RectTransform),
NumberType.Int)
2604 IntValue = MainSub.Info.Tier,
2606 MaxValueInt = SubmarineInfo.HighestTier,
2607 OnValueChanged = (numberInput) =>
2609 MainSub.Info.Tier = numberInput.IntValue;
2612 if (MainSub?.Info !=
null)
2614 MainSub.Info.Tier = Math.Clamp(MainSub.Info.Tier, 1, SubmarineInfo.HighestTier);
2617 var crewSizeArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal:
true)
2623 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), crewSizeArea.RectTransform),
2624 TextManager.Get(
"RecommendedCrewSize"), textAlignment: Alignment.CenterLeft, wrap:
true, font: GUIStyle.SmallFont);
2625 var crewSizeMin =
new GUINumberInput(
new RectTransform(
new Vector2(0.17f, 1.0f), crewSizeArea.RectTransform),
NumberType.Int, relativeButtonAreaWidth: 0.25f)
2630 new GUITextBlock(
new RectTransform(
new Vector2(0.06f, 1.0f), crewSizeArea.RectTransform),
"-", textAlignment: Alignment.Center);
2631 var crewSizeMax =
new GUINumberInput(
new RectTransform(
new Vector2(0.17f, 1.0f), crewSizeArea.RectTransform),
NumberType.Int, relativeButtonAreaWidth: 0.25f)
2637 crewSizeMin.OnValueChanged += (numberInput) =>
2639 crewSizeMax.IntValue = Math.Max(crewSizeMax.IntValue, numberInput.IntValue);
2640 MainSub.Info.RecommendedCrewSizeMin = crewSizeMin.IntValue;
2641 MainSub.Info.RecommendedCrewSizeMax = crewSizeMax.IntValue;
2644 crewSizeMax.OnValueChanged += (numberInput) =>
2646 crewSizeMin.IntValue = Math.Min(crewSizeMin.IntValue, numberInput.IntValue);
2647 MainSub.Info.RecommendedCrewSizeMin = crewSizeMin.IntValue;
2648 MainSub.Info.RecommendedCrewSizeMax = crewSizeMax.IntValue;
2651 var crewExpArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal:
true)
2657 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), crewExpArea.RectTransform),
2658 TextManager.Get(
"RecommendedCrewExperience"), textAlignment: Alignment.CenterLeft, wrap:
true, font: GUIStyle.SmallFont);
2660 var toggleExpLeft =
new GUIButton(
new RectTransform(
new Vector2(0.05f, 1.0f), crewExpArea.RectTransform), style:
"GUIButtonToggleLeft");
2661 var experienceText =
new GUITextBlock(
new RectTransform(
new Vector2(0.3f, 1.0f), crewExpArea.RectTransform),
2662 text: TextManager.Get(SubmarineInfo.CrewExperienceLevel.CrewExperienceLow.ToIdentifier()), textAlignment: Alignment.Center);
2663 var toggleExpRight =
new GUIButton(
new RectTransform(
new Vector2(0.05f, 1.0f), crewExpArea.RectTransform), style:
"GUIButtonToggleRight");
2665 toggleExpLeft.OnClicked += (btn, userData) =>
2667 MainSub.Info.RecommendedCrewExperience--;
2668 if (MainSub.Info.RecommendedCrewExperience < SubmarineInfo.CrewExperienceLevel.CrewExperienceLow)
2670 MainSub.Info.RecommendedCrewExperience = SubmarineInfo.CrewExperienceLevel.CrewExperienceHigh;
2672 experienceText.Text = TextManager.Get(MainSub.Info.RecommendedCrewExperience.ToIdentifier());
2676 toggleExpRight.OnClicked += (btn, userData) =>
2678 MainSub.Info.RecommendedCrewExperience++;
2679 if (MainSub.Info.RecommendedCrewExperience > SubmarineInfo.CrewExperienceLevel.CrewExperienceHigh)
2681 MainSub.Info.RecommendedCrewExperience = SubmarineInfo.CrewExperienceLevel.CrewExperienceLow;
2683 experienceText.Text = TextManager.Get(MainSub.Info.RecommendedCrewExperience.ToIdentifier());
2687 var hideInMenusArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
2692 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), hideInMenusArea.RectTransform),
2693 TextManager.Get(
"HideInMenus"), textAlignment: Alignment.CenterLeft, wrap:
true, font: GUIStyle.SmallFont);
2695 new GUITickBox(
new RectTransform((0.4f, 1.0f), hideInMenusArea.RectTransform),
"")
2712 var outFittingArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
2717 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), outFittingArea.RectTransform),
2718 TextManager.Get(
"ManuallyOutfitted"), textAlignment: Alignment.CenterLeft, wrap:
true, font: GUIStyle.SmallFont)
2720 ToolTip = TextManager.Get(
"manuallyoutfittedtooltip")
2722 new GUITickBox(
new RectTransform((0.4f, 1.0f), outFittingArea.RectTransform),
"")
2724 ToolTip = TextManager.Get(
"manuallyoutfittedtooltip"),
2725 Selected = MainSub.Info.IsManuallyOutfitted,
2728 MainSub.Info.IsManuallyOutfitted = box.Selected;
2733 if (MainSub !=
null)
2735 int min = MainSub.Info.RecommendedCrewSizeMin;
2736 int max = MainSub.Info.RecommendedCrewSizeMax;
2737 crewSizeMin.IntValue = min;
2738 crewSizeMax.IntValue = max;
2739 if (MainSub.Info.RecommendedCrewExperience == SubmarineInfo.CrewExperienceLevel.Unknown)
2741 MainSub.Info.RecommendedCrewExperience = SubmarineInfo.CrewExperienceLevel.CrewExperienceLow;
2743 experienceText.Text = TextManager.Get(MainSub.Info.RecommendedCrewExperience.ToIdentifier());
2746 subTypeDropdown.OnSelected += (selected, userdata) =>
2749 MainSub.Info.Type = type;
2752 MainSub.Info.OutpostModuleInfo ??=
new OutpostModuleInfo(MainSub.Info);
2756 MainSub.Info.BeaconStationInfo ??=
new BeaconStationInfo(MainSub.Info);
2760 MainSub.Info.WreckInfo ??=
new WreckInfo(MainSub.Info);
2762 previewImageButtonHolder.
Children.ForEach(c => c.Enabled = MainSub.Info.AllowPreviewImage);
2763 outpostSettingsContainer.Visible = type ==
SubmarineType.OutpostModule;
2765 beaconSettingsContainer.Visible = type ==
SubmarineType.BeaconStation;
2766 subSettingsContainer.Visible = type ==
SubmarineType.Player;
2769 subSettingsContainer.RectTransform.MinSize =
new Point(0, subSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0));
2773 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), rightColumn.RectTransform), TextManager.Get(
"SubPreviewImage"), font: GUIStyle.SubHeadingFont);
2775 var previewImageHolder =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.4f), rightColumn.RectTransform), style:
null) { Color = Color.Black, CanBeFocused =
false };
2776 previewImage =
new GUIImage(
new RectTransform(Vector2.One, previewImageHolder.RectTransform), MainSub?.Info.PreviewImage, scaleToFit:
true);
2778 previewImageButtonHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.05f), rightColumn.RectTransform), isHorizontal:
true) { Stretch =
true, RelativeSpacing = 0.05f };
2780 new GUIButton(
new RectTransform(
new Vector2(0.5f, 1.0f), previewImageButtonHolder.
RectTransform), TextManager.Get(
"SubPreviewImageCreate"), style:
"GUIButtonSmall")
2782 Enabled = MainSub?.Info.AllowPreviewImage ??
false,
2783 OnClicked = (btn, userdata) =>
2785 using (System.IO.MemoryStream imgStream =
new System.IO.MemoryStream())
2787 CreateImage(defaultPreviewImageSize.X, defaultPreviewImageSize.Y, imgStream);
2788 previewImage.
Sprite =
new Sprite(TextureLoader.FromStream(imgStream, compress:
false),
null,
null);
2789 if (MainSub !=
null)
2791 MainSub.Info.PreviewImage = previewImage.
Sprite;
2798 new GUIButton(
new RectTransform(
new Vector2(0.5f, 1.0f), previewImageButtonHolder.
RectTransform), TextManager.Get(
"SubPreviewImageBrowse"), style:
"GUIButtonSmall")
2800 Enabled = MainSub?.Info.AllowPreviewImage ??
false,
2801 OnClicked = (btn, userdata) =>
2803 FileSelection.OnFileSelected = (file) =>
2807 new GUIMessageBox(TextManager.Get(
"Error"), TextManager.Get(
"WorkshopItemPreviewImageTooLarge"));
2811 previewImage.
Sprite =
new Sprite(file, sourceRectangle:
null);
2812 if (MainSub !=
null)
2814 MainSub.Info.PreviewImage = previewImage.
Sprite;
2817 FileSelection.ClearFileTypeFilters();
2818 FileSelection.AddFileTypeFilter(
"PNG",
"*.png");
2819 FileSelection.AddFileTypeFilter(
"JPEG",
"*.jpg, *.jpeg");
2820 FileSelection.AddFileTypeFilter(
"All files",
"*.*");
2821 FileSelection.SelectFileTypeFilter(
"*.png");
2822 FileSelection.Open =
true;
2829 var contentPackageTabber =
new GUILayoutGroup(
new RectTransform((1.0f, 0.075f), rightColumn.RectTransform), isHorizontal:
true);
2831 GUIButton createTabberBtn(
string labelTag)
2833 var btn =
new GUIButton(
new RectTransform((0.5f, 1.0f), contentPackageTabber.RectTransform,
Anchor.BottomCenter,
Pivot.BottomCenter), TextManager.Get(labelTag), style:
"GUITabButton");
2834 btn.TextBlock.Wrap =
true;
2835 btn.TextBlock.SetTextPos();
2836 btn.RectTransform.MaxSize = RectTransform.MaxPoint;
2837 btn.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint);
2838 btn.Font = GUIStyle.SmallFont;
2842 var saveToPackageTabBtn = createTabberBtn(
"SaveToLocalPackage");
2843 saveToPackageTabBtn.Selected =
true;
2844 var reqPackagesTabBtn = createTabberBtn(
"RequiredContentPackages");
2845 reqPackagesTabBtn.Selected =
false;
2847 var horizontalArea =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.45f), rightColumn.RectTransform), style:
null);
2849 var saveInPackageLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One,
2850 horizontalArea.RectTransform,
Anchor.BottomRight))
2855 var packageToSaveInList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 1.0f),
2856 saveInPackageLayout.RectTransform));
2858 var packToSaveInFilter
2859 =
new GUITextBox(
new RectTransform((1.0f, 0.15f), saveInPackageLayout.RectTransform),
2860 createClearButton:
true);
2862 GUILayoutGroup addItemToPackageToSaveList(LocalizedString itemText, ContentPackage p)
2864 var listItem =
new GUIFrame(
new RectTransform((1.0f, 0.15f), packageToSaveInList.Content.RectTransform),
2865 style:
"ListBoxElement")
2869 if (p !=
null && p != ContentPackageManager.VanillaCorePackage) { listItem.ToolTip = p.Dir; }
2871 new GUILayoutGroup(
new RectTransform(Vector2.One, listItem.RectTransform),
2872 isHorizontal:
true) { Stretch =
true };
2875 new RectTransform(Vector2.One, retVal.RectTransform, scaleBasis:
ScaleBasis.BothHeight),
2876 style:
null) { CanBeFocused =
false };
2877 var pkgText =
new GUITextBlock(
new RectTransform(Vector2.One, retVal.RectTransform), itemText)
2878 { CanBeFocused =
false };
2884 var modifyVanillaListItem = addItemToPackageToSaveList(
"Modify Vanilla content package", ContentPackageManager.VanillaCorePackage);
2885 var modifyVanillaListIcon = modifyVanillaListItem.GetChild<GUIFrame>();
2886 GUIStyle.Apply(modifyVanillaListIcon,
"WorkshopMenu.EditButton");
2889 var newPackageListItem = addItemToPackageToSaveList(TextManager.Get(
"CreateNewLocalPackage"),
null);
2890 var newPackageListIcon = newPackageListItem.GetChild<GUIFrame>();
2891 var newPackageListText = newPackageListItem.GetChild<GUITextBlock>();
2892 GUIStyle.Apply(newPackageListIcon,
"NewContentPackageIcon");
2893 new GUICustomComponent(
new RectTransform(Vector2.Zero, saveInPackageLayout.RectTransform),
2894 onUpdate: (f, component) =>
2896 foreach (GUIComponent contentChild in packageToSaveInList.Content.Children)
2898 contentChild.Visible &= !(contentChild.GetChild<GUILayoutGroup>()?.GetChild<GUITextBlock>() is GUITextBlock tb &&
2899 !tb.Text.Contains(packToSaveInFilter.Text, StringComparison.OrdinalIgnoreCase));
2902 ContentPackage ownerPkg =
null;
2903 if (MainSub?.Info !=
null) { ownerPkg = GetLocalPackageThatOwnsSub(MainSub.Info); }
2904 foreach (var p
in ContentPackageManager.LocalPackages)
2906 var packageListItem = addItemToPackageToSaveList(p.Name, p);
2909 var packageListIcon = packageListItem.GetChild<GUIFrame>();
2910 var packageListText = packageListItem.GetChild<GUITextBlock>();
2911 GUIStyle.Apply(packageListIcon,
"WorkshopMenu.EditButton");
2912 packageListText.Text = TextManager.GetWithVariable(
"UpdateExistingLocalPackage",
"[mod]", p.Name);
2915 if (ownerPkg !=
null)
2917 var element = packageToSaveInList.Content.FindChild(ownerPkg);
2918 element?.RectTransform.SetAsFirstChild();
2920 packageToSaveInList.Select(0);
2922 var requiredContentPackagesLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One,
2923 horizontalArea.RectTransform,
Anchor.BottomRight))
2929 var requiredContentPackList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 1.0f),
2930 requiredContentPackagesLayout.RectTransform));
2932 var filterLayout =
new GUILayoutGroup(
2933 new RectTransform((1.0f, 0.15f), requiredContentPackagesLayout.RectTransform),
2934 isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
2936 var contentPackFilter
2937 =
new GUITextBox(
new RectTransform((0.6f, 1.0f), filterLayout.RectTransform),
2938 createClearButton:
true);
2939 contentPackFilter.OnTextChanged += (box, text) =>
2941 requiredContentPackList.Content.Children.ForEach(c
2942 => c.Visible = !(c is GUITickBox tb &&
2943 !tb.Text.Contains(text, StringComparison.OrdinalIgnoreCase)));
2947 var autoDetectBtn =
new GUIButton(
new RectTransform((0.4f, 1.0f), filterLayout.RectTransform),
2948 text: TextManager.Get(
"AutoDetectRequiredPackages"), style:
"GUIButtonSmall")
2950 OnClicked = (button, o) =>
2952 var requiredPackages = MapEntity.MapEntityList.Select(e => e?.Prefab?.ContentPackage)
2953 .Where(cp => cp !=
null)
2954 .Distinct().OfType<ContentPackage>().
Select(p => p.Name).ToHashSet();
2955 var tickboxes = requiredContentPackList.Content.Children.OfType<GUITickBox>().ToArray();
2956 tickboxes.ForEach(tb => tb.Selected = requiredPackages.Contains(tb.UserData as
string ??
""));
2961 if (MainSub !=
null)
2963 List<string> allContentPacks = MainSub.Info.RequiredContentPackages.ToList();
2964 foreach (ContentPackage contentPack
in ContentPackageManager.AllPackages)
2968 if (contentPack.Files.All(f => f is SubmarineFile || f is ItemAssemblyFile)) {
continue; }
2970 if (!allContentPacks.Contains(contentPack.Name))
2972 string altName = contentPack.AltNames.FirstOrDefault(n => allContentPacks.Contains(n));
2973 if (!
string.IsNullOrEmpty(altName))
2975 if (MainSub.Info.RequiredContentPackages.Contains(altName))
2977 MainSub.Info.RequiredContentPackages.Remove(altName);
2978 MainSub.Info.RequiredContentPackages.Add(contentPack.Name);
2980 allContentPacks.Remove(altName);
2982 allContentPacks.Add(contentPack.Name);
2986 foreach (
string contentPackageName
in allContentPacks)
2988 var cpTickBox =
new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.2f), requiredContentPackList.Content.RectTransform), contentPackageName, font: GUIStyle.SmallFont)
2990 Selected = MainSub.Info.RequiredContentPackages.Contains(contentPackageName),
2991 UserData = contentPackageName
2993 cpTickBox.OnSelected += tickBox =>
2995 if (tickBox.Selected)
2997 MainSub.Info.RequiredContentPackages.Add((
string)tickBox.UserData);
3001 MainSub.Info.RequiredContentPackages.Remove((
string)tickBox.UserData);
3008 GUIButton.OnClickedHandler switchToTab(GUIButton tabBtn, GUIComponent tab)
3011 horizontalArea.Children.ForEach(c => c.Visible =
false);
3012 contentPackageTabber.Children.ForEach(c => c.Selected =
false);
3013 tabBtn.Selected =
true;
3018 saveToPackageTabBtn.OnClicked = switchToTab(saveToPackageTabBtn, saveInPackageLayout);
3019 reqPackagesTabBtn.OnClicked = switchToTab(reqPackagesTabBtn, requiredContentPackagesLayout);
3021 var buttonArea =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.05f), paddedSaveFrame.RectTransform,
Anchor.BottomCenter, minSize:
new Point(0, 30)), style:
null);
3023 var cancelButton =
new GUIButton(
new RectTransform(
new Vector2(0.3f, 1.0f), buttonArea.RectTransform,
Anchor.BottomLeft),
3024 TextManager.Get(
"Cancel"))
3026 OnClicked = (GUIButton btn,
object userdata) =>
3033 var saveButton =
new GUIButton(
new RectTransform(
new Vector2(0.3f, 1.0f), buttonArea.RectTransform,
Anchor.BottomRight),
3034 TextManager.Get(
"SaveSubButton").Fallback(TextManager.Get(
"save")))
3036 OnClicked = (button, o) => SaveSub(packageToSaveInList.SelectedData as ContentPackage)
3038 paddedSaveFrame.Recalculate();
3039 leftColumn.Recalculate();
3041 subSettingsContainer.
RectTransform.
MinSize = outpostSettingsContainer.RectTransform.MinSize = beaconSettingsContainer.RectTransform.MinSize =
3042 new Point(0, Math.Max(subSettingsContainer.Rect.Height, outpostSettingsContainer.Rect.Height));
3043 subSettingsContainer.Recalculate();
3044 outpostSettingsContainer.Recalculate();
3045 beaconSettingsContainer.Recalculate();
3047 descriptionBox.
Text = MainSub ==
null ?
"" : MainSub.Info.Description.Value;
3048 submarineDescriptionCharacterCount.
Text = descriptionBox.
Text.Length +
" / " + submarineDescriptionLimit;
3050 subTypeDropdown.SelectItem(MainSub.Info.Type);
3052 if (quickSave) { SaveSub(packageToSaveInList.SelectedData as ContentPackage); }
3055 private void CreateSaveAssemblyScreen()
3057 SetMode(Mode.Default);
3059 saveFrame =
new GUIButton(
new RectTransform(Vector2.One, GUI.Canvas,
Anchor.Center), style:
null)
3061 OnClicked = (btn, userdata) => {
if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) saveFrame =
null;
return true; }
3064 new GUIFrame(
new RectTransform(GUI.Canvas.RelativeSize, saveFrame.
RectTransform,
Anchor.Center), style:
"GUIBackgroundBlocker");
3066 var innerFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.25f, 0.35f), saveFrame.
RectTransform,
Anchor.Center) { MinSize = new Point(400, 350) });
3067 GUILayoutGroup paddedSaveFrame =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.9f, 0.9f), innerFrame.RectTransform,
Anchor.Center))
3069 AbsoluteSpacing = GUI.IntScale(5),
3073 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedSaveFrame.RectTransform),
3074 TextManager.Get(
"SaveItemAssemblyDialogHeader"), font: GUIStyle.LargeFont);
3075 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedSaveFrame.RectTransform),
3076 TextManager.Get(
"SaveItemAssemblyDialogName"));
3077 nameBox =
new GUITextBox(
new RectTransform(
new Vector2(0.6f, 0.1f), paddedSaveFrame.RectTransform));
3080 new GUITickBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedSaveFrame.RectTransform), TextManager.Get(
"SaveItemAssemblyHideInMenus"))
3082 UserData =
"hideinmenus"
3086 var descriptionContainer =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 0.5f), paddedSaveFrame.RectTransform));
3087 descriptionBox =
new GUITextBox(
new RectTransform(Vector2.One, descriptionContainer.Content.RectTransform,
Anchor.TopLeft),
3088 font: GUIStyle.SmallFont, style:
"GUITextBoxNoBorder", wrap:
true, textAlignment: Alignment.TopLeft)
3090 Padding =
new Vector4(10 * GUI.Scale)
3095 Vector2 textSize = textBox.Font.MeasureString(descriptionBox.
WrappedText);
3096 textBox.RectTransform.NonScaledSize =
new Point(textBox.RectTransform.NonScaledSize.X, Math.Max(descriptionContainer.Content.Rect.Height, (
int)textSize.Y + 10));
3097 descriptionContainer.UpdateScrollBarSize();
3098 descriptionContainer.BarScroll = 1.0f;
3102 var buttonArea =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedSaveFrame.RectTransform), style:
null);
3103 new GUIButton(
new RectTransform(
new Vector2(0.25f, 1.0f), buttonArea.RectTransform,
Anchor.BottomLeft),
3104 TextManager.Get(
"Cancel"))
3106 OnClicked = (GUIButton btn,
object userdata) =>
3112 new GUIButton(
new RectTransform(
new Vector2(0.25f, 1.0f), buttonArea.RectTransform,
Anchor.BottomRight),
3113 TextManager.Get(
"SaveSubButton"))
3115 OnClicked = SaveAssembly
3117 buttonArea.
RectTransform.
MinSize =
new Point(0, buttonArea.Children.First().RectTransform.MinSize.Y);
3127 private List<Item> LoadItemAssemblyInventorySafe(ItemAssemblyPrefab assemblyPrefab)
3129 var realItems = assemblyPrefab.CreateInstance(Vector2.Zero, MainSub);
3130 var itemInstance =
new List<Item>();
3131 realItems.ForEach(entity =>
3133 if (entity is Item it && it.ParentInventory ==
null)
3135 itemInstance.Add(it);
3138 return itemInstance;
3141 private bool SaveAssembly(GUIButton button,
object obj)
3143 if (
string.IsNullOrWhiteSpace(nameBox.
Text))
3145 GUI.AddMessage(TextManager.Get(
"ItemAssemblyNameMissingWarning"), GUIStyle.Red);
3151 foreach (
char illegalChar
in Path.GetInvalidFileNameCharsCrossPlatform())
3153 if (nameBox.
Text.Contains(illegalChar))
3155 GUI.AddMessage(TextManager.GetWithVariable(
"ItemAssemblyNameIllegalCharsWarning",
"[illegalchar]", illegalChar.ToString()), GUIStyle.Red);
3161 nameBox.
Text = nameBox.
Text.Trim();
3164 string saveFolder = Path.Combine(ContentPackage.LocalModsDir, nameBox.
Text);
3165 string filePath = Path.Combine(saveFolder, $
"{nameBox.Text}.xml").CleanUpPathCrossPlatform();
3166 if (File.Exists(filePath))
3168 var msgBox =
new GUIMessageBox(TextManager.Get(
"Warning"), TextManager.Get(
"ItemAssemblyFileExistsWarning"),
new[] { TextManager.Get(
"Yes"), TextManager.Get(
"No") });
3169 msgBox.Buttons[0].OnClicked = (btn, userdata) =>
3175 msgBox.Buttons[1].OnClicked = msgBox.Close;
3184 ContentPackage existingContentPackage = ContentPackageManager.LocalPackages.Regular.FirstOrDefault(p => p.Files.Any(f => f.Path == filePath));
3185 if (existingContentPackage ==
null)
3188 ModProject modProject =
new ModProject { Name = nameBox.
Text };
3189 var newFile = ModProject.File.FromPath<ItemAssemblyFile>(Path.Combine(ContentPath.ModDirStr, $
"{nameBox.Text}.xml"));
3190 modProject.AddFile(newFile);
3191 string newPackagePath = ContentPackageManager.LocalPackages.SaveRegularMod(modProject);
3192 existingContentPackage = ContentPackageManager.LocalPackages.GetRegularModByPath(newPackagePath);
3195 XDocument doc =
new XDocument(ItemAssemblyPrefab.Save(MapEntity.SelectedList.ToList(), nameBox.
Text, descriptionBox.
Text, hideInMenus));
3198 doc.SaveSafe(filePath);
3202 DebugConsole.ThrowError($
"Failed to save the item assembly to \"{filePath}\".", e);
3206 var result = ContentPackageManager.ReloadContentPackage(existingContentPackage);
3207 if (!result.TryUnwrapSuccess(out var resultPackage))
3209 throw new Exception($
"Failed to reload content package \"{existingContentPackage.Name}\"",
3210 result.TryUnwrapFailure(out var exception) ? exception :
null);
3212 if (resultPackage is RegularPackage regularPackage
3213 && !ContentPackageManager.EnabledPackages.Regular.Contains(regularPackage))
3215 ContentPackageManager.EnabledPackages.EnableRegular(regularPackage);
3216 GameSettings.SaveCurrentConfig();
3220 OpenEntityMenu(selectedCategory);
3227 private static void SnapToGrid()
3230 foreach (MapEntity e
in MapEntity.SelectedList)
3233 Vector2 offset = e.Position;
3234 offset =
new Vector2((MathF.Floor(offset.X /
Submarine.GridSize.X) + .5f) *
Submarine.GridSize.X - offset.X, (MathF.Floor(offset.Y /
Submarine.GridSize.Y) + .5f) *
Submarine.GridSize.Y - offset.Y);
3237 var wire = item.GetComponent<
Wire>();
3238 if (wire !=
null) {
continue; }
3242 linkedGap.Move(item.Position - linkedGap.Position);
3245 else if (e is Structure structure)
3247 structure.Move(offset);
3252 foreach (Item item
in MapEntity.SelectedList.Where(entity => entity is Item).Cast<
Item>())
3254 var wire = item.GetComponent<
Wire>();
3257 for (
int i = 0; i < wire.GetNodes().Count; i++)
3261 offset =
new Vector2((MathF.Floor(offset.X /
Submarine.GridSize.X) + .5f) *
Submarine.GridSize.X - offset.X, (MathF.Floor(offset.Y /
Submarine.GridSize.Y) + .5f) *
Submarine.GridSize.Y - offset.Y);
3262 wire.MoveNode(i, offset);
3268 private static IEnumerable<SubmarineInfo> GetLoadableSubs()
3270 string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
3271 return SubmarineInfo.SavedSubmarines.Where(s
3272 => Path.GetDirectoryName(Path.GetFullPath(s.FilePath)) != downloadFolder);
3275 private void CreateLoadScreen()
3278 SubmarineInfo.RefreshSavedSubs();
3279 SetMode(Mode.Default);
3281 loadFrame =
new GUIFrame(
new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas,
Anchor.Center), style:
"GUIBackgroundBlocker");
3283 new GUIButton(
new RectTransform(Vector2.One, loadFrame.
RectTransform,
Anchor.Center), style:
null)
3285 OnClicked = (_, _) =>
3292 var innerFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.53f, 0.75f), loadFrame.
RectTransform,
Anchor.Center, scaleBasis:
ScaleBasis.Smallest) { MinSize = new Point(350, 500) });
3294 var paddedLoadFrame =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.9f, 0.9f), innerFrame.RectTransform,
Anchor.Center)) { Stretch =
true, RelativeSpacing = 0.01f };
3296 var deleteButtonHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.2f), paddedLoadFrame.RectTransform,
Anchor.Center))
3298 RelativeSpacing = 0.1f,
3302 var searchBox =
new GUITextBox(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedLoadFrame.RectTransform), font: GUIStyle.Font, createClearButton:
true);
3303 var searchTitle =
new GUITextBlock(
new RectTransform(Vector2.One, searchBox.RectTransform), TextManager.Get(
"serverlog.filter"),
3304 textAlignment: Alignment.CenterLeft, font: GUIStyle.Font)
3306 CanBeFocused =
false,
3307 IgnoreLayoutGroups =
true
3309 searchTitle.TextColor *= 0.5f;
3311 var subList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 0.7f), paddedLoadFrame.RectTransform))
3313 PlaySoundOnSelect =
true,
3314 ScrollBarVisible =
true,
3315 OnSelected = (GUIComponent selected,
object userData) =>
3317 if (deleteButtonHolder.FindChild(
"delete") is GUIButton deleteBtn)
3319 deleteBtn.ToolTip =
string.Empty;
3320 if (!(userData is SubmarineInfo subInfo))
3322 deleteBtn.Enabled =
false;
3326 var
package = GetLocalPackageThatOwnsSub(subInfo);
3327 if (package !=
null)
3329 deleteBtn.Enabled =
true;
3333 deleteBtn.Enabled =
false;
3334 if (IsVanillaSub(subInfo))
3336 deleteBtn.ToolTip = TextManager.Get(
"cantdeletevanillasub");
3338 else if (GetPackageThatOwnsSub(subInfo, ContentPackageManager.AllPackages) is ContentPackage subPackage)
3340 deleteBtn.ToolTip = TextManager.GetWithVariable(
"cantdeletemodsub",
"[modname]", subPackage.Name);
3348 searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible =
false; };
3349 searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible =
true; };
3350 searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text);
return true; };
3352 var sortedSubs = GetLoadableSubs()
3353 .OrderBy(s => s.Type)
3354 .ThenBy(s => s.Name)
3357 SubmarineInfo prevSub =
null;
3359 foreach (SubmarineInfo sub
in sortedSubs)
3361 if (prevSub ==
null || prevSub.Type != sub.Type)
3363 string textTag =
"SubmarineType." + sub.Type;
3364 if (sub.Type ==
SubmarineType.EnemySubmarine && !TextManager.ContainsTag(textTag))
3366 textTag =
"MissionType.Pirate";
3368 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), subList.Content.RectTransform) { MinSize = new Point(0, 35) },
3369 TextManager.Get(textTag), font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style:
"ListBoxElement")
3371 CanBeFocused =
false
3376 string pathWithoutUserName = Path.GetFullPath(sub.FilePath);
3377 string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
3378 if (pathWithoutUserName.StartsWith(saveFolder))
3380 pathWithoutUserName =
"..." + pathWithoutUserName[saveFolder.Length..];
3384 pathWithoutUserName = sub.FilePath;
3387 GUITextBlock textBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), subList.Content.RectTransform) { MinSize = new Point(0, 30) },
3388 ToolBox.LimitString(sub.Name, GUIStyle.Font, subList.Rect.Width - 80))
3391 ToolTip = pathWithoutUserName
3394 if (!(ContentPackageManager.VanillaCorePackage?.Files.Any(f => f.Path == sub.FilePath) ??
false))
3396 if (GetLocalPackageThatOwnsSub(sub) ==
null &&
3397 ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == sub.FilePath)) is ContentPackage subPackage)
3400 textBlock.OverrideTextColor(Color.MediumPurple);
3405 textBlock.OverrideTextColor(GUIStyle.TextColorBright);
3411 var shuttleText =
new GUITextBlock(
new RectTransform(
new Vector2(0.2f, 1.0f), textBlock.RectTransform,
Anchor.CenterRight),
3412 TextManager.Get(
"Shuttle",
"RespawnShuttle"), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont)
3414 TextColor = textBlock.TextColor * 0.8f,
3415 ToolTip = textBlock.ToolTip.SanitizedString
3418 else if (sub.IsPlayer)
3420 var classText =
new GUITextBlock(
new RectTransform(
new Vector2(0.2f, 1.0f), textBlock.RectTransform,
Anchor.CenterRight),
3421 TextManager.Get($
"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUIStyle.SmallFont)
3423 TextColor = textBlock.TextColor * 0.8f,
3424 ToolTip = textBlock.ToolTip.SanitizedString
3429 var deleteButton =
new GUIButton(
new RectTransform(Vector2.One, deleteButtonHolder.RectTransform,
Anchor.TopCenter),
3430 TextManager.Get(
"Delete"))
3435 deleteButton.OnClicked = (btn, userdata) =>
3437 if (subList.SelectedComponent !=
null)
3439 TryDeleteSub(subList.SelectedComponent.UserData as SubmarineInfo);
3441 deleteButton.Enabled =
false;
3446 if (AutoSaveInfo?.Root !=
null)
3448 int min = Math.Min(6, AutoSaveInfo.Root.Elements().Count());
3449 var loadAutoSave =
new GUIDropDown(
new RectTransform(Vector2.One, deleteButtonHolder.RectTransform,
Anchor.BottomCenter), TextManager.Get(
"LoadAutoSave"), elementCount: min)
3451 ToolTip = TextManager.Get(
"LoadAutoSaveTooltip"),
3452 UserData =
"loadautosave",
3453 OnSelected = (button, o) =>
3459 foreach (XElement saveElement
in AutoSaveInfo.Root.Elements().Reverse())
3461 DateTime time = DateTime.MinValue.AddSeconds(saveElement.GetAttributeUInt64(
"time", 0));
3462 TimeSpan difference = DateTime.UtcNow - time;
3464 LocalizedString tooltip = TextManager.GetWithVariables(
"subeditor.autosaveage",
3465 (
"[hours]", ((
int)Math.Floor(difference.TotalHours)).ToString()),
3466 (
"[minutes]", difference.Minutes.ToString()),
3467 (
"[seconds]", difference.Seconds.ToString()));
3469 string submarineName = saveElement.GetAttributeString(
"name", TextManager.Get(
"UnspecifiedSubFileName").Value);
3470 LocalizedString timeFormat;
3472 double totalMinutes = difference.TotalMinutes;
3474 if (totalMinutes < 1)
3476 timeFormat = TextManager.Get(
"subeditor.savedjustnow");
3478 else if (totalMinutes > 60)
3480 timeFormat = TextManager.Get(
"subeditor.savedmorethanhour");
3484 timeFormat = TextManager.GetWithVariable(
"subeditor.saveageminutes",
"[minutes]", difference.Minutes.ToString());
3487 LocalizedString entryName = TextManager.GetWithVariables(
"subeditor.autosaveentry", (
"[submarine]", submarineName), (
"[saveage]", timeFormat));
3489 loadAutoSave.AddItem(entryName, saveElement, tooltip);
3493 var controlBtnHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.1f), paddedLoadFrame.RectTransform), isHorizontal:
true) { RelativeSpacing = 0.2f, Stretch =
true };
3495 new GUIButton(
new RectTransform(
new Vector2(0.5f, 1.0f), controlBtnHolder.RectTransform,
Anchor.BottomLeft),
3496 TextManager.Get(
"Cancel"))
3498 OnClicked = (GUIButton btn,
object userdata) =>
3505 new GUIButton(
new RectTransform(
new Vector2(0.5f, 1.0f), controlBtnHolder.RectTransform,
Anchor.BottomRight),
3506 TextManager.Get(
"Load"))
3508 OnClicked = HitLoadSubButton
3511 controlBtnHolder.
RectTransform.
MaxSize =
new Point(
int.MaxValue, controlBtnHolder.Children.First().Rect.Height);
3514 private void FilterSubs(GUIListBox subList,
string filter)
3516 foreach (GUIComponent child
in subList.Content.Children)
3518 if (!(child.UserData is SubmarineInfo sub)) {
continue; }
3519 child.Visible =
string.IsNullOrEmpty(filter) || sub.Name.ToLower().Contains(filter.ToLower());
3523 bool subVisibleInCategory =
false;
3524 foreach (GUIComponent child
in subList.Content.Children.Reverse())
3526 if (!(child.UserData is SubmarineInfo sub))
3530 child.Visible = subVisibleInCategory;
3532 subVisibleInCategory =
false;
3536 subVisibleInCategory |= child.Visible;
3545 private void LoadAutoSave(
object userData)
3547 if (!(userData is XElement element)) {
return; }
3549 #warning TODO: revise
3550 string filePath = element.GetAttributeStringUnrestricted(
"file",
"");
3551 if (
string.IsNullOrWhiteSpace(filePath)) {
return; }
3553 var loadedSub =
Submarine.Load(
new SubmarineInfo(filePath),
true);
3557 loadedSub.Info.Name = loadedSub.Info.SubmarineElement.GetAttributeString(
"name", loadedSub.Info.Name);
3561 DebugConsole.ThrowError(
"Failed to find a name for the submarine.", e);
3562 var unspecifiedFileName = TextManager.Get(
"UnspecifiedSubFileName");
3563 loadedSub.Info.Name = unspecifiedFileName.Value;
3565 MainSub = loadedSub;
3566 MainSub.SetPrevTransform(MainSub.Position);
3567 MainSub.UpdateTransform();
3568 MainSub.Info.Name = loadedSub.Info.Name;
3569 subNameLabel.
Text = ToolBox.LimitString(loadedSub.Info.Name, subNameLabel.
Font, subNameLabel.
Rect.Width);
3571 CreateDummyCharacter();
3573 cam.Position = MainSub.Position + MainSub.HiddenSubPosition;
3578 private bool HitLoadSubButton(GUIButton button,
object obj)
3580 if (loadFrame ==
null)
3582 DebugConsole.NewMessage(
"load frame null", Color.Red);
3586 GUIListBox subList = loadFrame.GetAnyChild<GUIListBox>();
3587 if (subList ==
null)
3589 DebugConsole.NewMessage(
"Sublist null", Color.Red);
3593 if (!(subList.SelectedComponent?.UserData is SubmarineInfo selectedSubInfo)) {
return false; }
3595 var ownerPackage = GetLocalPackageThatOwnsSub(selectedSubInfo);
3596 if (ownerPackage is
null)
3598 if (IsVanillaSub(selectedSubInfo))
3601 LoadSub(selectedSubInfo);
3603 AskLoadVanillaSub(selectedSubInfo);
3606 else if (GetWorkshopPackageThatOwnsSub(selectedSubInfo) is ContentPackage workshopPackage)
3608 if (workshopPackage.TryExtractSteamWorkshopId(out var workshopId)
3609 && publishedWorkshopItemIds.Contains(workshopId.Value))
3611 AskLoadPublishedSub(selectedSubInfo, workshopPackage);
3615 AskLoadSubscribedSub(selectedSubInfo);
3621 LoadSub(selectedSubInfo);
3626 void AskLoadSub(SubmarineInfo info, LocalizedString header, LocalizedString desc)
3628 var msgBox =
new GUIMessageBox(
3631 new[] { TextManager.Get(
"LoadAnyway"), TextManager.Get(
"Cancel") });
3632 msgBox.Buttons[0].OnClicked = (button, o) =>
3638 msgBox.Buttons[1].OnClicked = msgBox.Close;
3641 void AskLoadPublishedSub(SubmarineInfo info, ContentPackage pkg)
3643 TextManager.Get(
"LoadingPublishedSubmarineHeader"),
3644 TextManager.GetWithVariable(
"LoadingPublishedSubmarineDesc",
"[modname]", pkg.Name));
3646 void AskLoadSubscribedSub(SubmarineInfo info)
3648 TextManager.Get(
"LoadingSubscribedSubmarineHeader"),
3649 TextManager.Get(
"LoadingSubscribedSubmarineDesc"));
3651 void AskLoadVanillaSub(SubmarineInfo info)
3653 TextManager.Get(
"LoadingVanillaSubmarineHeader"),
3654 TextManager.Get(
"LoadingVanillaSubmarineDesc"));
3661 if (checkIdConflicts)
3663 Dictionary<int, Identifier> entities =
new Dictionary<int, Identifier>();
3666 int id = subElement.GetAttributeInt(
"ID", -1);
3667 if (
id == -1) {
continue; }
3668 Identifier identifier = subElement.GetAttributeIdentifier(
"identifier",
string.Empty);
3669 if (entities.TryGetValue(
id, out Identifier duplicateEntity))
3672 TextManager.Get(
"error"),
3673 TextManager.GetWithVariables(
"subeditor.duplicateiderror",
3674 (
"[entity1]", $
"{duplicateEntity} ({id})"),
3675 (
"[entity2]", $
"{identifier} ({id})")),
3676 new LocalizedString[] { TextManager.Get(
"Yes"), TextManager.Get(
"No") });
3677 errorMsg.Buttons[0].OnClicked = (bnt, userdata) =>
3679 subElement.Remove();
3680 LoadSub(info, checkIdConflicts:
false);
3684 errorMsg.Buttons[1].OnClicked = (bnt, userdata) =>
3686 LoadSub(info, checkIdConflicts:
false);
3692 entities.Add(
id, identifier);
3699 MainSub = selectedSub;
3704 DebugConsole.ThrowError(
"Failed to load the submarine. The submarine file might be corrupted.", e);
3708 CreateDummyCharacter();
3710 string name = MainSub.Info.Name;
3711 subNameLabel.
Text = ToolBox.LimitString(name, subNameLabel.
Font, subNameLabel.
Rect.Width);
3713 cam.Position = MainSub.Position + MainSub.HiddenSubPosition;
3719 var adjustLightsPrompt =
new GUIMessageBox(TextManager.Get(
"Warning"), TextManager.Get(
"AdjustLightsPrompt"),
3720 new[] { TextManager.Get(
"Yes"), TextManager.Get(
"No") });
3721 adjustLightsPrompt.Buttons[0].OnClicked += adjustLightsPrompt.Close;
3722 adjustLightsPrompt.Buttons[0].OnClicked += (btn, userdata) =>
3730 light.
LightColor =
new Color(light.LightColor, light.LightColor.A / 255.0f * 0.5f);
3733 new GUIMessageBox(
"", TextManager.Get(
"AdjustedLightsNotification"));
3736 adjustLightsPrompt.Buttons[1].OnClicked += adjustLightsPrompt.Close;
3739 ReconstructLayers();
3743 => packages.FirstOrDefault(package => package.
Files.Any(f => f.Path == sub.FilePath));
3746 => GetPackageThatOwnsSub(sub, ContentPackageManager.LocalPackages);
3749 => GetPackageThatOwnsSub(sub, ContentPackageManager.WorkshopPackages);
3752 => GetPackageThatOwnsSub(sub, ContentPackageManager.VanillaCorePackage.ToEnumerable()) !=
null;
3756 if (sub ==
null) {
return; }
3761 var subPackage = GetLocalPackageThatOwnsSub(sub);
3762 if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) {
return; }
3764 var msgBox =
new GUIMessageBox(
3765 TextManager.Get(
"DeleteDialogLabel"),
3766 TextManager.GetWithVariable(
"DeleteDialogQuestion",
"[file]", sub.
Name),
3767 new LocalizedString[] { TextManager.Get(
"Yes"), TextManager.Get(
"Cancel") });
3768 msgBox.Buttons[0].OnClicked += (btn, userData) =>
3772 if (subPackage !=
null)
3775 ModProject modProject =
new ModProject(subPackage);
3776 modProject.RemoveFile(modProject.Files.First(f => ContentPath.FromRaw(subPackage, f.Path) == sub.
FilePath));
3777 modProject.Save(subPackage.Path);
3778 ReloadModifiedPackage(subPackage);
3779 if (MainSub?.Info !=
null && MainSub.Info.FilePath == sub.
FilePath)
3781 MainSub.Info.FilePath =
null;
3789 DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable(
"DeleteFileError",
"[file]", sub.
FilePath), e);
3793 msgBox.Buttons[0].OnClicked += msgBox.Close;
3794 msgBox.Buttons[1].OnClicked += msgBox.Close;
3797 private void OpenEntityMenu(MapEntityCategory? entityCategory)
3801 foreach (GUIButton categoryButton
in entityCategoryButtons)
3803 categoryButton.Selected = entityCategory.HasValue ?
3804 categoryButton.UserData is
MapEntityCategory category && entityCategory.Value == category :
3805 categoryButton.UserData ==
null;
3806 string categoryName = entityCategory.HasValue ? entityCategory.Value.ToString() :
"All";
3807 selectedCategoryText.
Text = TextManager.Get(
"MapEntityCategory." + categoryName);
3808 selectedCategoryButton.
ApplyStyle(GUIStyle.GetComponentStyle(
"CategoryButton." + categoryName));
3811 selectedCategory = entityCategory;
3813 SetMode(Mode.Default);
3818 foreach (GUIComponent child
in toggleEntityMenuButton.
Children)
3820 child.
SpriteEffects = entityMenuOpen ? SpriteEffects.None : SpriteEffects.FlipVertically;
3823 foreach (GUIComponent child
in categorizedEntityList.
Content.
Children)
3825 child.Visible = !entityCategory.HasValue || (
MapEntityCategory)child.UserData == entityCategory;
3826 var innerList = child.GetChild<GUIListBox>();
3827 foreach (GUIComponent grandChild
in innerList.Content.Children)
3829 grandChild.Visible =
true;
3833 if (!
string.IsNullOrEmpty(entityFilterBox.
Text))
3835 FilterEntities(entityFilterBox.
Text);
3844 private void FilterEntities(
string filter)
3846 if (
string.IsNullOrWhiteSpace(filter))
3848 allEntityList.
Visible =
false;
3849 categorizedEntityList.
Visible =
true;
3851 foreach (GUIComponent child
in categorizedEntityList.
Content.
Children)
3853 child.Visible = !selectedCategory.HasValue || selectedCategory == (
MapEntityCategory)child.UserData;
3854 if (!child.Visible) {
return; }
3855 var innerList = child.GetChild<GUIListBox>();
3856 foreach (GUIComponent grandChild
in innerList.Content.Children)
3858 grandChild.Visible = ((MapEntityPrefab)grandChild.UserData).Name.Value.Contains(filter, StringComparison.OrdinalIgnoreCase);
3867 categorizedEntityList.
Visible =
false;
3868 filter = filter.ToLower();
3872 (!selectedCategory.HasValue || ((MapEntityPrefab)child.UserData).Category.HasFlag(selectedCategory)) &&
3873 ((MapEntityPrefab)child.UserData).Name.Value.Contains(filter, StringComparison.OrdinalIgnoreCase);
3879 private void ClearFilter()
3884 entityFilterBox.
Text =
"";
3889 if (newMode == mode) {
return; }
3903 CreateDummyCharacter();
3904 if (newMode ==
Mode.Wiring)
3908 Point wirePos =
new Point((
int)(10 * GUI.Scale), TopPanel.
Rect.Height + entityCountPanel.
Rect.Height + (
int)(10 * GUI.Scale));
3909 wiringToolPanel = CreateWiringPanel(wirePos, SelectWire);
3913 private void RemoveDummyCharacter()
3915 if (dummyCharacter ==
null || dummyCharacter.
Removed) {
return; }
3919 dummyCharacter =
null;
3922 private void CreateContextMenu()
3924 if (GUIContextMenu.CurrentContextMenu !=
null) {
return; }
3926 List<MapEntity> targets = MapEntity.HighlightedEntities.Any(me => !MapEntity.SelectedList.Contains(me)) ?
3927 MapEntity.HighlightedEntities.ToList() :
3928 new List<MapEntity>(MapEntity.SelectedList);
3930 bool allowOpening =
false;
3931 var targetItem = (targets.Count == 1 ? targets.Single() :
null) as Item;
3933 allowOpening = targetItem is not
null && targetItem.Components.Any(
static ic =>
3939 bool hasTargets = targets.Count > 0;
3942 if (PlayerInput.IsShiftDown())
3944 GUIContextMenu.CreateContextMenu(
3945 new ContextMenuOption(
"SubEditor.EditBackgroundColor", isEnabled:
true, onSelected: CreateBackgroundColorPicker),
3946 new ContextMenuOption(
"SubEditor.ToggleTransparency", isEnabled:
true, onSelected: () => TransparentWiringMode = !TransparentWiringMode),
3947 new ContextMenuOption(
"SubEditor.ToggleGrid", isEnabled:
true, onSelected: () => ShouldDrawGrid = !ShouldDrawGrid),
3948 new ContextMenuOption(
"SubEditor.PasteAssembly", isEnabled:
true, () => PasteAssembly()),
3949 new ContextMenuOption(
"Editor.SelectSame", isEnabled: hasTargets, onSelected: delegate
3951 bool doorGapSelected = targets.Any(t => t is Gap gap && gap.ConnectedDoor !=
null);
3952 foreach (MapEntity match in MapEntity.MapEntityList.Where(e => e.Prefab !=
null && targets.Any(t => t.Prefab?.Identifier == e.Prefab.Identifier) && !MapEntity.SelectedList.Contains(e)))
3954 if (MapEntity.SelectedList.Contains(match)) { continue; }
3955 if (match is Gap gap)
3958 if ((gap.ConnectedDoor ==
null) == doorGapSelected) { continue; }
3960 else if (match is Item item)
3963 var door = item.GetComponent<
Door>();
3964 if (door?.LinkedGap !=
null && !MapEntity.SelectedList.Contains(door.LinkedGap))
3966 MapEntity.SelectedList.Add(door.LinkedGap);
3969 MapEntity.SelectedList.Add(match);
3972 new ContextMenuOption(
"SubEditor.AddImage", isEnabled:
true, onSelected: ImageManager.CreateImageWizard),
3973 new ContextMenuOption(
"SubEditor.ToggleImageEditing", isEnabled:
true, onSelected: delegate
3975 ImageManager.EditorMode = !ImageManager.EditorMode;
3976 if (!ImageManager.EditorMode) { GameSettings.SaveCurrentConfig(); }
3981 List<ContextMenuOption> availableLayers =
new List<ContextMenuOption>
3983 new ContextMenuOption(
"editor.layer.nolayer",
true, onSelected: () => { MoveToLayer(
null, targets); })
3985 availableLayers.AddRange(Layers.Select(layer =>
new ContextMenuOption(layer.Key,
true, onSelected: () => { MoveToLayer(layer.Key, targets); })));
3987 List<ContextMenuOption> availableLayerOptions =
new List<ContextMenuOption>
3989 new ContextMenuOption(
"editor.layer.movetolayer", isEnabled: hasTargets, availableLayers.ToArray()),
3990 new ContextMenuOption(
"editor.layer.createlayer", isEnabled: hasTargets, onSelected: () => { CreateNewLayer(
null, targets); }),
3991 new ContextMenuOption(
"editor.layer.selectall", isEnabled: hasTargets, onSelected: () =>
3993 foreach (MapEntity match
in MapEntity.MapEntityList.Where(e => targets.Any(t => !
string.IsNullOrWhiteSpace(t.Layer) && t.Layer == e.Layer && !MapEntity.SelectedList.Contains(e))))
3995 if (MapEntity.SelectedList.Contains(match)) {
continue; }
3996 MapEntity.SelectedList.Add(match);
4000 availableLayerOptions.AddRange(Layers.Select(layer =>
new ContextMenuOption(layer.Key,
true, onSelected: () => { MoveToLayer(layer.Key, targets); })));
4002 GUIContextMenu.CreateContextMenu(
4003 new ContextMenuOption(
"label.openlabel", isEnabled: allowOpening, onSelected: () => OpenItem(targetItem)),
4004 new ContextMenuOption(
"editor.cut", isEnabled: hasTargets, onSelected: () => MapEntity.Cut(targets)),
4005 new ContextMenuOption(
"editor.copytoclipboard", isEnabled: hasTargets, onSelected: () => MapEntity.Copy(targets)),
4006 new ContextMenuOption(
"editor.paste", isEnabled: MapEntity.CopiedList.Any(), onSelected: () => MapEntity.Paste(cam.ScreenToWorld(PlayerInput.MousePosition))),
4007 new ContextMenuOption(
"delete", isEnabled: hasTargets, onSelected: () =>
4009 StoreCommand(
new AddOrDeleteCommand(targets,
true));
4010 foreach (var me
in targets)
4012 if (!me.Removed) { me.Remove(); }
4015 new ContextMenuOption(
string.Empty, isEnabled:
false, onSelected: () => { }),
4016 new ContextMenuOption(
"editor.layer.movetoactivelayer", isEnabled: !(layerList?.SelectedData as
string).IsNullOrEmpty(), onSelected: () => { MoveToLayer(layerList.
SelectedData as
string, targets); }),
4017 new ContextMenuOption(
"editor.layer.removefromlayer", isEnabled: targets.Any(t => t.Layer !=
string.Empty), onSelected: () => { targets.ForEach(t => t.Layer =
string.Empty); }),
4018 new ContextMenuOption(
"editor.layeroptions", isEnabled: hasTargets, availableLayerOptions.ToArray()),
4019 new ContextMenuOption(TextManager.GetWithVariable(
"editortip.shiftforextraoptions",
"[button]", PlayerInput.SecondaryMouseLabel) +
'\n' + TextManager.Get(
"editortip.altforruler"), isEnabled:
false, onSelected:
null));
4023 private void MoveToLayer(
string layer, List<MapEntity> content)
4025 layer ??=
string.Empty;
4027 foreach (MapEntity entity
in content)
4029 if (MapEntity.SelectedList.Contains(entity))
4031 MapEntity.ResetEditingHUD();
4033 entity.Layer = layer;
4037 private void CreateNewLayer(
string name, List<MapEntity> content)
4039 if (
string.IsNullOrWhiteSpace(name))
4041 name = TextManager.Get(
"editor.layer.newlayer").Value;
4044 string incrementedName = name;
4046 for (
int i = 1; Layers.ContainsKey(incrementedName); i++)
4048 incrementedName = $
"{name} ({i})";
4051 name = incrementedName;
4053 if (content !=
null)
4055 MoveToLayer(name, content);
4058 Layers.Add(name,
new LayerData());
4062 private void RenameLayer(
string original,
string newName)
4064 Layers.Remove(original, out LayerData originalData);
4066 foreach (MapEntity entity
in MapEntity.MapEntityList.Where(entity => entity.Layer == original))
4068 entity.Layer = newName ??
string.Empty;
4071 if (!
string.IsNullOrWhiteSpace(newName))
4073 Layers.TryAdd(newName, originalData);
4083 if (!
string.IsNullOrWhiteSpace(entity.
Layer))
4091 private void ClearLayers()
4097 private static void SetLayerVisibility(
string layerName,
bool isVisible)
4099 if (Layers.Remove(layerName, out LayerData layerData))
4101 Layers.Add(layerName, layerData with { IsVisible = isVisible });
4105 Layers.Add(layerName,
new LayerData(isVisible));
4109 private void PasteAssembly(
string text =
null, Vector2? pos =
null)
4111 pos ??= cam.ScreenToWorld(PlayerInput.MousePosition);
4112 text ??= Clipboard.GetText();
4113 if (
string.IsNullOrWhiteSpace(text))
4115 DebugConsole.ThrowError(
"Unable to paste assembly: Clipboard content is empty.");
4119 XElement element =
null;
4123 element = XDocument.Parse(text).Root;
4125 catch (Exception) { }
4127 if (element ==
null)
4129 DebugConsole.ThrowError(
"Unable to paste assembly: Clipboard content is not valid XML.");
4134 List<MapEntity> entities;
4137 entities = ItemAssemblyPrefab.PasteEntities(pos.Value, sub, element, selectInstance:
true);
4141 DebugConsole.ThrowError(
"Unable to paste assembly: Failed to load items.", e);
4145 if (!entities.Any()) {
return; }
4146 StoreCommand(
new AddOrDeleteCommand(entities,
false, handleInventoryBehavior:
false));
4158 foreach (var component
in item.Components)
4160 if (component.GetType() == entity.GetType() && component != entity)
4162 entities.Add((component, (Color)
property.GetValue(component), property));
4167 if (selectedEntity.GetType() == entity.GetType())
4169 entities.Add((selectedEntity, (Color)
property.GetValue(selectedEntity), property));
4171 else if (selectedEntity is { SerializableProperties: { } props} )
4175 entities.Add((selectedEntity, (Color) foundProp.GetValue(selectedEntity), foundProp));
4182 bool setValues =
true;
4183 object sliderMutex =
new object(),
4184 sliderTextMutex =
new object(),
4185 pickerMutex =
new object(),
4186 hexMutex =
new object();
4188 Vector2 relativeSize =
new Vector2(0.4f * GUI.AspectRatioAdjustment, 0.3f);
4192 UserData =
"colorpicker",
4199 AutoScaleVertical =
true
4206 RelativeSpacing = 0.1f,
4217 var (h, s, v) = ToolBox.RGBToHSV(originalColor);
4218 colorPicker.
SelectedHue =
float.IsNaN(h) ? 0f : h;
4226 float currentHue = colorPicker.
SelectedHue / 360f;
4231 inputType:
NumberType.Float) { FloatValue = currentHue, MaxValueFloat = 1f, MinValueFloat = 0f, DecimalsToDisplay = 2 };
4234 new GUITextBlock(
new RectTransform(
new Vector2(0.1f, 0.2f), satSliderLayout.
RectTransform), text:
"S:", font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero, ToolTip =
"Saturation"};
4237 inputType:
NumberType.Float) { FloatValue = colorPicker.
SelectedSaturation, MaxValueFloat = 1f, MinValueFloat = 0f, DecimalsToDisplay = 2 };
4243 inputType:
NumberType.Float) { FloatValue = colorPicker.
SelectedValue, MaxValueFloat = 1f, MinValueFloat = 0f, DecimalsToDisplay = 2 };
4248 RelativeSpacing = 0.1f
4253 Rectangle rect = component.Rect;
4254 Point areaSize = new Point(rect.Width, rect.Height / 2);
4255 Rectangle newColorRect = new Rectangle(rect.Location, areaSize);
4256 Rectangle oldColorRect = new Rectangle(new Point(newColorRect.Left, newColorRect.Bottom), areaSize);
4258 GUI.DrawRectangle(batch, newColorRect, ToolBoxCore.HSVToRGB(colorPicker.SelectedHue, colorPicker.SelectedSaturation, colorPicker.SelectedValue), isFilled: true);
4259 GUI.DrawRectangle(batch, oldColorRect, originalColor, isFilled: true);
4260 GUI.DrawRectangle(batch, rect, Color.Black, isFilled: false);
4265 hueScrollBar.
OnMoved = (bar, scroll) => { SetColor(sliderMutex);
return true; };
4266 hueTextBox.
OnValueChanged = input => { SetColor(sliderTextMutex); };
4268 satScrollBar.
OnMoved = (bar, scroll) => { SetColor(sliderMutex);
return true; };
4269 satTextBox.
OnValueChanged = input => { SetColor(sliderTextMutex); };
4271 valueScrollBar.
OnMoved = (bar, scroll) => { SetColor(sliderMutex);
return true; };
4272 valueTextBox.
OnValueChanged = input => { SetColor(sliderTextMutex); };
4274 colorPicker.
OnColorSelected = (component, color) => { SetColor(pickerMutex);
return true; };
4276 hexValueBox.
OnEnterPressed = (box, text) => { SetColor(hexMutex);
return true; };
4277 hexValueBox.
OnDeselected += (sender, key) => { SetColor(hexMutex); };
4284 Color newColor = SetColor(
null);
4286 if (!IsSubEditor()) {
return true; }
4288 Dictionary<object, List<ISerializableEntity>> oldProperties =
new Dictionary<object, List<ISerializableEntity>>();
4290 foreach (var (sEntity, color, _) in entities)
4292 if (sEntity is
MapEntity { Removed:
true }) {
continue; }
4293 if (!oldProperties.ContainsKey(color))
4295 oldProperties.Add(color,
new List<ISerializableEntity>());
4297 oldProperties[color].Add(sEntity);
4300 List<ISerializableEntity> affected = entities.Select(t => t.Entity).Where(se => se is
MapEntity { Removed:
false } || se is
ItemComponent).ToList();
4301 StoreCommand(
new PropertyCommand(affected, property.Name.ToIdentifier(), newColor, oldProperties));
4314 editor.
UpdateValue(property, newColor, flash:
false);
4328 foreach (var (e, color, prop) in entities)
4330 if (e is
MapEntity { Removed:
true }) {
continue; }
4331 prop.TrySetValue(e, color);
4338 Color SetColor(
object source)
4344 if (source == sliderMutex)
4347 SetSliderTexts(hsv);
4348 SetColorPicker(hsv);
4351 else if (source == sliderTextMutex)
4355 SetColorPicker(hsv);
4358 else if (source == pickerMutex)
4362 SetSliderTexts(hsv);
4365 else if (source == hexMutex)
4367 Vector3 hsv = ToolBox.RGBToHSV(XMLExtensions.ParseColor(hexValueBox.
Text, errorMessages:
false));
4368 if (
float.IsNaN(hsv.X)) { hsv.X = 0f; }
4370 SetSliderTexts(hsv);
4371 SetColorPicker(hsv);
4379 foreach (var (e, origColor, prop) in entities)
4381 if (e is
MapEntity { Removed:
true }) {
continue; }
4382 color.A = origColor.A;
4383 prop.TrySetValue(e, color);
4387 void SetSliders(Vector3 hsv)
4394 void SetSliderTexts(Vector3 hsv)
4401 void SetColorPicker(Vector3 hsv)
4403 bool hueChanged = !MathUtils.NearlyEqual(colorPicker.
SelectedHue, hsv.X);
4407 if (hueChanged) { colorPicker.
RefreshHue(); }
4410 void SetHex(Vector3 hsv)
4412 Color hexColor = ToolBoxCore.HSVToRGB(hsv.X, hsv.Y, hsv.Z);
4413 hexValueBox!.
Text = ColorToHex(hexColor);
4417 static string ColorToHex(Color color) => $
"#{(color.R << 16 | color.G << 8 | color.B):X6}";
4423 { MinSize = new Point(120, 300), AbsoluteOffset = offset });
4427 PlaySoundOnSelect =
true,
4428 OnSelected = onWireSelected,
4429 CanTakeKeyBoardFocus =
false
4432 List<ItemPrefab> wirePrefabs =
new List<ItemPrefab>();
4437 if (!itemPrefab.
Tags.Contains(Tags.WireItem)) {
continue; }
4438 if (CircuitBox.IsInGame() && itemPrefab.
Tags.Contains(Tags.Thalamus)) {
continue; }
4440 wirePrefabs.Add(itemPrefab);
4443 foreach (
ItemPrefab itemPrefab
in wirePrefabs.OrderBy(
static w => !w.CanBeBought).ThenBy(
static w => w.UintIdentifier))
4445 GUIFrame imgFrame =
new GUIFrame(
new RectTransform(
new Point(listBox.Content.Rect.Width, listBox.Rect.Width / 2), listBox.Content.RectTransform), style:
"ListBoxElement")
4447 UserData = itemPrefab
4451 UserData = itemPrefab,
4453 HoverColor = Color.Lerp(itemPrefab.
SpriteColor, Color.White, 0.3f),
4454 SelectedColor = Color.Lerp(itemPrefab.
SpriteColor, Color.White, 0.6f)
4461 private bool SelectLinkedSub(
GUIComponent selected,
object userData)
4469 private bool SelectWire(GUIComponent component,
object userData)
4471 if (dummyCharacter ==
null)
return false;
4474 Item existingWire = dummyCharacter.
HeldItems.FirstOrDefault(i => i.Prefab == userData as ItemPrefab);
4475 if (existingWire !=
null)
4477 existingWire.Drop(
null);
4478 existingWire.Remove();
4482 var wire =
new Item(userData as ItemPrefab, Vector2.Zero,
null);
4488 if (existingWire !=
null && existingWire.Prefab != userData as ItemPrefab)
4490 existingWire.
Drop(
null);
4491 existingWire.Remove();
4504 private void OpenItem(Item item)
4506 if (dummyCharacter ==
null || item ==
null) {
return; }
4508 if ((item.GetComponent<
Holdable>() is { Attached: false } || item.GetComponent<
Wearable>() !=
null) && item.GetComponent<
ItemContainer>() !=
null)
4511 oldItemPosition = item.SimPosition;
4512 TeleportDummyCharacter(oldItemPosition);
4519 List<InvSlotType> allowedSlots =
new List<InvSlotType>();
4520 item.AllowedSlots.ForEach(type =>
4522 if (type !=
InvSlotType.Any) { allowedSlots.Add(type); }
4527 if (success) { OpenedItem = item; }
4531 MapEntity.FilteredSelectedList.Clear();
4532 MapEntity.SelectEntity(item);
4534 FilterEntities(entityFilterBox.
Text);
4535 MapEntity.StopSelection();
4541 private void CloseItem()
4543 if (dummyCharacter ==
null) {
return; }
4545 if (DraggedItemPrefab ==
null && dummyCharacter?.SelectedItem ==
null && OpenedItem ==
null) {
return; }
4546 DraggedItemPrefab =
null;
4548 OpenedItem?.
Drop(dummyCharacter);
4551 FilterEntities(entityFilterBox.
Text);
4558 private void TeleportDummyCharacter(Vector2 pos)
4560 if (dummyCharacter !=
null)
4564 limb.body.SetTransform(pos, 0.0f);
4570 private bool ChangeSubName(GUITextBox textBox,
string text)
4572 if (
string.IsNullOrWhiteSpace(text))
4574 textBox.Flash(GUIStyle.Red);
4578 if (MainSub !=
null) MainSub.Info.Name = text;
4581 textBox.Text = text;
4583 textBox.Flash(GUIStyle.Green);
4588 private void ChangeSubDescription(GUITextBox textBox,
string text)
4590 if (MainSub !=
null)
4592 MainSub.Info.Description = text;
4596 textBox.UserData = text;
4599 submarineDescriptionCharacterCount.
Text = text.
Length +
" / " + submarineDescriptionLimit;
4602 private bool SelectPrefab(GUIComponent component,
object obj)
4606 if (GUI.MouseOn is GUIButton || GUI.MouseOn?.Parent is GUIButton) {
return false; }
4608 AddPreviouslyUsed(obj as MapEntityPrefab);
4611 if (obj is CoreEntityPrefab prefab)
4613 var matchingTickBox = showEntitiesTickBoxes.Find(tb => tb.UserData as
string == prefab.Identifier);
4614 if (matchingTickBox !=
null && !matchingTickBox.Selected)
4616 previouslyUsedPanel.
Visible =
false;
4617 showEntitiesPanel.
Visible =
true;
4619 matchingTickBox.Selected =
true;
4620 matchingTickBox.Flash(GUIStyle.Green);
4624 if (dummyCharacter?.SelectedItem !=
null)
4631 case ItemAssemblyPrefab assemblyPrefab when PlayerInput.IsShiftDown():
4633 var itemInstance = LoadItemAssemblyInventorySafe(assemblyPrefab);
4634 var spawnedItem =
false;
4636 itemInstance.ForEach(newItem =>
4638 if (newItem !=
null)
4640 var placedItem = inv.TryPutItem(newItem, dummyCharacter);
4641 spawnedItem |= placedItem;
4646 newItem.OwnInventory?.DeleteAllItems();
4652 List<MapEntity> placedEntities = itemInstance.Where(it => !it.Removed).Cast<MapEntity>().ToList();
4653 if (placedEntities.Any())
4655 StoreCommand(
new AddOrDeleteCommand(placedEntities,
false));
4660 case ItemPrefab itemPrefab when PlayerInput.IsShiftDown():
4662 var item =
new Item(itemPrefab, Vector2.Zero, MainSub);
4663 if (!inv.TryPutItem(item, dummyCharacter))
4676 StoreCommand(
new AddOrDeleteCommand(
new List<MapEntity> { item },
false));
4680 case ItemAssemblyPrefab _:
4684 DraggedItemPrefab = (MapEntityPrefab)obj;
4700 private bool GenerateWaypoints()
4702 if (MainSub ==
null) {
return false; }
4703 return WayPoint.GenerateSubWaypoints(MainSub);
4706 private void AddPreviouslyUsed(MapEntityPrefab mapEntityPrefab)
4708 if (previouslyUsedList ==
null || mapEntityPrefab ==
null) {
return; }
4712 if (previouslyUsedList.
CountChildren == PreviouslyUsedCount)
4720 var textBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), previouslyUsedList.
Content.
RectTransform) { MinSize = new Point(0, 15) },
4721 ToolBox.LimitString(mapEntityPrefab.Name.Value, GUIStyle.SmallFont, previouslyUsedList.
Content.
Rect.Width), font: GUIStyle.SmallFont)
4723 UserData = mapEntityPrefab
4725 textBlock.RectTransform.SetAsFirstChild();
4740 List<Vector2> wallPoints =
new List<Vector2>();
4743 List<MapEntity> mapEntityList =
new List<MapEntity>();
4749 Door door = it.GetComponent<
Door>();
4752 int halfW = it.WorldRect.Width / 2;
4753 wallPoints.Add(
new Vector2(it.WorldRect.X + halfW, -it.WorldRect.Y + it.WorldRect.Height));
4754 mapEntityList.Add(it);
4762 mapEntityList.Add(e);
4778 if (wallPoints.Count < 4)
4780 DebugConsole.ThrowError(
"Generating hulls for the submarine failed. Not enough wall structures to generate hulls.");
4784 var min = wallPoints[0];
4785 max = wallPoints[0];
4786 for (
int i = 0; i < wallPoints.Count; i++)
4788 min.X = Math.Min(min.X, wallPoints[i].X);
4789 min.Y = Math.Min(min.Y, wallPoints[i].Y);
4790 max.X = Math.Max(max.X, wallPoints[i].X);
4791 max.Y = Math.Max(max.Y, wallPoints[i].Y);
4794 List<Rectangle> hullRects =
new List<Rectangle>
4796 new Rectangle((
int)min.X, (
int)min.Y, (
int)(max.X - min.X), (
int)(max.Y - min.Y))
4798 foreach (Vector2 point
in wallPoints)
4800 MathUtils.SplitRectanglesHorizontal(hullRects, point);
4801 MathUtils.SplitRectanglesVertical(hullRects, point);
4804 hullRects.Sort((a, b) =>
4806 if (a.Y < b.Y)
return -1;
4807 if (a.Y > b.Y)
return 1;
4808 if (a.X < b.X)
return -1;
4809 if (a.X > b.X)
return 1;
4813 for (
int i = 0; i < hullRects.Count - 1; i++)
4816 if (hullRects[i + 1].Y > rect.Y)
continue;
4818 Vector2 hullRPoint =
new Vector2(rect.X + rect.Width - 8, rect.Y + rect.Height / 2);
4819 Vector2 hullLPoint =
new Vector2(rect.X, rect.Y + rect.Height / 2);
4825 entRect.Y = -entRect.Y;
4826 if (entRect.Contains(hullRPoint))
4828 if (!entRect.Contains(hullLPoint)) container = e;
4832 if (container ==
null)
4834 rect.Width += hullRects[i + 1].Width;
4835 hullRects[i] = rect;
4836 hullRects.RemoveAt(i + 1);
4844 if (entRect.Width < entRect.Height)
continue;
4845 entRect.Y = -entRect.Y - 16;
4846 for (
int i = 0; i < hullRects.Count; i++)
4849 if (entRect.Intersects(hullRect))
4851 if (hullRect.Y < entRect.Y)
4853 hullRect.Height = Math.Max((entRect.Y + 16 + entRect.Height / 2) - hullRect.Y, hullRect.Height);
4854 hullRects[i] = hullRect;
4856 else if (hullRect.Y + hullRect.Height <= entRect.Y + 16 + entRect.Height)
4858 hullRects.RemoveAt(i);
4868 if (entRect.Width < entRect.Height)
continue;
4869 entRect.Y = -entRect.Y;
4870 for (
int i = 0; i < hullRects.Count; i++)
4873 if (entRect.Intersects(hullRect))
4875 if (hullRect.Y >= entRect.Y - 8 && hullRect.Y + hullRect.Height <= entRect.Y + entRect.Height + 8)
4877 hullRects.RemoveAt(i);
4884 for (
int i = 0; i < hullRects.Count;)
4887 Vector2 point =
new Vector2(hullRect.X+2, hullRect.Y+hullRect.Height/2);
4892 entRect.Y = -entRect.Y;
4893 if (entRect.Contains(point))
4899 if (container ==
null)
4901 hullRects.RemoveAt(i);
4905 while (hullRects[i].Y <= hullRect.Y)
4908 if (i >= hullRects.Count)
break;
4912 for (
int i = hullRects.Count-1; i >= 0;)
4915 Vector2 point =
new Vector2(hullRect.X+hullRect.Width-2, hullRect.Y+hullRect.Height/2);
4920 entRect.Y = -entRect.Y;
4921 if (entRect.Contains(point))
4927 if (container ==
null)
4929 hullRects.RemoveAt(i); i--;
4933 while (hullRects[i].Y >= hullRect.Y)
4940 hullRects.Sort((a, b) =>
4942 if (a.X < b.X)
return -1;
4943 if (a.X > b.X)
return 1;
4944 if (a.Y < b.Y)
return -1;
4945 if (a.Y > b.Y)
return 1;
4949 for (
int i = 0; i < hullRects.Count - 1; i++)
4952 if (hullRects[i + 1].Width != rect.Width)
continue;
4953 if (hullRects[i + 1].X > rect.X)
continue;
4955 Vector2 hullBPoint =
new Vector2(rect.X + rect.Width / 2, rect.Y + rect.Height - 8);
4956 Vector2 hullUPoint =
new Vector2(rect.X + rect.Width / 2, rect.Y);
4962 entRect.Y = -entRect.Y;
4963 if (entRect.Contains(hullBPoint))
4965 if (!entRect.Contains(hullUPoint)) container = e;
4969 if (container ==
null)
4971 rect.Height += hullRects[i + 1].Height;
4972 hullRects[i] = rect;
4973 hullRects.RemoveAt(i + 1);
4978 for (
int i = 0; i < hullRects.Count;i++)
4983 hullRects[i] = rect;
4986 hullRects.Sort((a, b) =>
4988 if (a.Y < b.Y)
return -1;
4989 if (a.Y > b.Y)
return 1;
4990 if (a.X < b.X)
return -1;
4991 if (a.X > b.X)
return 1;
4995 for (
int i = 0; i < hullRects.Count; i++)
4997 for (
int j = i+1; j < hullRects.Count; j++)
4999 if (hullRects[j].Y <= hullRects[i].Y)
continue;
5000 if (hullRects[j].Intersects(hullRects[i]))
5003 rect.Height = hullRects[j].Y - rect.Y;
5004 hullRects[i] = rect;
5013 hullRect.Y = -hullRect.Y;
5021 if (!(e as
Structure).IsPlatform)
continue;
5025 gapRect.Height = 16;
5032 if (GUI.DisableHUD) {
return; }
5053 if (dummyCharacter !=
null)
5065 if (loadFrame !=
null)
5080 if (GUI.MouseOn ==
null) {
return false; }
5088 private static void Redo(
int amount)
5090 for (
int i = 0; i < amount; i++)
5092 if (commandIndex < Commands.Count)
5094 Command command = Commands[commandIndex++];
5098 GameMain.SubEditorScreen.UpdateUndoHistoryPanel();
5101 private static void Undo(
int amount)
5103 for (
int i = 0; i < amount; i++)
5105 if (commandIndex > 0)
5107 Command command = Commands[--commandIndex];
5108 command.UnExecute();
5111 GameMain.SubEditorScreen.UpdateUndoHistoryPanel();
5114 private static void ClearUndoBuffer()
5116 SerializableEntityEditor.PropertyChangesActive =
false;
5117 SerializableEntityEditor.CommandBuffer =
null;
5118 Commands.ForEach(cmd => cmd.Cleanup());
5121 GameMain.SubEditorScreen.UpdateUndoHistoryPanel();
5126 if (commandIndex != Commands.Count)
5128 Commands.RemoveRange(commandIndex, Commands.Count - commandIndex);
5130 Commands.Add(command);
5134 if (Commands.Count > Math.Clamp(GameSettings.CurrentConfig.SubEditorUndoBuffer, 1, 10240))
5136 Commands.First()?.Cleanup();
5137 Commands.RemoveRange(0, 1);
5138 commandIndex = Commands.Count;
5143 if (command is AddOrDeleteCommand addOrDelete)
5149 private string prevSelectedLayer;
5150 private void EntityAddedOrDeleted(IEnumerable<MapEntity> entities)
5152 if (layerList?.SelectedData is
string selectedLayer)
5155 foreach (var entity
in entities)
5157 if (!entity.Removed)
5159 entity.Layer = selectedLayer;
5163 layerElement?.
Flash(GUIStyle.Green);
5167 private void UpdateLayerPanel()
5169 if (layerPanel is
null || layerList is
null) {
return; }
5174 layerSpecificButtons.ForEach(btn => btn.Enabled =
false);
5175 GUILayoutGroup buttonHeaders =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.075f), layerList.
Content.
RectTransform), isHorizontal:
true, childAnchor:
Anchor.BottomLeft);
5177 new GUIButton(
new RectTransform(
new Vector2(0.25f, 1f), buttonHeaders.RectTransform), TextManager.Get(
"editor.layer.headervisible"), style:
"GUIButtonSmallFreeScale") {
ForceUpperCase =
ForceUpperCase.Yes };
5178 new GUIButton(
new RectTransform(
new Vector2(0.15f, 1f), buttonHeaders.RectTransform), TextManager.Get(
"editor.layer.headerlink"), style:
"GUIButtonSmallFreeScale")
5181 ToolTip = TextManager.Get(
"editor.layer.headerlink.tooltip")
5183 new GUIButton(
new RectTransform(
new Vector2(0.6f, 1f), buttonHeaders.RectTransform), TextManager.Get(
"name"), style:
"GUIButtonSmallFreeScale") {
ForceUpperCase =
ForceUpperCase.Yes };
5185 foreach ((
string layer, (
bool isVisible,
bool isGrouped)) in Layers)
5187 GUIFrame parent =
new GUIFrame(
new RectTransform(
new Vector2(1f, 0.1f), layerList.
Content.
RectTransform), style:
"ListBoxElement")
5192 GUILayoutGroup layerGroup =
new GUILayoutGroup(
new RectTransform(Vector2.One, parent.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
5194 GUILayoutGroup layerVisibilityLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.25f, 1f), layerGroup.RectTransform), childAnchor:
Anchor.Center);
5195 GUITickBox layerVisibleButton =
new GUITickBox(
new RectTransform(Vector2.One, layerVisibilityLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight),
string.Empty)
5200 if (!Layers.TryGetValue(layer, out LayerData data))
5205 if (!box.Selected && layerList.
SelectedData as
string == layer)
5213 Layers[layer] = data with { IsVisible = box.Selected };
5218 GUILayoutGroup layerChainLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.15f, 1f), layerGroup.RectTransform), childAnchor:
Anchor.Center);
5219 GUITickBox layerChainButton =
new GUITickBox(
new RectTransform(Vector2.One, layerChainLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight),
string.Empty)
5224 if (!Layers.TryGetValue(layer, out LayerData data))
5230 Layers[layer] = data with { IsGrouped = box.Selected };
5235 layerGroup.Recalculate();
5237 var textBlock =
new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1f), layerGroup.RectTransform), layer, textAlignment: Alignment.CenterLeft);
5238 if (textBlock.TextSize.X > textBlock.Rect.Width)
5240 textBlock.ToolTip = textBlock.Text;
5241 textBlock.Text = ToolBox.LimitString(textBlock.Text, textBlock.Font, textBlock.Rect.Width);
5244 layerGroup.Recalculate();
5245 layerChainLayout.Recalculate();
5246 layerVisibilityLayout.Recalculate();
5250 buttonHeaders.Recalculate();
5251 foreach (var child
in buttonHeaders.Children)
5253 var btn = child as GUIButton;
5254 string originalBtnText = btn.Text.Value;
5255 btn.Text = ToolBox.LimitString(btn.Text, btn.Font, btn.Rect.Width);
5256 if (originalBtnText != btn.Text && btn.ToolTip.IsNullOrEmpty())
5258 btn.ToolTip = originalBtnText;
5265 if (undoBufferPanel is
null) {
return; }
5267 undoBufferDisclaimer.
Visible = mode ==
Mode.Wiring;
5274 for (
int i = 0; i < Commands.Count; i++)
5276 Command command = Commands[i];
5278 CreateTextBlock(description, description, i + 1, command).RectTransform.SetAsFirstChild();
5281 CreateTextBlock(TextManager.Get(
"undo.beginning"), TextManager.Get(
"undo.beginningtooltip"), 0,
null);
5286 ToolBox.LimitString(name.
Value, GUIStyle.SmallFont, undoBufferList.
Content.
Rect.Width), font: GUIStyle.SmallFont, textColor: index == commandIndex ? GUIStyle.Green : (Color?)
null)
5289 ToolTip = description
5294 private static void CommitBulkItemBuffer()
5296 if (BulkItemBuffer.Any())
5298 AddOrDeleteCommand master = BulkItemBuffer[0];
5299 for (
int i = 1; i < BulkItemBuffer.Count; i++)
5301 AddOrDeleteCommand command = BulkItemBuffer[i];
5302 command.MergeInto(master);
5305 StoreCommand(master);
5306 BulkItemBuffer.Clear();
5309 bulkItemBufferinUse =
null;
5316 public override void Update(
double deltaTime)
5318 SkipInventorySlotUpdate =
false;
5319 ImageManager.Update((
float)deltaTime);
5327 saveAssemblyFrame =
null;
5328 snapToGridFrame =
null;
5333 if (OpenedItem !=
null && OpenedItem.
Removed)
5338 if (WiringMode && dummyCharacter !=
null)
5344 if (equippedWire ==
null)
5350 foreach (var child
in lBox.Content.Children)
5352 if (child.UserData is
Item item)
5359 var highlightedEntities =
new List<MapEntity>();
5364 var wire = item.GetComponent<
Wire>();
5365 if (wire ==
null || !wire.IsMouseOn()) {
continue; }
5366 highlightedEntities.Add(item);
5375 bool isCircuitBoxOpened = dummyCharacter?.
SelectedItem?.GetComponent<CircuitBox>() is not
null;
5382 if (camTargetFocus != Vector2.Zero)
5384 if (GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.Up].IsDown() || GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.Down].IsDown() ||
5385 GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.Left].IsDown() || GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.Right].IsDown())
5387 camTargetFocus = Vector2.Zero;
5391 var targetWithOffset =
new Vector2(camTargetFocus.X, camTargetFocus.Y - offset / 2);
5392 if (Math.Abs(cam.Position.X - targetWithOffset.X) < 1.0f &&
5393 Math.Abs(cam.Position.Y - targetWithOffset.Y) < 1.0f)
5395 camTargetFocus = Vector2.Zero;
5399 cam.Position += (targetWithOffset - cam.Position) / cam.MoveSmoothness;
5409 if (GUI.KeyboardDispatcher.Subscriber ==
null
5411 && GUI.KeyboardDispatcher.Subscriber is
GUIComponent sub
5430 if (GUI.KeyboardDispatcher.Subscriber ==
null)
5432 if (WiringMode && dummyCharacter !=
null)
5436 if (!dummyCharacter.
HeldItems.Any(it => it.HasTag(Tags.WireItem)))
5445 int index = key == Keys.D0 ? numberKeys.Count : numberKeys.IndexOf(key) - 1;
5446 if (index > -1 && index < listBox.Content.CountChildren)
5448 listBox.Select(index);
5449 SkipInventorySlotUpdate =
true;
5455 if (mode ==
Mode.Default)
5459 if (dummyCharacter !=
null)
5467 var container = item.GetComponents<
ItemContainer>().ToList();
5468 if (!container.Any() || container.Any(ic => ic?.DrawInventory ??
false))
5488 if (selected.Count > 0)
5490 var dRect = selected.First().Rect;
5491 var rect =
new Rectangle(dRect.Left, dRect.Top, dRect.Width, dRect.Height * -1);
5492 if (selected.Count > 1)
5495 selected.Skip(1).ForEach(me =>
5497 var wRect = me.Rect;
5498 rect =
Rectangle.Union(rect,
new Rectangle(wRect.Left, wRect.Top, wRect.Width, wRect.Height * -1));
5501 camTargetFocus = rect.Center.ToVector2();
5506 entityFilterBox.
Select();
5510 if (toggleEntityListBind != GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.ToggleInventory])
5512 toggleEntityMenuButton.
ToolTip =
RichString.
Rich($
"{TextManager.Get("EntityMenuToggleTooltip
")}\n‖color:125,125,125‖{GameSettings.CurrentConfig.KeyMap.Bindings[InputType.ToggleInventory].Name}‖color:end‖");
5513 toggleEntityListBind = GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.ToggleInventory];
5515 if (GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.ToggleInventory].IsHit() && mode ==
Mode.Default)
5517 toggleEntityMenuButton.
OnClicked?.Invoke(toggleEntityMenuButton, toggleEntityMenuButton.
UserData);
5522 cam.MoveCamera((
float) deltaTime, allowMove:
false, allowZoom: GUI.MouseOn ==
null);
5529 CreateSaveScreen(subNameLabel !=
null && subNameLabel.
Text != TextManager.Get(
"unspecifiedsubfilename"));
5552 var wire = item.GetComponent<
Wire>();
5553 if (wire !=
null && wire.Connections.None(c => c ==
null) && !selectables.Contains(item))
5555 selectables.Add(item);
5571 cam.MoveCamera((
float) deltaTime, allowMove: !CircuitBox.IsCircuitBoxSelected(dummyCharacter), allowZoom: GUI.MouseOn ==
null);
5576 cam.MoveCamera((
float) deltaTime, allowMove:
false, allowZoom: GUI.MouseOn ==
null);
5582 moveSpeed.X = -moveSpeed.X;
5583 cam.Position += moveSpeed;
5585 camTargetFocus = Vector2.Zero;
5593 if (lightingEnabled)
5600 lightComponent.
Light.Color =
5620 List<Wire> wires =
new List<Wire>();
5623 var wire = item.GetComponent<
Wire>();
5624 if (wire !=
null) wires.Add(wire);
5636 slot.Rect.Y = EntityMenu.
Rect.Top;
5637 slot.Rect.X = EntityMenu.
Rect.X + (EntityMenu.
Rect.Width / 2) - (slot.Rect.Width /2);
5647 if (equippedWire !=
null && equippedWire.
GetNodes().Count > 0)
5649 Vector2 lastNode = equippedWire.
GetNodes().Last();
5657 bool isHorizontal = Math.Abs(cursorX - lastNode.X) < Math.Abs(cursorY - lastNode.Y);
5663 ?
new Vector2(lastNode.X, roundedY)
5664 :
new Vector2(roundedX, lastNode.Y);
5669 if (OpenedItem !=
null)
5671 TeleportDummyCharacter(oldItemPosition);
5674 if (WiringMode && dummyCharacter?.SelectedItem ==
null)
5676 TeleportDummyCharacter(FarseerPhysics.ConvertUnits.ToSimUnits(dummyCharacter.
CursorPosition));
5683 dummyCharacter.
Control((
float)deltaTime, cam);
5686 cam.TargetPos = Vector2.Zero;
5690 if (dummyCharacter?.SelectedItem !=
null)
5696 TryDragItemsToItem(linkedItem);
5700 void TryDragItemsToItem(
Item item)
5711 void TryDragItemsToInventory(
Inventory inv)
5715 var draggingMouse = MouseDragStart != Vector2.Zero && Vector2.Distance(
PlayerInput.
MousePosition, MouseDragStart) >= GUI.Scale * 20;
5720 switch (DraggedItemPrefab)
5725 bool spawnedItem =
false;
5726 for (var i = 0; i < inv.
Capacity; i++)
5734 var newItem =
new Item(itemPrefab, Vector2.Zero, MainSub);
5738 bool placedItem = inv.
TryPutItem(newItem, i,
false,
true, dummyCharacter);
5739 spawnedItem |= placedItem;
5746 else if (itemContainer !=
null && itemContainer.Inventory.CanBePut(itemPrefab))
5748 bool placedItem = itemContainer.Inventory.TryPutItem(newItem, dummyCharacter);
5749 spawnedItem |= placedItem;
5758 slot.ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.4f);
5764 slot.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.4f);
5767 if (!newItem.Removed)
5769 BulkItemBufferInUse = ItemAddMutex;
5770 BulkItemBuffer.Add(
new AddOrDeleteCommand(
new List<MapEntity> { newItem },
false));
5784 bool spawnedItems =
false;
5793 var itemInstance = LoadItemAssemblyInventorySafe(assemblyPrefab);
5796 var failedCount = 0;
5798 for (var j = 0; j < itemInstance.Count; j++)
5800 var newItem = itemInstance[j];
5801 var newSpot = i + j - failedCount;
5806 if (inv.
GetItemAt(newSpot) ==
null) {
break; }
5813 var placedItem = inv.
TryPutItem(newItem, newSpot,
false,
true, dummyCharacter);
5814 spawnedItems |= placedItem;
5820 newItem?.OwnInventory?.DeleteAllItems();
5826 var placedItem = inv.
TryPutItem(newItem, dummyCharacter);
5827 spawnedItems |= placedItem;
5833 newItem?.OwnInventory?.DeleteAllItems();
5839 List<MapEntity> placedEntities = itemInstance.Where(it => !it.Removed).Cast<
MapEntity>().ToList();
5840 if (placedEntities.Any())
5842 BulkItemBufferInUse = ItemAddMutex;
5843 BulkItemBuffer.Add(
new AddOrDeleteCommand(placedEntities,
false));
5856 CommitBulkItemBuffer();
5867 if (MouseDragStart == Vector2.Zero)
5874 MouseDragStart = Vector2.Zero;
5877 if ((GUI.MouseOn ==
null || !GUI.MouseOn.IsChildOf(TopPanel))
5881 if (layerList is { Visible:
true } && GUI.KeyboardDispatcher.Subscriber == layerList)
5883 GUI.KeyboardDispatcher.Subscriber =
null;
5891 MeasurePositionStart = Vector2.Zero;
5904 bool shouldCloseHud = dummyCharacter?.
SelectedItem !=
null && HUD.CloseHUD(dummyCharacter.
SelectedItem.
Rect) && DraggedItemPrefab ==
null;
5914 if (GUI.IsMouseOn(entityFilterBox))
5920 if (dummyCharacter?.SelectedItem ==
null)
5922 CreateContextMenu();
5924 DraggedItemPrefab =
null;
5936 entityMenuOpenState = entityMenuOpen && !WiringMode ?
5937 (float)Math.Min(entityMenuOpenState + deltaTime * 5.0f, 1.0f) :
5938 (float)Math.Max(entityMenuOpenState - deltaTime * 5.0f, 0.0f);
5947 if (loadFrame !=
null)
5954 else if (saveFrame !=
null)
5962 if (dummyCharacter !=
null)
5970 if (item.
body !=
null)
5976 Wire wire = item.GetComponent<
Wire>();
5977 wire?.
Update((
float)deltaTime, cam);
5995 item.
UpdateHUD(cam, dummyCharacter, (
float)deltaTime);
6005 public override void Draw(
double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
6007 cam.UpdateTransform();
6008 if (lightingEnabled)
6018 graphics.Clear(BackgroundColor);
6020 ImageManager.Draw(spriteBatch, cam);
6022 spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: cam.Transform);
6026 GUI.DrawLine(spriteBatch,
new Vector2(MainSub.HiddenSubPosition.X, -cam.WorldView.Y),
new Vector2(MainSub.HiddenSubPosition.X, -(cam.WorldView.Y - cam.WorldView.Height)), Color.White * 0.5f, 1.0f, (
int)(2.0f / cam.Zoom));
6027 GUI.DrawLine(spriteBatch,
new Vector2(cam.WorldView.X, -MainSub.HiddenSubPosition.Y),
new Vector2(cam.WorldView.Right, -MainSub.HiddenSubPosition.Y), Color.White * 0.5f, 1.0f, (
int)(2.0f / cam.Zoom));
6032 (e.
SpriteDepth >= 0.9f || s.Prefab.BackgroundSprite !=
null));
6036 spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: cam.Transform);
6040 if (OpenedItem?.GetComponent<Wearable>() !=
null)
6044 GUI.DrawRectangle(spriteBatch,
6046 new Vector2(OpenedItem.
Rect.Width, OpenedItem.
Rect.Height),
6047 Color.White,
false, 0, (
int)Math.Max(2.0f / cam.Zoom, 1.0f));
6055 spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: cam.Transform);
6059 spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, transformMatrix: cam.Transform);
6066 if (dummyCharacter !=
null && WiringMode)
6070 heldItem.
Draw(spriteBatch, editing:
false, back:
true);
6074 DrawGrid(spriteBatch);
6077 ImageManager.DrawEditing(spriteBatch, cam);
6081 spriteBatch.Begin(SpriteSortMode.Deferred, Lights.CustomBlendStates.Multiplicative,
null, DepthStencilState.None);
6093 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
6095 if (MainSub !=
null && cam.Zoom < 5f)
6097 Vector2 position = MainSub.SubBody !=
null ? MainSub.WorldPosition : MainSub.HiddenSubPosition;
6100 spriteBatch, position, cam,
6101 cam.WorldView.Width,
6102 GUIStyle.SubmarineLocationIcon.Value.Sprite, Color.LightBlue * 0.5f);
6105 var notificationIcon = GUIStyle.GetComponentStyle(
"GUINotificationButton");
6106 var tooltipStyle = GUIStyle.GetComponentStyle(
"GUIToolTip");
6122 if (DrawCharacterInventory)
6124 dummyCharacter.
DrawHUD(spriteBatch, cam,
false);
6129 GUI.Draw(Cam, spriteBatch);
6131 if (MeasurePositionStart != Vector2.Zero)
6133 Vector2 startPos = MeasurePositionStart;
6137 startPos = RoundToGrid(startPos);
6138 mouseWorldPos = RoundToGrid(mouseWorldPos);
6140 static Vector2 RoundToGrid(Vector2 position)
6148 GUI.DrawLine(spriteBatch, cam.WorldToScreen(startPos), cam.WorldToScreen(mouseWorldPos), GUIStyle.Green, width: 4);
6150 decimal realWorldDistance = decimal.Round((decimal) (Vector2.Distance(startPos, mouseWorldPos) * Physics.DisplayToRealWorldRatio), 2);
6152 Vector2 offset =
new Vector2(GUI.IntScale(24));
6153 GUI.DrawString(spriteBatch,
PlayerInput.
MousePosition + offset, $
"{realWorldDistance} m", GUIStyle.TextColorNormal, font: GUIStyle.Font, backgroundColor: Color.Black, backgroundPadding: 4);
6159 private void CreateImage(
int width,
int height, System.IO.Stream stream)
6167 Vector2 viewPos = subDimensions.Center.ToVector2();
6168 float scale = Math.Min(width / (
float)subDimensions.Width, height / (
float)subDimensions.Height);
6170 var viewMatrix = Matrix.CreateTranslation(
new Vector3(width / 2.0f, height / 2.0f, 0));
6171 var transform = Matrix.CreateTranslation(
6172 new Vector3(-viewPos.X, viewPos.Y, 0)) *
6173 Matrix.CreateScale(
new Vector3(scale, scale, 1)) *
6176 using (RenderTarget2D rt =
new RenderTarget2D(
6178 width, height,
false, SurfaceFormat.Color, DepthFormat.None))
6179 using (SpriteBatch spriteBatch =
new SpriteBatch(
GameMain.
Instance.GraphicsDevice))
6184 spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied,
null,
null,
null,
null, transform);
6192 rt.SaveAsPng(stream, width, height);
6200 private static readonly Color gridBaseColor = Color.White * 0.1f;
6202 private void DrawGrid(SpriteBatch spriteBatch)
6205 if (!ShouldDrawGrid) {
return; }
6207 var (gridX, gridY) =
Submarine.GridSize;
6208 DrawGrid(spriteBatch, cam, gridX, gridY, zoomTreshold:
true);
6211 public static void DrawGrid(SpriteBatch spriteBatch,
Camera cam,
float sizeX,
float sizeY,
bool zoomTreshold)
6213 if (zoomTreshold && cam.
Zoom < 0.5f) {
return; }
6214 int scale = Math.Max(1, GUI.IntScale(1));
6215 float zoom = cam.
Zoom / 2f;
6216 float lineThickness = Math.Max(1, scale / zoom);
6218 Color gridColor = gridBaseColor;
6219 if (zoomTreshold && cam.
Zoom < 1.0f)
6222 gridColor *= Math.Max(0, (cam.
Zoom - 0.5f) * 2f);
6227 for (
float x = snapX(camRect.X); x < snapX(camRect.X + camRect.Width) + sizeX; x += sizeX)
6229 spriteBatch.DrawLine(
new Vector2(x, -camRect.Y),
new Vector2(x, -(camRect.Y - camRect.Height)), gridColor, thickness: lineThickness);
6232 for (
float y = snapY(camRect.Y); y >= snapY(camRect.Y - camRect.Height) - sizeY; y -= sizeY)
6234 spriteBatch.DrawLine(
new Vector2(camRect.X, -y),
new Vector2(camRect.Right, -y), gridColor, thickness: lineThickness);
6237 float snapX(
int x) => (float) Math.Floor(x / sizeX) * sizeX;
6238 float snapY(
int y) => (float) Math.Ceiling(y / sizeY) * sizeY;
6245 RectangleF playableArea =
new RectangleF(
6246 -playableAreaSize / 2f,
6247 -playableAreaSize / 2f,
6252 RectangleF topRect =
new(
6256 playableArea.Top + camRect.Top
6260 float camRectBottom = -camRect.Top + camRect.Height;
6262 RectangleF bottomRect =
new(
6264 playableArea.Bottom,
6266 camRectBottom + playableArea.Bottom
6269 RectangleF rightRect =
new(
6272 camRect.Right - playableArea.Right,
6276 RectangleF leftRect =
new(
6279 camRect.Left - playableArea.Left,
6283 GUI.DrawFilledRectangle(spriteBatch, topRect, color);
6284 GUI.DrawFilledRectangle(spriteBatch, leftRect, color);
6285 GUI.DrawFilledRectangle(spriteBatch, rightRect, color);
6286 GUI.DrawFilledRectangle(spriteBatch, bottomRect, color);
6291 System.IO.Stream stream = File.OpenWrite(filePath);
6292 CreateImage(width, height, stream);
6298 if (
string.IsNullOrEmpty(subcategory) || !hiddenSubCategories.ContainsKey(subcategory))
6302 return hiddenSubCategories[subcategory];
6310 if (!IsSubEditor() ||
string.IsNullOrWhiteSpace(entity.
Layer)) {
return true; }
6312 if (!Layers.TryGetValue(entity.
Layer, out LayerData data))
6318 return data.IsVisible;
6323 if (!IsSubEditor() ||
string.IsNullOrWhiteSpace(entity.
Layer)) {
return false; }
6325 if (!Layers.TryGetValue(entity.
Layer, out LayerData data))
6331 return data.IsGrouped;
6336 if (
string.IsNullOrWhiteSpace(entity.
Layer)) {
return ImmutableHashSet<MapEntity>.Empty; }
Vector2 WorldToScreen(Vector2 coords)
static void AddToGUIUpdateList(Character character)
static void Update(float deltaTime, Character character, Camera cam)
Vector2? CursorWorldPosition
override Vector2? SimPosition
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
void Control(float deltaTime, Camera cam)
CharacterInventory Inventory
void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam=true)
Control the Character according to player input
static Character? Controlled
readonly AnimController AnimController
IEnumerable< Item >?? HeldItems
Items the character has in their hand slots. Doesn't return nulls and only returns items held in both...
void DrawHUD(SpriteBatch spriteBatch, Camera cam, bool drawHealth=true)
override void CreateSlots()
override bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
int FindLimbSlot(InvSlotType limbSlot)
ImmutableArray< ContentFile > Files
virtual Vector2 WorldPosition
virtual Vector2 DrawPosition
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
OnColorSelectedHandler? OnColorSelected
bool ClampMouseRectToParent
GUIComponent GetChild(int index)
virtual void RemoveChild(GUIComponent child)
virtual void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectInflate=null)
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
virtual Rectangle? MouseRect
GUIComponent GetChildByUserData(object obj)
virtual void ClearChildren()
GUIComponent FindChild(Func< GUIComponent, bool > predicate, bool recursive=false)
virtual void DrawManually(SpriteBatch spriteBatch, bool alsoChildren=false, bool recursive=true)
By default, all the gui elements are drawn automatically in the same order they appear on the update ...
IEnumerable< GUIComponent > GetAllChildren()
Returns all child elements in the hierarchy.
void DrawToolTip(SpriteBatch spriteBatch)
Creates and draws a tooltip.
RectTransform RectTransform
SpriteEffects SpriteEffects
IEnumerable< GUIComponent > Children
void FadeOut(float duration, bool removeAfter, float wait=0.0f, Action onRemove=null, bool alsoChildren=false)
IEnumerable< GUIComponent > FindChildren(object userData)
GUIComponent that can be used to render custom content on the UI
GUIComponent AddItem(LocalizedString text, object userData=null, LocalizedString toolTip=null, Color? color=null, Color? textColor=null)
OnSelectedHandler OnSelected
OnSelectedHandler OnDropped
override void ClearChildren()
override void RemoveChild(GUIComponent child)
void RecalculateChildren()
override void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
delegate bool OnSelectedHandler(GUIComponent component, object obj)
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
void UpdateScrollBarSize()
static readonly List< GUIComponent > MessageBoxes
TextBoxEvent OnDeselected
OnTextChangedHandler OnTextChanged
Don't set the Text property on delegates that register to this event, because modifying the Text will...
void Select(int forcedCaretIndex=-1, bool ignoreSelectSound=false)
OnEnterHandler OnEnterPressed
override void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectOffset=null)
static SubEditorScreen SubEditorScreen
static int GraphicsHeight
static Lights.LightManager LightManager
static Sounds.SoundManager SoundManager
static List< Gap > GapList
static void UpdateCheats(float deltaTime, Camera cam)
virtual bool CanBePutInSlot(Item item, int i, bool ignoreCondition=false)
Can the item be put in the specified slot.
virtual bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
static bool IsMouseOnSlot(VisualSlot slot)
Check if the mouse is hovering on top of the slot
static readonly List< Item > DraggingItems
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
Item GetItemAt(int index)
Get the item stored in the specified inventory slot. If the slot contains a stack of items,...
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
ItemInventory OwnInventory
Inventory ParentInventory
override void AddToGUIUpdateList(int order=0)
void SetTransform(Vector2 simPosition, float rotation, bool findNewHull=true, bool setPrevTransform=true)
override void Draw(SpriteBatch spriteBatch, bool editing, bool back=true)
void UpdateHUD(Camera cam, Character character, float deltaTime)
static readonly List< Item > ItemList
List< ItemComponent > Components
SpriteEffects SpriteEffects
static readonly PrefabCollection< ItemPrefab > Prefabs
override LocalizedString Name
override ImmutableHashSet< Identifier > Tags
The base class for components holding the different functionalities of the item
readonly ItemInventory Inventory
bool KeepOpenWhenEquipped
override void Move(Vector2 amount, bool ignoreContacts=false)
static void UpdateEditing(List< Wire > wires)
List< Vector2 > GetNodes()
override void Update(float deltaTime, Camera cam)
static HashSet< MapEntity > SelectedList
static List< MapEntity > FilteredSelectedList
static GUIListBox HighlightedListBox
static Vector2 StartMovingPos
static IEnumerable< MapEntity > HighlightedEntities
readonly MapEntityPrefab Prefab
static readonly List< MapEntity > MapEntityList
static void AddSelection(MapEntity entity)
static void DrawEditor(SpriteBatch spriteBatch, Camera cam)
static void UpdateEditor(Camera cam, float deltaTime)
static void DeselectAll()
bool IsLayerHidden
Is the layer this entity is in currently hidden? If it is, the entity is not updated and should do no...
static GUIComponent EditingHUD
readonly List< MapEntity > linkedTo
static void UpdateHighlighting(List< MapEntity > highlightedEntities, bool wiringMode=false)
Updates the logic that runs the highlight box when the mouse is sitting still.
static void UpdateSelecting(Camera cam)
Update the selection logic in submarine editor
static Vector2 SelectionPos
static void DrawSelecting(SpriteBatch spriteBatch, Camera cam)
Draw the "selection rectangle" and outlines of entities that are being dragged (if any)
static void ClearHighlightedEntities()
virtual void UpdatePlacing(Camera cam)
virtual void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
static bool SelectPrefab(object selection)
static MapEntityPrefab Selected
static MapEntityPrefab Find(string name, string identifier=null, bool showErrorMessages=true)
Find a matching map entity prefab
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
void FindHull(Vector2? worldPosition=null, bool setSubmarine=true)
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
Dictionary< Identifier, GUIComponent[]> Fields
Holds the references to the input fields.
void UpdateValue(SerializableProperty property, object newValue, bool flash=true)
static void CommitCommandBuffer()
static bool PropertyChangesActive
static DateTime NextCommandPush
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
static List< WarningType > SuppressedWarnings
override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
This is called when the game should draw itself.
void SetMode(Mode newMode)
void SaveScreenShot(int width, int height, string filePath)
override void Update(double deltaTime)
Allows the game to run logic such as updating the world, checking for collisions, gathering input,...
GUIButton ToggleEntityMenuButton
void Select(bool enableAutoSave=true)
static bool IsLayerVisible(MapEntity entity)
const int MaxShadowCastingLights
static XDocument AutoSaveInfo
void UpdateUndoHistoryPanel()
static readonly List< Command > Commands
Global undo/redo state for the sub editor and a selector index for it Command
static GUIMessageBox CreatePropertyColorPicker(Color originalColor, SerializableProperty property, ISerializableEntity entity)
static List< AddOrDeleteCommand > BulkItemBuffer
static void DrawOutOfBoundsArea(SpriteBatch spriteBatch, Camera cam, float playableAreaSize, Color color)
override void AddToGUIUpdateList()
By default, submits the screen's main GUIFrame and, if requested upon construction,...
bool IsMouseOnEditorGUI()
GUI.MouseOn doesn't get updated while holding primary mouse and we need it to
void ClearBackedUpSubInfo()
bool IsSubcategoryHidden(string subcategory)
static bool IsWiringMode()
static bool TransparentWiringMode
void LoadSub(SubmarineInfo info, bool checkIdConflicts=true)
static readonly EditorImageManager ImageManager
static void StoreCommand(Command command)
static readonly object ItemAddMutex
override void DeselectEditorSpecific()
static GUIFrame CreateWiringPanel(Point offset, GUIListBox.OnSelectedHandler onWireSelected)
static ImmutableHashSet< MapEntity > GetEntitiesInSameLayer(MapEntity entity)
static bool IsSubEditor()
static bool IsLayerLinked(MapEntity entity)
override void OnFileDropped(string filePath, string extension)
static Type DetermineSubFileType(SubmarineType type)
static bool ShouldDrawGrid
static void DrawGrid(SpriteBatch spriteBatch, Camera cam, float sizeX, float sizeY, bool zoomTreshold)
GUIComponent showEntitiesPanel
static object BulkItemBufferInUse
bool DrawCharacterInventory
static MapEntityPrefab DraggedItemPrefab
Prefab used for dragging from the item catalog into inventories GUI.Draw
static bool SkipInventorySlotUpdate
static Vector2 MouseDragStart
static void DrawFront(SpriteBatch spriteBatch, bool editing=false, Predicate< MapEntity > predicate=null)
static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing=false, Predicate< MapEntity > predicate=null)
static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing=false, Predicate< MapEntity > predicate=null)
static void Draw(SpriteBatch spriteBatch, bool editing=false)
Rectangle CalculateDimensions(bool onlyHulls=true)
static List< Submarine > Loaded
Vector2 HiddenSubPosition
static void DrawBack(SpriteBatch spriteBatch, bool editing=false, Predicate< MapEntity > predicate=null)
void UpdateTransform(bool interpolate=true)
static readonly Vector2 GridSize
override Vector2? Position
XElement SubmarineElement
LocalizedString Description
static bool ShowWayPoints