Client LuaCsForBarotrauma
LevelEditorScreen.cs
2 using Barotrauma.Lights;
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
6 using System;
7 using System.Collections.Generic;
8 using System.Linq;
9 using System.Xml.Linq;
10 using Microsoft.Xna.Framework.Input;
11 #if DEBUG
12 using System.IO;
13 using System.Xml;
14 #else
15 using Barotrauma.IO;
16 #endif
17 
18 namespace Barotrauma
19 {
21  {
22  public override Camera Cam { get; }
23 
24  private GUIFrame leftPanel, rightPanel, bottomPanel, topPanel;
25 
26  private LevelGenerationParams selectedParams;
27  private RuinGenerationParams selectedRuinGenerationParams;
28  private OutpostGenerationParams selectedOutpostGenerationParams;
29  private LevelObjectPrefab selectedLevelObject;
30 
31  private GUIListBox paramsList, ruinParamsList, caveParamsList, outpostParamsList, levelObjectList;
32  private GUIListBox editorContainer;
33 
34  private GUIButton spriteEditDoneButton;
35 
36  private GUITextBox seedBox;
37 
38  private GUITickBox lightingEnabled, cursorLightEnabled, allowInvalidOutpost, mirrorLevel;
39 
40  private GUIDropDown selectedSubDropDown;
41  private GUIDropDown selectedBeaconStationDropdown;
42  private GUIDropDown selectedWreckDropdown;
43  private GUINumberInput forceDifficultyInput;
44 
45  private Sprite editingSprite;
46 
47  private LightSource pointerLightSource;
48 
49  private readonly Color[] tunnelDebugColors = new Color[] { Color.White, Color.Cyan, Color.LightGreen, Color.Red, Color.LightYellow, Color.LightSeaGreen };
50 
51  private LevelData currentLevelData;
52 
53  private void RefreshUI(bool forceCreate = false)
54  {
55  if (forceCreate)
56  {
57  CreateUI();
58  }
59 
60  GUI.PreventPauseMenuToggle = false;
61  pointerLightSource = new LightSource(Vector2.Zero, 1000.0f, Color.White, submarine: null);
62  GameMain.LightManager.AddLight(pointerLightSource);
63  topPanel.ClearChildren();
64  new SerializableEntityEditor(topPanel.RectTransform, pointerLightSource.LightSourceParams, false, true);
65 
66  editingSprite = null;
67  UpdateParamsList();
68  UpdateRuinParamsList();
69  UpdateCaveParamsList();
70  UpdateOutpostParamsList();
71  UpdateLevelObjectsList();
72  }
73 
74  private void CreateUI()
75  {
76  leftPanel?.ClearChildren();
77  rightPanel?.ClearChildren();
78  leftPanel = new GUIFrame(new RectTransform(new Vector2(0.125f, 0.8f), Frame.RectTransform) { MinSize = new Point(150, 0) });
79  var paddedLeftPanel = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), leftPanel.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.02f, 0.0f) })
80  {
81  Stretch = true,
82  RelativeSpacing = 0.01f
83  };
84 
85  paramsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), paddedLeftPanel.RectTransform))
86  {
87  PlaySoundOnSelect = true
88  };
89  paramsList.OnSelected += (GUIComponent component, object obj) =>
90  {
91  selectedParams = obj as LevelGenerationParams;
92  currentLevelData = LevelData.CreateRandom(seedBox.Text, generationParams: selectedParams);
93  editorContainer.ClearChildren();
94  SortLevelObjectsList(currentLevelData);
95  new SerializableEntityEditor(editorContainer.Content.RectTransform, selectedParams, inGame: false, showName: true, elementHeight: 20, titleFont: GUIStyle.LargeFont);
96  forceDifficultyInput.FloatValue = (selectedParams.MinLevelDifficulty + selectedParams.MaxLevelDifficulty) / 2f;
97  return true;
98  };
99 
100  var ruinTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.ruinparams"), font: GUIStyle.SubHeadingFont);
101 
102  ruinParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.1f), paddedLeftPanel.RectTransform))
103  {
104  PlaySoundOnSelect = true
105  };
106  ruinParamsList.OnSelected += (GUIComponent component, object obj) =>
107  {
108  if (selectedRuinGenerationParams == obj)
109  {
110  // need to wait a frame before deselecting or the highlight on the list item gets left on
111  CoroutineManager.StartCoroutine(DeselectRuinParams());
112 
113  IEnumerable<CoroutineStatus> DeselectRuinParams()
114  {
115  if (Screen.Selected != this)
116  {
117  yield break;
118  }
119 
120  yield return null;
121  selectedRuinGenerationParams = null;
122  CreateOutpostGenerationParamsEditor(null);
123  ruinParamsList.Deselect();
124  }
125  }
126  else
127  {
128  selectedRuinGenerationParams = obj as RuinGenerationParams;
129  CreateOutpostGenerationParamsEditor(selectedRuinGenerationParams);
130  }
131 
132  return true;
133  };
134 
135  var caveTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.caveparams"), font: GUIStyle.SubHeadingFont);
136 
137  caveParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.1f), paddedLeftPanel.RectTransform))
138  {
139  PlaySoundOnSelect = true
140  };
141  caveParamsList.OnSelected += (GUIComponent component, object obj) =>
142  {
143  CreateCaveParamsEditor(obj as CaveGenerationParams);
144  return true;
145  };
146 
147  var outpostTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.outpostparams"), font: GUIStyle.SubHeadingFont);
148  GUITextBlock.AutoScaleAndNormalize(ruinTitle, caveTitle, outpostTitle);
149 
150  outpostParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), paddedLeftPanel.RectTransform))
151  {
152  PlaySoundOnSelect = true
153  };
154  outpostParamsList.OnSelected += (GUIComponent component, object obj) =>
155  {
156  selectedOutpostGenerationParams = obj as OutpostGenerationParams;
157  CreateOutpostGenerationParamsEditor(selectedOutpostGenerationParams);
158  return true;
159  };
160 
161  var createLevelObjButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedLeftPanel.RectTransform),
162  TextManager.Get("leveleditor.createlevelobj"))
163  {
164  OnClicked = (btn, obj) =>
165  {
166  Wizard.Instance.Create();
167  return true;
168  }
169  };
170  GUITextBlock.AutoScaleAndNormalize(createLevelObjButton.TextBlock);
171 
172  lightingEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.025f), paddedLeftPanel.RectTransform),
173  TextManager.Get("leveleditor.lightingenabled"));
174 
175  cursorLightEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.025f), paddedLeftPanel.RectTransform),
176  TextManager.Get("leveleditor.cursorlightenabled"));
177 
178  new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedLeftPanel.RectTransform),
179  TextManager.Get("leveleditor.reloadtextures"))
180  {
181  OnClicked = (btn, obj) =>
182  {
183  Level.Loaded?.ReloadTextures();
184  return true;
185  }
186  };
187 
188  new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedLeftPanel.RectTransform),
189  TextManager.Get("editor.saveall"))
190  {
191  OnClicked = (btn, obj) =>
192  {
193  SerializeAll();
194  GUI.AddMessage(TextManager.Get("leveleditor.allsaved"), GUIStyle.Green);
195  return true;
196  }
197  };
198 
199  rightPanel = new GUIFrame(new RectTransform(new Vector2(0.25f, 1.0f), Frame.RectTransform, Anchor.TopRight) { MinSize = new Point(450, 0) });
200  var paddedRightPanel = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), rightPanel.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.02f, 0.0f) })
201  {
202  Stretch = true,
203  RelativeSpacing = 0.01f
204  };
205 
206  editorContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), paddedRightPanel.RectTransform));
207 
208  var seedContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), paddedRightPanel.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
209  Vector2 randomizeButtonRelativeSize = GetRandomizeButtonRelativeSize();
210  Vector2 elementRelativeSize = GetSeedElementRelativeSize();
211  var seedLabel = new GUITextBlock(new RectTransform(elementRelativeSize, seedContainer.RectTransform), TextManager.Get("leveleditor.levelseed"));
212  seedBox = new GUITextBox(new RectTransform(elementRelativeSize, seedContainer.RectTransform), GetLevelSeed());
213  var seedButton = new GUIButton(new RectTransform(randomizeButtonRelativeSize, seedContainer.RectTransform), style: "RandomizeButton")
214  {
215  OnClicked = (button, userData) =>
216  {
217  if (seedBox == null) { return false; }
218  seedBox.Text = GetLevelSeed();
219  return true;
220  }
221  };
222  seedContainer.RectTransform.SizeChanged += () =>
223  {
224  Vector2 randomizeButtonRelativeSize = GetRandomizeButtonRelativeSize();
225  Vector2 elementRelativeSize = GetSeedElementRelativeSize();
226  seedLabel.RectTransform.RelativeSize = elementRelativeSize;
227  seedBox.RectTransform.RelativeSize = elementRelativeSize;
228  seedButton.RectTransform.RelativeSize = randomizeButtonRelativeSize;
229  };
230  Vector2 GetRandomizeButtonRelativeSize() => 0.2f * seedContainer.Rect.Width > seedContainer.Rect.Height ?
231  new Vector2(Math.Min((float)seedContainer.Rect.Height / seedContainer.Rect.Width, 0.2f), 1.0f) :
232  new Vector2(0.15f, Math.Min((0.2f * seedContainer.Rect.Width) / seedContainer.Rect.Height, 1.0f));
233  Vector2 GetSeedElementRelativeSize() => new Vector2(0.5f * (1.0f - randomizeButtonRelativeSize.X), 1.0f);
234  static string GetLevelSeed() => ToolBox.RandomSeed(8);
235 
236  var subDropDownContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), isHorizontal: true);
237  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), subDropDownContainer.RectTransform), TextManager.Get("submarine"));
238  selectedSubDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1.0f), subDropDownContainer.RectTransform));
239  foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines)
240  {
241  if (sub.Type != SubmarineType.Player) { continue; }
242  selectedSubDropDown.AddItem(sub.DisplayName, userData: sub);
243  }
244  subDropDownContainer.RectTransform.MinSize = new Point(0, selectedSubDropDown.RectTransform.MinSize.Y);
245 
246  var beaconStationDropDownContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), isHorizontal: true);
247  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), beaconStationDropDownContainer.RectTransform), TextManager.Get("submarinetype.beaconstation"));
248  selectedBeaconStationDropdown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1.0f), beaconStationDropDownContainer.RectTransform));
249  selectedBeaconStationDropdown.AddItem(TextManager.Get("Any"), userData: null);
250  foreach (SubmarineInfo beaconStation in SubmarineInfo.SavedSubmarines)
251  {
252  if (beaconStation.Type != SubmarineType.BeaconStation) { continue; }
253  selectedBeaconStationDropdown.AddItem(beaconStation.DisplayName, userData: beaconStation);
254  }
255  beaconStationDropDownContainer.RectTransform.MinSize = new Point(0, selectedBeaconStationDropdown.RectTransform.MinSize.Y);
256 
257  var wreckDropDownContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), isHorizontal: true);
258  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), wreckDropDownContainer.RectTransform), TextManager.Get("submarinetype.wreck"));
259  selectedWreckDropdown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1.0f), wreckDropDownContainer.RectTransform));
260  selectedWreckDropdown.AddItem(TextManager.Get("Any"), userData: null);
261  foreach (SubmarineInfo wreck in SubmarineInfo.SavedSubmarines)
262  {
263  if (wreck.Type != SubmarineType.Wreck) { continue; }
264  selectedWreckDropdown.AddItem(wreck.DisplayName, userData: wreck);
265  }
266  wreckDropDownContainer.RectTransform.MinSize = new Point(0, selectedWreckDropdown.RectTransform.MinSize.Y);
267 
268  var forceDifficultyContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), paddedRightPanel.RectTransform), isHorizontal: true);
269  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), forceDifficultyContainer.RectTransform), TextManager.Get("leveldifficulty"));
270  forceDifficultyInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1.0f), forceDifficultyContainer.RectTransform), NumberType.Float)
271  {
272  MinValueFloat = 0,
273  MaxValueFloat = 100,
274  FloatValue = Level.ForcedDifficulty ?? selectedParams?.MinLevelDifficulty ?? 0f,
275  OnValueChanged = (numberInput) =>
276  {
277  if (Level.ForcedDifficulty == null) { return; }
278  Level.ForcedDifficulty = numberInput.FloatValue;
279  }
280  };
281  forceDifficultyContainer.RectTransform.MinSize = new Point(0, forceDifficultyInput.RectTransform.MinSize.Y);
282 
283  var tickBoxContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), paddedRightPanel.RectTransform), isHorizontal: true);
284  mirrorLevel = new GUITickBox(new RectTransform(new Vector2(0.5f, 0.02f), tickBoxContainer.RectTransform), TextManager.Get("mirrorentityx"));
285 
286  allowInvalidOutpost = new GUITickBox(new RectTransform(new Vector2(0.5f, 0.025f), tickBoxContainer.RectTransform),
287  TextManager.Get("leveleditor.allowinvalidoutpost"))
288  {
289  ToolTip = TextManager.Get("leveleditor.allowinvalidoutpost.tooltip")
290  };
291 
292  new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedRightPanel.RectTransform),
293  TextManager.Get("leveleditor.generate"))
294  {
295  OnClicked = (btn, obj) =>
296  {
297  bool wasLevelLoaded = Level.Loaded != null;
298  Submarine.Unload();
299 
300  if (selectedSubDropDown.SelectedData is SubmarineInfo subInfo)
301  {
302  Submarine.MainSub = new Submarine(subInfo);
303  }
304  GameMain.LightManager.ClearLights();
305  currentLevelData = LevelData.CreateRandom(seedBox.Text, difficulty: forceDifficultyInput.FloatValue, generationParams: selectedParams);
306  currentLevelData.ForceOutpostGenerationParams = outpostParamsList.SelectedData as OutpostGenerationParams;
307  currentLevelData.ForceBeaconStation = selectedBeaconStationDropdown.SelectedData as SubmarineInfo;
308  currentLevelData.ForceWreck = selectedWreckDropdown.SelectedData as SubmarineInfo;
309  currentLevelData.ForceRuinGenerationParams = selectedRuinGenerationParams;
310  currentLevelData.AllowInvalidOutpost = allowInvalidOutpost.Selected;
311  var dummyLocations = GameSession.CreateDummyLocations(currentLevelData);
312  Level.Generate(currentLevelData, mirror: mirrorLevel.Selected, startLocation: dummyLocations[0], endLocation: dummyLocations[1]);
313 
314  if (Submarine.MainSub != null)
315  {
316  Vector2 startPos = Level.Loaded.StartPosition;
317  if (Level.Loaded.StartOutpost != null)
318  {
319  startPos.Y -= Level.Loaded.StartOutpost.Borders.Height / 2 + Submarine.MainSub.Borders.Height / 2;
320  }
321  Submarine.MainSub?.SetPosition(startPos);
322  }
323 
324  GameMain.LightManager.AddLight(pointerLightSource);
325  if (!wasLevelLoaded || Cam.Position.X < 0 || Cam.Position.Y < 0 || Cam.Position.Y > Level.Loaded.Size.X || Cam.Position.Y > Level.Loaded.Size.Y)
326  {
327  Cam.Position = new Vector2(Level.Loaded.Size.X / 2, Level.Loaded.Size.Y / 2);
328  }
329  foreach (GUITextBlock param in paramsList.Content.Children)
330  {
331  param.TextColor = param.UserData == selectedParams ? GUIStyle.Green : param.Style.TextColor;
332  }
333  seedBox.Deselect();
334  return true;
335  }
336  };
337 
338  new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedRightPanel.RectTransform),
339  TextManager.Get("leveleditor.test"))
340  {
341  OnClicked = (btn, obj) =>
342  {
343  if (Level.Loaded?.LevelData == null) { return false; }
344 
345  GameMain.GameScreen.Select();
346 
347  var currEntities = Entity.GetEntities().ToList();
348  if (Submarine.MainSub != null)
349  {
350  var toRemove = Entity.GetEntities().Where(e => e.Submarine == Submarine.MainSub).ToList();
351  foreach (Entity ent in toRemove)
352  {
353  ent.Remove();
354  }
355  Submarine.MainSub.Remove();
356  }
357 
358  var nonPlayerFiles = ContentPackageManager.EnabledPackages.All.SelectMany(p => p
359  .GetFiles<BaseSubFile>()
360  .Where(f => f is not SubmarineFile)).ToArray();
361  SubmarineInfo subInfo = selectedSubDropDown.SelectedData as SubmarineInfo;
362  subInfo ??= SubmarineInfo.SavedSubmarines.GetRandomUnsynced(s =>
363  s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) &&
364  !nonPlayerFiles.Any(f => f.Path == s.FilePath));
365  GameSession gameSession = new GameSession(subInfo, Option.None, CampaignDataPath.Empty, GameModePreset.TestMode, CampaignSettings.Empty, null);
366  gameSession.StartRound(Level.Loaded.LevelData);
367  (gameSession.GameMode as TestGameMode).OnRoundEnd = () =>
368  {
369  GameMain.LevelEditorScreen.Select();
370  Submarine.MainSub.Remove();
371 
372  var toRemove = Entity.GetEntities().Where(e => !currEntities.Contains(e)).ToList();
373  foreach (Entity ent in toRemove)
374  {
375  ent.Remove();
376  }
377 
378  Submarine.MainSub = null;
379  };
380 
381  GameMain.GameSession = gameSession;
382 
383  return true;
384  }
385  };
386 
387  bottomPanel = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.22f), Frame.RectTransform, Anchor.BottomLeft)
388  { MaxSize = new Point(GameMain.GraphicsWidth - rightPanel.Rect.Width, 1000) }, style: "GUIFrameBottom");
389 
390  levelObjectList = new GUIListBox(new RectTransform(new Vector2(0.99f, 0.85f), bottomPanel.RectTransform, Anchor.Center))
391  {
392  PlaySoundOnSelect = true,
393  UseGridLayout = true
394  };
395  levelObjectList.OnSelected += (GUIComponent component, object obj) =>
396  {
397  selectedLevelObject = obj as LevelObjectPrefab;
398  CreateLevelObjectEditor(selectedLevelObject);
399  return true;
400  };
401 
402  spriteEditDoneButton = new GUIButton(new RectTransform(new Point(200, 30), anchor: Anchor.BottomRight) { AbsoluteOffset = new Point(20, 20) },
403  TextManager.Get("leveleditor.spriteeditdone"))
404  {
405  OnClicked = (btn, userdata) =>
406  {
407  editingSprite = null;
408  return true;
409  }
410  };
411 
412  topPanel = new GUIFrame(new RectTransform(new Point(400, 100), GUI.Canvas)
413  { RelativeOffset = new Vector2(leftPanel.RectTransform.RelativeSize.X * 2, 0.0f) }, style: "GUIFrameTop");
414  }
415 
417  {
418  Cam = new Camera()
419  {
420  MinZoom = 0.01f,
421  MaxZoom = 1.0f
422  };
423 
424  RefreshUI(forceCreate: true);
425  }
426 
427  public void TestLevelGenerationForErrors(int amountOfLevelsToGenerate)
428  {
429  CoroutineManager.StartCoroutine(GenerateLevels());
430 
431  IEnumerable<CoroutineStatus> GenerateLevels()
432  {
433  using var errorCatcher = DebugConsole.ErrorCatcher.Create();
434  for (int i = 0; i < amountOfLevelsToGenerate; i++)
435  {
436  Submarine.Unload();
438 
439  currentLevelData = LevelData.CreateRandom(ToolBox.RandomSeed(10), generationParams: selectedParams);
440  currentLevelData.ForceOutpostGenerationParams = outpostParamsList.SelectedData as OutpostGenerationParams;
441  currentLevelData.ForceBeaconStation = selectedBeaconStationDropdown.SelectedData as SubmarineInfo;
442  currentLevelData.ForceWreck = selectedWreckDropdown.SelectedData as SubmarineInfo;
443 
444  currentLevelData.AllowInvalidOutpost = allowInvalidOutpost.Selected;
445  var dummyLocations = GameSession.CreateDummyLocations(currentLevelData);
446  DebugConsole.NewMessage("*****************************************************************************");
447  DebugConsole.NewMessage($"Generating level {(i + 1)}/{amountOfLevelsToGenerate}: ");
448  DebugConsole.NewMessage(" Seed: " + currentLevelData.Seed);
449  DebugConsole.NewMessage(" Outpost parameters: " + (currentLevelData.ForceOutpostGenerationParams?.Name ?? "None"));
450  DebugConsole.NewMessage(" Level generation params: " + selectedParams.Identifier);
451  DebugConsole.NewMessage(" Mirrored: " + mirrorLevel.Selected);
452  DebugConsole.NewMessage(" Adjacent locations: " + (dummyLocations[0]?.Type.Identifier ?? "none".ToIdentifier()) + ", " + (dummyLocations[1]?.Type.Identifier ?? "none".ToIdentifier()));
453 
454  yield return CoroutineStatus.Running;
455 
456  Level.Generate(currentLevelData, mirror: mirrorLevel.Selected, startLocation: dummyLocations[0], endLocation: dummyLocations[1]);
458  GameMain.LightManager.AddLight(pointerLightSource);
459  seedBox.Deselect();
460 
461  if (errorCatcher.Errors.Any())
462  {
463  DebugConsole.ThrowError("Error while generating level:");
464  errorCatcher.Errors.ToList().ForEach(e => DebugConsole.ThrowError(e.Text));
465  yield return CoroutineStatus.Success;
466  }
467  yield return CoroutineStatus.Running;
468  }
469  }
470  }
471 
472  public override void Select()
473  {
474  base.Select();
475 
476  RefreshUI(forceCreate: false);
477  }
478 
479  protected override void DeselectEditorSpecific()
480  {
481  pointerLightSource?.Remove();
482  pointerLightSource = null;
483  }
484 
485  private void UpdateParamsList()
486  {
487  editorContainer.ClearChildren();
488  paramsList.Content.ClearChildren();
489 
490  foreach (LevelGenerationParams genParams in LevelGenerationParams.LevelParams.OrderBy(p => p.Name))
491  {
492  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paramsList.Content.RectTransform) { MinSize = new Point(0, 20) },
493  genParams.Identifier.Value)
494  {
495  Padding = Vector4.Zero,
496  UserData = genParams
497  };
498  }
499  }
500 
501  private void UpdateCaveParamsList()
502  {
503  editorContainer.ClearChildren();
504  caveParamsList.Content.ClearChildren();
505 
506  foreach (CaveGenerationParams genParams in CaveGenerationParams.CaveParams.OrderBy(p => p.Name))
507  {
508  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), caveParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
509  genParams.Name)
510  {
511  Padding = Vector4.Zero,
512  UserData = genParams
513  };
514  }
515  }
516 
517  private void UpdateRuinParamsList()
518  {
519  editorContainer.ClearChildren();
520  ruinParamsList.Content.ClearChildren();
521 
522  foreach (RuinGenerationParams genParams in RuinGenerationParams.RuinParams.OrderBy(p => p.Identifier))
523  {
524  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), ruinParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
525  genParams.Identifier.Value)
526  {
527  Padding = Vector4.Zero,
528  UserData = genParams
529  };
530  }
531  }
532 
533  private void UpdateOutpostParamsList()
534  {
535  editorContainer.ClearChildren();
536  outpostParamsList.Content.ClearChildren();
537 
538  foreach (OutpostGenerationParams genParams in OutpostGenerationParams.OutpostParams.OrderBy(p => p.Name))
539  {
540  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), outpostParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
541  genParams.Name)
542  {
543  Padding = Vector4.Zero,
544  UserData = genParams
545  };
546  }
547  }
548 
549  private void UpdateLevelObjectsList()
550  {
551  editorContainer.ClearChildren();
552  levelObjectList.Content.ClearChildren();
553 
554  int objectsPerRow = (int)Math.Ceiling(levelObjectList.Content.Rect.Width / Math.Max(100 * GUI.Scale, 100));
555  float relWidth = 1.0f / objectsPerRow;
556 
557  foreach (LevelObjectPrefab levelObjPrefab in LevelObjectPrefab.Prefabs)
558  {
559  var frame = new GUIFrame(new RectTransform(
560  new Vector2(relWidth, relWidth * ((float)levelObjectList.Content.Rect.Width / levelObjectList.Content.Rect.Height)),
561  levelObjectList.Content.RectTransform) { MinSize = new Point(0, 60) }, style: "ListBoxElementSquare")
562  {
563  UserData = levelObjPrefab
564  };
565  var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), style: null);
566 
567  GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform, Anchor.BottomCenter),
568  text: ToolBox.LimitString(levelObjPrefab.Name, GUIStyle.SmallFont, paddedFrame.Rect.Width), textAlignment: Alignment.Center, font: GUIStyle.SmallFont)
569  {
570  CanBeFocused = false,
571  ToolTip = levelObjPrefab.Name
572  };
573 
574  Sprite sprite = levelObjPrefab.Sprites.FirstOrDefault() ?? levelObjPrefab.DeformableSprite?.Sprite;
575  new GUIImage(new RectTransform(new Point(paddedFrame.Rect.Height, paddedFrame.Rect.Height - textBlock.Rect.Height),
576  paddedFrame.RectTransform, Anchor.TopCenter), sprite, scaleToFit: true)
577  {
578  LoadAsynchronously = true,
579  CanBeFocused = false
580  };
581  }
582  }
583 
584  private void CreateCaveParamsEditor(CaveGenerationParams caveGenerationParams)
585  {
586  editorContainer.ClearChildren();
587  var editor = new SerializableEntityEditor(editorContainer.Content.RectTransform, caveGenerationParams, false, true, elementHeight: 20);
588 
589  if (selectedParams != null)
590  {
591  var commonnessContainer = new GUILayoutGroup(new RectTransform(new Point(editor.Rect.Width, 70)) { IsFixedSize = true },
592  isHorizontal: false, childAnchor: Anchor.TopCenter)
593  {
594  AbsoluteSpacing = 5,
595  Stretch = true
596  };
597  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), commonnessContainer.RectTransform),
598  TextManager.GetWithVariable("leveleditor.levelobjcommonness", "[leveltype]", selectedParams.Identifier.Value), textAlignment: Alignment.Center);
599  new GUINumberInput(new RectTransform(new Vector2(0.5f, 0.4f), commonnessContainer.RectTransform), NumberType.Float)
600  {
601  MinValueFloat = 0,
602  MaxValueFloat = 100,
603  FloatValue = caveGenerationParams.GetCommonness(currentLevelData, abyss: false),
604  OnValueChanged = (numberInput) =>
605  {
606  caveGenerationParams.OverrideCommonness[selectedParams.Identifier] = numberInput.FloatValue;
607  }
608  };
609  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), commonnessContainer.RectTransform), style: null);
610  editor.AddCustomContent(commonnessContainer, 1);
611  }
612  }
613 
614  private void CreateOutpostGenerationParamsEditor(OutpostGenerationParams outpostGenerationParams)
615  {
616  editorContainer.ClearChildren();
617  if (outpostGenerationParams == null) { return; }
618  var outpostParamsEditor = new SerializableEntityEditor(editorContainer.Content.RectTransform, outpostGenerationParams, false, true, elementHeight: 20);
619 
620  // location type -------------------------
621 
622  var locationTypeGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, 20)), isHorizontal: true, childAnchor: Anchor.CenterLeft)
623  {
624  Stretch = true
625  };
626 
627  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), TextManager.Get("outpostmoduleallowedlocationtypes"), textAlignment: Alignment.CenterLeft);
628  HashSet<Identifier> availableLocationTypes = new HashSet<Identifier> { "any".ToIdentifier() };
629  foreach (LocationType locationType in LocationType.Prefabs) { availableLocationTypes.Add(locationType.Identifier); }
630 
631  var locationTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform),
632  text: LocalizedString.Join(", ", outpostGenerationParams.AllowedLocationTypes.Select(lt => TextManager.Capitalize(lt.Value)) ?? ((LocalizedString)"any").ToEnumerable()), selectMultiple: true);
633  foreach (Identifier locationType in availableLocationTypes)
634  {
635  locationTypeDropDown.AddItem(TextManager.Capitalize(locationType.Value), locationType);
636  if (outpostGenerationParams.AllowedLocationTypes.Contains(locationType))
637  {
638  locationTypeDropDown.SelectItem(locationType);
639  }
640  }
641  if (!outpostGenerationParams.AllowedLocationTypes.Any())
642  {
643  locationTypeDropDown.SelectItem("any");
644  }
645 
646  locationTypeDropDown.AfterSelected += (_, __) =>
647  {
648  outpostGenerationParams.SetAllowedLocationTypes(locationTypeDropDown.SelectedDataMultiple.Cast<Identifier>());
649  locationTypeDropDown.Text = ToolBox.LimitString(locationTypeDropDown.Text, locationTypeDropDown.Font, locationTypeDropDown.Rect.Width);
650  return true;
651  };
652  locationTypeGroup.RectTransform.MinSize = new Point(locationTypeGroup.Rect.Width, locationTypeGroup.RectTransform.Children.Max(c => c.MinSize.Y));
653 
654  outpostParamsEditor.AddCustomContent(locationTypeGroup, 100);
655 
656  // module count -------------------------
657 
658  foreach (var moduleCount in outpostGenerationParams.ModuleCounts)
659  {
660  var editor = new SerializableEntityEditor(editorContainer.Content.RectTransform, moduleCount, inGame: false, showName: true, elementHeight: 20, titleFont: GUIStyle.Font);
661  foreach (var componentList in editor.Fields.Values)
662  {
663  foreach (var component in componentList)
664  {
665  if (component is GUINumberInput numberInput)
666  {
667  numberInput.OnValueChanged += (numInput) =>
668  {
669  if (moduleCount.Count == 0)
670  {
671  //refresh to remove this module count from the editor
672  outpostParamsList.Select(outpostParamsList.SelectedData);
673  }
674  };
675  }
676  }
677  }
678  editor.RectTransform.MaxSize = new Point(int.MaxValue, editor.Rect.Height);
679  outpostParamsEditor.AddCustomContent(editor, 100);
680  editor.Recalculate();
681  }
682 
683  // add module count -------------------------
684 
685  var addModuleCountGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(40 * GUI.Scale))), isHorizontal: true, childAnchor: Anchor.Center);
686 
687  HashSet<Identifier> availableFlags = new HashSet<Identifier>();
688  foreach (Identifier flag in OutpostGenerationParams.OutpostParams.SelectMany(p => p.ModuleCounts.Select(m => m.Identifier))) { availableFlags.Add(flag); }
689  foreach (var sub in SubmarineInfo.SavedSubmarines)
690  {
691  if (sub.OutpostModuleInfo == null) { continue; }
692  foreach (Identifier flag in sub.OutpostModuleInfo.ModuleFlags) { availableFlags.Add(flag); }
693  }
694 
695  var moduleTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.8f, 0.8f), addModuleCountGroup.RectTransform),
696  text: TextManager.Get("leveleditor.addmoduletype"));
697  foreach (Identifier flag in availableFlags)
698  {
699  if (outpostGenerationParams.ModuleCounts.Any(mc => mc.Identifier == flag)) { continue; }
700  moduleTypeDropDown.AddItem(TextManager.Capitalize(flag.Value), flag);
701  }
702  moduleTypeDropDown.OnSelected += (_, userdata) =>
703  {
704  outpostGenerationParams.SetModuleCount((Identifier)userdata, 1);
705  outpostParamsList.Select(outpostParamsList.SelectedData);
706  return true;
707  };
708  addModuleCountGroup.RectTransform.MinSize = new Point(addModuleCountGroup.Rect.Width, addModuleCountGroup.RectTransform.Children.Max(c => c.MinSize.Y));
709  outpostParamsEditor.AddCustomContent(addModuleCountGroup, 100);
710  outpostParamsEditor.Recalculate();
711  }
712 
713  private void CreateLevelObjectEditor(LevelObjectPrefab levelObjectPrefab)
714  {
715  editorContainer.ClearChildren();
716 
717  var editor = new SerializableEntityEditor(editorContainer.Content.RectTransform, levelObjectPrefab, false, true, elementHeight: 20, titleFont: GUIStyle.LargeFont);
718 
719  if (selectedParams != null)
720  {
721  List<Identifier> availableIdentifiers = new List<Identifier>();
722  if (selectedParams != null) { availableIdentifiers.Add(selectedParams.Identifier); }
723  foreach (var caveParam in CaveGenerationParams.CaveParams)
724  {
725  if (selectedParams != null && caveParam.GetCommonness(currentLevelData, abyss: false) <= 0.0f) { continue; }
726  availableIdentifiers.Add(caveParam.Identifier);
727  }
728  availableIdentifiers.Reverse();
729 
730  foreach (Identifier paramsId in availableIdentifiers)
731  {
732  var commonnessContainer = new GUILayoutGroup(new RectTransform(new Point(editor.Rect.Width, 70)) { IsFixedSize = true },
733  isHorizontal: false, childAnchor: Anchor.TopCenter)
734  {
735  AbsoluteSpacing = 5,
736  Stretch = true
737  };
738  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), commonnessContainer.RectTransform),
739  TextManager.GetWithVariable("leveleditor.levelobjcommonness", "[leveltype]", paramsId.Value), textAlignment: Alignment.Center);
740  new GUINumberInput(new RectTransform(new Vector2(0.5f, 0.4f), commonnessContainer.RectTransform), NumberType.Float)
741  {
742  MinValueFloat = 0,
743  MaxValueFloat = 100,
744  FloatValue = selectedParams.Identifier == paramsId ? levelObjectPrefab.GetCommonness(currentLevelData) : levelObjectPrefab.GetCommonness(CaveGenerationParams.CaveParams.Find(p => p.Identifier == paramsId)),
745  OnValueChanged = (numberInput) =>
746  {
747  levelObjectPrefab.OverrideCommonness[paramsId] = numberInput.FloatValue;
748  }
749  };
750  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), commonnessContainer.RectTransform), style: null);
751  editor.AddCustomContent(commonnessContainer, 1);
752  }
753  }
754 
755  Sprite sprite = levelObjectPrefab.Sprites.FirstOrDefault() ?? levelObjectPrefab.DeformableSprite?.Sprite;
756  if (sprite != null)
757  {
758  editor.AddCustomContent(new GUIButton(new RectTransform(new Point(editor.Rect.Width / 2, (int)(25 * GUI.Scale))) { IsFixedSize = true },
759  TextManager.Get("leveleditor.editsprite"))
760  {
761  OnClicked = (btn, userdata) =>
762  {
763  editingSprite = sprite;
764  GameMain.SpriteEditorScreen.SelectSprite(editingSprite);
765  return true;
766  }
767  }, 1);
768  }
769 
770  if (levelObjectPrefab.DeformableSprite != null)
771  {
772  var deformEditor = levelObjectPrefab.DeformableSprite.CreateEditor(editor, levelObjectPrefab.SpriteDeformations, levelObjectPrefab.Name);
773  deformEditor.GetChild<GUIDropDown>().OnSelected += (selected, userdata) =>
774  {
775  CreateLevelObjectEditor(selectedLevelObject);
776  return true;
777  };
778  editor.AddCustomContent(deformEditor, editor.ContentCount);
779  }
780  //child object editing
781  new GUITextBlock(new RectTransform(new Point(editor.Rect.Width, 40), editorContainer.Content.RectTransform),
782  TextManager.Get("leveleditor.childobjects"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomCenter);
783  foreach (LevelObjectPrefab.ChildObject childObj in levelObjectPrefab.ChildObjects)
784  {
785  var childObjFrame = new GUIFrame(new RectTransform(new Point(editor.Rect.Width, 30)));
786  var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), childObjFrame.RectTransform, Anchor.Center), isHorizontal: true)
787  {
788  Stretch = true,
789  RelativeSpacing = 0.05f
790  };
791  var selectedChildObj = childObj;
792  var dropdown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform), elementCount: 10, selectMultiple: true);
793  foreach (LevelObjectPrefab objPrefab in LevelObjectPrefab.Prefabs)
794  {
795  dropdown.AddItem(objPrefab.Name, objPrefab);
796  if (childObj.AllowedNames.Contains(objPrefab.Name)) { dropdown.SelectItem(objPrefab); }
797  }
798  dropdown.AfterSelected = (selected, obj) =>
799  {
800  childObj.AllowedNames = dropdown.SelectedDataMultiple.Select(d => ((LevelObjectPrefab)d).Name).ToList();
801  return true;
802  };
803  new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), paddedFrame.RectTransform), NumberType.Int)
804  {
805  MinValueInt = 0,
806  MaxValueInt = 10,
807  OnValueChanged = (numberInput) =>
808  {
809  selectedChildObj.MinCount = numberInput.IntValue;
810  selectedChildObj.MaxCount = Math.Max(selectedChildObj.MaxCount, selectedChildObj.MinCount);
811  }
812  }.IntValue = childObj.MinCount;
813  new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), paddedFrame.RectTransform), NumberType.Int)
814  {
815  MinValueInt = 0,
816  MaxValueInt = 10,
817  OnValueChanged = (numberInput) =>
818  {
819  selectedChildObj.MaxCount = numberInput.IntValue;
820  selectedChildObj.MinCount = Math.Min(selectedChildObj.MaxCount, selectedChildObj.MinCount);
821  }
822  }.IntValue = childObj.MaxCount;
823 
824  new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), paddedFrame.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUICancelButton")
825  {
826  OnClicked = (btn, userdata) =>
827  {
828  selectedLevelObject.ChildObjects.Remove(selectedChildObj);
829  CreateLevelObjectEditor(selectedLevelObject);
830  return true;
831  }
832  };
833 
834  childObjFrame.RectTransform.Parent = editorContainer.Content.RectTransform;
835  }
836 
837  var buttonContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), editorContainer.Content.RectTransform), style: null);
838  new GUIButton(new RectTransform(new Point(editor.Rect.Width / 2, 20), buttonContainer.RectTransform, Anchor.Center),
839  TextManager.Get("leveleditor.addchildobject"))
840  {
841  OnClicked = (btn, userdata) =>
842  {
843  selectedLevelObject.ChildObjects.Add(new LevelObjectPrefab.ChildObject());
844  CreateLevelObjectEditor(selectedLevelObject);
845  return true;
846  }
847  };
848  buttonContainer.RectTransform.MinSize = buttonContainer.RectTransform.Children.First().MinSize;
849 
850  //light editing
851  new GUITextBlock(new RectTransform(new Point(editor.Rect.Width, 40), editorContainer.Content.RectTransform),
852  TextManager.Get("leveleditor.lightsources"), textAlignment: Alignment.BottomCenter, font: GUIStyle.SubHeadingFont);
853  foreach (LightSourceParams lightSourceParams in selectedLevelObject.LightSourceParams)
854  {
855  new SerializableEntityEditor(editorContainer.Content.RectTransform, lightSourceParams, inGame: false, showName: true);
856  }
857  buttonContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), editorContainer.Content.RectTransform), style: null);
858  new GUIButton(new RectTransform(new Point(editor.Rect.Width / 2, 20), buttonContainer.RectTransform, Anchor.Center),
859  TextManager.Get("leveleditor.addlightsource"))
860  {
861  OnClicked = (btn, userdata) =>
862  {
863  selectedLevelObject.LightSourceTriggerIndex.Add(-1);
864  selectedLevelObject.LightSourceParams.Add(new LightSourceParams(100.0f, Color.White));
865  CreateLevelObjectEditor(selectedLevelObject);
866  return true;
867  }
868  };
869  buttonContainer.RectTransform.MinSize = buttonContainer.RectTransform.Children.First().MinSize;
870  }
871 
872  private void SortLevelObjectsList(LevelData levelData)
873  {
874  //fade out levelobjects that don't spawn in this type of level
875  foreach (GUIComponent levelObjFrame in levelObjectList.Content.Children)
876  {
877  var levelObj = levelObjFrame.UserData as LevelObjectPrefab;
878  float commonness = levelObj.GetCommonness(levelData);
879  levelObjFrame.Color = commonness > 0.0f ? GUIStyle.Green * 0.4f : Color.Transparent;
880  levelObjFrame.SelectedColor = commonness > 0.0f ? GUIStyle.Green * 0.6f : Color.White * 0.5f;
881  levelObjFrame.HoverColor = commonness > 0.0f ? GUIStyle.Green * 0.7f : Color.White * 0.6f;
882 
883  levelObjFrame.GetAnyChild<GUIImage>().Color = commonness > 0.0f ? Color.White : Color.DarkGray;
884  if (commonness <= 0.0f)
885  {
886  levelObjFrame.GetAnyChild<GUITextBlock>().TextColor = Color.DarkGray;
887  }
888  }
889 
890  //sort the levelobjects according to commonness in this level
891  levelObjectList.Content.RectTransform.SortChildren((c1, c2) =>
892  {
893  var levelObj1 = c1.GUIComponent.UserData as LevelObjectPrefab;
894  var levelObj2 = c2.GUIComponent.UserData as LevelObjectPrefab;
895  return Math.Sign(levelObj2.GetCommonness(levelData) - levelObj1.GetCommonness(levelData));
896  });
897  }
898 
899  public override void AddToGUIUpdateList()
900  {
901  base.AddToGUIUpdateList();
902  rightPanel.Visible = leftPanel.Visible = bottomPanel.Visible = editingSprite == null;
903  if (editingSprite != null)
904  {
906  spriteEditDoneButton.AddToGUIUpdateList();
907  }
908  else if (lightingEnabled.Selected && cursorLightEnabled.Selected)
909  {
910  topPanel.AddToGUIUpdateList();
911  }
912  }
913 
914  public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
915  {
916  if (lightingEnabled.Selected)
917  {
918  GameMain.LightManager.RenderLightMap(graphics, spriteBatch, Cam);
919  }
920  graphics.Clear(Color.Black);
921 
922  if (Level.Loaded != null)
923  {
924  Level.Loaded.DrawBack(graphics, spriteBatch, Cam);
925  Level.Loaded.DrawFront(spriteBatch, Cam);
926  spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.DepthRead, transformMatrix: Cam.Transform);
927  Level.Loaded.DrawDebugOverlay(spriteBatch, Cam);
928  Submarine.Draw(spriteBatch, false);
929  Submarine.DrawFront(spriteBatch);
930  Submarine.DrawDamageable(spriteBatch, null);
931  GUI.DrawRectangle(spriteBatch, new Rectangle(new Point(0, -Level.Loaded.Size.Y), Level.Loaded.Size), Color.Gray, thickness: (int)(1.0f / Cam.Zoom));
932 
933  for (int i = 0; i < Level.Loaded.Tunnels.Count; i++)
934  {
935  var tunnel = Level.Loaded.Tunnels[i];
936  Color tunnelColor = tunnelDebugColors[i % tunnelDebugColors.Length] * 0.2f;
937  for (int j = 1; j < tunnel.Nodes.Count; j++)
938  {
939  Vector2 start = new Vector2(tunnel.Nodes[j - 1].X, -tunnel.Nodes[j - 1].Y);
940  Vector2 end = new Vector2(tunnel.Nodes[j].X, -tunnel.Nodes[j].Y);
941  GUI.DrawLine(spriteBatch, start, end, tunnelColor, width: (int)(2.0f / Cam.Zoom));
942  }
943  }
944 
945  foreach (Level.InterestingPosition interestingPos in Level.Loaded.PositionsOfInterest)
946  {
947  if (interestingPos.Position.X < Cam.WorldView.X || interestingPos.Position.X > Cam.WorldView.Right ||
948  interestingPos.Position.Y > Cam.WorldView.Y || interestingPos.Position.Y < Cam.WorldView.Y - Cam.WorldView.Height)
949  {
950  continue;
951  }
952 
953  Vector2 pos = new Vector2(interestingPos.Position.X, -interestingPos.Position.Y);
954  spriteBatch.DrawCircle(pos, 500, 6, Color.White * 0.5f, thickness: (int)(2 / Cam.Zoom));
955  GUI.DrawString(spriteBatch, pos, interestingPos.PositionType.ToString(), Color.White, font: GUIStyle.LargeFont);
956  }
957 
958  // TODO: Improve this temporary level editor debug solution
959  foreach (var pathPoint in Level.Loaded.PathPoints)
960  {
961  Vector2 pathPointPos = new Vector2(pathPoint.Position.X, -pathPoint.Position.Y);
962  foreach (var location in pathPoint.ClusterLocations)
963  {
964  if (location.Resources == null) { continue; }
965  foreach (var resource in location.Resources)
966  {
967  Vector2 resourcePos = new Vector2(resource.Position.X, -resource.Position.Y);
968  spriteBatch.DrawCircle(resourcePos, 100, 6, Color.DarkGreen * 0.5f, thickness: (int)(2 / Cam.Zoom));
969  GUI.DrawString(spriteBatch, resourcePos, resource.Name, Color.DarkGreen, font: GUIStyle.LargeFont);
970  var dist = Vector2.Distance(resourcePos, pathPointPos);
971  var lineStartPos = Vector2.Lerp(resourcePos, pathPointPos, 110 / dist);
972  var lineEndPos = Vector2.Lerp(pathPointPos, resourcePos, 310 / dist);
973  GUI.DrawLine(spriteBatch, lineStartPos, lineEndPos, Color.DarkGreen * 0.5f, width: (int)(2 / Cam.Zoom));
974  }
975  }
976  var color = pathPoint.ShouldContainResources ? Color.DarkGreen : Color.DarkRed;
977  spriteBatch.DrawCircle(pathPointPos, 300, 6, color * 0.5f, thickness: (int)(2 / Cam.Zoom));
978  GUI.DrawString(spriteBatch, pathPointPos, "Path Point\n" + pathPoint.Id, color, font: GUIStyle.LargeFont);
979  }
980 
981  foreach (var location in Level.Loaded.AbyssResources)
982  {
983  if (location.Resources == null) { continue; }
984  foreach (var resource in location.Resources)
985  {
986  Vector2 resourcePos = new Vector2(resource.Position.X, -resource.Position.Y);
987  spriteBatch.DrawCircle(resourcePos, 100, 6, Color.DarkGreen * 0.5f, thickness: (int)(2 / Cam.Zoom));
988  GUI.DrawString(spriteBatch, resourcePos, resource.Name, Color.DarkGreen, font: GUIStyle.LargeFont);
989  }
990  }
991 
992  /*for (int i = 0; i < Level.Loaded.distanceField.Count; i++)
993  {
994  GUI.DrawRectangle(spriteBatch,
995  new Vector2(Level.Loaded.distanceField[i].First.X, -Level.Loaded.distanceField[i].First.Y),
996  Vector2.One * 5 / cam.Zoom,
997  ToolBox.GradientLerp((float)(Level.Loaded.distanceField[i].Second / 20000.0), Color.Red, Color.Orange, Color.Yellow, Color.LightGreen),
998  isFilled: true);
999  }*/
1000  /*for (int i = 0; i < Level.Loaded.siteCoordsX.Count; i++)
1001  {
1002  GUI.DrawRectangle(spriteBatch,
1003  new Vector2((float)Level.Loaded.siteCoordsX[i], -(float)Level.Loaded.siteCoordsY[i]),
1004  Vector2.One * 5 / cam.Zoom,
1005  Color.Red,
1006  isFilled: true);
1007  }*/
1008 
1009  spriteBatch.End();
1010 
1011  if (lightingEnabled.Selected)
1012  {
1013  spriteBatch.Begin(SpriteSortMode.Immediate, Lights.CustomBlendStates.Multiplicative, null, DepthStencilState.None, null, null, null);
1014  spriteBatch.Draw(GameMain.LightManager.LightMap, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
1015  spriteBatch.End();
1016  }
1017  }
1018 
1019  if (editingSprite != null)
1020  {
1021  GameMain.SpriteEditorScreen.Draw(deltaTime, graphics, spriteBatch);
1022  }
1023 
1024  spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
1025  if (Level.Loaded != null)
1026  {
1027  float hullUpgradePrcIncrease = UpgradePrefab.CrushDepthUpgradePrc / 100f;
1028  for (int upgradeLevel = 0; upgradeLevel <= UpgradePrefab.IncreaseWallHealthMaxLevel; upgradeLevel++)
1029  {
1030  float upgradeLevelCrushDepth = Level.DefaultRealWorldCrushDepth + (Level.DefaultRealWorldCrushDepth * upgradeLevel * hullUpgradePrcIncrease);
1031  float subCrushDepth = (upgradeLevelCrushDepth / Physics.DisplayToRealWorldRatio) - Level.Loaded.LevelData.InitialDepth;
1032  string labelText = $"Crush depth (upgrade level {upgradeLevel})";
1033  if (upgradeLevel == 0)
1034  {
1035  labelText = $"Crush depth (no upgrade)";
1036  }
1037  DrawCrushDepth(subCrushDepth, labelText, Color.Red);
1038  }
1039 
1040  float abyssStartScreen = Cam.WorldToScreen(new Vector2(0.0f, Level.Loaded.AbyssArea.Bottom)).Y;
1041  if (abyssStartScreen > 0.0f && abyssStartScreen < GameMain.GraphicsHeight)
1042  {
1043  GUI.DrawLine(spriteBatch, new Vector2(0, abyssStartScreen), new Vector2(GameMain.GraphicsWidth, abyssStartScreen), GUIStyle.Blue * 0.25f, width: 5);
1044  GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, abyssStartScreen), "Abyss start", GUIStyle.Blue, backgroundColor: Color.Black);
1045  }
1046  float abyssEndScreen = Cam.WorldToScreen(new Vector2(0.0f, Level.Loaded.AbyssArea.Y)).Y;
1047  if (abyssEndScreen > 0.0f && abyssEndScreen < GameMain.GraphicsHeight)
1048  {
1049  GUI.DrawLine(spriteBatch, new Vector2(0, abyssEndScreen), new Vector2(GameMain.GraphicsWidth, abyssEndScreen), GUIStyle.Blue * 0.25f, width: 5);
1050  GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, abyssEndScreen), "Abyss end", GUIStyle.Blue, backgroundColor: Color.Black);
1051  }
1052  }
1053  GUI.Draw(Cam, spriteBatch);
1054  spriteBatch.End();
1055 
1056  void DrawCrushDepth(float crushDepth, string labelText, Color color)
1057  {
1058  float crushDepthScreen = Cam.WorldToScreen(new Vector2(0.0f, -crushDepth)).Y;
1059  if (crushDepthScreen > 0.0f && crushDepthScreen < GameMain.GraphicsHeight)
1060  {
1061  GUI.DrawLine(spriteBatch, new Vector2(0, crushDepthScreen), new Vector2(GameMain.GraphicsWidth, crushDepthScreen), color * 0.25f, width: 5);
1062  GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, crushDepthScreen), labelText, GUIStyle.Red, backgroundColor: Color.Black);
1063  }
1064  }
1065  }
1066 
1067  public override void Update(double deltaTime)
1068  {
1069  if (lightingEnabled.Selected)
1070  {
1071  foreach (Item item in Item.ItemList)
1072  {
1073  if (item == null || item.IsHidden) { continue; }
1074  foreach (var light in item.GetComponents<Items.Components.LightComponent>())
1075  {
1076  light.Update((float)deltaTime, Cam);
1077  }
1078  }
1079  }
1080  GameMain.LightManager?.Update((float)deltaTime);
1081 
1082  pointerLightSource.Position = Cam.ScreenToWorld(PlayerInput.MousePosition);
1083  pointerLightSource.Enabled = cursorLightEnabled.Selected;
1084  pointerLightSource.IsBackground = true;
1085  Cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null);
1086  Cam.UpdateTransform();
1087  Level.Loaded?.Update((float)deltaTime, Cam);
1088 
1089  if (editingSprite != null)
1090  {
1091  GameMain.SpriteEditorScreen.Update(deltaTime);
1092  }
1093 
1094  // in case forced difficulty was changed by console command or such
1095  if (Level.ForcedDifficulty != null && MathHelper.Distance((float)Level.ForcedDifficulty, forceDifficultyInput.FloatValue) > 0.001f)
1096  {
1097  forceDifficultyInput.FloatValue = (float)Level.ForcedDifficulty;
1098  }
1099 
1100 #if DEBUG
1101  if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.R))
1102  {
1103  RefreshUI(forceCreate: true);
1104  }
1105 #endif
1106  }
1107 
1108  private void SerializeAll()
1109  {
1110  IEnumerable<ContentPackage> packages = ContentPackageManager.LocalPackages;
1111 #if DEBUG
1112  packages = packages.Union(ContentPackageManager.VanillaCorePackage.ToEnumerable());
1113 #endif
1114 
1115  System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
1116  {
1117  Indent = true,
1118  NewLineOnAttributes = true
1119  };
1120  foreach (var configFile in packages.SelectMany(p => p.GetFiles<LevelGenerationParametersFile>()))
1121  {
1122  XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
1123  if (doc == null) { continue; }
1124 
1125  foreach (LevelGenerationParams genParams in LevelGenerationParams.LevelParams)
1126  {
1127  foreach (XElement element in doc.Root.Elements())
1128  {
1129  if (element.IsOverride())
1130  {
1131  foreach (var subElement in element.Elements())
1132  {
1133  string id = element.GetAttributeString("identifier", null) ?? element.Name.ToString();
1134  if (!id.Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) { continue; }
1135  SerializableProperty.SerializeProperties(genParams, element, true);
1136  }
1137  }
1138  else
1139  {
1140  string id = element.GetAttributeString("identifier", null) ?? element.Name.ToString();
1141  if (!id.Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) { continue; }
1142  SerializableProperty.SerializeProperties(genParams, element, true);
1143  }
1144  break;
1145  }
1146  }
1147  using (var writer = XmlWriter.Create(configFile.Path.Value, settings))
1148  {
1149  doc.WriteTo(writer);
1150  writer.Flush();
1151  }
1152  }
1153 
1154  foreach (var configFile in packages.SelectMany(p => p.GetFiles<CaveGenerationParametersFile>()))
1155  {
1156  XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
1157  if (doc == null) { continue; }
1158 
1159  foreach (CaveGenerationParams genParams in CaveGenerationParams.CaveParams)
1160  {
1161  foreach (XElement element in doc.Root.Elements())
1162  {
1163  if (element.IsOverride())
1164  {
1165  foreach (var subElement in element.Elements())
1166  {
1167  string id = subElement.GetAttributeString("identifier", null) ?? subElement.Name.ToString();
1168  if (!id.Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) { continue; }
1169  genParams.Save(subElement);
1170  }
1171  }
1172  else
1173  {
1174  string id = element.GetAttributeString("identifier", null) ?? element.Name.ToString();
1175  if (!id.Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) { continue; }
1176  genParams.Save(element);
1177  }
1178  break;
1179  }
1180  }
1181  using (var writer = XmlWriter.Create(configFile.Path.Value, settings))
1182  {
1183  doc.WriteTo(writer);
1184  writer.Flush();
1185  }
1186  }
1187 
1188  settings.NewLineOnAttributes = false;
1189  foreach (var configFile in packages.SelectMany(p => p.GetFiles<LevelObjectPrefabsFile>()))
1190  {
1191  XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
1192  if (doc == null) { continue; }
1193 
1194  foreach (LevelObjectPrefab levelObjPrefab in LevelObjectPrefab.Prefabs)
1195  {
1196  foreach (XElement element in doc.Root.Elements())
1197  {
1198  Identifier identifier = element.GetAttributeIdentifier("identifier", "");
1199  if (identifier != levelObjPrefab.Identifier) { continue; }
1200  levelObjPrefab.Save(element);
1201  break;
1202  }
1203  }
1204  using (var writer = XmlWriter.Create(configFile.Path.Value, settings))
1205  {
1206  doc.WriteTo(writer);
1207  writer.Flush();
1208  }
1209  }
1210 
1212  }
1213 
1214  private void Serialize(LevelGenerationParams genParams)
1215  {
1216  foreach (var configFile in ContentPackageManager.AllPackages.SelectMany(p => p.GetFiles<LevelGenerationParametersFile>()))
1217  {
1218  XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
1219  if (doc == null) { continue; }
1220 
1221  bool elementFound = false;
1222  foreach (XElement element in doc.Root.Elements())
1223  {
1224  string id = element.GetAttributeString("identifier", null) ?? element.Name.ToString();
1225  if (!id.Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) { continue; }
1226  SerializableProperty.SerializeProperties(genParams, element, true);
1227  elementFound = true;
1228  }
1229 
1230  if (elementFound)
1231  {
1232  System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
1233  {
1234  Indent = true,
1235  NewLineOnAttributes = true
1236  };
1237 
1238  using (var writer = XmlWriter.Create(configFile.Path.Value, settings))
1239  {
1240  doc.WriteTo(writer);
1241  writer.Flush();
1242  }
1243  return;
1244  }
1245  }
1246  }
1247 
1248 
1249 #region LevelObject Wizard
1250  private class Wizard
1251  {
1252  private LevelObjectPrefab newPrefab;
1253 
1254  private static Wizard instance;
1255  public static Wizard Instance
1256  {
1257  get
1258  {
1259  if (instance == null)
1260  {
1261  instance = new Wizard();
1262  }
1263  return instance;
1264  }
1265  }
1266 
1267  public void AddToGUIUpdateList()
1268  {
1269  //activeView?.Box.AddToGUIUpdateList();
1270  }
1271 
1272  public GUIMessageBox Create()
1273  {
1274  var box = new GUIMessageBox(TextManager.Get("leveleditor.createlevelobj"), string.Empty,
1275  new LocalizedString[] { TextManager.Get("cancel"), TextManager.Get("done") }, new Vector2(0.5f, 0.8f));
1276 
1277  box.Content.ChildAnchor = Anchor.TopCenter;
1278  box.Content.AbsoluteSpacing = 20;
1279  int elementSize = 30;
1280  var listBox = new GUIListBox(new RectTransform(new Vector2(1, 0.75f), box.Content.RectTransform));
1281 
1282  new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, elementSize), listBox.Content.RectTransform),
1283  TextManager.Get("leveleditor.levelobjname")) { CanBeFocused = false };
1284  var nameBox = new GUITextBox(new RectTransform(new Point(listBox.Content.Rect.Width, elementSize), listBox.Content.RectTransform));
1285 
1286  new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, elementSize), listBox.Content.RectTransform),
1287  TextManager.Get("leveleditor.levelobjtexturepath")) { CanBeFocused = false };
1288  var texturePathBox = new GUITextBox(new RectTransform(new Point(listBox.Content.Rect.Width, elementSize), listBox.Content.RectTransform));
1289  foreach (LevelObjectPrefab prefab in LevelObjectPrefab.Prefabs)
1290  {
1291  if (prefab.Sprites.FirstOrDefault() == null) { continue; }
1292  texturePathBox.Text = Path.GetDirectoryName(prefab.Sprites.FirstOrDefault().FilePath.Value);
1293  break;
1294  }
1295 
1296  //this is nasty :(
1297  newPrefab = new LevelObjectPrefab(null, null, new Identifier("No identifier"));
1298 
1299  new SerializableEntityEditor(listBox.Content.RectTransform, newPrefab, false, false);
1300 
1301  box.Buttons[0].OnClicked += (b, d) =>
1302  {
1303  box.Close();
1304  return true;
1305  };
1306  // Next
1307  box.Buttons[1].OnClicked += (b, d) =>
1308  {
1309  if (string.IsNullOrEmpty(nameBox.Text))
1310  {
1311  nameBox.Flash(GUIStyle.Red);
1312  GUI.AddMessage(TextManager.Get("leveleditor.levelobjnameempty"), GUIStyle.Red);
1313  return false;
1314  }
1315 
1316  if (LevelObjectPrefab.Prefabs.Any(obj => obj.Identifier == nameBox.Text))
1317  {
1318  nameBox.Flash(GUIStyle.Red);
1319  GUI.AddMessage(TextManager.Get("leveleditor.levelobjnametaken"), GUIStyle.Red);
1320  return false;
1321  }
1322 
1323  if (!File.Exists(texturePathBox.Text))
1324  {
1325  texturePathBox.Flash(GUIStyle.Red);
1326  GUI.AddMessage(TextManager.Get("leveleditor.levelobjtexturenotfound"), GUIStyle.Red);
1327  return false;
1328  }
1329 
1330  System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings { Indent = true };
1331 
1332  var newElement = new XElement(nameBox.Text);
1333  newPrefab.Save(newElement);
1334  newElement.Add(new XElement("Sprite",
1335  new XAttribute("texture", texturePathBox.Text),
1336  new XAttribute("sourcerect", "0,0,100,100"),
1337  new XAttribute("origin", "0.5,0.5")));
1338 
1339  // Create a new mod for the purpose of providing this new prefab
1340  #warning TODO: add a clear way to tack it into an existing content package?
1341  string modDir = Path.Combine(ContentPackage.LocalModsDir, nameBox.Text);
1342  Directory.CreateDirectory(modDir);
1343 
1344  string fileListPath = Path.Combine(modDir, ContentPackage.FileListFileName);
1345  string prefabFilePath = Path.Combine(modDir, $"{nameBox.Text}.xml");
1346 
1347  var newMod = new ModProject { Name = nameBox.Text };
1348  var newFile = ModProject.File.FromPath<LevelObjectPrefabsFile>(prefabFilePath);
1349  newMod.AddFile(newFile);
1350 
1351  XDocument fileListDoc = newMod.ToXDocument();
1352  Directory.CreateDirectory(Path.GetDirectoryName(fileListPath));
1353  using (XmlWriter writer = XmlWriter.Create(fileListPath, settings)) { fileListDoc.Save(writer); }
1354 
1355  XDocument prefabDoc = new XDocument();
1356  var prefabFileRoot = new XElement("LevelObjects");
1357  prefabFileRoot.Add(newElement);
1358  prefabDoc.Add(prefabFileRoot);
1359  using (XmlWriter writer = XmlWriter.Create(prefabFilePath, settings)) { prefabDoc.Save(writer); }
1360 
1361  ContentPackageManager.UpdateContentPackageList();
1362 
1363  var newRegularList = ContentPackageManager.EnabledPackages.Regular.ToList();
1364  newRegularList.Add(ContentPackageManager.RegularPackages.First(p => p.Name == nameBox.Text));
1365  ContentPackageManager.EnabledPackages.SetRegular(newRegularList);
1366 
1367  GameMain.LevelEditorScreen.UpdateLevelObjectsList();
1368 
1369  box.Close();
1370  return true;
1371  };
1372  return box;
1373  }
1374 
1375  }
1376 #endregion
1377  }
1378 }
Vector2 Position
Definition: Camera.cs:398
static CoroutineStatus Running
static CoroutineStatus Success
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
virtual void ClearChildren()
virtual Rectangle Rect
RectTransform RectTransform
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
GUIComponent AddItem(LocalizedString text, object userData=null, LocalizedString toolTip=null, Color? color=null, Color? textColor=null)
Definition: GUIDropDown.cs:270
OnSelectedHandler OnSelected
Triggers when some element is clicked on the listbox. Note that SelectedData is not set yet when this...
Definition: GUIListBox.cs:21
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:42
void Select(object userData, Force force=Force.No, AutoScroll autoScroll=AutoScroll.Enabled)
Definition: GUIListBox.cs:449
override void ClearChildren()
Definition: GUIListBox.cs:1264
override bool Selected
Definition: GUITickBox.cs:18
static int GraphicsWidth
Definition: GameMain.cs:162
static SpriteEditorScreen SpriteEditorScreen
Definition: GameMain.cs:72
static RasterizerState ScissorTestEnable
Definition: GameMain.cs:195
static int GraphicsHeight
Definition: GameMain.cs:168
static Lights.LightManager LightManager
Definition: GameMain.cs:78
static Location[] CreateDummyLocations(LevelData levelData, LocationType? forceLocationType=null)
static XmlWriter Create(string path, System.Xml.XmlWriterSettings settings)
Definition: SafeIO.cs:164
static readonly List< Item > ItemList
static LevelData CreateRandom(string seed="", float? difficulty=null, LevelGenerationParams generationParams=null, Identifier biomeId=default, bool requireOutpost=false, bool pvpOnly=false)
Definition: LevelData.cs:282
OutpostGenerationParams ForceOutpostGenerationParams
Definition: LevelData.cs:45
readonly int InitialDepth
The depth at which the level starts at, in in-game coordinates. E.g. if this was set to 100 000 (= 10...
Definition: LevelData.cs:71
override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
override void AddToGUIUpdateList()
By default, submits the screen's main GUIFrame and, if requested upon construction,...
override void DeselectEditorSpecific()
void TestLevelGenerationForErrors(int amountOfLevelsToGenerate)
override void Update(double deltaTime)
static readonly PrefabCollection< LevelGenerationParams > LevelParams
void DrawBack(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam)
void DrawDebugOverlay(SpriteBatch spriteBatch, Camera cam)
void Update(float deltaTime, Camera cam)
static Level Generate(LevelData levelData, bool mirror, Location startLocation, Location endLocation, SubmarineInfo startOutpost=null, SubmarineInfo endOutpost=null)
void DrawFront(SpriteBatch spriteBatch, Camera cam)
void Update(float deltaTime)
void RenderLightMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, RenderTarget2D backgroundObstructor=null)
void AddLight(LightSource light)
LightSourceParams LightSourceParams
Definition: LightSource.cs:291
bool IsBackground
Background lights are drawn behind submarines and they don't cast shadows.
Definition: LightSource.cs:442
bool IsHidden
Is the entity hidden due to HiddenInGame being enabled or the layer the entity is in being hidden?
static bool KeyDown(InputType inputType)
readonly Identifier Identifier
Definition: Prefab.cs:34
Vector2 RelativeSize
Relative to the parent rect.
void SortChildren(Comparison< RectTransform > comparison)
Point?? MinSize
Min size in pixels. Does not affect scaling.
static readonly PrefabCollection< RuinGenerationParams > RuinParams
override void Update(double deltaTime)
override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
static void DrawFront(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)
static Submarine MainSub
Note that this can be null in some situations, e.g. editors and missions that don't load a submarine.
void SetPosition(Vector2 position, List< Submarine > checkd=null, bool forceUndockFromStaticSubmarines=true)
NumberType
Definition: Enums.cs:741