1 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
14 private Identifier name;
15 private bool isHumanoid;
18 private string texturePath;
19 private string xmlPath;
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>();
26 public bool IsCopy {
get;
private set; }
44 texturePath = ragdoll.
Limbs.FirstOrDefault()?.GetSprite().Texture;
65 CharacterView.Get().Release();
66 RagdollView.Get().Release();
71 private View activeView;
72 private Tab currentTab;
77 activeView?.Box.Close();
81 activeView = CharacterView.Get();
84 activeView = RagdollView.Get();
95 activeView?.Box.AddToGUIUpdateList();
98 public void CreateCharacter(XElement ragdollElement, XElement characterElement =
null, IEnumerable<AnimationParams> animations =
null)
102 bool isSamePackage = contentPackage.
GetFiles<
CharacterFile>().
Any(f => Path.GetFileNameWithoutExtension(f.Path.Value) == name);
106 UserData =
"verificationprompt"
108 msgBox.
Buttons[0].OnClicked = (_, userdata) =>
119 msgBox.Buttons[1].OnClicked = (_, userdata) =>
135 private class CharacterView : View
137 private static CharacterView
instance;
138 public static CharacterView Get() => Get(ref
instance);
140 public override void Release() =>
instance =
null;
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)),
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;
164 string pathBase = $
"{ContentPath.ModDirStr}/Characters/{Name}/{Name}";
165 XMLPath = $
"{pathBase}.xml";
166 xmlPathElement.Text = XMLPath;
167 if (updateTexturePath)
169 TexturePath = $
"{pathBase}.png";
170 texturePathElement.Text = TexturePath;
173 for (
int i = 0; i < 7; i++)
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);
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) =>
186 Name = ProcessText(text).ToIdentifier();
193 var tickBox =
new GUITickBox(
new RectTransform(
new Vector2(0.7f, 1), mainElement.RectTransform,
Anchor.CenterRight),
string.Empty)
197 OnSelected = (tB) => IsHumanoid = tB.Selected
199 if (!tickBox.Enabled)
201 label.TextColor *= 0.6f;
206 var t =
new GUITickBox(
new RectTransform(
new Vector2(0.7f, 1), mainElement.RectTransform,
Anchor.CenterRight),
string.Empty)
224 var txt =
new GUITickBox(
new RectTransform(
new Vector2(0.7f, 1), mainElement.RectTransform,
Anchor.CenterRight),
string.Empty)
228 OnSelected = (tB) => CanWalk = tB.Selected
232 lbl.TextColor *= 0.6f;
237 xmlPathElement =
new GUITextBox(
new RectTransform(
new Vector2(0.7f, 1), mainElement.RectTransform,
Anchor.CenterRight),
string.Empty)
240 CaretColor = Color.White
242 xmlPathElement.OnTextChanged += (tb, text) =>
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)
255 CaretColor = Color.White,
257 texturePathElement.OnTextChanged += (tb, text) =>
259 updateTexturePath =
false;
264 new GUIButton(
new RectTransform(
new Vector2(0.3f / texturePathElement.RectTransform.RelativeSize.X, 1.0f), texturePathElement.RectTransform,
Anchor.CenterRight,
Pivot.CenterLeft), title, style:
"GUIButtonSmall")
266 OnClicked = (button, data) =>
268 FileSelection.OnFileSelected = (file) =>
270 string relativePath = Path.GetRelativePath(Environment.CurrentDirectory, Path.GetFullPath(file));
272 if (relativePath.StartsWith(ContentPackage.LocalModsDir))
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)
279 if (modDirName == selectedModDir)
281 relativePath = ContentPath.ModDirStr +
"/" +
282 string.Join(
"/", pathSplit[2..]);
286 relativePath =
string.Format(ContentPath.OtherModDirFmt,
287 pathSplit[1]) +
"/" +
288 string.Join(
"/", pathSplit[2..]);
292 string destinationPath = relativePath;
295 if (relativePath.StartsWith(
"..") ||
296 Path.GetPathRoot(Environment.CurrentDirectory) != Path.GetPathRoot(file))
298 destinationPath = Path.Combine(Path.GetDirectoryName(XMLPath), Path.GetFileName(file));
300 string destinationDir = Path.GetDirectoryName(destinationPath);
301 if (!Directory.Exists(destinationDir))
303 Directory.CreateDirectory(destinationDir, catchUnauthorizedAccessExceptions:
true);
306 if (!File.Exists(destinationPath))
308 File.Copy(file, Path.GetFullPath(destinationPath), overwrite:
true);
312 isTextureSelected =
true;
313 texturePathElement.Text = destinationPath.CleanUpPath();
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;
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)
336 if (contentPackage != GameMain.VanillaContent)
338 contentPackageDropDown.AddItem(contentPackage.Name, userData: contentPackage, toolTip: contentPackage.Path);
341 contentPackageDropDown.OnSelected = (obj, userdata) =>
343 ContentPackage = userdata as ContentPackage;
344 updateTexturePath = !isTextureSelected && !
IsCopy;
348 contentPackageDropDown.Select(0);
349 var contentPackageNameElement =
new GUITextBox(
new RectTransform(
new Vector2(0.7f, 0.5f), rightContainer.RectTransform,
Anchor.BottomLeft),
352 CaretColor = Color.White,
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")
356 OnClicked = (btn, userdata) =>
358 if (
string.IsNullOrEmpty(contentPackageNameElement.Text))
360 contentPackageNameElement.Flash(useRectangleFlash:
true);
363 if (ContentPackageManager.AllPackages.Any(cp => cp.Name.ToLower() == contentPackageNameElement.Text.ToLower()))
365 new GUIMessageBox(
"", TextManager.Get(
"charactereditor.contentpackagenameinuse",
"leveleditorlevelobjnametaken"), type: GUIMessageBox.Type.Warning);
368 string modName = contentPackageNameElement.Text;
370 var modProject =
new ModProject { Name = modName };
371 ContentPackage = ContentPackageManager.LocalPackages.SaveAndEnableRegularMod(modProject);
373 contentPackageDropDown.AddItem(ContentPackage.Name, ContentPackage, ContentPackage.Path);
374 contentPackageDropDown.SelectItem(ContentPackage);
375 contentPackageNameElement.Text =
"";
380 Color textColor = contentPackageNameElement.TextColor;
381 contentPackageNameElement.TextColor *= 0.6f;
382 contentPackageNameElement.OnSelected += (sender, key) =>
384 contentPackageNameElement.Text =
"";
386 contentPackageNameElement.OnTextChanged += (textBox, text) =>
388 textBox.TextColor = textColor;
389 createNewPackageButton.Enabled = !
string.IsNullOrWhiteSpace(text);
392 rightContainer.RectTransform.MinSize =
new Point(0,
393 contentPackageDropDown.RectTransform.MinSize.Y + Math.Max(contentPackageNameElement.RectTransform.MinSize.Y, createNewPackageButton.RectTransform.MinSize.Y));
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)));
401 box.Buttons[0].Parent.RectTransform.SetAsLastChild();
402 box.Buttons[1].RectTransform.SetAsLastChild();
404 box.Buttons[0].OnClicked += (b, d) =>
406 Wizard.Instance.SelectTab(
Tab.None);
410 box.Buttons[1].OnClicked += (b, d) =>
412 if (ContentPackage ==
null)
414 contentPackageDropDown.Flash(useRectangleFlash:
true);
418 if (Name.Value.IsNullOrWhiteSpace())
420 nameField?.Flash(useRectangleFlash:
true);
424 string evaluatedTexturePath = ContentPath.FromRaw(
425 contentPackageDropDown.SelectedData as ContentPackage,
429 if (!File.Exists(evaluatedTexturePath))
432 texturePathElement.Flash(useRectangleFlash:
true);
436 var path = Path.GetFileName(evaluatedTexturePath);
437 if (!path.EndsWith(
".png", StringComparison.OrdinalIgnoreCase))
439 GUI.AddMessage(TextManager.Get(
"WrongFileType"), GUIStyle.Red);
440 texturePathElement.Flash(useRectangleFlash:
true);
443 if (Name == CharacterPrefab.HumanSpeciesName && !
IsCopy)
446 if (!CharacterEditorScreen.Instance.SpawnedCharacter.IsHuman)
448 CharacterEditorScreen.Instance.SpawnCharacter(CharacterPrefab.HumanSpeciesName);
450 CharacterEditorScreen.Instance.PrepareCharacterCopy();
470 private class RagdollView : View
472 private static RagdollView
instance;
473 public static RagdollView Get() => Get(ref
instance);
475 public override void Release() =>
instance =
null;
477 protected override GUIMessageBox Create()
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)
489 var content =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.9f), frame.RectTransform,
Anchor.TopCenter), childAnchor:
Anchor.TopCenter)
492 RelativeSpacing = 0.02f
495 var limbsElement =
new GUIFrame(
new RectTransform(
new Vector2(1, 0.05f), content.RectTransform), style:
null) { CanBeFocused =
false };
497 var limbEditLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, limbsElement.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
500 RelativeSpacing = 0.02f
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))
505 PlaySoundOnSelect =
true,
507 var limbButtonSize = Vector2.One * 0.8f;
508 var removeLimbButton =
new GUIButton(
new RectTransform(limbButtonSize, limbEditLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
"GUIMinusButton")
510 OnClicked = (b, d) =>
512 var element = LimbGUIElements.LastOrDefault();
513 if (element ==
null) {
return false; }
514 element.RectTransform.Parent =
null;
515 LimbGUIElements.Remove(element);
519 var addLimbButton =
new GUIButton(
new RectTransform(limbButtonSize, limbEditLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
"GUIPlusButton")
521 OnClicked = (b, d) =>
524 switch (LimbGUIElements.Count)
533 CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize,
id: LimbGUIElements.Count, limbType: limbType);
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)
542 RelativeSpacing = 0.01f
544 for (
int i = 3; i >= 0; i--)
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)
550 Font = GUIStyle.SmallFont
556 numberInput.IntValue = 1;
557 numberInput.MinValueInt = 1;
558 numberInput.MaxValueInt = 100;
562 numberInput.IntValue = 100;
563 numberInput.MinValueInt = 0;
564 numberInput.MaxValueInt = 999;
569 numberInput.OnValueChanged += (numInput) =>
574 _x = numInput.IntValue;
577 _y = numInput.IntValue;
580 w = numInput.IntValue;
583 h = numInput.IntValue;
588 inputArea.Recalculate();
589 new GUIButton(
new RectTransform(
new Vector2(0.15f, 1.0f), limbEditLayout.RectTransform),
592 OnClicked = (b, d) =>
594 CreateMultipleLimbs(_x, _y);
598 limbsElement.RectTransform.MinSize =
new Point(0, limbEditLayout.RectTransform.Children.Max(c => c.MinSize.Y));
601 if (LimbGUIElements.None())
605 CreateMultipleLimbs(2, 6);
607 CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize,
id: LimbGUIElements.Count, limbType:
LimbType.Waist, sourceRect:
new Rectangle(_x, h * LimbGUIElements.Count / 2, w, h));
611 CreateMultipleLimbs(1, 2);
614 void CreateMultipleLimbs(
int x,
int y)
616 for (
int i = 0; i < x; i++)
618 for (
int j = 0; j < y; j++)
621 switch (LimbGUIElements.Count)
632 switch (LimbGUIElements.Count)
669 CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize,
id: LimbGUIElements.Count, limbType: limbType, sourceRect:
new Rectangle(i * w, j * h, w, h));
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)
679 RelativeOffset = new Vector2(0.15f, 0)
684 var jointsList =
new GUIListBox(
new RectTransform(
new Vector2(1, 0.45f), content.RectTransform))
686 PlaySoundOnSelect =
true,
688 var removeJointButton =
new GUIButton(
new RectTransform(
new Point(jointButtonElement.Rect.Height, jointButtonElement.Rect.Height), jointButtonElement.RectTransform), style:
"GUIMinusButton")
690 OnClicked = (b, d) =>
692 var element = JointGUIElements.LastOrDefault();
693 if (element ==
null) {
return false; }
694 element.RectTransform.Parent =
null;
695 JointGUIElements.Remove(element);
699 var addJointButton =
new GUIButton(
new RectTransform(
new Point(jointButtonElement.Rect.Height), jointButtonElement.RectTransform)
701 AbsoluteOffset = new Point(removeJointButton.Rect.Width + 10, 0)
702 }, style:
"GUIPlusButton")
704 OnClicked = (b, d) =>
706 CreateJointGUIElement(jointsList.Content.RectTransform, elementSize);
711 box.Buttons[0].OnClicked += (b, d) =>
713 Wizard.Instance.SelectTab(
Tab.Character);
717 box.Buttons[1].OnClicked += (b, d) =>
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();
730 if (!IsValid(LimbXElements.Values,
true, out
string missingType))
732 GUI.AddMessage(
GetCharacterEditorTranslation(
"MissingLimbType").Replace(
"[limbtype]", missingType.FormatCamelCaseWithSpaces()), GUIStyle.Red);
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;
747 colliderHeight = width - radius * 2;
754 colliderHeight = height - radius * 2;
759 colliderHeight = width - radius * 2;
762 radius = Math.Max(radius, 1);
764 else if (height > -1 || width > -1)
767 colliderHeight = width > height ? width : height;
769 var colliderAttributes =
new List<XAttribute>() {
new XAttribute(
"radius", radius) };
770 if (colliderHeight > -1)
772 colliderHeight = Math.Max(colliderHeight, 1);
775 colliderAttributes.Add(
new XAttribute(
"height", colliderHeight));
779 colliderAttributes.Add(
new XAttribute(
"width", colliderHeight));
782 var colliderElements =
new List<XElement>() {
new XElement(
"collider", colliderAttributes) };
786 var secondaryCollider =
new XElement(
"collider",
new XAttribute(
"radius", radius));
787 if (colliderHeight > -1)
789 colliderHeight = Math.Max(colliderHeight, 1);
792 secondaryCollider.Add(
new XAttribute(
"height", colliderHeight * 0.75f));
796 secondaryCollider.Add(
new XAttribute(
"width", colliderHeight * 0.75f));
799 colliderElements.Add(secondaryCollider);
801 var mainElement =
new XElement(
"Ragdoll",
802 new XAttribute(
"type", Name),
803 new XAttribute(
"texture", TexturePath),
805 new XAttribute(
"canwalk", CanWalk),
807 LimbXElements.Values,
809 Wizard.Instance.CreateCharacter(mainElement);
815 private void CreateLimbGUIElement(RectTransform parent,
int elementSize,
int id,
string name =
"",
LimbType limbType =
LimbType.None, Rectangle? sourceRect =
null)
817 var limbElement =
new GUIFrame(
new RectTransform(
new Point(parent.Rect.Width, elementSize * 5 + 40), parent), style:
null, color: Color.Gray * 0.25f)
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);
828 new GUINumberInput(
new RectTransform(
new Vector2(0.5f, 1), idField.RectTransform,
Anchor.TopRight),
NumberType.Int)
831 MaxValueInt =
byte.MaxValue,
833 OnValueChanged = numInput =>
835 id = numInput.IntValue;
836 string text = nameField.GetChild<GUITextBox>().Text;
837 string t =
string.IsNullOrWhiteSpace(text) ?
id.ToString() : text;
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)
844 CaretColor = Color.White,
846 nameInput.OnTextChanged += (tb, text) =>
848 string t =
string.IsNullOrWhiteSpace(text) ?
id.ToString() : text;
852 LimbGUIElements.Add(limbElement);
855 private void CreateJointGUIElement(RectTransform parent,
int elementSize,
int id1 = 0,
int id2 = 1, Vector2? anchor1 =
null, Vector2? anchor2 =
null,
string jointName =
"")
857 var jointElement =
new GUIFrame(
new RectTransform(
new Point(parent.Rect.Width, elementSize * 6 + 40), parent), style:
null, color: Color.Gray * 0.25f)
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)
867 CaretColor = Color.White,
869 nameInput.OnTextChanged += (textB, text) =>
872 label.Text = jointName;
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)
880 MaxValueInt =
byte.MaxValue,
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)
888 MaxValueInt =
byte.MaxValue,
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;
901 private abstract class View
910 public Identifier Name
915 public bool IsHumanoid
923 set =>
Instance.canEnterSubmarine = value;
930 public ContentPackage ContentPackage
933 set =>
Instance.contentPackage = value;
935 public string TexturePath
938 set =>
Instance.texturePath = value;
940 public string XMLPath
945 public Dictionary<string, XElement> LimbXElements
948 set =>
Instance.limbXElements = value;
950 public List<GUIComponent> LimbGUIElements
953 set =>
Instance.limbGUIElements = value;
955 public List<XElement> JointXElements
958 set =>
Instance.jointXElements = value;
960 public List<GUIComponent> JointGUIElements
963 set =>
Instance.jointGUIElements = value;
966 private GUIMessageBox box;
967 public GUIMessageBox Box
979 protected abstract GUIMessageBox Create();
980 protected static T Get<T>(ref T
instance) where T : View,
new()
989 public abstract void Release();
991 protected void ParseLimbsFromGUIElements()
993 LimbXElements.Clear();
994 for (
int i = 0; i < LimbGUIElements.Count; i++)
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;
1000 string limbName = GetField(TextManager.Get(
"Name")).Parent.GetChild<GUITextBox>().Text;
1004 int width = rectInputs[2].IntValue;
1005 int height = rectInputs[3].IntValue;
1006 var colliderAttributes =
new List<XAttribute>();
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()),
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)
1041 protected void ParseJointsFromGUIElements()
1043 JointXElements.Clear();
1044 for (
int i = 0; i < JointGUIElements.Count; i++)
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;
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)}")));
1064 protected LimbType ParseLimbType(
string limbName)
1067 string n = limbName.ToLowerInvariant();
1086 if (n.Contains(
"tail"))
1090 else if (n.Contains(
"arm") && !n.Contains(
"lower"))
1092 if (n.Contains(
"right"))
1096 else if (n.Contains(
"left"))
1101 else if (n.Contains(
"hand") || n.Contains(
"palm"))
1103 if (n.Contains(
"right"))
1107 else if (n.Contains(
"left"))
1112 else if (n.Contains(
"thigh") || n.Contains(
"upperleg"))
1114 if (n.Contains(
"right"))
1118 else if (n.Contains(
"left"))
1123 else if (n.Contains(
"shin") || n.Contains(
"lowerleg"))
1125 if (n.Contains(
"right"))
1129 else if (n.Contains(
"left"))
1134 else if (n.Contains(
"foot"))
1136 if (n.Contains(
"right"))
1140 else if (n.Contains(
"left"))
1149 public static bool IsValid(IEnumerable<XElement> elements,
bool isHumanoid, out
string missingType)
1151 missingType =
"none";
1152 if (!HasAtLeastOneLimbOfType(elements,
"torso") && !HasAtLeastOneLimbOfType(elements,
"head"))
1154 missingType =
"TorsoOrHead";
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; }
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);
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 CharacterEditorScreen Instance
static LocalizedString GetCharacterEditorTranslation(string text)
void CopyExisting(CharacterParams character, RagdollParams ragdoll, IEnumerable< AnimationParams > animations)
void CreateCharacter(XElement ragdollElement, XElement characterElement=null, IEnumerable< AnimationParams > animations=null)
IEnumerable< AnimationParams > SourceAnimations
CharacterParams SourceCharacter
void AddToGUIUpdateList()
RagdollParams SourceRagdoll
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
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
bool Serialize(XElement element=null, bool alsoChildren=true, bool recursive=true)
CanEnterSubmarine CanEnterSubmarine