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