Client LuaCsForBarotrauma
Wizard.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using Barotrauma.IO;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Xml.Linq;
8 
10 {
11  class Wizard
12  {
13  // Ragdoll data
14  private Identifier name;
15  private bool isHumanoid;
16  private CanEnterSubmarine canEnterSubmarine = CanEnterSubmarine.True;
17  private bool canWalk;
18  private string texturePath;
19  private string xmlPath;
20  private ContentPackage contentPackage;
21  private Dictionary<string, XElement> limbXElements = new Dictionary<string, XElement>();
22  private List<GUIComponent> limbGUIElements = new List<GUIComponent>();
23  private List<XElement> jointXElements = new List<XElement>();
24  private List<GUIComponent> jointGUIElements = new List<GUIComponent>();
25 
26  public bool IsCopy { get; private set; }
27  public CharacterParams SourceCharacter { get; private set; }
28  public RagdollParams SourceRagdoll { get; private set; }
29  public IEnumerable<AnimationParams> SourceAnimations { get; private set; }
30 
31  public void CopyExisting(CharacterParams character, RagdollParams ragdoll, IEnumerable<AnimationParams> animations)
32  {
33  IsCopy = true;
34  SourceCharacter = character;
35  SourceRagdoll = ragdoll;
36  SourceAnimations = animations;
37  name = character.SpeciesName;
38  isHumanoid = character.Humanoid;
39  canEnterSubmarine = ragdoll.CanEnterSubmarine;
40  canWalk = ragdoll.CanWalk;
41  texturePath = ragdoll.Texture;
42  if (string.IsNullOrEmpty(texturePath) && name != CharacterPrefab.HumanSpeciesName)
43  {
44  texturePath = ragdoll.Limbs.FirstOrDefault()?.GetSprite().Texture;
45  }
46  }
47 
48  public static Wizard instance;
49  public static Wizard Instance
50  {
51  get
52  {
53  if (instance == null)
54  {
55  instance = new Wizard();
56  }
57  return instance;
58  }
59  }
60 
62 
63  public void Reset()
64  {
65  CharacterView.Get().Release();
66  RagdollView.Get().Release();
67  instance = null;
68  }
69 
70  public enum Tab { None, Character, Ragdoll }
71  private View activeView;
72  private Tab currentTab;
73 
74  public void SelectTab(Tab tab)
75  {
76  currentTab = tab;
77  activeView?.Box.Close();
78  switch (currentTab)
79  {
80  case Tab.Character:
81  activeView = CharacterView.Get();
82  break;
83  case Tab.Ragdoll:
84  activeView = RagdollView.Get();
85  break;
86  case Tab.None:
87  default:
88  Reset();
89  break;
90  }
91  }
92 
93  public void AddToGUIUpdateList()
94  {
95  activeView?.Box.AddToGUIUpdateList();
96  }
97 
98  public void CreateCharacter(XElement ragdollElement, XElement characterElement = null, IEnumerable<AnimationParams> animations = null)
99  {
100  if (CharacterPrefab.Find(p => p.Identifier == name) != null)
101  {
102  bool isSamePackage = contentPackage.GetFiles<CharacterFile>().Any(f => Path.GetFileNameWithoutExtension(f.Path.Value) == name);
103  LocalizedString verificationText = isSamePackage ? GetCharacterEditorTranslation("existingcharacterfoundreplaceverification") : GetCharacterEditorTranslation("existingcharacterfoundoverrideverification");
104  var msgBox = new GUIMessageBox("", verificationText, new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }, type: GUIMessageBox.Type.Warning)
105  {
106  UserData = "verificationprompt"
107  };
108  msgBox.Buttons[0].OnClicked = (_, userdata) =>
109  {
110  msgBox.Close();
111  if (CharacterEditorScreen.Instance.CreateCharacter(name, Path.GetDirectoryName(xmlPath), isHumanoid, contentPackage, ragdollElement, characterElement, animations))
112  {
113  GUI.AddMessage(GetCharacterEditorTranslation("CharacterCreated").Replace("[name]", name.Value), GUIStyle.Green, font: GUIStyle.Font);
114  }
116  return true;
117  };
118  //msgBox.Buttons[0].OnClicked += msgBox.Close;
119  msgBox.Buttons[1].OnClicked = (_, userdata) =>
120  {
121  msgBox.Close();
122  return true;
123  };
124  }
125  else
126  {
127  if (CharacterEditorScreen.Instance.CreateCharacter(name, Path.GetDirectoryName(xmlPath), isHumanoid, contentPackage, ragdollElement, characterElement, animations))
128  {
129  GUI.AddMessage(GetCharacterEditorTranslation("CharacterCreated").Replace("[name]", name.Value), GUIStyle.Green, font: GUIStyle.Font);
130  }
132  }
133  }
134 
135  private class CharacterView : View
136  {
137  private static CharacterView instance;
138  public static CharacterView Get() => Get(ref instance);
139 
140  public override void Release() => instance = null;
141 
142  protected override GUIMessageBox Create()
143  {
144  var box = new GUIMessageBox(GetCharacterEditorTranslation("CreateNewCharacter"), string.Empty, new LocalizedString[] { TextManager.Get("Cancel"), IsCopy ? TextManager.Get("Create") : TextManager.Get("Next") }, new Vector2(0.65f, 0.9f));
145  box.Header.Font = GUIStyle.LargeFont;
146  box.Content.ChildAnchor = Anchor.TopCenter;
147  box.Content.AbsoluteSpacing = 20;
148  int elementSize = 30;
149  var frame = new GUIFrame(new RectTransform(new Point(box.Content.Rect.Width - (int)(40 * GUI.xScale), box.Content.Rect.Height - (int)(50 * GUI.yScale)),
150  box.Content.RectTransform, Anchor.Center), style: null, color: ParamsEditor.Color)
151  {
152  CanBeFocused = false
153  };
154  var topGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.99f, 1), frame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 2 };
155  var fields = new List<GUIComponent>();
156  GUITextBox nameField = null;
157  GUITextBox texturePathElement = null;
158  GUITextBox xmlPathElement = null;
159  GUIDropDown contentPackageDropDown = null;
160  bool updateTexturePath = !IsCopy;
161  bool isTextureSelected = false;
162  void UpdatePaths()
163  {
164  string pathBase = $"{ContentPath.ModDirStr}/Characters/{Name}/{Name}";
165  XMLPath = $"{pathBase}.xml";
166  xmlPathElement.Text = XMLPath;
167  if (updateTexturePath)
168  {
169  TexturePath = $"{pathBase}.png";
170  texturePathElement.Text = TexturePath;
171  }
172  }
173  for (int i = 0; i < 7; i++)
174  {
175  var mainElement = new GUIFrame(new RectTransform(new Point(topGroup.RectTransform.Rect.Width, elementSize), topGroup.RectTransform), style: null, color: Color.Gray * 0.25f);
176  fields.Add(mainElement);
177  switch (i)
178  {
179  case 0:
180  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), TextManager.Get("Name"));
181  nameField = new GUITextBox(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), Name.Value ?? GetCharacterEditorTranslation("DefaultName").Value) { CaretColor = Color.White };
182  string ProcessText(string text) => text.RemoveWhitespace().CapitaliseFirstInvariant();
183  Name = ProcessText(nameField.Text).ToIdentifier();
184  nameField.OnTextChanged += (tb, text) =>
185  {
186  Name = ProcessText(text).ToIdentifier();
187  UpdatePaths();
188  return true;
189  };
190  break;
191  case 1:
192  var label = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), GetCharacterEditorTranslation("IsHumanoid"));
193  var tickBox = new GUITickBox(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), string.Empty)
194  {
195  Selected = IsHumanoid,
196  Enabled = !IsCopy,
197  OnSelected = (tB) => IsHumanoid = tB.Selected
198  };
199  if (!tickBox.Enabled)
200  {
201  label.TextColor *= 0.6f;
202  }
203  break;
204  case 2:
205  var l = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), GetCharacterEditorTranslation("CanEnterSubmarines"));
206  var t = new GUITickBox(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), string.Empty)
207  {
208  //TODO: allow ternary selection (true, false, partial)
210  Enabled = !IsCopy,
211  OnSelected = (tB) =>
212  {
213  CanEnterSubmarine = tB.Selected ? CanEnterSubmarine.True : CanEnterSubmarine.False;
214  return true;
215  }
216  };
217  if (!t.Enabled)
218  {
219  l.TextColor *= 0.6f;
220  }
221  break;
222  case 3:
223  var lbl = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), GetCharacterEditorTranslation("CanWalk"));
224  var txt = new GUITickBox(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), string.Empty)
225  {
226  Selected = CanWalk,
227  Enabled = !IsCopy,
228  OnSelected = (tB) => CanWalk = tB.Selected
229  };
230  if (!txt.Enabled)
231  {
232  lbl.TextColor *= 0.6f;
233  }
234  break;
235  case 4:
236  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), GetCharacterEditorTranslation("ConfigFileOutput"));
237  xmlPathElement = new GUITextBox(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), string.Empty)
238  {
239  Text = XMLPath,
240  CaretColor = Color.White
241  };
242  xmlPathElement.OnTextChanged += (tb, text) =>
243  {
244  XMLPath = text;
245  return true;
246  };
247  break;
248  case 5:
249  {
250  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), GetCharacterEditorTranslation("TexturePath"));
251  var rightContainer = new GUIFrame(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), style: null);
252  texturePathElement = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), rightContainer.RectTransform, Anchor.CenterLeft), string.Empty)
253  {
254  Text = TexturePath,
255  CaretColor = Color.White,
256  };
257  texturePathElement.OnTextChanged += (tb, text) =>
258  {
259  updateTexturePath = false;
260  TexturePath = text;
261  return true;
262  };
263  LocalizedString title = GetCharacterEditorTranslation("SelectTexture");
264  new GUIButton(new RectTransform(new Vector2(0.3f / texturePathElement.RectTransform.RelativeSize.X, 1.0f), texturePathElement.RectTransform, Anchor.CenterRight, Pivot.CenterLeft), title, style: "GUIButtonSmall")
265  {
266  OnClicked = (button, data) =>
267  {
268  FileSelection.OnFileSelected = (file) =>
269  {
270  string relativePath = Path.GetRelativePath(Environment.CurrentDirectory, Path.GetFullPath(file));
271 
272  if (relativePath.StartsWith(ContentPackage.LocalModsDir))
273  {
274  string[] pathSplit = relativePath.Split('/', '\\');
275  string modDirName = $"{ContentPackage.LocalModsDir}/{pathSplit[1]}";
276  string selectedModDir
277  = (contentPackageDropDown.ListBox.SelectedData as ContentPackage)?.Dir.CleanUpPathCrossPlatform(correctFilenameCase: false)
278  ?? "";
279  if (modDirName == selectedModDir)
280  {
281  relativePath = ContentPath.ModDirStr + "/" +
282  string.Join("/", pathSplit[2..]);
283  }
284  else
285  {
286  relativePath = string.Format(ContentPath.OtherModDirFmt,
287  pathSplit[1]) + "/" +
288  string.Join("/", pathSplit[2..]);
289  }
290  }
291 
292  string destinationPath = relativePath;
293 
294  //copy file to XML path if it's not located relative to the game's files
295  if (relativePath.StartsWith("..") ||
296  Path.GetPathRoot(Environment.CurrentDirectory) != Path.GetPathRoot(file))
297  {
298  destinationPath = Path.Combine(Path.GetDirectoryName(XMLPath), Path.GetFileName(file));
299 
300  string destinationDir = Path.GetDirectoryName(destinationPath);
301  if (!Directory.Exists(destinationDir))
302  {
303  Directory.CreateDirectory(destinationDir, catchUnauthorizedAccessExceptions: true);
304  }
305 
306  if (!File.Exists(destinationPath))
307  {
308  File.Copy(file, Path.GetFullPath(destinationPath), overwrite: true);
309  }
310  }
311 
312  isTextureSelected = true;
313  texturePathElement.Text = destinationPath.CleanUpPath();
314  };
315  FileSelection.ClearFileTypeFilters();
316  FileSelection.AddFileTypeFilter("PNG", "*.png");
317  FileSelection.AddFileTypeFilter("JPEG", "*.jpg, *.jpeg");
318  FileSelection.AddFileTypeFilter("All files", "*.*");
319  FileSelection.SelectFileTypeFilter("*.png");
320  FileSelection.Open = true;
321  return true;
322  }
323  };
324  }
325  break;
326  case 6:
327  {
328  mainElement.RectTransform.NonScaledSize = new Point(
329  mainElement.RectTransform.NonScaledSize.X,
330  mainElement.RectTransform.NonScaledSize.Y * 2);
331  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), TextManager.Get("ContentPackage"));
332  var rightContainer = new GUIFrame(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), style: null);
333  contentPackageDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.5f), rightContainer.RectTransform, Anchor.TopRight));
334  foreach (ContentPackage contentPackage in ContentPackageManager.EnabledPackages.All)
335  {
336  if (contentPackage != GameMain.VanillaContent)
337  {
338  contentPackageDropDown.AddItem(contentPackage.Name, userData: contentPackage, toolTip: contentPackage.Path);
339  }
340  }
341  contentPackageDropDown.OnSelected = (obj, userdata) =>
342  {
343  ContentPackage = userdata as ContentPackage;
344  updateTexturePath = !isTextureSelected && !IsCopy;
345  UpdatePaths();
346  return true;
347  };
348  contentPackageDropDown.Select(0);
349  var contentPackageNameElement = new GUITextBox(new RectTransform(new Vector2(0.7f, 0.5f), rightContainer.RectTransform, Anchor.BottomLeft),
350  GetCharacterEditorTranslation("NewContentPackage").Value)
351  {
352  CaretColor = Color.White,
353  };
354  var createNewPackageButton = new GUIButton(new RectTransform(new Vector2(0.3f / contentPackageNameElement.RectTransform.RelativeSize.X, 1.0f), contentPackageNameElement.RectTransform, Anchor.CenterRight, Pivot.CenterLeft), TextManager.Get("CreateNew"), style: "GUIButtonSmall")
355  {
356  OnClicked = (btn, userdata) =>
357  {
358  if (string.IsNullOrEmpty(contentPackageNameElement.Text))
359  {
360  contentPackageNameElement.Flash(useRectangleFlash: true);
361  return false;
362  }
363  if (ContentPackageManager.AllPackages.Any(cp => cp.Name.ToLower() == contentPackageNameElement.Text.ToLower()))
364  {
365  new GUIMessageBox("", TextManager.Get("charactereditor.contentpackagenameinuse", "leveleditorlevelobjnametaken"), type: GUIMessageBox.Type.Warning);
366  return false;
367  }
368  string modName = contentPackageNameElement.Text;
369 
370  var modProject = new ModProject { Name = modName };
371  ContentPackage = ContentPackageManager.LocalPackages.SaveAndEnableRegularMod(modProject);
372 
373  contentPackageDropDown.AddItem(ContentPackage.Name, ContentPackage, ContentPackage.Path);
374  contentPackageDropDown.SelectItem(ContentPackage);
375  contentPackageNameElement.Text = "";
376  return true;
377  },
378  Enabled = false
379  };
380  Color textColor = contentPackageNameElement.TextColor;
381  contentPackageNameElement.TextColor *= 0.6f;
382  contentPackageNameElement.OnSelected += (sender, key) =>
383  {
384  contentPackageNameElement.Text = "";
385  };
386  contentPackageNameElement.OnTextChanged += (textBox, text) =>
387  {
388  textBox.TextColor = textColor;
389  createNewPackageButton.Enabled = !string.IsNullOrWhiteSpace(text);
390  return true;
391  };
392  rightContainer.RectTransform.MinSize = new Point(0,
393  contentPackageDropDown.RectTransform.MinSize.Y + Math.Max(contentPackageNameElement.RectTransform.MinSize.Y, createNewPackageButton.RectTransform.MinSize.Y));
394  }
395  break;
396  }
397  int contentSize = mainElement.RectTransform.Children.Max(c => c.MinSize.Y) ;
398  mainElement.RectTransform.Resize(new Point(mainElement.Rect.Width, Math.Max(mainElement.Rect.Height, contentSize)));
399  }
400  UpdatePaths();
401  box.Buttons[0].Parent.RectTransform.SetAsLastChild();
402  box.Buttons[1].RectTransform.SetAsLastChild();
403  // Cancel
404  box.Buttons[0].OnClicked += (b, d) =>
405  {
406  Wizard.Instance.SelectTab(Tab.None);
407  return true;
408  };
409  // Next
410  box.Buttons[1].OnClicked += (b, d) =>
411  {
412  if (ContentPackage == null)
413  {
414  contentPackageDropDown.Flash(useRectangleFlash: true);
415  return false;
416  }
417 
418  if (Name.Value.IsNullOrWhiteSpace())
419  {
420  nameField?.Flash(useRectangleFlash: true);
421  return false;
422  }
423 
424  string evaluatedTexturePath = ContentPath.FromRaw(
425  contentPackageDropDown.SelectedData as ContentPackage,
426  TexturePath).Value;
427  if (SourceCharacter?.SpeciesName != CharacterPrefab.HumanSpeciesName)
428  {
429  if (!File.Exists(evaluatedTexturePath))
430  {
431  GUI.AddMessage(GetCharacterEditorTranslation("TextureDoesNotExist"), GUIStyle.Red);
432  texturePathElement.Flash(useRectangleFlash: true);
433  return false;
434  }
435  }
436  var path = Path.GetFileName(evaluatedTexturePath);
437  if (!path.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
438  {
439  GUI.AddMessage(TextManager.Get("WrongFileType"), GUIStyle.Red);
440  texturePathElement.Flash(useRectangleFlash: true);
441  return false;
442  }
443  if (Name == CharacterPrefab.HumanSpeciesName && !IsCopy)
444  {
445  // Force a copy when trying to override a human, because handling the crash would be very difficult (we require humans to have certain definitions).
446  if (!CharacterEditorScreen.Instance.SpawnedCharacter.IsHuman)
447  {
448  CharacterEditorScreen.Instance.SpawnCharacter(CharacterPrefab.HumanSpeciesName);
449  }
450  CharacterEditorScreen.Instance.PrepareCharacterCopy();
451  }
452  if (IsCopy)
453  {
454  SourceRagdoll.Texture = evaluatedTexturePath;
456  SourceRagdoll.CanWalk = CanWalk;
459  }
460  else
461  {
462  Instance.SelectTab(Tab.Ragdoll);
463  }
464  return true;
465  };
466  return box;
467  }
468  }
469 
470  private class RagdollView : View
471  {
472  private static RagdollView instance;
473  public static RagdollView Get() => Get(ref instance);
474 
475  public override void Release() => instance = null;
476 
477  protected override GUIMessageBox Create()
478  {
479  var box = new GUIMessageBox(GetCharacterEditorTranslation("DefineRagdoll"), string.Empty, new LocalizedString[] { TextManager.Get("Previous"), TextManager.Get("Create") }, new Vector2(0.65f, 1f));
480  box.Header.Font = GUIStyle.LargeFont;
481  box.Content.ChildAnchor = Anchor.TopCenter;
482  box.Content.AbsoluteSpacing = (int)(20 * GUI.Scale);
483  int elementSize = (int)(40 * GUI.Scale);
484  var frame = new GUIFrame(new RectTransform(new Point(box.Content.Rect.Width - (int)(80 * GUI.xScale), box.Content.Rect.Height - (int)(200 * GUI.yScale)),
485  box.Content.RectTransform, Anchor.Center), style: null, color: ParamsEditor.Color)
486  {
487  CanBeFocused = false
488  };
489  var content = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), frame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.TopCenter)
490  {
491  Stretch = true,
492  RelativeSpacing = 0.02f
493  };
494  // Limbs
495  var limbsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false };
496 
497  var limbEditLayout = new GUILayoutGroup(new RectTransform(Vector2.One, limbsElement.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
498  {
499  Stretch = true,
500  RelativeSpacing = 0.02f
501  };
502  new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), limbEditLayout.RectTransform), GetCharacterEditorTranslation("Limbs"), font: GUIStyle.SubHeadingFont);
503  var limbsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), content.RectTransform))
504  {
505  PlaySoundOnSelect = true,
506  };
507  var limbButtonSize = Vector2.One * 0.8f;
508  var removeLimbButton = new GUIButton(new RectTransform(limbButtonSize, limbEditLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton")
509  {
510  OnClicked = (b, d) =>
511  {
512  var element = LimbGUIElements.LastOrDefault();
513  if (element == null) { return false; }
514  element.RectTransform.Parent = null;
515  LimbGUIElements.Remove(element);
516  return true;
517  }
518  };
519  var addLimbButton = new GUIButton(new RectTransform(limbButtonSize, limbEditLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton")
520  {
521  OnClicked = (b, d) =>
522  {
523  LimbType limbType = LimbType.None;
524  switch (LimbGUIElements.Count)
525  {
526  case 0:
527  limbType = LimbType.Torso;
528  break;
529  case 1:
530  limbType = LimbType.Head;
531  break;
532  }
533  CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize, id: LimbGUIElements.Count, limbType: limbType);
534  return true;
535  }
536  };
537 
538  int _x = 1, _y = 1, w = 100, h = 100;
539  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), limbEditLayout.RectTransform), isHorizontal: true)
540  {
541  Stretch = true,
542  RelativeSpacing = 0.01f
543  };
544  for (int i = 3; i >= 0; i--)
545  {
546  var element = new GUIFrame(new RectTransform(new Vector2(0.22f, 1), inputArea.RectTransform) { MinSize = new Point(50, 0), MaxSize = new Point(150, 50) }, style: null);
547  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), GUI.RectComponentLabels[i], font: GUIStyle.SmallFont, textAlignment: Alignment.CenterLeft);
548  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight), NumberType.Int)
549  {
550  Font = GUIStyle.SmallFont
551  };
552  switch (i)
553  {
554  case 0:
555  case 1:
556  numberInput.IntValue = 1;
557  numberInput.MinValueInt = 1;
558  numberInput.MaxValueInt = 100;
559  break;
560  case 2:
561  case 3:
562  numberInput.IntValue = 100;
563  numberInput.MinValueInt = 0;
564  numberInput.MaxValueInt = 999;
565  break;
566 
567  }
568  int comp = i;
569  numberInput.OnValueChanged += (numInput) =>
570  {
571  switch (comp)
572  {
573  case 0:
574  _x = numInput.IntValue;
575  break;
576  case 1:
577  _y = numInput.IntValue;
578  break;
579  case 2:
580  w = numInput.IntValue;
581  break;
582  case 3:
583  h = numInput.IntValue;
584  break;
585  }
586  };
587  }
588  inputArea.Recalculate();
589  new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), limbEditLayout.RectTransform),
590  GetCharacterEditorTranslation("AddMultipleLimbsButton"))
591  {
592  OnClicked = (b, d) =>
593  {
594  CreateMultipleLimbs(_x, _y);
595  return true;
596  }
597  };
598  limbsElement.RectTransform.MinSize = new Point(0, limbEditLayout.RectTransform.Children.Max(c => c.MinSize.Y));
599 
600  // If no elements are defined, create some as default
601  if (LimbGUIElements.None())
602  {
603  if (IsHumanoid)
604  {
605  CreateMultipleLimbs(2, 6);
606  // Create the missing waist (13th element)
607  CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize, id: LimbGUIElements.Count, limbType: LimbType.Waist, sourceRect: new Rectangle(_x, h * LimbGUIElements.Count / 2, w, h));
608  }
609  else
610  {
611  CreateMultipleLimbs(1, 2);
612  }
613  }
614  void CreateMultipleLimbs(int x, int y)
615  {
616  for (int i = 0; i < x; i++)
617  {
618  for (int j = 0; j < y; j++)
619  {
620  LimbType limbType = LimbType.None;
621  switch (LimbGUIElements.Count)
622  {
623  case 0:
624  limbType = LimbType.Torso;
625  break;
626  case 1:
627  limbType = LimbType.Head;
628  break;
629  }
630  if (IsHumanoid)
631  {
632  switch (LimbGUIElements.Count)
633  {
634  case 2:
635  limbType = LimbType.LeftArm;
636  break;
637  case 3:
638  limbType = LimbType.LeftHand;
639  break;
640  case 4:
641  limbType = LimbType.RightArm;
642  break;
643  case 5:
644  limbType = LimbType.RightHand;
645  break;
646  case 6:
647  limbType = LimbType.LeftThigh;
648  break;
649  case 7:
650  limbType = LimbType.LeftLeg;
651  break;
652  case 8:
653  limbType = LimbType.LeftFoot;
654  break;
655  case 9:
656  limbType = LimbType.RightThigh;
657  break;
658  case 10:
659  limbType = LimbType.RightLeg;
660  break;
661  case 11:
662  limbType = LimbType.RightFoot;
663  break;
664  case 12:
665  limbType = LimbType.Waist;
666  break;
667  }
668  }
669  CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize, id: LimbGUIElements.Count, limbType: limbType, sourceRect: new Rectangle(i * w, j * h, w, h));
670  }
671  }
672  }
673  // Joints
674  new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false };
675  var jointsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false };
676  new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), jointsElement.RectTransform), GetCharacterEditorTranslation("Joints"), font: GUIStyle.SubHeadingFont);
677  var jointButtonElement = new GUIFrame(new RectTransform(new Vector2(0.5f, 1f), jointsElement.RectTransform)
678  {
679  RelativeOffset = new Vector2(0.15f, 0)
680  }, style: null)
681  {
682  CanBeFocused = false
683  };
684  var jointsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), content.RectTransform))
685  {
686  PlaySoundOnSelect = true,
687  };
688  var removeJointButton = new GUIButton(new RectTransform(new Point(jointButtonElement.Rect.Height, jointButtonElement.Rect.Height), jointButtonElement.RectTransform), style: "GUIMinusButton")
689  {
690  OnClicked = (b, d) =>
691  {
692  var element = JointGUIElements.LastOrDefault();
693  if (element == null) { return false; }
694  element.RectTransform.Parent = null;
695  JointGUIElements.Remove(element);
696  return true;
697  }
698  };
699  var addJointButton = new GUIButton(new RectTransform(new Point(jointButtonElement.Rect.Height), jointButtonElement.RectTransform)
700  {
701  AbsoluteOffset = new Point(removeJointButton.Rect.Width + 10, 0)
702  }, style: "GUIPlusButton")
703  {
704  OnClicked = (b, d) =>
705  {
706  CreateJointGUIElement(jointsList.Content.RectTransform, elementSize);
707  return true;
708  }
709  };
710  // Previous
711  box.Buttons[0].OnClicked += (b, d) =>
712  {
713  Wizard.Instance.SelectTab(Tab.Character);
714  return true;
715  };
716  // Parse and create
717  box.Buttons[1].OnClicked += (b, d) =>
718  {
719  ParseLimbsFromGUIElements();
720  ParseJointsFromGUIElements();
721  var main = LimbXElements.Values.Select(xe => xe.Attribute("type")).Where(a => a.Value.Equals("torso", StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ??
722  LimbXElements.Values.Select(xe => xe.Attribute("type")).Where(a => a.Value.Equals("head", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
723  if (main == null)
724  {
725  GUI.AddMessage(GetCharacterEditorTranslation("MissingTorsoOrHead"), GUIStyle.Red);
726  return false;
727  }
728  if (IsHumanoid)
729  {
730  if (!IsValid(LimbXElements.Values, true, out string missingType))
731  {
732  GUI.AddMessage(GetCharacterEditorTranslation("MissingLimbType").Replace("[limbtype]", missingType.FormatCamelCaseWithSpaces()), GUIStyle.Red);
733  return false;
734  }
735  }
736  XElement mainLimb = main.Parent;
737  int radius = mainLimb.GetAttributeInt("radius", -1);
738  int height = mainLimb.GetAttributeInt("height", -1);
739  int width = mainLimb.GetAttributeInt("width", -1);
740  int colliderHeight = -1;
741  if (radius == -1)
742  {
743  // the collider is a box -> calculate the capsule
744  if (width == height)
745  {
746  radius = width / 2;
747  colliderHeight = width - radius * 2;
748  }
749  else
750  {
751  if (height > width)
752  {
753  radius = width / 2;
754  colliderHeight = height - radius * 2;
755  }
756  else
757  {
758  radius = height / 2;
759  colliderHeight = width - radius * 2;
760  }
761  }
762  radius = Math.Max(radius, 1);
763  }
764  else if (height > -1 || width > -1)
765  {
766  // the collider is a capsule -> use the capsule as it is
767  colliderHeight = width > height ? width : height;
768  }
769  var colliderAttributes = new List<XAttribute>() { new XAttribute("radius", radius) };
770  if (colliderHeight > -1)
771  {
772  colliderHeight = Math.Max(colliderHeight, 1);
773  if (height > width)
774  {
775  colliderAttributes.Add(new XAttribute("height", colliderHeight));
776  }
777  else
778  {
779  colliderAttributes.Add(new XAttribute("width", colliderHeight));
780  }
781  }
782  var colliderElements = new List<XElement>() { new XElement("collider", colliderAttributes) };
783  if (IsHumanoid)
784  {
785  // For humanoids, we need a secondary, shorter collider for crouching
786  var secondaryCollider = new XElement("collider", new XAttribute("radius", radius));
787  if (colliderHeight > -1)
788  {
789  colliderHeight = Math.Max(colliderHeight, 1);
790  if (height > width)
791  {
792  secondaryCollider.Add(new XAttribute("height", colliderHeight * 0.75f));
793  }
794  else
795  {
796  secondaryCollider.Add(new XAttribute("width", colliderHeight * 0.75f));
797  }
798  }
799  colliderElements.Add(secondaryCollider);
800  }
801  var mainElement = new XElement("Ragdoll",
802  new XAttribute("type", Name),
803  new XAttribute("texture", TexturePath),
804  new XAttribute("canentersubmarine", CanEnterSubmarine),
805  new XAttribute("canwalk", CanWalk),
806  colliderElements,
807  LimbXElements.Values,
808  JointXElements);
809  Wizard.Instance.CreateCharacter(mainElement);
810  return true;
811  };
812  return box;
813  }
814 
815  private void CreateLimbGUIElement(RectTransform parent, int elementSize, int id, string name = "", LimbType limbType = LimbType.None, Rectangle? sourceRect = null)
816  {
817  var limbElement = new GUIFrame(new RectTransform(new Point(parent.Rect.Width, elementSize * 5 + 40), parent), style: null, color: Color.Gray * 0.25f)
818  {
819  CanBeFocused = false
820  };
821  var group = new GUILayoutGroup(new RectTransform(Vector2.One, limbElement.RectTransform)) { AbsoluteSpacing = 16 };
822  var label = new GUITextBlock(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), name, font: GUIStyle.SubHeadingFont);
823  var idField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
824  var nameField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
825  var limbTypeField = GUI.CreateEnumField(limbType, elementSize, GetCharacterEditorTranslation("LimbType"), group.RectTransform, font: GUIStyle.Font);
826  var sourceRectField = GUI.CreateRectangleField(sourceRect ?? new Rectangle(0, 100 * LimbGUIElements.Count, 100, 100), elementSize, GetCharacterEditorTranslation("SourceRectangle"), group.RectTransform, font: GUIStyle.Font);
827  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), idField.RectTransform, Anchor.TopLeft), GetCharacterEditorTranslation("ID"));
828  new GUINumberInput(new RectTransform(new Vector2(0.5f, 1), idField.RectTransform, Anchor.TopRight), NumberType.Int)
829  {
830  MinValueInt = 0,
831  MaxValueInt = byte.MaxValue,
832  IntValue = id,
833  OnValueChanged = numInput =>
834  {
835  id = numInput.IntValue;
836  string text = nameField.GetChild<GUITextBox>().Text;
837  string t = string.IsNullOrWhiteSpace(text) ? id.ToString() : text;
838  label.Text = t;
839  }
840  };
841  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), nameField.RectTransform, Anchor.TopLeft), TextManager.Get("Name"));
842  var nameInput = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), nameField.RectTransform, Anchor.TopRight), name)
843  {
844  CaretColor = Color.White,
845  };
846  nameInput.OnTextChanged += (tb, text) =>
847  {
848  string t = string.IsNullOrWhiteSpace(text) ? id.ToString() : text;
849  label.Text = t;
850  return true;
851  };
852  LimbGUIElements.Add(limbElement);
853  }
854 
855  private void CreateJointGUIElement(RectTransform parent, int elementSize, int id1 = 0, int id2 = 1, Vector2? anchor1 = null, Vector2? anchor2 = null, string jointName = "")
856  {
857  var jointElement = new GUIFrame(new RectTransform(new Point(parent.Rect.Width, elementSize * 6 + 40), parent), style: null, color: Color.Gray * 0.25f)
858  {
859  CanBeFocused = false
860  };
861  var group = new GUILayoutGroup(new RectTransform(Vector2.One, jointElement.RectTransform)) { AbsoluteSpacing = 2 };
862  var label = new GUITextBlock(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), jointName, font: GUIStyle.SubHeadingFont);
863  var nameField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
864  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), nameField.RectTransform, Anchor.TopLeft), TextManager.Get("Name"));
865  var nameInput = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), nameField.RectTransform, Anchor.TopRight), jointName)
866  {
867  CaretColor = Color.White,
868  };
869  nameInput.OnTextChanged += (textB, text) =>
870  {
871  jointName = text;
872  label.Text = jointName;
873  return true;
874  };
875  var limb1Field = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
876  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), limb1Field.RectTransform, Anchor.TopLeft), GetCharacterEditorTranslation("LimbWithIndex").Replace("[index]", "1"));
877  var limb1InputField = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1), limb1Field.RectTransform, Anchor.TopRight), NumberType.Int)
878  {
879  MinValueInt = 0,
880  MaxValueInt = byte.MaxValue,
881  IntValue = id1
882  };
883  var limb2Field = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
884  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), limb2Field.RectTransform, Anchor.TopLeft), GetCharacterEditorTranslation("LimbWithIndex").Replace("[index]", "2"));
885  var limb2InputField = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1), limb2Field.RectTransform, Anchor.TopRight), NumberType.Int)
886  {
887  MinValueInt = 0,
888  MaxValueInt = byte.MaxValue,
889  IntValue = id2
890  };
891  GUI.CreateVector2Field(anchor1 ?? Vector2.Zero, elementSize, GetCharacterEditorTranslation("LimbWithIndexAnchor").Replace("[index]", "1"), group.RectTransform, font: GUIStyle.Font, decimalsToDisplay: 2);
892  GUI.CreateVector2Field(anchor2 ?? Vector2.Zero, elementSize, GetCharacterEditorTranslation("LimbWithIndexAnchor").Replace("[index]", "2"), group.RectTransform, font: GUIStyle.Font, decimalsToDisplay: 2);
893  label.Text = GetJointName(jointName);
894  limb1InputField.OnValueChanged += nInput => label.Text = GetJointName(jointName);
895  limb2InputField.OnValueChanged += nInput => label.Text = GetJointName(jointName);
896  JointGUIElements.Add(jointElement);
897  string GetJointName(string n) => string.IsNullOrWhiteSpace(n) ? $"{GetCharacterEditorTranslation("Joint")} {limb1InputField.IntValue} - {limb2InputField.IntValue}" : n;
898  }
899  }
900 
901  private abstract class View
902  {
903  // Easy accessors to the common data.
904 
905  public bool IsCopy => Instance.IsCopy;
906  public IEnumerable<AnimationParams> SourceAnimations => Instance.SourceAnimations;
907  public CharacterParams SourceCharacter => Instance.SourceCharacter;
908  public RagdollParams SourceRagdoll => Instance.SourceRagdoll;
909 
910  public Identifier Name
911  {
912  get => Instance.name;
913  set => Instance.name = value;
914  }
915  public bool IsHumanoid
916  {
917  get => Instance.isHumanoid;
918  set => Instance.isHumanoid = value;
919  }
921  {
922  get => Instance.canEnterSubmarine;
923  set => Instance.canEnterSubmarine = value;
924  }
925  public bool CanWalk
926  {
927  get => Instance.canWalk;
928  set => Instance.canWalk = value;
929  }
930  public ContentPackage ContentPackage
931  {
932  get => Instance.contentPackage;
933  set => Instance.contentPackage = value;
934  }
935  public string TexturePath
936  {
937  get => Instance.texturePath;
938  set => Instance.texturePath = value;
939  }
940  public string XMLPath
941  {
942  get => Instance.xmlPath;
943  set => Instance.xmlPath = value;
944  }
945  public Dictionary<string, XElement> LimbXElements
946  {
947  get => Instance.limbXElements;
948  set => Instance.limbXElements = value;
949  }
950  public List<GUIComponent> LimbGUIElements
951  {
952  get => Instance.limbGUIElements;
953  set => Instance.limbGUIElements = value;
954  }
955  public List<XElement> JointXElements
956  {
957  get => Instance.jointXElements;
958  set => Instance.jointXElements = value;
959  }
960  public List<GUIComponent> JointGUIElements
961  {
962  get => Instance.jointGUIElements;
963  set => Instance.jointGUIElements = value;
964  }
965 
966  private GUIMessageBox box;
967  public GUIMessageBox Box
968  {
969  get
970  {
971  if (box == null)
972  {
973  box = Create();
974  }
975  return box;
976  }
977  }
978 
979  protected abstract GUIMessageBox Create();
980  protected static T Get<T>(ref T instance) where T : View, new()
981  {
982  if (instance == null)
983  {
984  instance = new T();
985  }
986  return instance;
987  }
988 
989  public abstract void Release();
990 
991  protected void ParseLimbsFromGUIElements()
992  {
993  LimbXElements.Clear();
994  for (int i = 0; i < LimbGUIElements.Count; i++)
995  {
996  var limbGUIElement = LimbGUIElements[i];
997  var allChildren = limbGUIElement.GetAllChildren();
998  GUITextBlock GetField(LocalizedString n) => allChildren.First(c => c is GUITextBlock textBlock && textBlock.Text == n) as GUITextBlock;
999  int id = GetField(GetCharacterEditorTranslation("ID")).Parent.GetChild<GUINumberInput>().IntValue;
1000  string limbName = GetField(TextManager.Get("Name")).Parent.GetChild<GUITextBox>().Text;
1001  LimbType limbType = (LimbType)GetField(GetCharacterEditorTranslation("LimbType")).Parent.GetChild<GUIDropDown>().SelectedData;
1002  // Reverse, because the elements are created from right to left
1003  var rectInputs = GetField(GetCharacterEditorTranslation("SourceRectangle")).Parent.GetAllChildren<GUINumberInput>().Reverse().ToArray();
1004  int width = rectInputs[2].IntValue;
1005  int height = rectInputs[3].IntValue;
1006  var colliderAttributes = new List<XAttribute>();
1007  // Capsules/Circles
1008  //if (width == height)
1009  //{
1010  // colliderAttributes.Add(new XAttribute("radius", (int)(width / 2 * 0.85f)));
1011  //}
1012  //else
1013  //{
1014  // if (height > width)
1015  // {
1016  // colliderAttributes.Add(new XAttribute("radius", (int)(width / 2 * 0.85f)));
1017  // colliderAttributes.Add(new XAttribute("height",(int) (height - width * 0.85f)));
1018  // }
1019  // else
1020  // {
1021  // colliderAttributes.Add(new XAttribute("radius", (int)(height / 2 * 0.85f)));
1022  // colliderAttributes.Add(new XAttribute("width", (int)(width - height * 0.85f)));
1023  // }
1024  //}
1025  // Rectangles
1026  colliderAttributes.Add(new XAttribute("height", (int)(height * 0.85f)));
1027  colliderAttributes.Add(new XAttribute("width", (int)(width * 0.85f)));
1028  LimbXElements.Add(id.ToString(), new XElement("limb",
1029  new XAttribute("id", id),
1030  new XAttribute("name", limbName),
1031  new XAttribute("type", limbType.ToString()),
1032  colliderAttributes,
1033  new XElement("sprite",
1034  new XAttribute("texture", ""),
1035  new XAttribute("sourcerect", $"{rectInputs[0].IntValue}, {rectInputs[1].IntValue}, {width}, {height}")),
1036  new XAttribute("notes", null ?? string.Empty)
1037  ));
1038  }
1039  }
1040 
1041  protected void ParseJointsFromGUIElements()
1042  {
1043  JointXElements.Clear();
1044  for (int i = 0; i < JointGUIElements.Count; i++)
1045  {
1046  var jointGUIElement = JointGUIElements[i];
1047  var allChildren = jointGUIElement.GetAllChildren();
1048  GUITextBlock GetField(LocalizedString n) => allChildren.First(c => c is GUITextBlock textBlock && textBlock.Text == n) as GUITextBlock;
1049  string jointName = GetField(TextManager.Get("Name")).Parent.GetChild<GUITextBox>().Text;
1050  int limb1ID = GetField(GetCharacterEditorTranslation("LimbWithIndex").Replace("[index]", "1")).Parent.GetChild<GUINumberInput>().IntValue;
1051  int limb2ID = GetField(GetCharacterEditorTranslation("LimbWithIndex").Replace("[index]", "2")).Parent.GetChild<GUINumberInput>().IntValue;
1052  // Reverse, because the elements are created from right to left
1053  var anchor1Inputs = GetField(GetCharacterEditorTranslation("LimbWithIndexAnchor").Replace("[index]", "1")).Parent.GetAllChildren<GUINumberInput>().Reverse().ToArray();
1054  var anchor2Inputs = GetField(GetCharacterEditorTranslation("LimbWithIndexAnchor").Replace("[index]", "2")).Parent.GetAllChildren<GUINumberInput>().Reverse().ToArray();
1055  JointXElements.Add(new XElement("joint",
1056  new XAttribute("name", jointName),
1057  new XAttribute("limb1", limb1ID),
1058  new XAttribute("limb2", limb2ID),
1059  new XAttribute("limb1anchor", $"{anchor1Inputs[0].FloatValue.Format(2)}, {anchor1Inputs[1].FloatValue.Format(2)}"),
1060  new XAttribute("limb2anchor", $"{anchor2Inputs[0].FloatValue.Format(2)}, {anchor2Inputs[1].FloatValue.Format(2)}")));
1061  }
1062  }
1063 
1064  protected LimbType ParseLimbType(string limbName)
1065  {
1066  var limbType = LimbType.None;
1067  string n = limbName.ToLowerInvariant();
1068  switch (n)
1069  {
1070  case "head":
1071  limbType = LimbType.Head;
1072  break;
1073  case "torso":
1074  limbType = LimbType.Torso;
1075  break;
1076  case "waist":
1077  case "pelvis":
1078  limbType = LimbType.Waist;
1079  break;
1080  case "tail":
1081  limbType = LimbType.Tail;
1082  break;
1083  }
1084  if (limbType == LimbType.None)
1085  {
1086  if (n.Contains("tail"))
1087  {
1088  limbType = LimbType.Tail;
1089  }
1090  else if (n.Contains("arm") && !n.Contains("lower"))
1091  {
1092  if (n.Contains("right"))
1093  {
1094  limbType = LimbType.RightArm;
1095  }
1096  else if (n.Contains("left"))
1097  {
1098  limbType = LimbType.LeftArm;
1099  }
1100  }
1101  else if (n.Contains("hand") || n.Contains("palm"))
1102  {
1103  if (n.Contains("right"))
1104  {
1105  limbType = LimbType.RightHand;
1106  }
1107  else if (n.Contains("left"))
1108  {
1109  limbType = LimbType.LeftHand;
1110  }
1111  }
1112  else if (n.Contains("thigh") || n.Contains("upperleg"))
1113  {
1114  if (n.Contains("right"))
1115  {
1116  limbType = LimbType.RightThigh;
1117  }
1118  else if (n.Contains("left"))
1119  {
1120  limbType = LimbType.LeftThigh;
1121  }
1122  }
1123  else if (n.Contains("shin") || n.Contains("lowerleg"))
1124  {
1125  if (n.Contains("right"))
1126  {
1127  limbType = LimbType.RightLeg;
1128  }
1129  else if (n.Contains("left"))
1130  {
1131  limbType = LimbType.LeftLeg;
1132  }
1133  }
1134  else if (n.Contains("foot"))
1135  {
1136  if (n.Contains("right"))
1137  {
1138  limbType = LimbType.RightFoot;
1139  }
1140  else if (n.Contains("left"))
1141  {
1142  limbType = LimbType.LeftFoot;
1143  }
1144  }
1145  }
1146  return limbType;
1147  }
1148 
1149  public static bool IsValid(IEnumerable<XElement> elements, bool isHumanoid, out string missingType)
1150  {
1151  missingType = "none";
1152  if (!HasAtLeastOneLimbOfType(elements, "torso") && !HasAtLeastOneLimbOfType(elements, "head"))
1153  {
1154  missingType = "TorsoOrHead";
1155  return false;
1156  }
1157  if (isHumanoid)
1158  {
1159  if (!HasOnlyOneLimbOfType(elements, missingType = "LeftArm")) { return false; }
1160  if (!HasOnlyOneLimbOfType(elements, missingType = "LeftHand")) { return false; }
1161  if (!HasOnlyOneLimbOfType(elements, missingType = "RightArm")) { return false; }
1162  if (!HasOnlyOneLimbOfType(elements, missingType = "RightHand")) { return false; }
1163  if (!HasOnlyOneLimbOfType(elements, missingType = "Waist")) { return false; }
1164  if (!HasOnlyOneLimbOfType(elements, missingType = "LeftThigh")) { return false; }
1165  if (!HasOnlyOneLimbOfType(elements, missingType = "LeftLeg")) { return false; }
1166  if (!HasOnlyOneLimbOfType(elements, missingType = "LeftFoot")) { return false; }
1167  if (!HasOnlyOneLimbOfType(elements, missingType = "RightThigh")) { return false; }
1168  if (!HasOnlyOneLimbOfType(elements, missingType = "RightLeg")) { return false; }
1169  if (!HasOnlyOneLimbOfType(elements, missingType = "RightFoot")) { return false; }
1170  }
1171  return true;
1172  }
1173 
1174  public static bool HasAtLeastOneLimbOfType(IEnumerable<XElement> elements, string type) => elements.Any(e => IsType(e, type));
1175  public static bool HasOnlyOneLimbOfType(IEnumerable<XElement> elements, string type) => elements.Count(e => IsType(e, type)) == 1;
1176  private static bool IsType(XElement element, string type) => element.GetAttributeString("type", "").Equals(type, StringComparison.OrdinalIgnoreCase);
1177  }
1178  }
1179 }
bool CreateCharacter(Identifier name, string mainFolder, bool isHumanoid, ContentPackage contentPackage, XElement ragdoll, XElement config=null, IEnumerable< AnimationParams > animations=null)
static LocalizedString GetCharacterEditorTranslation(string tag)
static LocalizedString GetCharacterEditorTranslation(string text)
void CopyExisting(CharacterParams character, RagdollParams ragdoll, IEnumerable< AnimationParams > animations)
Definition: Wizard.cs:31
void CreateCharacter(XElement ragdollElement, XElement characterElement=null, IEnumerable< AnimationParams > animations=null)
Definition: Wizard.cs:98
IEnumerable< AnimationParams > SourceAnimations
Definition: Wizard.cs:29
CharacterParams SourceCharacter
Definition: Wizard.cs:27
Contains character data that should be editable in the character editor.
override ContentXElement? MainElement
static CharacterPrefab Find(Predicate< CharacterPrefab > predicate)
static readonly Identifier HumanSpeciesName
IEnumerable< ContentFile > GetFiles(Type type)
virtual ContentXElement? MainElement
List< GUIButton > Buttons
override GUIFont Font
Definition: GUITextBlock.cs:66
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
bool Serialize(XElement element=null, bool alsoChildren=true, bool recursive=true)
List< LimbParams > Limbs
CanEnterSubmarine CanEnterSubmarine
NumberType
Definition: Enums.cs:741