Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs
2019-07-27 20:20:43 +03:00

5871 lines
302 KiB
C#

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using FarseerPhysics;
namespace Barotrauma
{
class CharacterEditorScreen : Screen
{
private static CharacterEditorScreen instance;
private Camera cam;
public override Camera Cam
{
get
{
if (cam == null)
{
cam = new Camera()
{
MinZoom = 0.1f,
MaxZoom = 5.0f
};
}
return cam;
}
}
private bool ShowExtraRagdollControls => selectedLimbs.Any() && editLimbs || selectedJoints.Any() && editJoints;
private Character character;
private Vector2 spawnPosition;
private bool editAnimations;
private bool editLimbs;
private bool editJoints;
private bool editIK;
private bool editRagdoll;
private bool showParamsEditor;
private bool showSpritesheet;
private bool isFreezed;
private bool autoFreeze;
private bool limbPairEditing;
private bool uniformScaling;
private bool lockSpriteOrigin;
private bool lockSpritePosition;
private bool lockSpriteSize;
private bool recalculateCollider;
private bool copyJointSettings;
private bool showColliders;
private bool displayWearables;
private bool displayBackgroundColor;
private bool ragdollResetRequiresForceLoading;
private bool animationResetRequiresForceLoading;
private bool jointCreationMode;
private bool useMouseOffset;
private bool isExtrudingJoint;
private bool isDrawingJoint;
private Limb closestSelectedLimb;
private Limb targetLimb;
private Vector2? anchor1Pos;
private float spriteSheetZoom = 1;
private float spriteSheetMinZoom = 0.25f;
private float spriteSheetMaxZoom = 1;
private int spriteSheetOffsetY = 20;
private int spriteSheetOffsetX;
private bool hideBodySheet;
private Color backgroundColor = new Color(0.2f, 0.2f, 0.2f, 1.0f);
private Vector2 cameraOffset;
private List<LimbJoint> selectedJoints = new List<LimbJoint>();
private List<Limb> selectedLimbs = new List<Limb>();
private bool isEndlessRunner;
private Rectangle spriteSheetRect;
private Rectangle CalculateSpritesheetRectangle() =>
new Rectangle(
spriteSheetOffsetX,
spriteSheetOffsetY,
(int)(Textures.OrderByDescending(t => t.Width).First().Width * spriteSheetZoom),
(int)(Textures.Sum(t => t.Height) * spriteSheetZoom));
private const string screenTextTag = "CharacterEditor.";
public override void Select()
{
base.Select();
SoundPlayer.OverrideMusicType = "none";
SoundPlayer.OverrideMusicDuration = null;
GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", 0.0f);
GUI.ForceMouseOn(null);
CalculateSpritesheetPosition();
if (Submarine.MainSub == null)
{
ResetVariables();
Submarine.MainSub = new Submarine("Content/AnimEditor.sub");
Submarine.MainSub.Load(unloadPrevious: false, showWarningMessages: false);
originalWall = new WallGroup(new List<Structure>(Structure.WallList));
CloneWalls();
CalculateMovementLimits();
isEndlessRunner = true;
GameMain.LightManager.LightingEnabled = false;
}
else if (instance == null)
{
ResetVariables();
}
Submarine.MainSub.GodMode = true;
if (Character.Controlled == null)
{
SpawnCharacter(Character.HumanConfigFile);
//SpawnCharacter(AllFiles.First());
}
else
{
OnPreSpawn();
character = Character.Controlled;
OnPostSpawn();
}
OpenDoors();
GameMain.Instance.OnResolutionChanged += OnResolutionChanged;
instance = this;
if (!GameMain.Config.EditorDisclaimerShown)
{
GameMain.Instance.ShowEditorDisclaimer();
}
}
private void ResetVariables()
{
editAnimations = false;
editLimbs = false;
editJoints = false;
editIK = false;
editRagdoll = false;
showParamsEditor = false;
showSpritesheet = false;
isFreezed = false;
autoFreeze = true;
limbPairEditing = true;
uniformScaling = true;
lockSpriteOrigin = false;
lockSpritePosition = false;
lockSpriteSize = false;
recalculateCollider = false;
copyJointSettings = false;
showColliders = false;
displayWearables = true;
displayBackgroundColor = false;
ragdollResetRequiresForceLoading = false;
animationResetRequiresForceLoading = false;
jointCreationMode = false;
isExtrudingJoint = false;
isDrawingJoint = false;
cameraOffset = Vector2.Zero;
targetLimb = null;
anchor1Pos = null;
useMouseOffset = false;
closestSelectedLimb = null;
allFiles = null;
Wizard.instance?.Reset();
}
private void Reset()
{
ResetVariables();
if (character != null)
{
AnimParams.ForEach(a => a.Reset(true));
RagdollParams.Reset(true);
RagdollParams.ClearHistory();
CurrentAnimation.ClearHistory();
if (!character.Removed)
{
character.Remove();
}
character = null;
}
}
public override void Deselect()
{
base.Deselect();
SoundPlayer.OverrideMusicType = null;
GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", GameMain.Config.SoundVolume);
GUI.ForceMouseOn(null);
if (isEndlessRunner)
{
Submarine.MainSub.Remove();
isEndlessRunner = false;
Reset();
GameMain.World.ProcessChanges();
}
else
{
if (character != null)
{
character.ForceRun = false;
character.AnimController.ForceSelectAnimationType = AnimationType.NotDefined;
}
}
GameMain.Instance.OnResolutionChanged -= OnResolutionChanged;
GameMain.LightManager.LightingEnabled = true;
ClearWidgets();
ClearSelection();
}
private void OnResolutionChanged()
{
CreateGUI();
CalculateSpritesheetPosition();
}
private static string GetCharacterEditorTranslation(string tag)
{
return TextManager.Get(screenTextTag + tag);
}
#region Main methods
public override void AddToGUIUpdateList()
{
//base.AddToGUIUpdateList();
fileEditPanel.AddToGUIUpdateList();
modesPanel.AddToGUIUpdateList();
toolsPanel.AddToGUIUpdateList();
optionsPanel.AddToGUIUpdateList();
characterSelectionPanel.AddToGUIUpdateList();
Wizard.instance?.AddToGUIUpdateList();
if (displayBackgroundColor)
{
backgroundColorPanel.AddToGUIUpdateList();
}
if (editAnimations)
{
animationControls.AddToGUIUpdateList();
}
if (showSpritesheet)
{
spriteSheetControls.AddToGUIUpdateList();
}
if (editRagdoll)
{
ragdollControls.AddToGUIUpdateList();
}
if (editJoints)
{
jointControls.AddToGUIUpdateList();
}
if (editLimbs)
{
limbControls.AddToGUIUpdateList();
}
if (showParamsEditor)
{
ParamsEditor.Instance.EditorBox.Parent.AddToGUIUpdateList();
}
if (ShowExtraRagdollControls)
{
extraRagdollControls.AddToGUIUpdateList();
}
}
public override void Update(double deltaTime)
{
base.Update(deltaTime);
spriteSheetRect = CalculateSpritesheetRectangle();
// Handle shortcut keys
if (GUI.KeyboardDispatcher.Subscriber == null && Wizard.instance == null)
{
if (PlayerInput.KeyHit(Keys.D1))
{
SetToggle(editLimbsToggle, true);
}
else if (PlayerInput.KeyHit(Keys.D2))
{
SetToggle(jointsToggle, true);
}
else if (PlayerInput.KeyHit(Keys.D3))
{
SetToggle(editAnimsToggle, true);
}
if (PlayerInput.KeyDown(Keys.LeftControl))
{
Character.DisableControls = true;
Widget.EnableMultiSelect = !editAnimations;
// Undo/Redo
if (PlayerInput.KeyHit(Keys.Z))
{
if (editJoints || editLimbs || editIK)
{
RagdollParams.Undo();
character.AnimController.ResetJoints();
character.AnimController.ResetLimbs();
ClearWidgets();
CreateGUI();
//ragdollResetRequiresForceLoading = true;
ResetParamsEditor();
}
if (editAnimations)
{
CurrentAnimation.Undo();
ClearWidgets();
ResetParamsEditor();
//CreateGUI();
animationResetRequiresForceLoading = true;
}
}
else if (PlayerInput.KeyHit(Keys.R))
{
if (editJoints || editLimbs || editIK)
{
RagdollParams.Redo();
character.AnimController.ResetJoints();
character.AnimController.ResetLimbs();
ClearWidgets();
CreateGUI();
//ragdollResetRequiresForceLoading = true;
ResetParamsEditor();
}
if (editAnimations)
{
CurrentAnimation.Redo();
ClearWidgets();
ResetParamsEditor();
//CreateGUI();
animationResetRequiresForceLoading = true;
}
}
}
else
{
Widget.EnableMultiSelect = false;
}
if (PlayerInput.KeyHit(Keys.C) && !PlayerInput.KeyDown(Keys.LeftControl))
{
copyJointsToggle.Selected = !copyJointsToggle.Selected;
}
if (character.IsHumanoid)
{
if (PlayerInput.KeyHit(Keys.T) || PlayerInput.KeyHit(Keys.X))
{
animTestPoseToggle.Selected = !animTestPoseToggle.Selected;
}
}
if (PlayerInput.KeyHit(InputType.Run))
{
// TODO: refactor this horrible hacky index manipulation mess
int index = 0;
bool isSwimming = character.AnimController.ForceSelectAnimationType == AnimationType.SwimFast || character.AnimController.ForceSelectAnimationType == AnimationType.SwimSlow;
bool isMovingFast = character.AnimController.ForceSelectAnimationType == AnimationType.Run || character.AnimController.ForceSelectAnimationType == AnimationType.SwimFast;
if (isMovingFast)
{
if (isSwimming || !character.AnimController.CanWalk)
{
index = !character.AnimController.CanWalk ? 0 : (int)AnimationType.SwimSlow - 1;
}
else
{
index = (int)AnimationType.Walk - 1;
}
}
else
{
if (isSwimming || !character.AnimController.CanWalk)
{
index = !character.AnimController.CanWalk ? 1 : (int)AnimationType.SwimFast - 1;
}
else
{
index = (int)AnimationType.Run - 1;
}
}
if (animSelection.SelectedIndex != index)
{
CurrentAnimation.ClearHistory();
animSelection.Select(index);
CurrentAnimation.CreateSnapshot();
}
}
if (!PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.E))
{
bool isSwimming = character.AnimController.ForceSelectAnimationType == AnimationType.SwimFast || character.AnimController.ForceSelectAnimationType == AnimationType.SwimSlow;
if (isSwimming)
{
animSelection.Select((int)AnimationType.Walk - 1);
}
else
{
animSelection.Select((int)AnimationType.SwimSlow - 1);
}
}
if (PlayerInput.KeyHit(Keys.F))
{
SetToggle(freezeToggle, !freezeToggle.Selected);
}
if (PlayerInput.RightButtonClicked() || PlayerInput.KeyHit(Keys.Escape))
{
bool reset = false;
if (selectedLimbs.Any())
{
selectedLimbs.Clear();
reset = true;
}
if (selectedJoints.Any())
{
selectedJoints.Clear();
foreach (var w in jointSelectionWidgets.Values)
{
w.refresh();
w.linkedWidget?.refresh();
}
reset = true;
}
if (reset)
{
ResetParamsEditor();
}
jointCreationMode = false;
closestSelectedLimb = null;
}
if (PlayerInput.KeyHit(Keys.Delete))
{
DeleteSelected();
}
if (editLimbs && PlayerInput.KeyDown(Keys.LeftControl))
{
var selectedLimb = selectedLimbs.FirstOrDefault();
if (selectedLimb != null)
{
if (PlayerInput.KeyHit(Keys.C))
{
CopyLimb(selectedLimb);
}
}
}
if (ShowExtraRagdollControls && PlayerInput.KeyDown(Keys.LeftControl))
{
if (PlayerInput.KeyHit(Keys.E))
{
jointCreationMode = !jointCreationMode;
useMouseOffset = true;
}
}
if (jointCreationMode)
{
createJointButton.HoverColor = Color.LightGreen;
createJointButton.Color = Color.LightGreen;
}
else
{
createJointButton.HoverColor = Color.White;
createJointButton.Color = Color.White;
}
UpdateJointCreation();
if (PlayerInput.KeyHit(Keys.Left))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.X--;
UpdateSourceRect(limb, newRect);
}
}
if (PlayerInput.KeyHit(Keys.Right))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.X++;
UpdateSourceRect(limb, newRect);
}
}
if (PlayerInput.KeyHit(Keys.Down))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.Y++;
UpdateSourceRect(limb, newRect);
}
}
if (PlayerInput.KeyHit(Keys.Up))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.Y--;
UpdateSourceRect(limb, newRect);
}
}
}
if (!isFreezed && Wizard.instance == null)
{
if (character.AnimController.Invalid)
{
Reset();
SpawnCharacter(currentCharacterConfig);
}
Submarine.MainSub.SetPrevTransform(Submarine.MainSub.Position);
Submarine.MainSub.Update((float)deltaTime);
foreach (PhysicsBody body in PhysicsBody.List)
{
body.SetPrevTransform(body.SimPosition, body.Rotation);
body.Update((float)deltaTime);
}
// Handle ragdolling here, because we are not calling the Character.Update() method.
if (!Character.DisableControls)
{
character.IsRagdolled = PlayerInput.KeyDown(InputType.Ragdoll);
}
if (character.IsRagdolled)
{
character.AnimController.ResetPullJoints();
}
character.ControlLocalPlayer((float)deltaTime, Cam, false);
character.Control((float)deltaTime, Cam);
character.AnimController.UpdateAnim((float)deltaTime);
character.AnimController.Update((float)deltaTime, Cam);
character.CurrentHull = character.AnimController.CurrentHull;
if (isEndlessRunner)
{
if (character.Position.X < min)
{
UpdateWalls(false);
}
else if (character.Position.X > max)
{
UpdateWalls(true);
}
}
GameMain.World.Step((float)deltaTime);
}
// Camera
Cam.MoveCamera((float)deltaTime, allowMove: false);
Vector2 targetPos = character.WorldPosition;
if (PlayerInput.MidButtonHeld())
{
// Pan
Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 100.0f / Cam.Zoom;
moveSpeed.X = -moveSpeed.X;
cameraOffset += moveSpeed;
Vector2 max = new Vector2(GameMain.GraphicsWidth * 0.3f, GameMain.GraphicsHeight * 0.38f) / Cam.Zoom;
Vector2 min = -max;
cameraOffset = Vector2.Clamp(cameraOffset, min, max);
}
Cam.Position = targetPos + cameraOffset;
MapEntity.mapEntityList.ForEach(e => e.IsHighlighted = false);
// Update widgets
jointSelectionWidgets.Values.ForEach(w => w.Update((float)deltaTime));
limbEditWidgets.Values.ForEach(w => w.Update((float)deltaTime));
animationWidgets.Values.ForEach(w => w.Update((float)deltaTime));
// Handle limb selection
if (editLimbs && PlayerInput.LeftButtonDown() && GUI.MouseOn == null && Widget.selectedWidgets.None())
{
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb == null || limb.ActiveSprite == null) { continue; }
// Select limbs on ragdoll
if (!spriteSheetRect.Contains(PlayerInput.MousePosition) && MathUtils.RectangleContainsPoint(GetLimbPhysicRect(limb), PlayerInput.MousePosition))
{
HandleLimbSelection(limb);
}
// Select limbs on sprite sheet
if (GetLimbSpritesheetRect(limb).Contains(PlayerInput.MousePosition))
{
HandleLimbSelection(limb);
}
}
}
optionsToggle?.UpdateOpenState((float)deltaTime, new Vector2(optionsPanel.Rect.Width + rightArea.RectTransform.AbsoluteOffset.X, 0), optionsPanel.RectTransform);
fileEditToggle?.UpdateOpenState((float)deltaTime, new Vector2(-fileEditPanel.Rect.Width - rightArea.RectTransform.AbsoluteOffset.X, 0), fileEditPanel.RectTransform);
characterPanelToggle?.UpdateOpenState((float)deltaTime, new Vector2(-characterSelectionPanel.Rect.Width - rightArea.RectTransform.AbsoluteOffset.X, 0), characterSelectionPanel.RectTransform);
modesToggle?.UpdateOpenState((float)deltaTime, new Vector2(-modesPanel.Rect.Width - leftArea.RectTransform.AbsoluteOffset.X, 0), modesPanel.RectTransform);
toolsToggle?.UpdateOpenState((float)deltaTime, new Vector2(-toolsPanel.Rect.Width - leftArea.RectTransform.AbsoluteOffset.X, 0), toolsPanel.RectTransform);
}
/// <summary>
/// Fps independent mouse input. The draw method is called multiple times per frame.
/// </summary>
private Vector2 scaledMouseSpeed;
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{
if (isFreezed)
{
Timing.Alpha = 0.0f;
}
scaledMouseSpeed = PlayerInput.MouseSpeedPerSecond * (float)deltaTime;
Cam.UpdateTransform(true);
Submarine.CullEntities(Cam);
Submarine.MainSub.UpdateTransform();
// Lightmaps
if (GameMain.LightManager.LightingEnabled)
{
GameMain.LightManager.ObstructVision = Character.Controlled.ObstructVision;
GameMain.LightManager.UpdateLightMap(graphics, spriteBatch, cam);
GameMain.LightManager.UpdateObstructVision(graphics, spriteBatch, cam, Character.Controlled.CursorWorldPosition);
}
base.Draw(deltaTime, graphics, spriteBatch);
graphics.Clear(backgroundColor);
// Submarine
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, transformMatrix: Cam.Transform);
Submarine.Draw(spriteBatch, isEndlessRunner);
spriteBatch.End();
// Character(s)
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, transformMatrix: Cam.Transform);
Character.CharacterList.ForEach(c => c.Draw(spriteBatch, Cam));
if (GameMain.DebugDraw)
{
character.AnimController.DebugDraw(spriteBatch);
}
else if (showColliders)
{
character.AnimController.Collider.DebugDraw(spriteBatch, Color.White, forceColor: true);
character.AnimController.Limbs.ForEach(l => l.body.DebugDraw(spriteBatch, Color.LightGreen, forceColor: true));
}
spriteBatch.End();
// Lights
if (GameMain.LightManager.LightingEnabled)
{
spriteBatch.Begin(SpriteSortMode.Deferred, Lights.CustomBlendStates.Multiplicative, null, DepthStencilState.None, null, null, null);
spriteBatch.Draw(GameMain.LightManager.LightMap, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
spriteBatch.End();
}
// GUI
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
if (editAnimations)
{
DrawAnimationControls(spriteBatch, (float)deltaTime);
}
if (editLimbs)
{
DrawLimbEditor(spriteBatch);
}
if (editRagdoll || editJoints || editLimbs)
{
DrawRagdoll(spriteBatch, (float)deltaTime);
}
if (showSpritesheet)
{
DrawSpritesheetEditor(spriteBatch, (float)deltaTime);
}
if (jointCreationMode)
{
var textPos = new Vector2(GameMain.GraphicsWidth / 2 - 120, GameMain.GraphicsHeight / 4);
if (isExtrudingJoint)
{
var selectedJoint = selectedJoints.LastOrDefault();
if (selectedJoint != null)
{
GUI.DrawString(spriteBatch, textPos, GetCharacterEditorTranslation("CreatingNewJoint"), Color.White, font: GUI.LargeFont);
if (spriteSheetRect.Contains(PlayerInput.MousePosition))
{
var startPos = GetLimbSpritesheetRect(selectedJoint.LimbB).Center.ToVector2();
var offset = ConvertUnits.ToDisplayUnits(selectedJoint.LocalAnchorB) * spriteSheetZoom;
offset.Y = -offset.Y;
DrawJointCreationOnSpritesheet(spriteBatch, startPos + offset);
}
else
{
DrawJointCreationOnRagdoll(spriteBatch, SimToScreen(selectedJoint.WorldAnchorB));
}
}
}
else if (isDrawingJoint)
{
if (closestSelectedLimb != null)
{
GUI.DrawString(spriteBatch, textPos, GetCharacterEditorTranslation("CreatingNewJoint"), Color.White, font: GUI.LargeFont);
if (spriteSheetRect.Contains(PlayerInput.MousePosition))
{
var startPos = GetLimbSpritesheetRect(closestSelectedLimb).Center.ToVector2();
if (anchor1Pos.HasValue)
{
var offset = anchor1Pos.Value;
offset = -offset;
startPos += offset;
}
DrawJointCreationOnSpritesheet(spriteBatch, startPos);
}
else
{
var startPos = anchor1Pos.HasValue
? SimToScreen(closestSelectedLimb.SimPosition + Vector2.Transform(ConvertUnits.ToSimUnits(anchor1Pos.Value), Matrix.CreateRotationZ(closestSelectedLimb.Rotation)))
: SimToScreen(closestSelectedLimb.SimPosition);
DrawJointCreationOnRagdoll(spriteBatch, startPos);
}
}
}
}
if (isEndlessRunner)
{
Structure wall = CurrentWall.walls.FirstOrDefault();
Vector2 indicatorPos = wall == null ? originalWall.walls.First().DrawPosition : wall.DrawPosition;
GUI.DrawIndicator(spriteBatch, indicatorPos, Cam, 700, GUI.SubmarineIcon, Color.White);
}
GUI.Draw(Cam, spriteBatch);
if (isFreezed)
{
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2 - 35, 200), GetCharacterEditorTranslation("Frozen"), Color.Blue, Color.White * 0.5f, 10, GUI.Font);
}
if (animTestPoseToggle.Selected)
{
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2 - 100, 300), GetCharacterEditorTranslation("AnimationTestPoseEnabled"), Color.Blue, Color.White * 0.5f, 10, GUI.Font);
}
if (showSpritesheet)
{
var topLeft = spriteSheetControls.RectTransform.TopLeft;
GUI.DrawString(spriteBatch, new Vector2(topLeft.X + 300, GameMain.GraphicsHeight - 80), GetCharacterEditorTranslation("SpriteSheetOrientation") + ":", Color.White, Color.Gray * 0.5f, 10, GUI.Font);
DrawRadialWidget(spriteBatch, new Vector2(topLeft.X + 510, GameMain.GraphicsHeight - 60), RagdollParams.SpritesheetOrientation, string.Empty, Color.White,
angle => TryUpdateRagdollParam("spritesheetorientation", angle), circleRadius: 40, widgetSize: 15, rotationOffset: MathHelper.Pi, autoFreeze: false);
}
// Debug
if (GameMain.DebugDraw)
{
// Limb positions
foreach (Limb limb in character.AnimController.Limbs)
{
Vector2 limbDrawPos = Cam.WorldToScreen(limb.WorldPosition);
GUI.DrawLine(spriteBatch, limbDrawPos + Vector2.UnitY * 5.0f, limbDrawPos - Vector2.UnitY * 5.0f, Color.White);
GUI.DrawLine(spriteBatch, limbDrawPos + Vector2.UnitX * 5.0f, limbDrawPos - Vector2.UnitX * 5.0f, Color.White);
}
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, 0), $"Cursor World Pos: {character.CursorWorldPosition}", Color.White, font: GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, 20), $"Cursor Pos: {character.CursorPosition}", Color.White, font: GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, 40), $"Cursor Screen Pos: {PlayerInput.MousePosition}", Color.White, font: GUI.SmallFont);
// Collider
var collider = character.AnimController.Collider;
var colliderDrawPos = SimToScreen(collider.SimPosition);
Vector2 forward = Vector2.Transform(Vector2.UnitY, Matrix.CreateRotationZ(collider.Rotation));
var endPos = SimToScreen(collider.SimPosition + forward * collider.radius);
GUI.DrawLine(spriteBatch, colliderDrawPos, endPos, Color.LightGreen);
GUI.DrawLine(spriteBatch, colliderDrawPos, SimToScreen(collider.SimPosition + forward * 0.25f), Color.Blue);
Vector2 left = forward.Left();
GUI.DrawLine(spriteBatch, colliderDrawPos, SimToScreen(collider.SimPosition + left * 0.25f), Color.Red);
ShapeExtensions.DrawCircle(spriteBatch, colliderDrawPos, (endPos - colliderDrawPos).Length(), 40, Color.LightGreen);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 300, 0), $"Collider rotation: {MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(collider.Rotation))}", Color.White, font: GUI.SmallFont);
}
spriteBatch.End();
}
#endregion
#region Ragdoll Manipulation
private void UpdateJointCreation()
{
isExtrudingJoint = !editLimbs && editJoints && jointCreationMode;
isDrawingJoint = !editJoints && editLimbs && jointCreationMode;
if (isExtrudingJoint)
{
var selectedJoint = selectedJoints.LastOrDefault();
if (selectedJoint != null)
{
if (spriteSheetRect.Contains(PlayerInput.MousePosition))
{
targetLimb = GetClosestLimbOnSpritesheet(PlayerInput.MousePosition, l => l != null && l != selectedJoint.LimbB && l.ActiveSprite != null);
if (targetLimb != null && PlayerInput.LeftButtonClicked())
{
Vector2 anchor1 = ConvertUnits.ToDisplayUnits(selectedJoint.LocalAnchorB);
Vector2 anchor2 = (GetLimbSpritesheetRect(targetLimb).Center.ToVector2() - PlayerInput.MousePosition) / spriteSheetZoom;
anchor2.X = -anchor2.X;
ExtrudeJoint(selectedJoint, targetLimb.limbParams.ID, anchor1, anchor2);
}
}
else
{
targetLimb = GetClosestLimbOnRagdoll(PlayerInput.MousePosition, l => l != null && l != selectedJoint.LimbB && l.ActiveSprite != null);
if (targetLimb != null && PlayerInput.LeftButtonClicked())
{
Vector2 anchor1 = ConvertUnits.ToDisplayUnits(selectedJoint.LocalAnchorB);
Vector2 anchor2 = ConvertUnits.ToDisplayUnits(targetLimb.body.FarseerBody.GetLocalPoint(ScreenToSim(PlayerInput.MousePosition)));
ExtrudeJoint(selectedJoint, targetLimb.limbParams.ID, anchor1, anchor2);
}
}
}
else
{
targetLimb = null;
}
}
else if (isDrawingJoint)
{
if (selectedLimbs.Any())
{
if (spriteSheetRect.Contains(PlayerInput.MousePosition))
{
if (closestSelectedLimb == null)
{
closestSelectedLimb = GetClosestLimbOnSpritesheet(PlayerInput.MousePosition, l => selectedLimbs.Contains(l));
}
if (anchor1Pos == null && useMouseOffset)
{
anchor1Pos = GetLimbSpritesheetRect(closestSelectedLimb).Center.ToVector2() - PlayerInput.MousePosition;
}
targetLimb = GetClosestLimbOnSpritesheet(PlayerInput.MousePosition, l => l != null && l != closestSelectedLimb && l.ActiveSprite != null);
if (targetLimb != null && PlayerInput.LeftButtonClicked())
{
Vector2 anchor1 = anchor1Pos.HasValue ? anchor1Pos.Value / spriteSheetZoom : Vector2.Zero;
anchor1.X = -anchor1.X;
Vector2 anchor2 = (GetLimbSpritesheetRect(targetLimb).Center.ToVector2() - PlayerInput.MousePosition) / spriteSheetZoom;
anchor2.X = -anchor2.X;
CreateJoint(closestSelectedLimb.limbParams.ID, targetLimb.limbParams.ID, anchor1, anchor2);
jointCreationMode = false;
closestSelectedLimb = null;
}
}
else
{
if (closestSelectedLimb == null)
{
closestSelectedLimb = GetClosestLimbOnRagdoll(PlayerInput.MousePosition, l => selectedLimbs.Contains(l));
}
if (anchor1Pos == null && useMouseOffset)
{
anchor1Pos = ConvertUnits.ToDisplayUnits(closestSelectedLimb.body.FarseerBody.GetLocalPoint(ScreenToSim(PlayerInput.MousePosition)));
}
targetLimb = GetClosestLimbOnRagdoll(PlayerInput.MousePosition, l => l != null && l != closestSelectedLimb && l.ActiveSprite != null);
if (targetLimb != null && PlayerInput.LeftButtonClicked())
{
Vector2 anchor1 = anchor1Pos ?? Vector2.Zero;
Vector2 anchor2 = ConvertUnits.ToDisplayUnits(targetLimb.body.FarseerBody.GetLocalPoint(ScreenToSim(PlayerInput.MousePosition)));
CreateJoint(closestSelectedLimb.limbParams.ID, targetLimb.limbParams.ID, anchor1, anchor2);
jointCreationMode = false;
closestSelectedLimb = null;
}
}
}
else
{
targetLimb = null;
anchor1Pos = null;
}
}
else
{
targetLimb = null;
anchor1Pos = null;
}
}
private void CopyLimb(Limb limb)
{
if (limb == null) { return; }
//RagdollParams.StoreState();
// TODO: copy all params and sub params -> use a generic method?
var rect = limb.ActiveSprite.SourceRect;
var spriteParams = limb.limbParams.normalSpriteParams;
if (spriteParams == null)
{
spriteParams = limb.limbParams.deformSpriteParams;
}
var newLimbElement = new XElement("limb",
new XAttribute("id", RagdollParams.Limbs.Last().ID + 1),
new XAttribute("radius", limb.limbParams.Radius),
new XAttribute("width", limb.limbParams.Width),
new XAttribute("height", limb.limbParams.Height),
new XAttribute("mass", limb.limbParams.Mass),
new XElement("sprite",
new XAttribute("texture", spriteParams.Texture),
new XAttribute("sourcerect", $"{rect.X}, {rect.Y}, {rect.Size.X}, {rect.Size.Y}"))
);
var lastLimbElement = RagdollParams.MainElement.Elements("limb").Last();
lastLimbElement.AddAfterSelf(newLimbElement);
var newLimbParams = new LimbParams(newLimbElement, RagdollParams);
RagdollParams.Limbs.Add(newLimbParams);
character.AnimController.Recreate();
CreateTextures();
TeleportTo(spawnPosition);
ClearWidgets();
ClearSelection();
selectedLimbs.Add(character.AnimController.Limbs.Single(l => l.limbParams == newLimbParams));
ResetParamsEditor();
ragdollResetRequiresForceLoading = true;
}
/// <summary>
/// Creates a new joint between the last limb of the given joint and the target limb.
/// </summary>
private void ExtrudeJoint(LimbJoint joint, int targetLimb, Vector2? anchor1 = null, Vector2? anchor2 = null)
{
if (joint == null) { return; }
CreateJoint(joint.jointParams.Limb2, targetLimb, anchor1, anchor2);
}
/// <summary>
/// Creates a new joint using the limb IDs.
/// </summary>
private void CreateJoint(int fromLimb, int toLimb, Vector2? anchor1 = null, Vector2? anchor2 = null)
{
//RagdollParams.StoreState();
Vector2 a1 = anchor1 ?? Vector2.Zero;
Vector2 a2 = anchor2 ?? Vector2.Zero;
var newJointElement = new XElement("joint",
new XAttribute("limb1", fromLimb),
new XAttribute("limb2", toLimb),
new XAttribute("limb1anchor", $"{a1.X.Format(2)}, {a1.Y.Format(2)}"),
new XAttribute("limb2anchor", $"{a2.X.Format(2)}, {a2.Y.Format(2)}")
);
var lastJointElement = RagdollParams.MainElement.Elements("joint").LastOrDefault();
if (lastJointElement == null)
{
// If no joints exist, use the last limb element.
lastJointElement = RagdollParams.MainElement.Elements("limb").LastOrDefault();
}
if (lastJointElement == null)
{
DebugConsole.ThrowError(GetCharacterEditorTranslation("CantAddJointsNoLimbElements"));
return;
}
lastJointElement.AddAfterSelf(newJointElement);
var newJointParams = new JointParams(newJointElement, RagdollParams);
RagdollParams.Joints.Add(newJointParams);
character.AnimController.Recreate();
CreateTextures();
TeleportTo(spawnPosition);
ClearWidgets();
ClearSelection();
selectedJoints.Add(character.AnimController.LimbJoints.Single(j => j.jointParams == newJointParams));
jointsToggle.Selected = true;
ResetParamsEditor();
ragdollResetRequiresForceLoading = true;
}
/// <summary>
/// Removes all selected joints and limbs in the params level (-> serializable). The method also recreates the ids and names, when required.
/// </summary>
private void DeleteSelected()
{
//RagdollParams.StoreState();
for (int i = 0; i < selectedJoints.Count; i++)
{
var joint = selectedJoints[i];
joint.jointParams.Element.Remove();
RagdollParams.Joints.Remove(joint.jointParams);
}
var removedIDs = new List<int>();
for (int i = 0; i < selectedLimbs.Count; i++)
{
if (character.IsHumanoid)
{
DebugConsole.ThrowError(GetCharacterEditorTranslation("HumanoidLimbDeletionDisabled"));
break;
}
var limb = selectedLimbs[i];
if (limb == character.AnimController.MainLimb)
{
DebugConsole.ThrowError("Can't remove the main limb, because it will cause unreveratable issues.");
continue;
}
removedIDs.Add(limb.limbParams.ID);
limb.limbParams.Element.Remove();
RagdollParams.Limbs.Remove(limb.limbParams);
}
// Recreate ids
var renamedIDs = new Dictionary<int, int>();
for (int i = 0; i < RagdollParams.Limbs.Count; i++)
{
int oldID = RagdollParams.Limbs[i].ID;
int newID = i;
if (oldID != newID)
{
var limbParams = RagdollParams.Limbs[i];
limbParams.ID = newID;
limbParams.Name = limbParams.GenerateName();
renamedIDs.Add(oldID, newID);
}
}
// Refresh/recreate joints
var jointsToRemove = new List<JointParams>();
for (int i = 0; i < RagdollParams.Joints.Count; i++)
{
var joint = RagdollParams.Joints[i];
if (removedIDs.Contains(joint.Limb1) || removedIDs.Contains(joint.Limb2))
{
// At least one of the limbs has been removed -> remove the joint
jointsToRemove.Add(joint);
}
else
{
// Both limbs still remains -> update
bool rename = false;
if (renamedIDs.TryGetValue(joint.Limb1, out int newID1))
{
joint.Limb1 = newID1;
rename = true;
}
if (renamedIDs.TryGetValue(joint.Limb2, out int newID2))
{
joint.Limb2 = newID2;
rename = true;
}
if (rename)
{
joint.Name = joint.GenerateName();
}
}
}
foreach (var jointParam in jointsToRemove)
{
jointParam.Element.Remove();
RagdollParams.Joints.Remove(jointParam);
}
RecreateRagdoll();
ragdollResetRequiresForceLoading = true;
}
#endregion
#region Endless runner
private int min;
private int max;
private void CalculateMovementLimits()
{
min = CurrentWall.walls.Select(w => w.Rect.Left).OrderBy(p => p).First();
max = CurrentWall.walls.Select(w => w.Rect.Right).OrderBy(p => p).Last();
}
private WallGroup originalWall;
private WallGroup[] clones = new WallGroup[3];
private IEnumerable<Structure> AllWalls => originalWall.walls.Concat(clones.SelectMany(c => c.walls));
private WallGroup _currentWall;
private WallGroup CurrentWall
{
get
{
if (_currentWall == null)
{
_currentWall = originalWall;
}
return _currentWall;
}
set
{
_currentWall = value;
}
}
private class WallGroup
{
public readonly List<Structure> walls;
public WallGroup(List<Structure> walls)
{
this.walls = walls;
}
public WallGroup Clone()
{
var clones = new List<Structure>();
walls.ForEachMod(w => clones.Add(w.Clone() as Structure));
return new WallGroup(clones);
}
}
private void CloneWalls()
{
for (int i = 0; i < 3; i++)
{
clones[i] = originalWall.Clone();
for (int j = 0; j < originalWall.walls.Count; j++)
{
if (i == 1)
{
clones[i].walls[j].Move(new Vector2(originalWall.walls[j].Rect.Width, 0));
}
else if (i == 2)
{
clones[i].walls[j].Move(new Vector2(-originalWall.walls[j].Rect.Width, 0));
}
}
}
}
private WallGroup SelectClosestWallGroup(Vector2 pos)
{
var closestWall = clones.SelectMany(c => c.walls).OrderBy(w => Vector2.Distance(pos, w.Position)).First();
return clones.Where(c => c.walls.Contains(closestWall)).FirstOrDefault();
}
private WallGroup SelectLastClone(bool right)
{
var lastWall = right
? clones.SelectMany(c => c.walls).OrderBy(w => w.Rect.Right).Last()
: clones.SelectMany(c => c.walls).OrderBy(w => w.Rect.Left).First();
return clones.Where(c => c.walls.Contains(lastWall)).FirstOrDefault();
}
private void UpdateWalls(bool right)
{
CurrentWall = SelectClosestWallGroup(character.Position);
CalculateMovementLimits();
var lastClone = SelectLastClone(!right);
for (int i = 0; i < lastClone.walls.Count; i++)
{
var amount = right ? lastClone.walls[i].Rect.Width : -lastClone.walls[i].Rect.Width;
var distance = CurrentWall.walls[i].Position.X - lastClone.walls[i].Position.X;
lastClone.walls[i].Move(new Vector2(amount + distance, 0));
}
GameMain.World.ProcessChanges();
}
private bool wallCollisionsEnabled;
private void SetWallCollisions(bool enabled)
{
if (!isEndlessRunner) { return; }
wallCollisionsEnabled = enabled;
var collisionCategory = enabled ? FarseerPhysics.Dynamics.Category.Cat1 : FarseerPhysics.Dynamics.Category.None;
AllWalls.ForEach(w => w.SetCollisionCategory(collisionCategory));
GameMain.World.ProcessChanges();
}
#endregion
#region Character spawning
private int characterIndex = -1;
private string currentCharacterConfig;
private string selectedJob = null;
private List<string> allFiles;
private List<string> AllFiles
{
get
{
if (allFiles == null)
{
allFiles = GameMain.Instance.GetFilesOfType(ContentType.Character).OrderBy(f => f).ToList();
allFiles.ForEach(f => DebugConsole.NewMessage(f, Color.White));
}
return allFiles;
}
}
private List<string> vanillaCharacters;
private List<string> VanillaCharacters
{
get
{
if (vanillaCharacters == null)
{
vanillaCharacters = GameMain.VanillaContent?.GetFilesOfType(ContentType.Character).ToList();
}
return vanillaCharacters;
}
}
private string GetNextConfigFile()
{
GetCurrentCharacterIndex();
IncreaseIndex();
currentCharacterConfig = AllFiles[characterIndex];
return currentCharacterConfig;
}
private string GetPreviousConfigFile()
{
GetCurrentCharacterIndex();
ReduceIndex();
currentCharacterConfig = AllFiles[characterIndex];
return currentCharacterConfig;
}
private void GetCurrentCharacterIndex()
{
characterIndex = AllFiles.IndexOf(Character.GetConfigFile(character.SpeciesName));
}
private void IncreaseIndex()
{
characterIndex++;
if (characterIndex > AllFiles.Count - 1)
{
characterIndex = 0;
}
}
private void ReduceIndex()
{
characterIndex--;
if (characterIndex < 0)
{
characterIndex = AllFiles.Count - 1;
}
}
private Character SpawnCharacter(string configFile, RagdollParams ragdoll = null)
{
DebugConsole.NewMessage(GetCharacterEditorTranslation("TryingToSpawnCharacter").Replace("[config]", configFile.ToString()), Color.HotPink);
OnPreSpawn();
bool dontFollowCursor = true;
if (character != null)
{
dontFollowCursor = character.dontFollowCursor;
RagdollParams.ClearHistory();
CurrentAnimation.ClearHistory();
if (!character.Removed)
{
character.Remove();
}
character = null;
}
if (configFile == Character.HumanConfigFile && selectedJob != null)
{
var characterInfo = new CharacterInfo(configFile, jobPrefab: JobPrefab.List.First(job => job.Identifier == selectedJob));
character = Character.Create(configFile, spawnPosition, ToolBox.RandomSeed(8), characterInfo, hasAi: false, ragdoll: ragdoll);
character.GiveJobItems();
HideWearables();
if (displayWearables)
{
ShowWearables();
}
selectedJob = characterInfo.Job.Prefab.Identifier;
}
else
{
character = Character.Create(configFile, spawnPosition, ToolBox.RandomSeed(8), hasAi: false, ragdoll: ragdoll);
selectedJob = null;
}
if (character != null)
{
character.dontFollowCursor = dontFollowCursor;
}
if (character == null)
{
if (currentCharacterConfig == configFile)
{
return null;
}
else
{
// Respawn the current character;
SpawnCharacter(currentCharacterConfig);
}
}
OnPostSpawn();
return character;
}
private void OnPreSpawn()
{
cameraOffset = Vector2.Zero;
WayPoint wayPoint = null;
if (!isEndlessRunner)
{
wayPoint = WayPoint.GetRandom(spawnType: SpawnType.Human, sub: Submarine.MainSub);
}
if (wayPoint == null)
{
wayPoint = WayPoint.GetRandom(sub: Submarine.MainSub);
}
spawnPosition = wayPoint.WorldPosition;
ragdollResetRequiresForceLoading = false;
animationResetRequiresForceLoading = false;
}
private void OnPostSpawn()
{
currentCharacterConfig = character.ConfigPath;
GetCurrentCharacterIndex();
character.Submarine = Submarine.MainSub;
character.AnimController.forceStanding = character.AnimController.CanWalk;
character.AnimController.ForceSelectAnimationType = character.AnimController.CanWalk ? AnimationType.Walk : AnimationType.SwimSlow;
Character.Controlled = character;
SetWallCollisions(character.AnimController.forceStanding);
CreateTextures();
limbPairEditing = character.IsHumanoid;
CreateGUI();
ClearWidgets();
ClearSelection();
ResetParamsEditor();
CurrentAnimation.CreateSnapshot();
RagdollParams.CreateSnapshot();
Cam.Position = character.WorldPosition;
}
private void ClearWidgets()
{
Widget.selectedWidgets.Clear();
animationWidgets.Clear();
jointSelectionWidgets.Clear();
limbEditWidgets.Clear();
}
private void ClearSelection()
{
selectedLimbs.Clear();
selectedJoints.Clear();
}
private void RecreateRagdoll(RagdollParams ragdoll = null)
{
character.AnimController.Recreate(ragdoll);
TeleportTo(spawnPosition);
// For some reason Enumerable.Contains() method does not find the match, threfore the conversion to a list.
var selectedJointParams = selectedJoints.Select(j => j.jointParams).ToList();
var selectedLimbParams = selectedLimbs.Select(l => l.limbParams).ToList();
CreateTextures();
ClearWidgets();
ClearSelection();
foreach (var joint in character.AnimController.LimbJoints)
{
if (selectedJointParams.Contains(joint.jointParams))
{
selectedJoints.Add(joint);
}
}
foreach (var limb in character.AnimController.Limbs)
{
if (selectedLimbParams.Contains(limb.limbParams))
{
selectedLimbs.Add(limb);
}
}
ResetParamsEditor();
}
private void TeleportTo(Vector2 position)
{
if (isEndlessRunner)
{
character.AnimController.SetPosition(ConvertUnits.ToSimUnits(position), false);
}
else
{
character.TeleportTo(position);
}
Cam.Position = character.WorldPosition;
}
private bool CreateCharacter(string name, string mainFolder, bool isHumanoid, ContentPackage contentPackage = null, params object[] ragdollConfig)
{
var vanilla = GameMain.VanillaContent;
if (contentPackage == null)
{
#if DEBUG
contentPackage = GameMain.Config.SelectedContentPackages.LastOrDefault();
#else
contentPackage = GameMain.Config.SelectedContentPackages.LastOrDefault(cp => cp != vanilla);
#endif
}
if (contentPackage == null)
{
string modName = "NewCharacterMod";
if (ContentPackage.List.Any(cp => cp.Name == modName))
{
string tempName = modName;
for (int i = 0; i < 100; i++)
{
tempName = modName + i.ToString();
if (ContentPackage.List.None(cp => cp.Name == tempName))
{
modName = tempName;
break;
}
}
}
contentPackage = ContentPackage.CreatePackage(modName, Path.Combine(ContentPackage.Folder, $"{modName}.xml"), false);
ContentPackage.List.Add(contentPackage);
}
if (contentPackage == null)
{
// This should not be possible.
DebugConsole.ThrowError(GetCharacterEditorTranslation("NoContentPackageSelected"));
return false;
}
if (!GameMain.Config.SelectedContentPackages.Contains(contentPackage))
{
GameMain.Config.SelectedContentPackages.Add(contentPackage);
GameMain.Config.SaveNewPlayerConfig();
}
#if !DEBUG
if (vanilla != null && contentPackage == vanilla)
{
GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont);
return false;
}
#endif
string speciesName = name;
// Config file
string configFilePath = Path.Combine(mainFolder, $"{speciesName}.xml").Replace(@"\", @"/");
if (ContentPackage.GetFilesOfType(GameMain.SelectedPackages, ContentType.Character).Any(path => path.Contains(speciesName)))
{
GUI.AddMessage(GetCharacterEditorTranslation("ExistingCharacterFound"), Color.Red, font: GUI.LargeFont);
// TODO: add a prompt: "Do you want to replace it?" + functionality
return false;
}
// Create the config file
XElement mainElement = new XElement("Character",
new XAttribute("name", speciesName),
new XAttribute("humanoid", isHumanoid),
new XElement("ragdolls", new XAttribute("folder", Path.Combine(mainFolder, $"Ragdolls/").Replace(@"\", @"/"))),
new XElement("animations", new XAttribute("folder", Path.Combine(mainFolder, $"Animations/").Replace(@"\", @"/"))),
new XElement("health"),
new XElement("ai"));
XDocument doc = new XDocument(mainElement);
if (!Directory.Exists(mainFolder))
{
Directory.CreateDirectory(mainFolder);
}
doc.Save(configFilePath);
// Add to the selected content package
contentPackage.AddFile(configFilePath, ContentType.Character);
contentPackage.Save(contentPackage.Path);
DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path));
// Ragdoll
string ragdollFolder = RagdollParams.GetFolder(speciesName);
string ragdollPath = RagdollParams.GetDefaultFile(speciesName);
RagdollParams ragdollParams = isHumanoid
? RagdollParams.CreateDefault<HumanRagdollParams>(ragdollPath, speciesName, ragdollConfig)
: RagdollParams.CreateDefault<FishRagdollParams>(ragdollPath, speciesName, ragdollConfig) as RagdollParams;
// Animations
string animFolder = AnimationParams.GetFolder(speciesName);
foreach (AnimationType animType in Enum.GetValues(typeof(AnimationType)))
{
switch (animType)
{
case AnimationType.Walk:
case AnimationType.Run:
if (!ragdollParams.CanEnterSubmarine) { continue; }
break;
case AnimationType.SwimSlow:
case AnimationType.SwimFast:
break;
default: continue;
}
Type type = AnimationParams.GetParamTypeFromAnimType(animType, isHumanoid);
string fullPath = AnimationParams.GetDefaultFile(speciesName, animType);
AnimationParams.Create(fullPath, speciesName, animType, type);
}
if (!AllFiles.Contains(configFilePath))
{
AllFiles.Add(configFilePath);
}
SpawnCharacter(configFilePath, ragdollParams);
editLimbsToggle.Selected = true;
recalculateColliderToggle.Selected = true;
selectedLimbs.Add(character.AnimController.Limbs.First());
return true;
}
private void ShowWearables()
{
foreach (var item in character.Inventory.Items)
{
if (item == null) { continue; }
// Temp condition, todo: remove
if (item.AllowedSlots.Contains(InvSlotType.Head) || item.AllowedSlots.Contains(InvSlotType.Headset)) { continue; }
item.Equip(character);
}
}
private void HideWearables()
{
character.Inventory.Items.ForEachMod(i => i?.Unequip(character));
}
#endregion
#region GUI
private static Point outerMargin = new Point(0, 0);
private static Point innerMargin = new Point(40, 40);
private static Color panelColor = new Color(20, 20, 20, 255);
private static Color toggleButtonColor = new Color(0.4f, 0.4f, 0.4f, 1);
private GUIFrame rightArea;
private GUIFrame leftArea;
private GUIFrame centerArea;
private GUIFrame characterSelectionPanel;
private GUIFrame fileEditPanel;
private GUIFrame modesPanel;
private GUIFrame toolsPanel;
private GUIFrame optionsPanel;
private GUIFrame ragdollControls;
private GUIFrame jointControls;
private GUIFrame animationControls;
private GUIFrame limbControls;
private GUIFrame spriteSheetControls;
private GUIFrame backgroundColorPanel;
private GUIDropDown animSelection;
private GUITickBox freezeToggle;
private GUITickBox animTestPoseToggle;
private GUITickBox showCollidersToggle;
private GUIScrollBar jointScaleBar;
private GUIScrollBar limbScaleBar;
private GUIScrollBar spriteSheetZoomBar;
private GUITickBox copyJointsToggle;
private GUITickBox recalculateColliderToggle;
private GUITickBox jointsToggle;
private GUITickBox editAnimsToggle;
private GUITickBox editLimbsToggle;
private GUITickBox paramsToggle;
private GUITickBox spritesheetToggle;
private GUITickBox ragdollToggle;
private GUITickBox ikToggle;
private GUIFrame extraRagdollControls;
private GUIButton duplicateLimbButton;
private GUIButton deleteSelectedButton;
private GUIButton createJointButton;
private ToggleButton modesToggle;
private ToggleButton toolsToggle;
private ToggleButton optionsToggle;
private ToggleButton characterPanelToggle;
private ToggleButton fileEditToggle;
private void CreateGUI()
{
// Release the old areas
if (rightArea != null)
{
rightArea.RectTransform.Parent = null;
}
if (centerArea != null)
{
centerArea.RectTransform.Parent = null;
}
if (leftArea != null)
{
leftArea.RectTransform.Parent = null;
}
// Create the areas
rightArea = new GUIFrame(new RectTransform(new Vector2(0.15f, 0.95f), parent: Frame.RectTransform, anchor: Anchor.CenterRight)
{
AbsoluteOffset = new Point(outerMargin.X, 0)
}, style: null) { CanBeFocused = false };
centerArea = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.95f), parent: Frame.RectTransform, anchor: Anchor.TopRight)
{
AbsoluteOffset = new Point((int)(rightArea.RectTransform.ScaledSize.X + rightArea.RectTransform.RelativeOffset.X * rightArea.RectTransform.Parent.ScaledSize.X + 20), outerMargin.Y + 20)
}, style: null)
{ CanBeFocused = false };
leftArea = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.95f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft)
{
AbsoluteOffset = new Point(outerMargin.X, 0)
}, style: null)
{
CanBeFocused = false
};
Vector2 buttonSize = new Vector2(1, 0.04f);
Vector2 toggleSize = new Vector2(0.03f, 0.03f);
CreateCharacterSelectionPanel();
CreateModesPanel(toggleSize);
CreateToolsPanel();
CreateFileEditPanel();
CreateOptionsPanel(toggleSize);
CreateContextualControls();
}
private void CreateModesPanel(Vector2 toggleSize)
{
modesPanel = new GUIFrame(new RectTransform(new Vector2(0.6f, 0.3f), leftArea.RectTransform, Anchor.BottomLeft), style: null, color: panelColor);
var layoutGroup = new GUILayoutGroup(new RectTransform(new Point(modesPanel.Rect.Width - innerMargin.X, modesPanel.Rect.Height - innerMargin.Y),
modesPanel.RectTransform, Anchor.Center))
{
AbsoluteSpacing = 2,
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(0.03f, 0.06f), layoutGroup.RectTransform), GetCharacterEditorTranslation("ModesPanel"), font: GUI.LargeFont);
// Main modes
editLimbsToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("EditLimbs")) { Selected = editLimbs };
jointsToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("EditJoints")) { Selected = editJoints };
editAnimsToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("EditAnimations")) { Selected = editAnimations };
// Spacing
new GUIFrame(new RectTransform(toggleSize, layoutGroup.RectTransform), style: null) { CanBeFocused = false };
// Minor modes
ragdollToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("ShowRagdoll")) { Selected = editRagdoll };
paramsToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("ShowParameters")) { Selected = showParamsEditor };
spritesheetToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("ShowSpriteSheet")) { Selected = showSpritesheet };
showCollidersToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("ShowColliders"))
{
Selected = showColliders,
OnSelected = box =>
{
showColliders = box.Selected;
return true;
}
};
ikToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("EditIKTargets")) { Selected = editIK };
editAnimsToggle.OnSelected = box =>
{
editAnimations = box.Selected;
if (editAnimations)
{
SetToggle(editLimbsToggle, false);
SetToggle(jointsToggle, false);
spritesheetToggle.Selected = false;
ClearSelection();
}
ResetParamsEditor();
return true;
};
paramsToggle.OnSelected = box =>
{
showParamsEditor = box.Selected;
return true;
};
editLimbsToggle.OnSelected = box =>
{
editLimbs = box.Selected;
if (editLimbs)
{
SetToggle(editAnimsToggle, false);
SetToggle(jointsToggle, false);
spritesheetToggle.Selected = true;
ClearSelection();
}
ResetParamsEditor();
return true;
};
ragdollToggle.OnSelected = box =>
{
editRagdoll = box.Selected;
if (editRagdoll)
{
if (!editIK)
{
paramsToggle.Selected = true;
}
ClearSelection();
}
ResetParamsEditor();
return true;
};
jointsToggle.OnSelected = box =>
{
editJoints = box.Selected;
if (editJoints)
{
SetToggle(editLimbsToggle, false);
SetToggle(editAnimsToggle, false);
ikToggle.Selected = false;
spritesheetToggle.Selected = true;
ClearSelection();
}
ResetParamsEditor();
return true;
};
ikToggle.OnSelected = box =>
{
editIK = box.Selected;
if (editIK)
{
ragdollToggle.Selected = true;
}
return true;
};
spritesheetToggle.OnSelected = box =>
{
showSpritesheet = box.Selected;
return true;
};
modesToggle = new ToggleButton(new RectTransform(new Vector2(0.125f, 1), modesPanel.RectTransform, Anchor.CenterRight, Pivot.CenterLeft), Direction.Left);
}
private void SetToggle(GUITickBox toggle, bool value)
{
if (toggle.Selected != value)
{
if (value)
{
toggle.Box.Flash(Color.LightGreen, useRectangleFlash: true);
}
else
{
toggle.Box.Flash(Color.Red, useRectangleFlash: true);
}
}
toggle.Selected = value;
}
private void CreateToolsPanel()
{
Vector2 buttonSize = new Vector2(1, 0.06f);
toolsPanel = new GUIFrame(new RectTransform(new Vector2(0.6f, 0.15f), leftArea.RectTransform, Anchor.CenterLeft)
{
RelativeOffset = new Vector2(0, 0.1f)
}, style: null, color: panelColor);
var layoutGroup = new GUILayoutGroup(new RectTransform(new Point(toolsPanel.Rect.Width - innerMargin.X, toolsPanel.Rect.Height - innerMargin.Y),
toolsPanel.RectTransform, Anchor.Center))
{
AbsoluteSpacing = 2,
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(0.03f, 0.06f), layoutGroup.RectTransform), GetCharacterEditorTranslation("ToolsPanel"), font: GUI.LargeFont);
var reloadTexturesButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("ReloadTextures"));
reloadTexturesButton.OnClicked += (button, userData) =>
{
foreach (var limb in character.AnimController.Limbs)
{
limb.ActiveSprite.ReloadTexture();
limb.WearingItems.ForEach(i => i.Sprite.ReloadTexture());
limb.OtherWearables.ForEach(w => w.Sprite.ReloadTexture());
}
CreateTextures();
return true;
};
new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("RecreateRagdoll"))
{
ToolTip = GetCharacterEditorTranslation("RecreateRagdollTooltip"),
OnClicked = (button, data) =>
{
RecreateRagdoll();
character.AnimController.ResetLimbs();
return true;
}
};
toolsToggle = new ToggleButton(new RectTransform(new Vector2(0.125f, 1), toolsPanel.RectTransform, Anchor.CenterRight, Pivot.CenterLeft), Direction.Left);
}
private void CreateOptionsPanel(Vector2 toggleSize)
{
optionsPanel = new GUIFrame(new RectTransform(new Vector2(1, 0.3f), rightArea.RectTransform, Anchor.Center)
{
RelativeOffset = new Vector2(0, -0.075f)
}, style: null, color: panelColor);
var layoutGroup = new GUILayoutGroup(new RectTransform(new Point(optionsPanel.Rect.Width - innerMargin.X, optionsPanel.Rect.Height - innerMargin.Y),
optionsPanel.RectTransform, Anchor.Center))
{
AbsoluteSpacing = 2,
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(0.03f, 0.06f), layoutGroup.RectTransform), GetCharacterEditorTranslation("OptionsPanel"), font: GUI.LargeFont);
freezeToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("Freeze")) { Selected = isFreezed };
var autoFreezeToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("AutoFreeze")) { Selected = autoFreeze };
var limbPairEditToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("LimbPairEditing"))
{
Selected = limbPairEditing,
Enabled = character.IsHumanoid // TODO: remove when limb pair editing works for non-humanoids
};
animTestPoseToggle = new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("AnimationTestPose"))
{
Selected = character.AnimController.AnimationTestPose,
Enabled = character.IsHumanoid
};
new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("AutoMove"))
{
Selected = character.OverrideMovement != null,
OnSelected = box =>
{
character.OverrideMovement = box.Selected ? new Vector2(1, 0) as Vector2? : null;
return true;
}
};
new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("FollowCursor"))
{
Selected = !character.dontFollowCursor,
OnSelected = box =>
{
character.dontFollowCursor = !box.Selected;
return true;
}
};
new GUITickBox(new RectTransform(toggleSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("EditBackgroundColor"))
{
Selected = displayBackgroundColor,
OnSelected = box =>
{
displayBackgroundColor = box.Selected;
return true;
}
};
freezeToggle.OnSelected = box =>
{
isFreezed = box.Selected;
return true;
};
autoFreezeToggle.OnSelected = box =>
{
autoFreeze = box.Selected;
return true;
};
limbPairEditToggle.OnSelected = box =>
{
limbPairEditing = box.Selected;
return true;
};
animTestPoseToggle.OnSelected = box =>
{
character.AnimController.AnimationTestPose = box.Selected;
return true;
};
optionsToggle = new ToggleButton(new RectTransform(new Vector2(0.1f, 1), optionsPanel.RectTransform, Anchor.CenterLeft, Pivot.CenterRight), Direction.Right);
}
private void CreateContextualControls()
{
Point elementSize = new Point(120, 20);
int textAreaHeight = 20;
// General controls
backgroundColorPanel = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f), centerArea.RectTransform, Anchor.TopRight)
{
AbsoluteOffset = new Point(10, 0)
}, style: null)
{
CanBeFocused = false
};
// Background color
var frame = new GUIFrame(new RectTransform(new Point(500, 80), backgroundColorPanel.RectTransform, Anchor.TopRight), style: null, color: Color.Black * 0.4f);
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), frame.RectTransform) { MinSize = new Point(80, 26) }, GetCharacterEditorTranslation("BackgroundColor") + ":", textColor: Color.WhiteSmoke);
var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 1), frame.RectTransform, Anchor.TopRight)
{
AbsoluteOffset = new Point(20, 0)
}, isHorizontal: true, childAnchor: Anchor.CenterRight)
{
Stretch = true,
RelativeSpacing = 0.01f
};
var fields = new GUIComponent[4];
string[] colorComponentLabels = { "R", "G", "B" };
for (int i = 2; i >= 0; i--)
{
var element = new GUIFrame(new RectTransform(new Vector2(0.3f, 1), inputArea.RectTransform)
{
MinSize = new Point(40, 0),
MaxSize = new Point(100, 50)
}, style: null, color: Color.Black * 0.6f);
var colorLabel = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), colorComponentLabels[i],
font: GUI.SmallFont, textAlignment: Alignment.CenterLeft);
GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
GUINumberInput.NumberType.Int, relativeButtonAreaWidth: 0.25f)
{
Font = GUI.SmallFont
};
numberInput.MinValueInt = 0;
numberInput.MaxValueInt = 255;
numberInput.Font = GUI.SmallFont;
switch (i)
{
case 0:
colorLabel.TextColor = Color.Red;
numberInput.IntValue = backgroundColor.R;
numberInput.OnValueChanged += (numInput) => backgroundColor.R = (byte)numInput.IntValue;
break;
case 1:
colorLabel.TextColor = Color.LightGreen;
numberInput.IntValue = backgroundColor.G;
numberInput.OnValueChanged += (numInput) => backgroundColor.G = (byte)numInput.IntValue;
break;
case 2:
colorLabel.TextColor = Color.DeepSkyBlue;
numberInput.IntValue = backgroundColor.B;
numberInput.OnValueChanged += (numInput) => backgroundColor.B = (byte)numInput.IntValue;
break;
}
}
// Spritesheet controls
spriteSheetControls = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f), centerArea.RectTransform, Anchor.BottomLeft)
{
RelativeOffset = new Vector2(0, 0.05f)
}, style: null)
{
CanBeFocused = false
};
var layoutGroupSpriteSheet = new GUILayoutGroup(new RectTransform(Vector2.One, spriteSheetControls.RectTransform)) { AbsoluteSpacing = 5, CanBeFocused = false };
new GUITextBlock(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupSpriteSheet.RectTransform), GetCharacterEditorTranslation("SpriteSheetZoom") + ":", Color.White);
var spriteSheetControlElement = new GUIFrame(new RectTransform(new Point(elementSize.X * 2, textAreaHeight), layoutGroupSpriteSheet.RectTransform), style: null);
CalculateSpritesheetZoom();
spriteSheetZoomBar = new GUIScrollBar(new RectTransform(new Vector2(0.75f, 1), spriteSheetControlElement.RectTransform), barSize: 0.2f)
{
BarScroll = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(spriteSheetMinZoom, spriteSheetMaxZoom, spriteSheetZoom)),
Step = 0.01f,
OnMoved = (scrollBar, value) =>
{
spriteSheetZoom = MathHelper.Lerp(spriteSheetMinZoom, spriteSheetMaxZoom, value);
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.3f, 1), spriteSheetControlElement.RectTransform, Anchor.TopRight), GetCharacterEditorTranslation("Reset"))
{
OnClicked = (box, data) =>
{
spriteSheetZoom = Math.Min(1, spriteSheetMaxZoom);
spriteSheetZoomBar.BarScroll = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(spriteSheetMinZoom, spriteSheetMaxZoom, spriteSheetZoom));
return true;
}
};
new GUITickBox(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupSpriteSheet.RectTransform), GetCharacterEditorTranslation("HideBodySprites"))
{
TextColor = Color.White,
Selected = hideBodySheet,
OnSelected = (GUITickBox box) =>
{
hideBodySheet = box.Selected;
return true;
}
};
new GUITickBox(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupSpriteSheet.RectTransform), GetCharacterEditorTranslation("ShowWearables"))
{
TextColor = Color.White,
Selected = displayWearables,
OnSelected = (GUITickBox box) =>
{
displayWearables = box.Selected;
if (displayWearables)
{
ShowWearables();
}
else
{
HideWearables();
}
return true;
}
};
//new GUITextBlock(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupSpriteSheet.RectTransform), "Texture scale:", Color.White);
//new GUIScrollBar(new RectTransform(new Point((int)(elementSize.X * 1.75f), textAreaHeight), layoutGroupSpriteSheet.RectTransform), barSize: 0.2f)
//{
// BarScroll = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(textureMinScale, textureMaxScale, RagdollParams.TextureScale)),
// Step = 0.01f,
// OnMoved = (scrollBar, value) =>
// {
// RagdollParams.TextureScale = MathHelper.Lerp(textureMinScale, textureMaxScale, value);
// return true;
// }
//};
// Limb controls
limbControls = new GUIFrame(new RectTransform(Vector2.One, centerArea.RectTransform), style: null) { CanBeFocused = false };
var layoutGroupLimbControls = new GUILayoutGroup(new RectTransform(Vector2.One, limbControls.RectTransform), childAnchor: Anchor.TopLeft) { CanBeFocused = false };
var lockSpriteOriginToggle = new GUITickBox(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupLimbControls.RectTransform), GetCharacterEditorTranslation("LockSpriteOrigin"))
{
Selected = lockSpriteOrigin,
OnSelected = (GUITickBox box) =>
{
lockSpriteOrigin = box.Selected;
return true;
}
};
lockSpriteOriginToggle.TextColor = Color.White;
var lockSpritePositionToggle = new GUITickBox(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupLimbControls.RectTransform), GetCharacterEditorTranslation("LockSpritePosition"))
{
Selected = lockSpritePosition,
OnSelected = (GUITickBox box) =>
{
lockSpritePosition = box.Selected;
return true;
}
};
lockSpritePositionToggle.TextColor = Color.White;
var lockSpriteSizeToggle = new GUITickBox(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupLimbControls.RectTransform), GetCharacterEditorTranslation("LockSpriteSize"))
{
Selected = lockSpriteSize,
OnSelected = (GUITickBox box) =>
{
lockSpriteSize = box.Selected;
return true;
}
};
lockSpriteSizeToggle.TextColor = Color.White;
recalculateColliderToggle = new GUITickBox(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupLimbControls.RectTransform), GetCharacterEditorTranslation("AdjustCollider"))
{
Selected = recalculateCollider,
OnSelected = (GUITickBox box) =>
{
recalculateCollider = box.Selected;
showCollidersToggle.Selected = recalculateCollider;
return true;
}
};
recalculateColliderToggle.TextColor = Color.White;
// Joint controls
Point sliderSize = new Point(300, 20);
jointControls = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.075f), centerArea.RectTransform), style: null) { CanBeFocused = false };
var layoutGroupJoints = new GUILayoutGroup(new RectTransform(Vector2.One, jointControls.RectTransform), childAnchor: Anchor.TopLeft) { CanBeFocused = false };
copyJointsToggle = new GUITickBox(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupJoints.RectTransform), GetCharacterEditorTranslation("CopyJointSettings"))
{
ToolTip = GetCharacterEditorTranslation("CopyJointSettingsTooltip"),
Selected = copyJointSettings,
TextColor = copyJointSettings ? Color.Red : Color.White,
OnSelected = (GUITickBox box) =>
{
copyJointSettings = box.Selected;
box.TextColor = copyJointSettings ? Color.Red : Color.White;
return true;
}
};
// Ragdoll controls
ragdollControls = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.25f), centerArea.RectTransform) { AbsoluteOffset = new Point(0, jointControls.Rect.Bottom) }, style: null) { CanBeFocused = false };
var layoutGroupRagdoll = new GUILayoutGroup(new RectTransform(Vector2.One, ragdollControls.RectTransform), childAnchor: Anchor.TopLeft) { CanBeFocused = false };
var uniformScalingToggle = new GUITickBox(new RectTransform(new Point(elementSize.X, textAreaHeight), layoutGroupRagdoll.RectTransform), GetCharacterEditorTranslation("UniformScale"))
{
Selected = uniformScaling,
OnSelected = (GUITickBox box) =>
{
uniformScaling = box.Selected;
return true;
}
};
uniformScalingToggle.TextColor = Color.White;
var jointScaleElement = new GUIFrame(new RectTransform(sliderSize + new Point(0, textAreaHeight), layoutGroupRagdoll.RectTransform), style: null);
var jointScaleText = new GUITextBlock(new RectTransform(new Point(elementSize.X, textAreaHeight), jointScaleElement.RectTransform), $"{GetCharacterEditorTranslation("JointScale")}: {RagdollParams.JointScale.FormatDoubleDecimal()}", Color.WhiteSmoke, textAlignment: Alignment.Center);
var limbScaleElement = new GUIFrame(new RectTransform(sliderSize + new Point(0, textAreaHeight), layoutGroupRagdoll.RectTransform), style: null);
var limbScaleText = new GUITextBlock(new RectTransform(new Point(elementSize.X, textAreaHeight), limbScaleElement.RectTransform), $"{GetCharacterEditorTranslation("LimbScale")}: {RagdollParams.LimbScale.FormatDoubleDecimal()}", Color.WhiteSmoke, textAlignment: Alignment.Center);
jointScaleBar = new GUIScrollBar(new RectTransform(sliderSize, jointScaleElement.RectTransform, Anchor.BottomLeft), barSize: 0.1f)
{
BarScroll = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE, RagdollParams.JointScale)),
Step = 0.001f,
OnMoved = (scrollBar, value) =>
{
float v = MathHelper.Lerp(RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE, value);
UpdateJointScale(v);
if (uniformScaling)
{
UpdateLimbScale(v);
limbScaleBar.BarScroll = value;
}
return true;
}
};
limbScaleBar = new GUIScrollBar(new RectTransform(sliderSize, limbScaleElement.RectTransform, Anchor.BottomLeft), barSize: 0.1f)
{
BarScroll = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE, RagdollParams.LimbScale)),
Step = 0.001f,
OnMoved = (scrollBar, value) =>
{
float v = MathHelper.Lerp(RagdollParams.MIN_SCALE, RagdollParams.MAX_SCALE, value);
UpdateLimbScale(v);
if (uniformScaling)
{
UpdateJointScale(v);
jointScaleBar.BarScroll = value;
}
return true;
}
};
void UpdateJointScale(float value)
{
freezeToggle.Selected = false;
TryUpdateRagdollParam("jointscale", value);
jointScaleText.Text = $"{GetCharacterEditorTranslation("JointScale")}: {RagdollParams.JointScale.FormatDoubleDecimal()}";
character.AnimController.ResetJoints();
}
void UpdateLimbScale(float value)
{
TryUpdateRagdollParam("limbscale", value);
limbScaleText.Text = $"{GetCharacterEditorTranslation("LimbScale")}: {RagdollParams.LimbScale.FormatDoubleDecimal()}";
}
// TODO: doesn't trigger if the mouse is released while the cursor is outside the button rect
limbScaleBar.Bar.OnClicked += (button, data) =>
{
RecreateRagdoll();
RagdollParams.CreateSnapshot();
ragdollResetRequiresForceLoading = true;
return true;
};
jointScaleBar.Bar.OnClicked += (button, data) =>
{
if (uniformScaling)
{
RecreateRagdoll();
}
RagdollParams.CreateSnapshot();
ragdollResetRequiresForceLoading = true;
return true;
};
// Ragdoll manipulation
extraRagdollControls = new GUIFrame(new RectTransform(new Point(140, 30), centerArea.RectTransform, Anchor.BottomRight)
{
RelativeOffset = new Vector2(0.2f, 0.15f)
}, style: null)
{
CanBeFocused = false
};
var extraRagdollLayout = new GUILayoutGroup(new RectTransform(Vector2.One, extraRagdollControls.RectTransform));
duplicateLimbButton = new GUIButton(new RectTransform(new Point(140, 30), extraRagdollLayout.RectTransform), "Duplicate Limb")
{
OnClicked = (button, data) =>
{
CopyLimb(selectedLimbs.FirstOrDefault());
return true;
}
};
deleteSelectedButton = new GUIButton(new RectTransform(new Point(140, 30), extraRagdollLayout.RectTransform), "Delete Selected")
{
OnClicked = (button, data) =>
{
DeleteSelected();
return true;
}
};
createJointButton = new GUIButton(new RectTransform(new Point(140, 30), extraRagdollLayout.RectTransform), "Create Joint")
{
OnClicked = (button, data) =>
{
jointCreationMode = !jointCreationMode;
useMouseOffset = false;
return true;
}
};
// Animation
animationControls = new GUIFrame(new RectTransform(Vector2.One, centerArea.RectTransform), style: null) { CanBeFocused = false };
var layoutGroupAnimation = new GUILayoutGroup(new RectTransform(Vector2.One, animationControls.RectTransform), childAnchor: Anchor.TopLeft) { CanBeFocused = false };
var animationSelectionElement = new GUIFrame(new RectTransform(new Point(elementSize.X * 2 - 5, elementSize.Y), layoutGroupAnimation.RectTransform), style: null);
var animationSelectionText = new GUITextBlock(new RectTransform(new Point(elementSize.X, elementSize.Y), animationSelectionElement.RectTransform), GetCharacterEditorTranslation("SelectedAnimation") + ": ", Color.WhiteSmoke, textAlignment: Alignment.Center);
animSelection = new GUIDropDown(new RectTransform(new Point(100, elementSize.Y), animationSelectionElement.RectTransform, Anchor.TopRight), elementCount: 4);
if (character.AnimController.CanWalk)
{
animSelection.AddItem(AnimationType.Walk.ToString(), AnimationType.Walk);
animSelection.AddItem(AnimationType.Run.ToString(), AnimationType.Run);
}
animSelection.AddItem(AnimationType.SwimSlow.ToString(), AnimationType.SwimSlow);
animSelection.AddItem(AnimationType.SwimFast.ToString(), AnimationType.SwimFast);
if (character.AnimController.ForceSelectAnimationType == AnimationType.NotDefined)
{
animSelection.SelectItem(character.AnimController.CanWalk ? AnimationType.Walk : AnimationType.SwimSlow);
}
else
{
animSelection.SelectItem(character.AnimController.ForceSelectAnimationType);
}
animSelection.OnSelected += (element, data) =>
{
AnimationType previousAnim = character.AnimController.ForceSelectAnimationType;
character.AnimController.ForceSelectAnimationType = (AnimationType)data;
switch (character.AnimController.ForceSelectAnimationType)
{
case AnimationType.Walk:
character.AnimController.forceStanding = true;
character.ForceRun = false;
if (!wallCollisionsEnabled)
{
SetWallCollisions(true);
}
if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run)
{
TeleportTo(spawnPosition);
}
break;
case AnimationType.Run:
character.AnimController.forceStanding = true;
character.ForceRun = true;
if (!wallCollisionsEnabled)
{
SetWallCollisions(true);
}
if (previousAnim != AnimationType.Walk && previousAnim != AnimationType.Run)
{
TeleportTo(spawnPosition);
}
break;
case AnimationType.SwimSlow:
character.AnimController.forceStanding = false;
character.ForceRun = false;
if (wallCollisionsEnabled)
{
SetWallCollisions(false);
}
break;
case AnimationType.SwimFast:
character.AnimController.forceStanding = false;
character.ForceRun = true;
if (wallCollisionsEnabled)
{
SetWallCollisions(false);
}
break;
default:
throw new NotImplementedException();
}
return true;
};
}
private void CreateCharacterSelectionPanel()
{
characterSelectionPanel = new GUIFrame(new RectTransform(new Vector2(1, 0.25f), rightArea.RectTransform, Anchor.TopRight), style: null, color: panelColor);
var padding = new GUIFrame(new RectTransform(new Point(characterSelectionPanel.Rect.Width - innerMargin.X, characterSelectionPanel.Rect.Height - innerMargin.Y),
characterSelectionPanel.RectTransform, Anchor.Center), style: null)
{
CanBeFocused = false
};
// Disclaimer
var disclaimerBtnHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), padding.RectTransform), style: null);
var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), disclaimerBtnHolder.RectTransform, Anchor.TopRight), style: "GUINotificationButton")
{
OnClicked = (btn, userdata) => { GameMain.Instance.ShowEditorDisclaimer(); return true; }
};
disclaimerBtn.RectTransform.MaxSize = new Point(disclaimerBtn.Rect.Height);
// Character selection
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.2f), padding.RectTransform), GetCharacterEditorTranslation("CharacterPanel"), font: GUI.LargeFont);
var characterDropDown = new GUIDropDown(new RectTransform(new Vector2(1, 0.2f), padding.RectTransform)
{
RelativeOffset = new Vector2(0, 0.2f)
}, elementCount: 8, style: null);
characterDropDown.ListBox.Color = new Color(characterDropDown.ListBox.Color.R, characterDropDown.ListBox.Color.G, characterDropDown.ListBox.Color.B, byte.MaxValue);
foreach (var file in AllFiles)
{
characterDropDown.AddItem(Path.GetFileNameWithoutExtension(file).CapitaliseFirstInvariant(), file);
}
characterDropDown.SelectItem(currentCharacterConfig);
characterDropDown.OnSelected = (component, data) =>
{
SpawnCharacter((string)data);
return true;
};
if (currentCharacterConfig == Character.HumanConfigFile)
{
var jobDropDown = new GUIDropDown(new RectTransform(new Vector2(1, 0.15f), padding.RectTransform)
{
RelativeOffset = new Vector2(0, 0.45f)
}, elementCount: 8, style: null);
jobDropDown.ListBox.Color = new Color(jobDropDown.ListBox.Color.R, jobDropDown.ListBox.Color.G, jobDropDown.ListBox.Color.B, byte.MaxValue);
jobDropDown.AddItem("None");
JobPrefab.List.ForEach(j => jobDropDown.AddItem(j.Name, j.Identifier));
jobDropDown.SelectItem(selectedJob);
jobDropDown.OnSelected = (component, data) =>
{
string newJob = data is string jobIdentifier ? jobIdentifier : null;
if (newJob != selectedJob)
{
selectedJob = newJob;
SpawnCharacter(currentCharacterConfig);
}
return true;
};
}
var charButtons = new GUIFrame(new RectTransform(new Vector2(1, 0.25f), parent: padding.RectTransform, anchor: Anchor.BottomLeft), style: null);
var prevCharacterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), charButtons.RectTransform, Anchor.TopLeft), GetCharacterEditorTranslation("PreviousCharacter"));
prevCharacterButton.TextBlock.AutoScale = true;
prevCharacterButton.OnClicked += (b, obj) =>
{
SpawnCharacter(GetPreviousConfigFile());
return true;
};
var nextCharacterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), charButtons.RectTransform, Anchor.TopRight), GetCharacterEditorTranslation("NextCharacter"));
prevCharacterButton.TextBlock.AutoScale = true;
nextCharacterButton.OnClicked += (b, obj) =>
{
SpawnCharacter(GetNextConfigFile());
return true;
};
characterPanelToggle = new ToggleButton(new RectTransform(new Vector2(0.1f, 1), characterSelectionPanel.RectTransform, Anchor.CenterLeft, Pivot.CenterRight), Direction.Right);
}
private void CreateFileEditPanel()
{
Vector2 buttonSize = new Vector2(1, 0.04f);
fileEditPanel = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), rightArea.RectTransform, Anchor.BottomRight), style: null, color: panelColor);
var layoutGroup = new GUILayoutGroup(new RectTransform(new Point(fileEditPanel.Rect.Width - innerMargin.X, fileEditPanel.Rect.Height - innerMargin.Y),
fileEditPanel.RectTransform, Anchor.Center))
{
AbsoluteSpacing = 1,
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(0.03f, 0.06f), layoutGroup.RectTransform), GetCharacterEditorTranslation("FileEditPanel"), font: GUI.LargeFont);
// Spacing
new GUIFrame(new RectTransform(buttonSize / 2, layoutGroup.RectTransform), style: null) { CanBeFocused = false };
var quickSaveAnimButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("QuickSaveAnimations"));
quickSaveAnimButton.Color = Color.LightGreen;
quickSaveAnimButton.OnClicked += (button, userData) =>
{
#if !DEBUG
if (VanillaCharacters != null && VanillaCharacters.Contains(currentCharacterConfig))
{
GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont);
return false;
}
#endif
AnimParams.ForEach(p => p.Save());
animationResetRequiresForceLoading = true;
GUI.AddMessage(GetCharacterEditorTranslation("AllAnimationsSaved"), Color.Green, font: GUI.Font);
return true;
};
var quickSaveRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("QuickSaveRagdoll"));
quickSaveRagdollButton.Color = Color.LightGreen;
quickSaveRagdollButton.OnClicked += (button, userData) =>
{
#if !DEBUG
if (VanillaCharacters != null && VanillaCharacters.Contains(currentCharacterConfig))
{
GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont);
return false;
}
#endif
character.AnimController.SaveRagdoll();
ragdollResetRequiresForceLoading = true;
GUI.AddMessage(GetCharacterEditorTranslation("RagdollSavedTo").Replace("[path]", RagdollParams.FullPath), Color.Green, font: GUI.Font);
return true;
};
// Spacing
new GUIFrame(new RectTransform(buttonSize / 2, layoutGroup.RectTransform), style: null) { CanBeFocused = false };
Vector2 messageBoxRelSize = new Vector2(0.5f, 0.5f);
int messageBoxWidth = GameMain.GraphicsWidth / 2;
int messageBoxHeight = GameMain.GraphicsHeight / 2;
var saveRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("SaveRagdoll"));
saveRagdollButton.OnClicked += (button, userData) =>
{
var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveRagdoll"), $"{GetCharacterEditorTranslation("ProvideFileName")}: ", new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxRelSize);
var inputField = new GUITextBox(new RectTransform(new Point(box.Content.Rect.Width, 30), box.Content.RectTransform, Anchor.Center), RagdollParams.Name);
box.Buttons[0].OnClicked += (b, d) =>
{
box.Close();
return true;
};
box.Buttons[1].OnClicked += (b, d) =>
{
#if !DEBUG
if (VanillaCharacters != null && VanillaCharacters.Contains(currentCharacterConfig))
{
GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont);
box.Close();
return false;
}
#endif
character.AnimController.SaveRagdoll(inputField.Text);
ragdollResetRequiresForceLoading = true;
GUI.AddMessage(GetCharacterEditorTranslation("RagdollSavedTo").Replace("[path]", RagdollParams.FullPath), Color.Green, font: GUI.Font);
box.Close();
return true;
};
return true;
};
var loadRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("LoadRagdoll"));
loadRagdollButton.OnClicked += (button, userData) =>
{
var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadRagdoll"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize);
loadBox.Buttons[0].OnClicked += loadBox.Close;
var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform, Anchor.TopCenter));
var deleteButton = loadBox.Buttons[2];
deleteButton.Enabled = false;
void PopulateListBox()
{
try
{
var filePaths = Directory.GetFiles(RagdollParams.Folder);
foreach (var path in filePaths)
{
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform) { MinSize = new Point(0, 30) },
ToolBox.LimitString(Path.GetFileNameWithoutExtension(path), GUI.Font, listBox.Rect.Width - 80))
{
UserData = path,
ToolTip = path
};
}
}
catch (Exception e)
{
DebugConsole.ThrowError(GetCharacterEditorTranslation("CouldntOpenDirectory").Replace("[folder]", RagdollParams.Folder), e);
}
}
PopulateListBox();
// Handle file selection
string selectedFile = null;
listBox.OnSelected += (component, data) =>
{
selectedFile = data as string;
// Don't allow to delete the ragdoll that is currently in use, nor the default file.
var fileName = Path.GetFileNameWithoutExtension(selectedFile);
deleteButton.Enabled = fileName != RagdollParams.Name && fileName != RagdollParams.GetDefaultFileName(character.SpeciesName);
return true;
};
deleteButton.OnClicked += (btn, data) =>
{
if (selectedFile == null)
{
loadBox.Close();
return false;
}
var msgBox = new GUIMessageBox(
TextManager.Get("DeleteDialogLabel"),
TextManager.Get("DeleteDialogQuestion").Replace("[file]", selectedFile),
new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
msgBox.Buttons[0].OnClicked += (b, d) =>
{
try
{
File.Delete(selectedFile);
GUI.AddMessage(GetCharacterEditorTranslation("RagdollDeletedFrom").Replace("[file]", selectedFile), Color.Red, font: GUI.Font);
}
catch (Exception e)
{
DebugConsole.ThrowError(TextManager.Get("DeleteFileError").Replace("[file]", selectedFile), e);
}
msgBox.Close();
listBox.ClearChildren();
PopulateListBox();
selectedFile = null;
return true;
};
msgBox.Buttons[1].OnClicked += (b, d) =>
{
msgBox.Close();
return true;
};
return true;
};
loadBox.Buttons[1].OnClicked += (btn, data) =>
{
string fileName = Path.GetFileNameWithoutExtension(selectedFile);
var ragdoll = character.IsHumanoid ? HumanRagdollParams.GetRagdollParams(character.SpeciesName, fileName) as RagdollParams : RagdollParams.GetRagdollParams<FishRagdollParams>(character.SpeciesName, fileName);
ragdoll.Reset(true);
GUI.AddMessage(GetCharacterEditorTranslation("RagdollLoadedFrom").Replace("[file]", selectedFile), Color.WhiteSmoke, font: GUI.Font);
RecreateRagdoll(ragdoll);
CreateContextualControls();
loadBox.Close();
return true;
};
return true;
};
var saveAnimationButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("SaveAnimation"));
saveAnimationButton.OnClicked += (button, userData) =>
{
var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveAnimation"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxRelSize);
var textArea = new GUIFrame(new RectTransform(new Vector2(1, 0.1f), box.Content.RectTransform) { MinSize = new Point(350, 30) }, style: null);
var inputLabel = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), textArea.RectTransform) { MinSize = new Point(250, 30) }, $"{GetCharacterEditorTranslation("ProvideFileName")}: ");
var inputField = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), textArea.RectTransform, Anchor.TopRight) { MinSize = new Point(100, 30) }, CurrentAnimation.Name);
// Type filtering
var typeSelectionArea = new GUIFrame(new RectTransform(new Vector2(1f, 0.1f), box.Content.RectTransform) { MinSize = new Point(0, 30) }, style: null);
var typeLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), typeSelectionArea.RectTransform, Anchor.TopCenter, Pivot.TopRight), $"{GetCharacterEditorTranslation("SelectAnimationType")}: ");
var typeDropdown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1), typeSelectionArea.RectTransform, Anchor.TopCenter, Pivot.TopLeft), elementCount: 4);
foreach (object enumValue in Enum.GetValues(typeof(AnimationType)))
{
typeDropdown.AddItem(enumValue.ToString(), enumValue);
}
AnimationType selectedType = character.AnimController.ForceSelectAnimationType;
typeDropdown.OnSelected = (component, data) =>
{
selectedType = (AnimationType)data;
inputField.Text = character.AnimController.GetAnimationParamsFromType(selectedType).Name;
return true;
};
typeDropdown.SelectItem(selectedType);
box.Buttons[0].OnClicked += (b, d) =>
{
box.Close();
return true;
};
box.Buttons[1].OnClicked += (b, d) =>
{
#if !DEBUG
if (VanillaCharacters != null && VanillaCharacters.Contains(currentCharacterConfig))
{
GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), Color.Red, font: GUI.LargeFont);
box.Close();
return false;
}
#endif
var animParams = character.AnimController.GetAnimationParamsFromType(selectedType);
animParams.Save(inputField.Text);
animationResetRequiresForceLoading = true;
GUI.AddMessage(GetCharacterEditorTranslation("AnimationOfTypeSavedTo").Replace("[type]", animParams.AnimationType.ToString()).Replace("[path]", animParams.FullPath), Color.Green, font: GUI.Font);
ResetParamsEditor();
box.Close();
return true;
};
return true;
};
var loadAnimationButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("LoadAnimation"));
loadAnimationButton.OnClicked += (button, userData) =>
{
var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadAnimation"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize);
loadBox.Buttons[0].OnClicked += loadBox.Close;
var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform));
var deleteButton = loadBox.Buttons[2];
deleteButton.Enabled = false;
// Type filtering
var typeSelectionArea = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.1f), loadBox.Content.RectTransform) { MinSize = new Point(0, 30) }, style: null);
var typeLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), typeSelectionArea.RectTransform, Anchor.TopCenter, Pivot.TopRight), $"{GetCharacterEditorTranslation("SelectAnimationType")}: ");
var typeDropdown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1), typeSelectionArea.RectTransform, Anchor.TopCenter, Pivot.TopLeft), elementCount: 4);
foreach (object enumValue in Enum.GetValues(typeof(AnimationType)))
{
typeDropdown.AddItem(enumValue.ToString(), enumValue);
}
AnimationType selectedType = character.AnimController.ForceSelectAnimationType;
typeDropdown.OnSelected = (component, data) =>
{
selectedType = (AnimationType)data;
PopulateListBox();
return true;
};
typeDropdown.SelectItem(selectedType);
void PopulateListBox()
{
try
{
listBox.ClearChildren();
var filePaths = Directory.GetFiles(CurrentAnimation.Folder);
foreach (var path in AnimationParams.FilterFilesByType(filePaths, selectedType))
{
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform) { MinSize = new Point(0, 30) }, ToolBox.LimitString(Path.GetFileNameWithoutExtension(path), GUI.Font, listBox.Rect.Width - 80))
{
UserData = path,
ToolTip = path
};
}
}
catch (Exception e)
{
DebugConsole.ThrowError(GetCharacterEditorTranslation("CouldntOpenDirectory").Replace("[folder]", CurrentAnimation.Folder), e);
}
}
PopulateListBox();
// Handle file selection
string selectedFile = null;
listBox.OnSelected += (component, data) =>
{
selectedFile = data as string;
// Don't allow to delete the animation that is currently in use, nor the default file.
var fileName = Path.GetFileNameWithoutExtension(selectedFile);
deleteButton.Enabled = fileName != CurrentAnimation.Name && fileName != AnimationParams.GetDefaultFileName(character.SpeciesName, CurrentAnimation.AnimationType);
return true;
};
deleteButton.OnClicked += (btn, data) =>
{
if (selectedFile == null)
{
loadBox.Close();
return false;
}
var msgBox = new GUIMessageBox(
TextManager.Get("DeleteDialogLabel"),
TextManager.Get("DeleteDialogQuestion").Replace("[file]", selectedFile),
new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
msgBox.Buttons[0].OnClicked += (b, d) =>
{
try
{
File.Delete(selectedFile);
GUI.AddMessage(GetCharacterEditorTranslation("AnimationOfTypeDeleted").Replace("[type]", selectedType.ToString()).Replace("[file]", selectedFile), Color.Red, font: GUI.Font);
}
catch (Exception e)
{
DebugConsole.ThrowError(TextManager.Get("DeleteFileError").Replace("[file]", selectedFile), e);
}
msgBox.Close();
PopulateListBox();
selectedFile = null;
return true;
};
msgBox.Buttons[1].OnClicked += (b, d) =>
{
msgBox.Close();
return true;
};
return true;
};
loadBox.Buttons[1].OnClicked += (btn, data) =>
{
string fileName = Path.GetFileNameWithoutExtension(selectedFile);
if (character.IsHumanoid)
{
switch (selectedType)
{
case AnimationType.Walk:
character.AnimController.WalkParams = HumanWalkParams.GetAnimParams(character, fileName);
break;
case AnimationType.Run:
character.AnimController.RunParams = HumanRunParams.GetAnimParams(character, fileName);
break;
case AnimationType.SwimSlow:
character.AnimController.SwimSlowParams = HumanSwimSlowParams.GetAnimParams(character, fileName);
break;
case AnimationType.SwimFast:
character.AnimController.SwimFastParams = HumanSwimFastParams.GetAnimParams(character, fileName);
break;
default:
DebugConsole.ThrowError(GetCharacterEditorTranslation("AnimationTypeNotImplemented").Replace("[type]", selectedType.ToString()));
break;
}
}
else
{
switch (selectedType)
{
case AnimationType.Walk:
character.AnimController.WalkParams = FishWalkParams.GetAnimParams(character, fileName);
break;
case AnimationType.Run:
character.AnimController.RunParams = FishRunParams.GetAnimParams(character, fileName);
break;
case AnimationType.SwimSlow:
character.AnimController.SwimSlowParams = FishSwimSlowParams.GetAnimParams(character, fileName);
break;
case AnimationType.SwimFast:
character.AnimController.SwimFastParams = FishSwimFastParams.GetAnimParams(character, fileName);
break;
default:
DebugConsole.ThrowError(GetCharacterEditorTranslation("AnimationTypeNotImplemented").Replace("[type]", selectedType.ToString()));
break;
}
}
GUI.AddMessage(GetCharacterEditorTranslation("AnimationOfTypeLoaded").Replace("[type]", selectedType.ToString()).Replace("[file]", selectedFile), Color.WhiteSmoke, font: GUI.Font);
character.AnimController.AllAnimParams.ForEach(a => a.Reset(forceReload: true));
ResetParamsEditor();
loadBox.Close();
return true;
};
return true;
};
// Spacing
new GUIFrame(new RectTransform(buttonSize / 2, layoutGroup.RectTransform), style: null) { CanBeFocused = false };
var resetAnimButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("ResetAnimations"));
resetAnimButton.Color = Color.Red;
resetAnimButton.OnClicked += (button, userData) =>
{
AnimParams.ForEach(p => p.Reset(true));
ResetParamsEditor();
GUI.AddMessage(GetCharacterEditorTranslation("AllAnimationsReset"), Color.WhiteSmoke, font: GUI.Font);
animationResetRequiresForceLoading = false;
return true;
};
var resetRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("ResetRagdoll"));
resetRagdollButton.Color = Color.Red;
resetRagdollButton.OnClicked += (button, userData) =>
{
if (ragdollResetRequiresForceLoading)
{
character.AnimController.ResetRagdoll(forceReload: true);
RecreateRagdoll();
ragdollResetRequiresForceLoading = false;
}
else
{
character.AnimController.ResetRagdoll(forceReload: false);
// For some reason Enumerable.Contains() method does not find the match, threfore the conversion to a list.
var selectedJointParams = selectedJoints.Select(j => j.jointParams).ToList();
var selectedLimbParams = selectedLimbs.Select(l => l.limbParams).ToList();
ClearWidgets();
ClearSelection();
foreach (var joint in character.AnimController.LimbJoints)
{
if (selectedJointParams.Contains(joint.jointParams))
{
selectedJoints.Add(joint);
}
}
foreach (var limb in character.AnimController.Limbs)
{
if (selectedLimbParams.Contains(limb.limbParams))
{
selectedLimbs.Add(limb);
}
}
ResetParamsEditor();
}
jointCreationMode = false;
closestSelectedLimb = null;
CreateGUI();
GUI.AddMessage(GetCharacterEditorTranslation("RagdollReset"), Color.WhiteSmoke, font: GUI.Font);
return true;
};
// Spacing
new GUIFrame(new RectTransform(buttonSize / 2, layoutGroup.RectTransform), style: null) { CanBeFocused = false };
new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("CreateNewCharacter"))
{
OnClicked = (button, data) =>
{
editLimbsToggle.Selected = false;
editAnimsToggle.Selected = false;
spritesheetToggle.Selected = false;
jointsToggle.Selected = false;
paramsToggle.Selected = false;
ragdollToggle.Selected = false;
Wizard.Instance.SelectTab(Wizard.Tab.Character);
return true;
}
};
fileEditToggle = new ToggleButton(new RectTransform(new Vector2(0.1f, 1), fileEditPanel.RectTransform, Anchor.CenterLeft, Pivot.CenterRight), Direction.Right);
}
#endregion
#region ToggleButtons
private enum Direction
{
Left,
Right
}
private class ToggleButton
{
public readonly Direction dir;
public readonly GUIButton toggleButton;
public float OpenState { get; private set; } = 1;
private bool isHidden;
public bool IsHidden
{
get { return isHidden; }
set
{
isHidden = value;
RefreshToggleButtonState();
}
}
public ToggleButton(RectTransform rectT, Direction dir)
{
toggleButton = new GUIButton(rectT, style: "UIToggleButton")
{
Color = toggleButtonColor,
OnClicked = (button, data) =>
{
IsHidden = !IsHidden;
return true;
}
};
this.dir = dir;
RefreshToggleButtonState();
}
public void RefreshToggleButtonState()
{
foreach (GUIComponent child in toggleButton.Children)
{
switch (dir)
{
case Direction.Right:
child.SpriteEffects = isHidden ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
break;
case Direction.Left:
child.SpriteEffects = isHidden ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
break;
}
}
}
public void UpdateOpenState(float deltaTime, Vector2 hiddenPos, RectTransform panel)
{
panel.AbsoluteOffset = Vector2.SmoothStep(hiddenPos, Vector2.Zero, OpenState).ToPoint();
OpenState = isHidden ? Math.Max(OpenState - deltaTime * 2, 0) : Math.Min(OpenState + deltaTime * 2, 1);
}
}
#endregion
#region Params
private List<AnimationParams> AnimParams => character.AnimController.AllAnimParams;
private AnimationParams CurrentAnimation => character.AnimController.CurrentAnimationParams;
private RagdollParams RagdollParams => character.AnimController.RagdollParams;
private void ResetParamsEditor()
{
ParamsEditor.Instance.Clear();
if (editAnimations)
{
AnimParams.ForEach(p => p.AddToEditor(ParamsEditor.Instance));
}
else
{
if (editRagdoll || !editLimbs && !editJoints)
{
RagdollParams.AddToEditor(ParamsEditor.Instance, alsoChildren: false);
}
if (editJoints)
{
if (selectedJoints.None())
{
RagdollParams.Joints.ForEach(jp => jp.AddToEditor(ParamsEditor.Instance));
}
else
{
foreach (var joint in selectedJoints)
{
joint.jointParams.AddToEditor(ParamsEditor.Instance);
}
}
}
if (editLimbs)
{
if (selectedLimbs.None())
{
foreach (var limb in character.AnimController.Limbs)
{
limb.limbParams.AddToEditor(ParamsEditor.Instance);
if (limb.attack != null)
{
new SerializableEntityEditor(ParamsEditor.Instance.EditorBox.Content.RectTransform, limb.attack, inGame: false, showName: true);
}
}
}
else
{
foreach (var limb in selectedLimbs)
{
limb.limbParams.AddToEditor(ParamsEditor.Instance);
if (limb.attack != null)
{
new SerializableEntityEditor(ParamsEditor.Instance.EditorBox.Content.RectTransform, limb.attack, inGame: false, showName: true);
}
}
}
}
}
}
private void TryUpdateAnimParam(string name, object value) => TryUpdateParam(character.AnimController.CurrentAnimationParams, name, value);
private void TryUpdateRagdollParam(string name, object value) => TryUpdateParam(RagdollParams, name, value);
private void TryUpdateParam(EditableParams editableParams, string name, object value)
{
if (editableParams.SerializableProperties.TryGetValue(name, out SerializableProperty p))
{
editableParams.SerializableEntityEditor?.UpdateValue(p, value);
}
}
private void TryUpdateJointParam(LimbJoint joint, string name, object value) => TryUpdateSubParam(joint.jointParams, name, value);
private void TryUpdateLimbParam(Limb limb, string name, object value) => TryUpdateSubParam(limb.limbParams, name, value);
private void TryUpdateSubParam(RagdollSubParams ragdollSubParams, string name, object value)
{
if (ragdollSubParams.SerializableProperties.TryGetValue(name, out SerializableProperty p))
{
ragdollSubParams.SerializableEntityEditor?.UpdateValue(p, value);
}
else
{
var subParams = ragdollSubParams.SubParams.Where(sp => sp.SerializableProperties.ContainsKey(name)).FirstOrDefault();
if (subParams != null)
{
if (subParams.SerializableProperties.TryGetValue(name, out p))
{
subParams.SerializableEntityEditor?.UpdateValue(p, value);
}
}
else
{
DebugConsole.ThrowError(GetCharacterEditorTranslation("NoFieldForParameterFound").Replace("[parameter]", name));
//ragdollParams.SubParams.ForEach(sp => sp.SerializableProperties.ForEach(prop => DebugConsole.ThrowError($"{sp.Name}: sub param field: {prop.Key}")));
}
}
}
#endregion
#region Helpers
private Vector2 ScreenToSim(float x, float y) => ScreenToSim(new Vector2(x, y));
private Vector2 ScreenToSim(Vector2 p) => ConvertUnits.ToSimUnits(Cam.ScreenToWorld(p)) + Submarine.MainSub.SimPosition;
private Vector2 SimToScreen(float x, float y) => SimToScreen(new Vector2(x, y));
private Vector2 SimToScreen(Vector2 p) => Cam.WorldToScreen(ConvertUnits.ToDisplayUnits(p + Submarine.MainSub.SimPosition));
private bool IsMatchingLimb(Limb limb1, Limb limb2, LimbJoint joint1, LimbJoint joint2) =>
joint1.BodyA == limb1.body.FarseerBody && joint2.BodyA == limb2.body.FarseerBody ||
joint1.BodyB == limb1.body.FarseerBody && joint2.BodyB == limb2.body.FarseerBody;
private void ValidateJoint(LimbJoint limbJoint)
{
if (limbJoint.UpperLimit < limbJoint.LowerLimit)
{
if (limbJoint.LowerLimit > 0.0f)
{
limbJoint.LowerLimit -= MathHelper.TwoPi;
}
if (limbJoint.UpperLimit < 0.0f)
{
limbJoint.UpperLimit += MathHelper.TwoPi;
}
}
if (limbJoint.UpperLimit - limbJoint.LowerLimit > MathHelper.TwoPi)
{
limbJoint.LowerLimit = MathUtils.WrapAnglePi(limbJoint.LowerLimit);
limbJoint.UpperLimit = MathUtils.WrapAnglePi(limbJoint.UpperLimit);
}
}
private Limb GetClosestLimbOnRagdoll(Vector2 targetPos, Func<Limb, bool> filter = null)
{
Limb closestLimb = null;
float closestDistance = float.MaxValue;
foreach (Limb l in character.AnimController.Limbs)
{
if (filter == null ? true : filter(l))
{
float distance = Vector2.DistanceSquared(SimToScreen(l.SimPosition), targetPos);
if (distance < closestDistance)
{
closestLimb = l;
closestDistance = distance;
}
}
}
return closestLimb;
}
private Limb GetClosestLimbOnSpritesheet(Vector2 targetPos, Func<Limb, bool> filter = null)
{
Limb closestLimb = null;
float closestDistance = float.MaxValue;
foreach (Limb l in character.AnimController.Limbs)
{
if (l == null) { continue; }
if (filter == null ? true : filter(l))
{
float distance = Vector2.DistanceSquared(GetLimbSpritesheetRect(l).Center.ToVector2(), targetPos);
if (distance < closestDistance)
{
closestLimb = l;
closestDistance = distance;
}
}
}
return closestLimb;
}
private Rectangle GetLimbSpritesheetRect(Limb limb)
{
int offsetX = spriteSheetOffsetX;
int offsetY = spriteSheetOffsetY;
Rectangle rect = Rectangle.Empty;
for (int i = 0; i < Textures.Count; i++)
{
if (limb.ActiveSprite.FilePath != texturePaths[i])
{
offsetY += (int)(Textures[i].Height * spriteSheetZoom);
}
else
{
rect = limb.ActiveSprite.SourceRect;
rect.Size = rect.MultiplySize(spriteSheetZoom);
rect.Location = rect.Location.Multiply(spriteSheetZoom);
rect.X += offsetX;
rect.Y += offsetY;
break;
}
}
return rect;
}
// TODO: refactor this so that it can be used in all cases
private void UpdateSourceRect(Limb limb, Rectangle newRect)
{
limb.ActiveSprite.SourceRect = newRect;
if (limb.DamagedSprite != null)
{
limb.DamagedSprite.SourceRect = limb.ActiveSprite.SourceRect;
}
RecalculateOrigin(limb);
TryUpdateLimbParam(limb, "sourcerect", newRect);
if (limbPairEditing)
{
UpdateOtherLimbs(limb, otherLimb =>
{
otherLimb.ActiveSprite.SourceRect = newRect;
if (otherLimb.DamagedSprite != null)
{
otherLimb.DamagedSprite.SourceRect = newRect;
}
TryUpdateLimbParam(otherLimb, "sourcerect", newRect);
RecalculateOrigin(otherLimb);
});
};
void RecalculateOrigin(Limb l)
{
// Keeps the relative origin unchanged. The absolute origin will be recalculated.
l.ActiveSprite.RelativeOrigin = l.ActiveSprite.RelativeOrigin;
// TODO:
//if (lockSpriteOrigin)
//{
// // Keeps the absolute origin unchanged. The relative origin will be recalculated.
// var spritePos = new Vector2(spriteSheetOffsetX, GetOffsetY(l));
// l.ActiveSprite.Origin = (originWidget.DrawPos - spritePos - l.ActiveSprite.SourceRect.Location.ToVector2() * spriteSheetZoom) / spriteSheetZoom;
// TryUpdateLimbParam(l, "origin", l.ActiveSprite.RelativeOrigin);
//}
//else
//{
// // Keeps the relative origin unchanged. The absolute origin will be recalculated.
// l.ActiveSprite.RelativeOrigin = l.ActiveSprite.RelativeOrigin;
//}
}
}
private void DrawJointCreationOnSpritesheet(SpriteBatch spriteBatch, Vector2 startPos)
{
// Spritesheet
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2 - 200, GameMain.GraphicsHeight - 200), GetCharacterEditorTranslation("SelectTargetLimbForJointEnd"), Color.White, Color.Black * 0.5f, 10, GUI.Font);
GUI.DrawLine(spriteBatch, startPos, PlayerInput.MousePosition, Color.LightGreen, width: 3);
if (targetLimb != null && targetLimb.ActiveSprite != null)
{
GUI.DrawRectangle(spriteBatch, GetLimbSpritesheetRect(targetLimb), Color.LightGreen, thickness: 3);
}
}
private void DrawJointCreationOnRagdoll(SpriteBatch spriteBatch, Vector2 startPos)
{
// Ragdoll
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2 - 200, GameMain.GraphicsHeight - 200), GetCharacterEditorTranslation("SelectTargetLimbForJointEnd"), Color.White, Color.Black * 0.5f, 10, GUI.Font);
GUI.DrawLine(spriteBatch, startPos, PlayerInput.MousePosition, Color.LightGreen, width: 3);
if (targetLimb != null && targetLimb.ActiveSprite != null)
{
var sourceRect = targetLimb.ActiveSprite.SourceRect;
Vector2 size = sourceRect.Size.ToVector2() * Cam.Zoom * targetLimb.Scale * targetLimb.TextureScale;
Vector2 up = VectorExtensions.BackwardFlipped(targetLimb.Rotation);
Vector2 left = up.Right();
Vector2 limbScreenPos = SimToScreen(targetLimb.SimPosition);
var offset = targetLimb.ActiveSprite.RelativeOrigin.X * left + targetLimb.ActiveSprite.RelativeOrigin.Y * up;
Vector2 center = limbScreenPos + offset;
corners = MathUtils.GetImaginaryRect(corners, up, center, size);
GUI.DrawRectangle(spriteBatch, corners, Color.LightGreen, thickness: 3);
}
}
private void CalculateSpritesheetZoom()
{
float width = textures.OrderByDescending(t => t.Width).First().Width;
float height = textures.Sum(t => t.Height);
float margin = 20;
if (textures == null || textures.None())
{
spriteSheetMaxZoom = 1;
}
else if (height > width)
{
spriteSheetMaxZoom = (centerArea.Rect.Bottom - spriteSheetOffsetY - margin) / height;
}
else
{
spriteSheetMaxZoom = (centerArea.Rect.Left - spriteSheetOffsetX - margin) / width;
}
spriteSheetMinZoom = spriteSheetMinZoom > spriteSheetMaxZoom ? spriteSheetMaxZoom : 0.25f;
spriteSheetZoom = MathHelper.Clamp(1, spriteSheetMinZoom, spriteSheetMaxZoom);
}
private void HandleLimbSelection(Limb limb)
{
if (!selectedLimbs.Contains(limb))
{
if (!Widget.EnableMultiSelect)
{
selectedLimbs.Clear();
}
selectedLimbs.Add(limb);
ResetParamsEditor();
//RagdollParams.StoreState();
}
else if (Widget.EnableMultiSelect)
{
selectedLimbs.Remove(limb);
ResetParamsEditor();
}
}
private void OpenDoors()
{
foreach (var item in Item.ItemList)
{
foreach (var component in item.Components)
{
if (component is Items.Components.Door door)
{
door.IsOpen = true;
}
}
}
}
private void SaveSnapshot()
{
if (editJoints || editLimbs || editIK)
{
RagdollParams.CreateSnapshot();
}
if (editAnimations)
{
CurrentAnimation.CreateSnapshot();
}
}
#endregion
#region Animation Controls
private void DrawAnimationControls(SpriteBatch spriteBatch, float deltaTime)
{
var collider = character.AnimController.Collider;
var colliderDrawPos = SimToScreen(collider.SimPosition);
var animParams = character.AnimController.CurrentAnimationParams;
var groundedParams = animParams as GroundedMovementParams;
var humanParams = animParams as IHumanAnimation;
var humanGroundedParams = animParams as HumanGroundedParams;
var humanSwimParams = animParams as HumanSwimParams;
var fishParams = animParams as IFishAnimation;
var fishGroundedParams = animParams as FishGroundedParams;
var fishSwimParams = animParams as FishSwimParams;
var head = character.AnimController.GetLimb(LimbType.Head);
var torso = character.AnimController.GetLimb(LimbType.Torso);
var tail = character.AnimController.GetLimb(LimbType.Tail);
var legs = character.AnimController.GetLimb(LimbType.Legs);
var thigh = character.AnimController.GetLimb(LimbType.RightThigh) ?? character.AnimController.GetLimb(LimbType.LeftThigh);
var foot = character.AnimController.GetLimb(LimbType.RightFoot) ?? character.AnimController.GetLimb(LimbType.LeftFoot);
var hand = character.AnimController.GetLimb(LimbType.RightHand) ?? character.AnimController.GetLimb(LimbType.LeftHand);
var arm = character.AnimController.GetLimb(LimbType.RightArm) ?? character.AnimController.GetLimb(LimbType.LeftArm);
// Note: the main collider rotates only when swimming
float dir = character.AnimController.Dir;
Vector2 GetSimSpaceForward() => animParams.IsSwimAnimation ? Vector2.Transform(Vector2.UnitY, Matrix.CreateRotationZ(collider.Rotation)) : Vector2.UnitX * character.AnimController.Dir;
Vector2 GetScreenSpaceForward() => animParams.IsSwimAnimation ? VectorExtensions.BackwardFlipped(collider.Rotation, 1) : Vector2.UnitX * character.AnimController.Dir;
bool ShowCycleWidget() => PlayerInput.KeyDown(Keys.LeftAlt) && (CurrentAnimation is IHumanAnimation || CurrentAnimation is GroundedMovementParams);
if (!PlayerInput.KeyDown(Keys.LeftAlt) && (animParams is IHumanAnimation || animParams is GroundedMovementParams))
{
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2 - 120, 100), GetCharacterEditorTranslation("HoldLeftAltToAdjustCycleSpeed"), Color.White, Color.Black * 0.5f, 10, GUI.Font);
}
// Widgets for all anims -->
Vector2 referencePoint = SimToScreen(head != null ? head.SimPosition: collider.SimPosition);
Vector2 drawPos = referencePoint;
if (ShowCycleWidget())
{
GetAnimationWidget("CycleSpeed", Color.MediumPurple, size: 20, sizeMultiplier: 1.5f, shape: Widget.Shape.Circle, initMethod: w =>
{
float multiplier = 0.5f;
w.tooltip = GetCharacterEditorTranslation("CycleSpeed");
w.refresh = () =>
{
var refPoint = SimToScreen(head != null ? head.SimPosition : collider.SimPosition);
w.DrawPos = refPoint + GetScreenSpaceForward() * ConvertUnits.ToDisplayUnits(CurrentAnimation.CycleSpeed * multiplier) * Cam.Zoom;
// Update tooltip, because the cycle speed might be automatically adjusted by the movement speed widget.
w.tooltip = $"{GetCharacterEditorTranslation("CycleSpeed")}: {CurrentAnimation.CycleSpeed.FormatDoubleDecimal()}";
};
w.MouseHeld += dTime =>
{
// TODO: clamp so that cannot manipulate the local y axis -> remove the additional refresh callback in below
//Vector2 newPos = PlayerInput.MousePosition;
//w.DrawPos = newPos;
float speed = CurrentAnimation.CycleSpeed + ConvertUnits.ToSimUnits(Vector2.Multiply(PlayerInput.MouseSpeed / multiplier, GetScreenSpaceForward()).Combine()) / Cam.Zoom;
TryUpdateAnimParam("cyclespeed", speed);
w.tooltip = $"{GetCharacterEditorTranslation("CycleSpeed")}: {CurrentAnimation.CycleSpeed.FormatDoubleDecimal()}";
};
// Additional check, which overrides the previous value (because evaluated last)
w.PreUpdate += dTime =>
{
if (!ShowCycleWidget())
{
w.Enabled = false;
}
};
// Additional (remove if the position is updated when the mouse is held)
w.PreDraw += (sp, dTime) =>
{
if (w.IsControlled)
{
w.refresh();
}
};
w.PostDraw += (sp, dTime) =>
{
if (w.IsSelected)
{
GUI.DrawLine(spriteBatch, w.DrawPos, SimToScreen(head != null ? head.SimPosition : collider.SimPosition), Color.MediumPurple);
}
};
}).Draw(spriteBatch, deltaTime);
}
else
{
GetAnimationWidget("MovementSpeed", Color.Turquoise, size: 20, sizeMultiplier: 1.5f, shape: Widget.Shape.Circle, initMethod: w =>
{
float multiplier = 0.5f;
w.tooltip = GetCharacterEditorTranslation("MovementSpeed");
w.refresh = () =>
{
var refPoint = SimToScreen(head != null ? head.SimPosition : collider.SimPosition);
w.DrawPos = refPoint + GetScreenSpaceForward() * ConvertUnits.ToDisplayUnits(CurrentAnimation.MovementSpeed * multiplier) * Cam.Zoom;
};
w.MouseHeld += dTime =>
{
// TODO: clamp so that cannot manipulate the local y axis -> remove the additional refresh callback in below
//Vector2 newPos = PlayerInput.MousePosition;
//w.DrawPos = newPos;
float speed = CurrentAnimation.MovementSpeed + ConvertUnits.ToSimUnits(Vector2.Multiply(PlayerInput.MouseSpeed / multiplier, GetScreenSpaceForward()).Combine()) / Cam.Zoom;
TryUpdateAnimParam("movementspeed", MathHelper.Clamp(speed, 0.1f, Ragdoll.MAX_SPEED));
// Sync
if (humanSwimParams != null)
{
TryUpdateAnimParam("cyclespeed", character.AnimController.CurrentAnimationParams.MovementSpeed);
}
w.tooltip = $"{GetCharacterEditorTranslation("MovementSpeed")}: {CurrentAnimation.MovementSpeed.FormatSingleDecimal()}";
};
// Additional check, which overrides the previous value (because evaluated last)
w.PreUpdate += dTime =>
{
if (ShowCycleWidget())
{
w.Enabled = false;
}
};
// Additional (remove if the position is updated when the mouse is held)
w.PreDraw += (sp, dTime) =>
{
if (w.IsControlled)
{
w.refresh();
}
};
w.PostDraw += (sp, dTime) =>
{
if (w.IsSelected)
{
GUI.DrawLine(spriteBatch, w.DrawPos, SimToScreen(head != null ? head.SimPosition : collider.SimPosition), Color.Turquoise);
}
};
}).Draw(spriteBatch, deltaTime);
}
if (head != null)
{
// Head angle
DrawRadialWidget(spriteBatch, SimToScreen(head.SimPosition), animParams.HeadAngle, GetCharacterEditorTranslation("HeadAngle"), Color.White,
angle => TryUpdateAnimParam("headangle", angle), circleRadius: 25, rotationOffset: collider.Rotation + MathHelper.Pi, clockWise: dir < 0, wrapAnglePi: true);
// Head position and leaning
if (animParams.IsGroundedAnimation)
{
if (humanGroundedParams != null && character.AnimController is HumanoidAnimController humanAnimController)
{
GetAnimationWidget("HeadPosition", Color.Red, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("Head");
w.refresh = () => w.DrawPos = SimToScreen(head.SimPosition.X + humanAnimController.HeadLeanAmount * character.AnimController.Dir, head.PullJointWorldAnchorB.Y);
bool isHorizontal = false;
bool isDirectionSet = false;
w.MouseDown += () => isDirectionSet = false;
w.MouseHeld += dTime =>
{
if (PlayerInput.MouseSpeed.NearlyEquals(Vector2.Zero)) { return; }
if (!isDirectionSet)
{
isHorizontal = Math.Abs(PlayerInput.MouseSpeed.X) > Math.Abs(PlayerInput.MouseSpeed.Y);
isDirectionSet = true;
}
var scaledInput = ConvertUnits.ToSimUnits(PlayerInput.MouseSpeed) / Cam.Zoom;
if (PlayerInput.KeyDown(Keys.LeftAlt))
{
if (isHorizontal)
{
TryUpdateAnimParam("headleanamount", humanGroundedParams.HeadLeanAmount + scaledInput.X * character.AnimController.Dir);
w.refresh();
w.DrawPos = new Vector2(PlayerInput.MousePosition.X, w.DrawPos.Y);
}
else
{
TryUpdateAnimParam("headposition", humanGroundedParams.HeadPosition - scaledInput.Y / RagdollParams.JointScale);
w.refresh();
w.DrawPos = new Vector2(w.DrawPos.X, PlayerInput.MousePosition.Y);
}
}
else
{
TryUpdateAnimParam("headleanamount", humanGroundedParams.HeadLeanAmount + scaledInput.X * character.AnimController.Dir);
w.refresh();
w.DrawPos = new Vector2(PlayerInput.MousePosition.X, w.DrawPos.Y);
TryUpdateAnimParam("headposition", humanGroundedParams.HeadPosition - scaledInput.Y / RagdollParams.JointScale);
w.refresh();
w.DrawPos = new Vector2(w.DrawPos.X, PlayerInput.MousePosition.Y);
}
};
w.PostDraw += (sB, dTime) =>
{
if (w.IsControlled && isDirectionSet)
{
if (PlayerInput.KeyDown(Keys.LeftAlt))
{
if (isHorizontal)
{
GUI.DrawLine(spriteBatch, new Vector2(0, w.DrawPos.Y), new Vector2(GameMain.GraphicsWidth, w.DrawPos.Y), Color.Red);
}
else
{
GUI.DrawLine(spriteBatch, new Vector2(w.DrawPos.X, 0), new Vector2(w.DrawPos.X, GameMain.GraphicsHeight), Color.Red);
}
}
else
{
GUI.DrawLine(spriteBatch, new Vector2(0, w.DrawPos.Y), new Vector2(GameMain.GraphicsWidth, w.DrawPos.Y), Color.Red);
GUI.DrawLine(spriteBatch, new Vector2(w.DrawPos.X, 0), new Vector2(w.DrawPos.X, GameMain.GraphicsHeight), Color.Red);
}
}
else if (w.IsSelected)
{
GUI.DrawLine(spriteBatch, w.DrawPos, SimToScreen(head.SimPosition), Color.Red);
}
};
}).Draw(spriteBatch, deltaTime);
}
else
{
GetAnimationWidget("HeadPosition", Color.Red, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("HeadPosition");
w.refresh = () => w.DrawPos = SimToScreen(head.SimPosition.X, head.PullJointWorldAnchorB.Y);
w.MouseHeld += dTime =>
{
w.DrawPos = SimToScreen(head.SimPosition.X, head.PullJointWorldAnchorB.Y);
var scaledInput = ConvertUnits.ToSimUnits(PlayerInput.MouseSpeed) / Cam.Zoom / RagdollParams.JointScale;
TryUpdateAnimParam("headposition", groundedParams.HeadPosition - scaledInput.Y);
};
w.PostDraw += (sB, dTime) =>
{
if (w.IsControlled)
{
GUI.DrawLine(spriteBatch, new Vector2(w.DrawPos.X, 0), new Vector2(w.DrawPos.X, GameMain.GraphicsHeight), Color.Red);
}
};
}).Draw(spriteBatch, deltaTime);
}
}
}
if (torso != null)
{
referencePoint = torso.SimPosition;
if (animParams is HumanGroundedParams || animParams is HumanSwimParams)
{
var f = Vector2.Transform(Vector2.UnitY, Matrix.CreateRotationZ(collider.Rotation));
referencePoint -= f * 0.25f;
}
// Torso angle
DrawRadialWidget(spriteBatch, SimToScreen(referencePoint), animParams.TorsoAngle, GetCharacterEditorTranslation("TorsoAngle"), Color.White,
angle => TryUpdateAnimParam("torsoangle", angle), rotationOffset: collider.Rotation + MathHelper.Pi, clockWise: dir < 0, wrapAnglePi: true);
if (animParams.IsGroundedAnimation)
{
// Torso position and leaning
if (humanGroundedParams != null && character.AnimController is HumanoidAnimController humanAnimController)
{
GetAnimationWidget("TorsoPosition", Color.DarkRed, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("Torso");
w.refresh = () => w.DrawPos = SimToScreen(torso.SimPosition.X + humanAnimController.TorsoLeanAmount * character.AnimController.Dir, torso.PullJointWorldAnchorB.Y);
bool isHorizontal = false;
bool isDirectionSet = false;
w.MouseDown += () => isDirectionSet = false;
w.MouseHeld += dTime =>
{
if (PlayerInput.MouseSpeed.NearlyEquals(Vector2.Zero)) { return; }
if (!isDirectionSet)
{
isHorizontal = Math.Abs(PlayerInput.MouseSpeed.X) > Math.Abs(PlayerInput.MouseSpeed.Y);
isDirectionSet = true;
}
var scaledInput = ConvertUnits.ToSimUnits(PlayerInput.MouseSpeed) / Cam.Zoom;
if (PlayerInput.KeyDown(Keys.LeftAlt))
{
if (isHorizontal)
{
TryUpdateAnimParam("torsoleanamount", humanGroundedParams.TorsoLeanAmount + scaledInput.X * character.AnimController.Dir);
w.refresh();
w.DrawPos = new Vector2(PlayerInput.MousePosition.X, w.DrawPos.Y);
}
else
{
TryUpdateAnimParam("torsoposition", humanGroundedParams.TorsoPosition - scaledInput.Y / RagdollParams.JointScale);
w.refresh();
w.DrawPos = new Vector2(w.DrawPos.X, PlayerInput.MousePosition.Y);
}
}
else
{
TryUpdateAnimParam("torsoleanamount", humanGroundedParams.TorsoLeanAmount + scaledInput.X * character.AnimController.Dir);
w.refresh();
w.DrawPos = new Vector2(PlayerInput.MousePosition.X, w.DrawPos.Y);
TryUpdateAnimParam("torsoposition", humanGroundedParams.TorsoPosition - scaledInput.Y / RagdollParams.JointScale);
w.refresh();
w.DrawPos = new Vector2(w.DrawPos.X, PlayerInput.MousePosition.Y);
}
};
w.PostDraw += (sB, dTime) =>
{
if (w.IsControlled && isDirectionSet)
{
if (PlayerInput.KeyDown(Keys.LeftAlt))
{
if (isHorizontal)
{
GUI.DrawLine(spriteBatch, new Vector2(0, w.DrawPos.Y), new Vector2(GameMain.GraphicsWidth, w.DrawPos.Y), Color.DarkRed);
}
else
{
GUI.DrawLine(spriteBatch, new Vector2(w.DrawPos.X, 0), new Vector2(w.DrawPos.X, GameMain.GraphicsHeight), Color.DarkRed);
}
}
else
{
GUI.DrawLine(spriteBatch, new Vector2(0, w.DrawPos.Y), new Vector2(GameMain.GraphicsWidth, w.DrawPos.Y), Color.DarkRed);
GUI.DrawLine(spriteBatch, new Vector2(w.DrawPos.X, 0), new Vector2(w.DrawPos.X, GameMain.GraphicsHeight), Color.DarkRed);
}
}
else if (w.IsSelected)
{
GUI.DrawLine(spriteBatch, w.DrawPos, SimToScreen(torso.SimPosition), Color.DarkRed);
}
};
}).Draw(spriteBatch, deltaTime);
}
else
{
GetAnimationWidget("TorsoPosition", Color.DarkRed, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("TorsoPosition");
w.refresh = () => w.DrawPos = SimToScreen(torso.SimPosition.X, torso.PullJointWorldAnchorB.Y);
w.MouseHeld += dTime =>
{
w.DrawPos = SimToScreen(torso.SimPosition.X, torso.PullJointWorldAnchorB.Y);
var scaledInput = ConvertUnits.ToSimUnits(PlayerInput.MouseSpeed) / Cam.Zoom / RagdollParams.JointScale;
TryUpdateAnimParam("torsoposition", groundedParams.TorsoPosition - scaledInput.Y);
};
w.PostDraw += (sB, dTime) =>
{
if (w.IsControlled)
{
GUI.DrawLine(spriteBatch, new Vector2(w.DrawPos.X, 0), new Vector2(w.DrawPos.X, GameMain.GraphicsHeight), Color.DarkRed);
}
};
}).Draw(spriteBatch, deltaTime);
}
}
}
// Tail angle
if (tail != null && fishParams != null)
{
DrawRadialWidget(spriteBatch, SimToScreen(tail.SimPosition), fishParams.TailAngle, GetCharacterEditorTranslation("TailAngle"), Color.White,
angle => TryUpdateAnimParam("tailangle", angle), circleRadius: 25, rotationOffset: collider.Rotation + MathHelper.Pi, clockWise: dir < 0, wrapAnglePi: true);
}
// Foot angle
if (foot != null)
{
if (fishParams != null)
{
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.type != LimbType.LeftFoot && limb.type != LimbType.RightFoot) continue;
if (!fishParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID))
{
fishParams.FootAnglesInRadians[limb.limbParams.ID] = 0.0f;
}
DrawRadialWidget(spriteBatch,
SimToScreen(new Vector2(limb.SimPosition.X, colliderBottom.Y)),
MathHelper.ToDegrees(fishParams.FootAnglesInRadians[limb.limbParams.ID]),
GetCharacterEditorTranslation("FootAngle"), Color.White,
angle =>
{
fishParams.FootAnglesInRadians[limb.limbParams.ID] = MathHelper.ToRadians(angle);
TryUpdateAnimParam("footangles", fishParams.FootAngles);
},
circleRadius: 25, rotationOffset: collider.Rotation, clockWise: dir < 0, wrapAnglePi: true);
}
}
else if (humanParams != null)
{
DrawRadialWidget(spriteBatch, SimToScreen(foot.SimPosition), humanParams.FootAngle, GetCharacterEditorTranslation("FootAngle"), Color.White,
angle => TryUpdateAnimParam("footangle", angle), circleRadius: 25, rotationOffset: collider.Rotation + MathHelper.Pi, clockWise: dir < 0, wrapAnglePi: true);
}
// Grounded only
if (groundedParams != null)
{
GetAnimationWidget("StepSize", Color.LimeGreen, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("StepSize");
w.refresh = () =>
{
var refPoint = SimToScreen(character.AnimController.GetColliderBottom());
var stepSize = ConvertUnits.ToDisplayUnits(character.AnimController.StepSize.Value);
w.DrawPos = refPoint + new Vector2(stepSize.X * character.AnimController.Dir, -stepSize.Y) * Cam.Zoom;
};
w.MouseHeld += dTime =>
{
w.DrawPos = PlayerInput.MousePosition;
var transformedInput = ConvertUnits.ToSimUnits(new Vector2(PlayerInput.MouseSpeed.X * character.AnimController.Dir, -PlayerInput.MouseSpeed.Y)) / Cam.Zoom / RagdollParams.JointScale;
TryUpdateAnimParam("stepsize", groundedParams.StepSize + transformedInput);
w.tooltip = $"{GetCharacterEditorTranslation("StepSize")}: {groundedParams.StepSize.FormatDoubleDecimal()}";
};
w.PostDraw += (sp, dTime) =>
{
if (w.IsSelected)
{
GUI.DrawLine(sp, w.DrawPos, SimToScreen(character.AnimController.GetColliderBottom()), Color.LimeGreen);
}
};
}).Draw(spriteBatch, deltaTime);
}
}
// Human grounded only -->
if (humanGroundedParams != null)
{
if (hand != null || arm != null)
{
GetAnimationWidget("HandMoveAmount", Color.LightGreen, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("HandMoveAmount");
float offset = 0.1f;
w.refresh = () =>
{
var refPoint = SimToScreen(collider.SimPosition + GetSimSpaceForward() * offset);
var handMovement = ConvertUnits.ToDisplayUnits(humanGroundedParams.HandMoveAmount);
w.DrawPos = refPoint + new Vector2(handMovement.X * character.AnimController.Dir, handMovement.Y) * Cam.Zoom;
};
w.MouseHeld += dTime =>
{
w.DrawPos = PlayerInput.MousePosition;
var transformedInput = ConvertUnits.ToSimUnits(new Vector2(PlayerInput.MouseSpeed.X * character.AnimController.Dir, PlayerInput.MouseSpeed.Y) / Cam.Zoom);
TryUpdateAnimParam("handmoveamount", humanGroundedParams.HandMoveAmount + transformedInput);
w.tooltip = $"{GetCharacterEditorTranslation("HandMoveAmount")}: {humanGroundedParams.HandMoveAmount.FormatDoubleDecimal()}";
};
w.PostDraw += (sp, dTime) =>
{
if (w.IsSelected)
{
GUI.DrawLine(sp, w.DrawPos, SimToScreen(collider.SimPosition + GetSimSpaceForward() * offset), Color.LightGreen);
}
};
}).Draw(spriteBatch, deltaTime);
}
}
// Fish swim only -->
else if (tail != null && fishSwimParams != null)
{
float amplitudeMultiplier = 0.5f;
float lengthMultiplier = 20;
int points = 1000;
float GetAmplitude() => ConvertUnits.ToDisplayUnits(fishSwimParams.WaveAmplitude) * Cam.Zoom / amplitudeMultiplier;
float GetWaveLength() => ConvertUnits.ToDisplayUnits(fishSwimParams.WaveLength) * Cam.Zoom / lengthMultiplier;
Vector2 GetRefPoint() => SimToScreen(collider.SimPosition) - GetScreenSpaceForward() * ConvertUnits.ToDisplayUnits(collider.radius) * 3 * Cam.Zoom;
Vector2 GetDrawPos() => GetRefPoint() - GetScreenSpaceForward() * GetWaveLength();
Vector2 GetDir() => GetRefPoint() - GetDrawPos();
Vector2 GetStartPoint() => GetDrawPos() + GetDir() / 2;
Vector2 GetControlPoint() => GetStartPoint() + GetScreenSpaceForward().Right() * character.AnimController.Dir * GetAmplitude();
var lengthWidget = GetAnimationWidget("WaveLength", Color.NavajoWhite, size: 15, shape: Widget.Shape.Circle, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("TailMovementSpeed");
w.refresh = () => w.DrawPos = GetDrawPos();
w.MouseHeld += dTime =>
{
float input = Vector2.Multiply(ConvertUnits.ToSimUnits(PlayerInput.MouseSpeed), GetScreenSpaceForward()).Combine() / Cam.Zoom * lengthMultiplier;
TryUpdateAnimParam("wavelength", MathHelper.Clamp(fishSwimParams.WaveLength - input, 0, 150));
};
// Additional
w.PreDraw += (sp, dTime) =>
{
if (w.IsControlled)
{
w.refresh();
}
};
});
var amplitudeWidget = GetAnimationWidget("WaveAmplitude", Color.NavajoWhite, size: 15, shape: Widget.Shape.Circle, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("TailMovementAmount");
w.refresh = () => w.DrawPos = GetControlPoint();
w.MouseHeld += dTime =>
{
float input = Vector2.Multiply(ConvertUnits.ToSimUnits(PlayerInput.MouseSpeed), GetScreenSpaceForward().Right()).Combine() * character.AnimController.Dir / Cam.Zoom * amplitudeMultiplier;
TryUpdateAnimParam("waveamplitude", MathHelper.Clamp(fishSwimParams.WaveAmplitude + input, -4, 4));
};
// Additional
w.PreDraw += (sp, dTime) =>
{
if (w.IsControlled)
{
w.refresh();
}
};
});
if (lengthWidget.IsControlled || amplitudeWidget.IsControlled)
{
GUI.DrawSineWithDots(spriteBatch, GetRefPoint(), -GetDir(), GetAmplitude(), GetWaveLength(), 5000, points, Color.NavajoWhite);
}
lengthWidget.Draw(spriteBatch, deltaTime);
amplitudeWidget.Draw(spriteBatch, deltaTime);
}
// Human swim only -->
else if (humanSwimParams != null)
{
// Legs
float amplitudeMultiplier = 5;
float lengthMultiplier = 5;
int points = 1000;
float GetAmplitude() => ConvertUnits.ToDisplayUnits(humanSwimParams.LegMoveAmount) * Cam.Zoom / amplitudeMultiplier;
float GetWaveLength() => ConvertUnits.ToDisplayUnits(humanSwimParams.LegCycleLength) * Cam.Zoom / lengthMultiplier;
Vector2 GetRefPoint() => SimToScreen(character.SimPosition - GetScreenSpaceForward() / 2);
Vector2 GetDrawPos() => GetRefPoint() - GetScreenSpaceForward() * GetWaveLength();
Vector2 GetDir() => GetRefPoint() - GetDrawPos();
Vector2 GetStartPoint() => GetDrawPos() + GetDir() / 2;
Vector2 GetControlPoint() => GetStartPoint() + GetScreenSpaceForward().Right() * character.AnimController.Dir * GetAmplitude();
var lengthWidget = GetAnimationWidget("LegMovementSpeed", Color.NavajoWhite, size: 15, shape: Widget.Shape.Circle, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("LegMovementSpeed");
w.refresh = () => w.DrawPos = GetDrawPos();
w.MouseHeld += dTime =>
{
float input = Vector2.Multiply(ConvertUnits.ToSimUnits(PlayerInput.MouseSpeed), GetScreenSpaceForward()).Combine() / Cam.Zoom * lengthMultiplier;
TryUpdateAnimParam("legcyclelength", MathHelper.Clamp(humanSwimParams.LegCycleLength - input, 0, 20));
};
// Additional
w.PreDraw += (sp, dTime) =>
{
if (w.IsControlled)
{
w.refresh();
}
};
});
var amplitudeWidget = GetAnimationWidget("LegMovementAmount", Color.NavajoWhite, size: 15, shape: Widget.Shape.Circle, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("LegMovementAmount");
w.refresh = () => w.DrawPos = GetControlPoint();
w.MouseHeld += dTime =>
{
float input = Vector2.Multiply(ConvertUnits.ToSimUnits(PlayerInput.MouseSpeed), GetScreenSpaceForward().Right()).Combine() * character.AnimController.Dir / Cam.Zoom * amplitudeMultiplier;
TryUpdateAnimParam("legmoveamount", MathHelper.Clamp(humanSwimParams.LegMoveAmount + input, -2, 2));
};
// Additional
w.PreDraw += (sp, dTime) =>
{
if (w.IsControlled)
{
w.refresh();
}
};
});
if (lengthWidget.IsControlled || amplitudeWidget.IsControlled)
{
GUI.DrawSineWithDots(spriteBatch, GetRefPoint(), -GetDir(), GetAmplitude(), GetWaveLength(), 5000, points, Color.NavajoWhite);
}
lengthWidget.Draw(spriteBatch, deltaTime);
amplitudeWidget.Draw(spriteBatch, deltaTime);
// Arms
GetAnimationWidget("HandMoveAmount", Color.LightGreen, initMethod: w =>
{
w.tooltip = GetCharacterEditorTranslation("HandMoveAmount");
float offset = 0.4f;
w.refresh = () =>
{
var refPoint = SimToScreen(collider.SimPosition + GetSimSpaceForward() * offset);
var handMovement = ConvertUnits.ToDisplayUnits(humanSwimParams.HandMoveAmount);
w.DrawPos = refPoint + new Vector2(handMovement.X * character.AnimController.Dir, handMovement.Y) * Cam.Zoom;
};
w.MouseHeld += dTime =>
{
w.DrawPos = PlayerInput.MousePosition;
Vector2 transformedInput = ConvertUnits.ToSimUnits(new Vector2(PlayerInput.MouseSpeed.X * character.AnimController.Dir, PlayerInput.MouseSpeed.Y)) / Cam.Zoom;
Vector2 handMovement = humanSwimParams.HandMoveAmount + transformedInput;
TryUpdateAnimParam("handmoveamount", handMovement);
TryUpdateAnimParam("handcyclespeed", handMovement.X * 4);
w.tooltip = $"{GetCharacterEditorTranslation("HandMoveAmount")}: {humanSwimParams.HandMoveAmount.FormatDoubleDecimal()}";
};
w.PostDraw += (sp, dTime) =>
{
if (w.IsSelected)
{
GUI.DrawLine(sp, w.DrawPos, SimToScreen(collider.SimPosition + GetSimSpaceForward() * offset), Color.LightGreen);
}
};
}).Draw(spriteBatch, deltaTime);
}
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.type == LimbType.LeftFoot || limb.type == LimbType.RightFoot)
{
GUI.DrawRectangle(spriteBatch, SimToScreen(limb.DebugRefPos) - Vector2.One * 3, Vector2.One * 6, Color.White, isFilled: true);
GUI.DrawRectangle(spriteBatch, SimToScreen(limb.DebugTargetPos) - Vector2.One * 3, Vector2.One * 6, Color.LightGreen, isFilled: true);
}
}
}
#endregion
#region Ragdoll
private Vector2[] corners = new Vector2[4];
private Vector2[] GetLimbPhysicRect(Limb limb)
{
Vector2 size = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) * Cam.Zoom;
Vector2 up = VectorExtensions.BackwardFlipped(limb.Rotation);
Vector2 limbScreenPos = SimToScreen(limb.SimPosition);
corners = MathUtils.GetImaginaryRect(corners, up, limbScreenPos, size);
return corners;
}
private void DrawLimbEditor(SpriteBatch spriteBatch)
{
float inputMultiplier = 0.5f;
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb == null || limb.ActiveSprite == null) { continue; }
var origin = limb.ActiveSprite.Origin;
var sourceRect = limb.ActiveSprite.SourceRect;
Vector2 limbScreenPos = SimToScreen(limb.SimPosition);
bool isSelected = selectedLimbs.Contains(limb);
corners = GetLimbPhysicRect(limb);
if (isSelected)
{
GUI.DrawRectangle(spriteBatch, corners, Color.White, thickness: 3);
}
if (GUI.MouseOn == null && Widget.selectedWidgets.None() && !spriteSheetRect.Contains(PlayerInput.MousePosition) && MathUtils.RectangleContainsPoint(corners, PlayerInput.MousePosition))
{
if (isSelected)
{
// Origin
if (!lockSpriteOrigin && PlayerInput.LeftButtonHeld())
{
Vector2 forward = Vector2.Transform(Vector2.UnitY, Matrix.CreateRotationZ(limb.Rotation));
var input = -scaledMouseSpeed * inputMultiplier / Cam.Zoom / limb.Scale / limb.TextureScale;
var sprite = limb.ActiveSprite;
origin += input.TransformVector(forward);
var max = new Vector2(sourceRect.Width, sourceRect.Height);
sprite.Origin = origin.Clamp(Vector2.Zero, max);
if (limb.DamagedSprite != null)
{
limb.DamagedSprite.Origin = sprite.Origin;
}
if (character.AnimController.IsFlipped)
{
origin.X = Math.Abs(origin.X - sourceRect.Width);
}
TryUpdateLimbParam(limb, "origin", limb.ActiveSprite.RelativeOrigin);
if (limbPairEditing)
{
UpdateOtherLimbs(limb, otherLimb =>
{
otherLimb.ActiveSprite.Origin = sprite.Origin;
if (otherLimb.DamagedSprite != null)
{
otherLimb.DamagedSprite.Origin = sprite.Origin;
}
TryUpdateLimbParam(otherLimb, "origin", otherLimb.ActiveSprite.RelativeOrigin);
});
}
GUI.DrawString(spriteBatch, limbScreenPos + new Vector2(10, -10), limb.ActiveSprite.RelativeOrigin.FormatDoubleDecimal(), Color.Yellow, Color.Black * 0.5f);
}
}
else
{
GUI.DrawRectangle(spriteBatch, corners, Color.White);
GUI.DrawString(spriteBatch, limbScreenPos + new Vector2(10, -10), limb.Name, Color.White, Color.Black * 0.5f);
}
}
}
}
private void DrawRagdoll(SpriteBatch spriteBatch, float deltaTime)
{
bool altDown = PlayerInput.KeyDown(Keys.LeftAlt);
if (!altDown && editJoints && selectedJoints.Any())
{
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2 - 200, 250), GetCharacterEditorTranslation("HoldLeftAltToManipulateJoint"), Color.White, Color.Black * 0.5f, 10, GUI.Font);
}
foreach (Limb limb in character.AnimController.Limbs)
{
if (editIK)
{
if (limb.type == LimbType.LeftFoot || limb.type == LimbType.RightFoot || limb.type == LimbType.LeftHand || limb.type == LimbType.RightHand)
{
var pullJointWidgetSize = new Vector2(5, 5);
Vector2 tformedPullPos = SimToScreen(limb.PullJointWorldAnchorA);
GUI.DrawRectangle(spriteBatch, tformedPullPos - pullJointWidgetSize / 2, pullJointWidgetSize, Color.Red, true);
DrawWidget(spriteBatch, tformedPullPos, WidgetType.Rectangle, 8, Color.Cyan, $"IK ({limb.Name})", () =>
{
if (!selectedLimbs.Contains(limb))
{
selectedLimbs.Add(limb);
ResetParamsEditor();
}
limb.PullJointWorldAnchorA = ScreenToSim(PlayerInput.MousePosition);
TryUpdateLimbParam(limb, "pullpos", ConvertUnits.ToDisplayUnits(limb.PullJointLocalAnchorA / limb.limbParams.Ragdoll.LimbScale));
GUI.DrawLine(spriteBatch, SimToScreen(limb.SimPosition), tformedPullPos, Color.MediumPurple);
});
}
}
foreach (var joint in character.AnimController.LimbJoints)
{
Vector2 jointPos = Vector2.Zero;
Vector2 otherPos = Vector2.Zero;
Vector2 anchorPosA = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA);
Vector2 anchorPosB = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB);
if (joint.BodyA == limb.body.FarseerBody)
{
jointPos = anchorPosA;
otherPos = anchorPosB;
}
else if (joint.BodyB == limb.body.FarseerBody)
{
jointPos = anchorPosB;
otherPos = anchorPosA;
}
else
{
continue;
}
Vector2 limbScreenPos = SimToScreen(limb.SimPosition);
var f = Vector2.Transform(jointPos, Matrix.CreateRotationZ(limb.Rotation));
f.Y = -f.Y;
Vector2 tformedJointPos = limbScreenPos + f * Cam.Zoom;
if (editRagdoll)
{
ShapeExtensions.DrawPoint(spriteBatch, limbScreenPos, Color.Black, size: 5);
ShapeExtensions.DrawPoint(spriteBatch, limbScreenPos, Color.White, size: 1);
GUI.DrawLine(spriteBatch, limbScreenPos, tformedJointPos, Color.Black, width: 3);
GUI.DrawLine(spriteBatch, limbScreenPos, tformedJointPos, Color.White, width: 1);
}
if (editJoints)
{
if (altDown && joint.BodyA == limb.body.FarseerBody)
{
continue;
}
if (!altDown && joint.BodyB == limb.body.FarseerBody)
{
continue;
}
var selectionWidget = GetJointSelectionWidget($"{joint.jointParams.Name} selection widget ragdoll", joint);
selectionWidget.DrawPos = tformedJointPos;
selectionWidget.Draw(spriteBatch, deltaTime);
if (selectedJoints.Contains(joint))
{
if (joint.LimitEnabled)
{
DrawJointLimitWidgets(spriteBatch, limb, joint, tformedJointPos, autoFreeze: true, allowPairEditing: true, rotationOffset: limb.Rotation);
}
// Is the direction inversed incorrectly?
Vector2 to = tformedJointPos + VectorExtensions.ForwardFlipped(joint.LimbB.Rotation + MathHelper.ToRadians(-RagdollParams.SpritesheetOrientation), 20);
GUI.DrawLine(spriteBatch, tformedJointPos, to, Color.Magenta, width: 2);
var dotSize = new Vector2(5, 5);
var rect = new Rectangle((tformedJointPos - dotSize / 2).ToPoint(), dotSize.ToPoint());
//GUI.DrawRectangle(spriteBatch, tformedJointPos - dotSize / 2, dotSize, color, true);
//GUI.DrawLine(spriteBatch, tformedJointPos, tformedJointPos + up * 20, Color.White, width: 3);
GUI.DrawLine(spriteBatch, limbScreenPos, tformedJointPos, Color.Yellow, width: 3);
//GUI.DrawRectangle(spriteBatch, inputRect, Color.Red);
GUI.DrawString(spriteBatch, tformedJointPos + new Vector2(dotSize.X, -dotSize.Y) * 2, $"{joint.jointParams.Name} {jointPos.FormatZeroDecimal()}", Color.White, Color.Black * 0.5f);
if (PlayerInput.LeftButtonHeld())
{
if (!selectionWidget.IsControlled) { continue; }
if (autoFreeze)
{
isFreezed = true;
}
Vector2 input = ConvertUnits.ToSimUnits(scaledMouseSpeed) / Cam.Zoom;
input.Y = -input.Y;
input = input.TransformVector(VectorExtensions.ForwardFlipped(limb.Rotation));
if (joint.BodyA == limb.body.FarseerBody)
{
joint.LocalAnchorA += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale);
TryUpdateJointParam(joint, "limb1anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
{
foreach (var j in selectedJoints)
{
j.LocalAnchorA = joint.LocalAnchorA;
TryUpdateJointParam(j, "limb1anchor", transformedValue);
}
}
}
else if (joint.BodyB == limb.body.FarseerBody)
{
joint.LocalAnchorB += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale);
TryUpdateJointParam(joint, "limb2anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
{
foreach (var j in selectedJoints)
{
j.LocalAnchorB = joint.LocalAnchorB;
TryUpdateJointParam(j, "limb2anchor", transformedValue);
}
}
}
// Edit the other joints
if (limbPairEditing)
{
UpdateOtherJoints(limb, (otherLimb, otherJoint) =>
{
if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorA = joint.LocalAnchorA;
TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale));
}
else if (joint.BodyB == limb.body.FarseerBody && otherJoint.BodyB == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorB = joint.LocalAnchorB;
TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale));
}
});
}
}
else
{
isFreezed = freezeToggle.Selected;
}
}
}
}
}
}
private void UpdateOtherLimbs(Limb limb, Action<Limb> updateAction)
{
// Edit the other limbs
if (limbPairEditing)
{
string limbType = limb.type.ToString();
bool isLeft = limbType.Contains("Left");
bool isRight = limbType.Contains("Right");
if (isLeft || isRight)
{
if (character.AnimController.HasMultipleLimbsOfSameType)
{
GetOtherLimbs(limb)?.ForEach(l => UpdateOtherLimbs(l));
}
else
{
Limb otherLimb = GetOtherLimb(limbType, isLeft);
if (otherLimb != null)
{
UpdateOtherLimbs(otherLimb);
}
}
void UpdateOtherLimbs(Limb otherLimb)
{
updateAction(otherLimb);
}
}
}
}
private void UpdateOtherJoints(Limb limb, Action<Limb, LimbJoint> updateAction)
{
// Edit the other joints
if (limbPairEditing)
{
string limbType = limb.type.ToString();
bool isLeft = limbType.Contains("Left");
bool isRight = limbType.Contains("Right");
if (isLeft || isRight)
{
if (character.AnimController.HasMultipleLimbsOfSameType)
{
GetOtherLimbs(limb)?.ForEach(l => UpdateOtherJoints(l));
}
else
{
Limb otherLimb = GetOtherLimb(limbType, isLeft);
if (otherLimb != null)
{
UpdateOtherJoints(otherLimb);
}
}
void UpdateOtherJoints(Limb otherLimb)
{
foreach (var otherJoint in character.AnimController.LimbJoints)
{
updateAction(otherLimb, otherJoint);
}
}
}
}
}
private Limb GetOtherLimb(string limbType, bool isLeft)
{
string otherLimbType = isLeft ? limbType.Replace("Left", "Right") : limbType.Replace("Right", "Left");
if (Enum.TryParse(otherLimbType, out LimbType type))
{
return character.AnimController.GetLimb(type);
}
return null;
}
// TODO: optimize?, this method creates carbage (not much, but it's used frequently)
private IEnumerable<Limb> GetOtherLimbs(Limb limb)
{
var otherLimbs = character.AnimController.Limbs.Where(l => l.type == limb.type && l != limb);
string limbType = limb.type.ToString();
string otherLimbType = limbType.Contains("Left") ? limbType.Replace("Left", "Right") : limbType.Replace("Right", "Left");
if (Enum.TryParse(otherLimbType, out LimbType type))
{
otherLimbs = otherLimbs.Union(character.AnimController.Limbs.Where(l => l.type == type));
}
return otherLimbs;
}
#endregion
#region Spritesheet
private List<Texture2D> textures;
private List<Texture2D> Textures
{
get
{
if (textures == null)
{
CreateTextures();
}
return textures;
}
}
private List<string> texturePaths;
private void CreateTextures()
{
textures = new List<Texture2D>();
texturePaths = new List<string>();
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.ActiveSprite == null || texturePaths.Contains(limb.ActiveSprite.FilePath)) { continue; }
if (limb.ActiveSprite.Texture == null) { continue; }
textures.Add(limb.ActiveSprite.Texture);
texturePaths.Add(limb.ActiveSprite.FilePath);
}
}
private void CalculateSpritesheetPosition()
{
//spriteSheetOffsetX = (int)(GameMain.GraphicsWidth * 0.6f);
spriteSheetOffsetX = 20;
}
private void DrawSpritesheetEditor(SpriteBatch spriteBatch, float deltaTime)
{
int offsetX = spriteSheetOffsetX;
int offsetY = spriteSheetOffsetY;
for (int i = 0; i < Textures.Count; i++)
{
var texture = Textures[i];
if (!hideBodySheet)
{
spriteBatch.Draw(texture,
position: new Vector2(offsetX, offsetY),
rotation: 0,
origin: Vector2.Zero,
sourceRectangle: null,
scale: spriteSheetZoom,
effects: SpriteEffects.None,
color: Color.White,
layerDepth: 0);
}
GUI.DrawRectangle(spriteBatch, new Vector2(offsetX, offsetY), texture.Bounds.Size.ToVector2() * spriteSheetZoom, Color.White);
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.ActiveSprite == null || limb.ActiveSprite.FilePath != texturePaths[i]) continue;
Rectangle rect = limb.ActiveSprite.SourceRect;
rect.Size = rect.MultiplySize(spriteSheetZoom);
rect.Location = rect.Location.Multiply(spriteSheetZoom);
rect.X += offsetX;
rect.Y += offsetY;
Vector2 origin = limb.ActiveSprite.Origin;
Vector2 limbScreenPos = new Vector2(rect.X + origin.X * spriteSheetZoom, rect.Y + origin.Y * spriteSheetZoom);
// Draw the clothes
foreach (var wearable in limb.WearingItems)
{
Vector2 orig = limb.ActiveSprite.Origin;
if (!wearable.InheritOrigin)
{
orig = wearable.Sprite.Origin;
// If the wearable inherits the origin, flipping is already handled.
if (limb.body.Dir == -1.0f)
{
orig.X = wearable.Sprite.SourceRect.Width - orig.X;
}
}
spriteBatch.Draw(wearable.Sprite.Texture,
position: limbScreenPos,
rotation: 0,
origin: orig,
sourceRectangle: wearable.InheritSourceRect ? limb.ActiveSprite.SourceRect : wearable.Sprite.SourceRect,
scale: (wearable.InheritTextureScale ? 1 : 1 / RagdollParams.TextureScale) * spriteSheetZoom,
effects: SpriteEffects.None,
color: Color.White,
layerDepth: 0);
}
// The origin is manipulated when the character is flipped. We have to undo it here.
if (character.AnimController.Dir < 0)
{
limbScreenPos.X = rect.X + rect.Width - (float)Math.Round(origin.X * spriteSheetZoom);
}
if (editJoints)
{
DrawSpritesheetJointEditor(spriteBatch, deltaTime, limb, limbScreenPos);
}
if (editLimbs)
{
GUI.DrawRectangle(spriteBatch, rect, selectedLimbs.Contains(limb) ? Color.Yellow : Color.Red);
int widgetSize = 8;
int halfSize = widgetSize / 2;
Vector2 stringOffset = new Vector2(5, 14);
var topLeft = rect.Location.ToVector2();
var topRight = new Vector2(topLeft.X + rect.Width, topLeft.Y);
var bottomRight = new Vector2(topRight.X, topRight.Y + rect.Height);
if (selectedLimbs.Contains(limb))
{
var sprite = limb.ActiveSprite;
Vector2 GetTopLeft() => sprite.SourceRect.Location.ToVector2();
Vector2 GetTopRight() => new Vector2(GetTopLeft().X + sprite.SourceRect.Width, GetTopLeft().Y);
Vector2 GetBottomRight() => new Vector2(GetTopRight().X, GetTopRight().Y + sprite.SourceRect.Height);
var originWidget = GetLimbEditWidget($"{limb.limbParams.ID}_origin", limb, widgetSize, Widget.Shape.Cross, initMethod: w =>
{
w.refresh = () => w.tooltip = $"{GetCharacterEditorTranslation("Origin")}: {sprite.RelativeOrigin.FormatDoubleDecimal()}";
w.refresh();
w.MouseHeld += dTime =>
{
var spritePos = new Vector2(spriteSheetOffsetX, GetOffsetY(limb));
w.DrawPos = PlayerInput.MousePosition.Clamp(spritePos + GetTopLeft() * spriteSheetZoom, spritePos + GetBottomRight() * spriteSheetZoom);
sprite.Origin = (w.DrawPos - spritePos - sprite.SourceRect.Location.ToVector2() * spriteSheetZoom) / spriteSheetZoom;
if (limb.DamagedSprite != null)
{
limb.DamagedSprite.RelativeOrigin = sprite.RelativeOrigin;
}
TryUpdateLimbParam(limb, "origin", sprite.RelativeOrigin);
if (limbPairEditing)
{
UpdateOtherLimbs(limb, otherLimb =>
{
otherLimb.ActiveSprite.RelativeOrigin = sprite.RelativeOrigin;
if (otherLimb.DamagedSprite != null)
{
otherLimb.DamagedSprite.RelativeOrigin = sprite.RelativeOrigin;
}
TryUpdateLimbParam(otherLimb, "origin", sprite.RelativeOrigin);
});
}
};
w.PreUpdate += dTime =>
{
// Additional condition
if (w.Enabled)
{
w.Enabled = !lockSpriteOrigin;
}
};
w.PreDraw += (sb, dTime) =>
{
var spritePos = new Vector2(spriteSheetOffsetX, GetOffsetY(limb));
w.DrawPos = (spritePos + (sprite.Origin + sprite.SourceRect.Location.ToVector2()) * spriteSheetZoom)
.Clamp(spritePos + GetTopLeft() * spriteSheetZoom, spritePos + GetBottomRight() * spriteSheetZoom);
w.refresh();
};
});
originWidget.Draw(spriteBatch, deltaTime);
if (!lockSpritePosition)
{
var positionWidget = GetLimbEditWidget($"{limb.limbParams.ID}_position", limb, widgetSize, Widget.Shape.Rectangle, initMethod: w =>
{
w.refresh = () => w.tooltip = $"{GetCharacterEditorTranslation("Position")}: {limb.ActiveSprite.SourceRect.Location}";
w.refresh();
w.MouseHeld += dTime =>
{
w.DrawPos = PlayerInput.MousePosition;
var newRect = limb.ActiveSprite.SourceRect;
newRect.Location = new Point(
(int)((PlayerInput.MousePosition.X + halfSize - spriteSheetOffsetX) / spriteSheetZoom),
(int)((PlayerInput.MousePosition.Y + halfSize - GetOffsetY(limb)) / spriteSheetZoom));
limb.ActiveSprite.SourceRect = newRect;
if (limb.DamagedSprite != null)
{
limb.DamagedSprite.SourceRect = limb.ActiveSprite.SourceRect;
}
RecalculateOrigin(limb);
TryUpdateLimbParam(limb, "sourcerect", newRect);
if (limbPairEditing)
{
UpdateOtherLimbs(limb, otherLimb =>
{
otherLimb.ActiveSprite.SourceRect = newRect;
if (otherLimb.DamagedSprite != null)
{
otherLimb.DamagedSprite.SourceRect = newRect;
}
TryUpdateLimbParam(otherLimb, "sourcerect", newRect);
RecalculateOrigin(otherLimb);
});
};
void RecalculateOrigin(Limb l)
{
if (lockSpriteOrigin)
{
// Keeps the absolute origin unchanged. The relative origin will be recalculated.
var spritePos = new Vector2(spriteSheetOffsetX, GetOffsetY(l));
l.ActiveSprite.Origin = (originWidget.DrawPos - spritePos - l.ActiveSprite.SourceRect.Location.ToVector2() * spriteSheetZoom) / spriteSheetZoom;
TryUpdateLimbParam(l, "origin", l.ActiveSprite.RelativeOrigin);
}
else
{
// Keeps the relative origin unchanged. The absolute origin will be recalculated.
l.ActiveSprite.RelativeOrigin = l.ActiveSprite.RelativeOrigin;
}
}
};
w.PreDraw += (sb, dTime) => w.refresh();
});
if (!positionWidget.IsControlled)
{
positionWidget.DrawPos = topLeft - new Vector2(halfSize);
}
positionWidget.Draw(spriteBatch, deltaTime);
}
if (!lockSpriteSize)
{
var sizeWidget = GetLimbEditWidget($"{limb.limbParams.ID}_size", limb, widgetSize, Widget.Shape.Rectangle, initMethod: w =>
{
w.refresh = () => w.tooltip = $"{GetCharacterEditorTranslation("Size")}: {limb.ActiveSprite.SourceRect.Size}";
w.refresh();
w.MouseHeld += dTime =>
{
w.DrawPos = PlayerInput.MousePosition;
var newRect = limb.ActiveSprite.SourceRect;
float offset_y = limb.ActiveSprite.SourceRect.Y * spriteSheetZoom + GetOffsetY(limb);
float offset_x = limb.ActiveSprite.SourceRect.X * spriteSheetZoom + spriteSheetOffsetX;
int width = (int)((PlayerInput.MousePosition.X - halfSize - offset_x) / spriteSheetZoom);
int height = (int)((PlayerInput.MousePosition.Y - halfSize - offset_y) / spriteSheetZoom);
newRect.Size = new Point(width, height);
limb.ActiveSprite.SourceRect = newRect;
limb.ActiveSprite.size = new Vector2(width, height);
if (recalculateCollider)
{
RecalculateCollider(limb);
}
RecalculateOrigin(limb);
if (limb.DamagedSprite != null)
{
limb.DamagedSprite.SourceRect = limb.ActiveSprite.SourceRect;
}
TryUpdateLimbParam(limb, "sourcerect", newRect);
if (limbPairEditing)
{
UpdateOtherLimbs(limb, otherLimb =>
{
otherLimb.ActiveSprite.SourceRect = newRect;
RecalculateOrigin(otherLimb);
if (recalculateCollider)
{
RecalculateCollider(otherLimb);
}
if (otherLimb.DamagedSprite != null)
{
otherLimb.DamagedSprite.SourceRect = newRect;
}
TryUpdateLimbParam(otherLimb, "sourcerect", newRect);
});
};
void RecalculateCollider(Limb l)
{
// We want the collider to be slightly smaller than the source rect, because the source rect is usually a bit bigger than the graphic.
float multiplier = 0.85f;
l.body.SetSize(new Vector2(ConvertUnits.ToSimUnits(width), ConvertUnits.ToSimUnits(height)) * RagdollParams.LimbScale * RagdollParams.TextureScale * multiplier);
TryUpdateLimbParam(l, "radius", ConvertUnits.ToDisplayUnits(l.body.radius / RagdollParams.LimbScale / RagdollParams.TextureScale));
TryUpdateLimbParam(l, "width", ConvertUnits.ToDisplayUnits(l.body.width / RagdollParams.LimbScale / RagdollParams.TextureScale));
TryUpdateLimbParam(l, "height", ConvertUnits.ToDisplayUnits(l.body.height / RagdollParams.LimbScale / RagdollParams.TextureScale));
}
void RecalculateOrigin(Limb l)
{
if (lockSpriteOrigin)
{
// Keeps the absolute origin unchanged. The relative origin will be recalculated.
l.ActiveSprite.Origin = l.ActiveSprite.Origin;
TryUpdateLimbParam(l, "origin", l.ActiveSprite.RelativeOrigin);
}
else
{
// Keeps the relative origin unchanged. The absolute origin will be recalculated.
l.ActiveSprite.RelativeOrigin = l.ActiveSprite.RelativeOrigin;
}
}
};
w.PreDraw += (sb, dTime) => w.refresh();
});
if (!sizeWidget.IsControlled)
{
sizeWidget.DrawPos = bottomRight + new Vector2(halfSize);
}
sizeWidget.Draw(spriteBatch, deltaTime);
}
}
else if (rect.Contains(PlayerInput.MousePosition) && GUI.MouseOn == null && Widget.selectedWidgets.None())
{
// TODO: only one limb name should be displayed (needs to be done in a separate loop)
GUI.DrawString(spriteBatch, limbScreenPos + new Vector2(10, -10), limb.Name, Color.White, Color.Black * 0.5f);
}
}
}
offsetY += (int)(texture.Height * spriteSheetZoom);
}
int GetTextureHeight(Limb limb)
{
int textureIndex = Textures.IndexOf(limb.ActiveSprite.Texture);
int height = 0;
foreach (var t in Textures)
{
if (Textures.IndexOf(t) < textureIndex)
{
height += t.Height;
}
}
return (int)(height * spriteSheetZoom);
}
int GetOffsetY(Limb limb) => spriteSheetOffsetY + GetTextureHeight(limb);
}
private void DrawSpritesheetJointEditor(SpriteBatch spriteBatch, float deltaTime, Limb limb, Vector2 limbScreenPos, float spriteRotation = 0)
{
foreach (var joint in character.AnimController.LimbJoints)
{
Vector2 jointPos = Vector2.Zero;
Vector2 anchorPosA = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA);
Vector2 anchorPosB = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB);
string anchorID;
string otherID;
if (joint.BodyA == limb.body.FarseerBody)
{
jointPos = anchorPosA;
anchorID = "1";
otherID = "2";
}
else if (joint.BodyB == limb.body.FarseerBody)
{
jointPos = anchorPosB;
anchorID = "2";
otherID = "1";
}
else
{
continue;
}
Vector2 tformedJointPos = jointPos = jointPos / RagdollParams.JointScale / limb.TextureScale * spriteSheetZoom;
tformedJointPos.Y = -tformedJointPos.Y;
tformedJointPos.X *= character.AnimController.Dir;
tformedJointPos += limbScreenPos;
var jointSelectionWidget = GetJointSelectionWidget($"{joint.jointParams.Name} selection widget {anchorID}", joint, $"{joint.jointParams.Name} selection widget {otherID}");
jointSelectionWidget.DrawPos = tformedJointPos;
jointSelectionWidget.Draw(spriteBatch, deltaTime);
var otherWidget = GetJointSelectionWidget($"{joint.jointParams.Name} selection widget {otherID}", joint, $"{joint.jointParams.Name} selection widget {anchorID}");
if (anchorID == "2")
{
bool isSelected = selectedJoints.Contains(joint);
bool isHovered = jointSelectionWidget.IsSelected || otherWidget.IsSelected;
if (isSelected || isHovered)
{
GUI.DrawLine(spriteBatch, jointSelectionWidget.DrawPos, otherWidget.DrawPos, jointSelectionWidget.color, width: 2);
}
}
if (selectedJoints.Contains(joint))
{
if (joint.LimitEnabled)
{
DrawJointLimitWidgets(spriteBatch, limb, joint, tformedJointPos, autoFreeze: false, allowPairEditing: true);
}
if (jointSelectionWidget.IsControlled)
{
Vector2 input = ConvertUnits.ToSimUnits(scaledMouseSpeed);
input.Y = -input.Y;
input.X *= character.AnimController.Dir;
input *= RagdollParams.JointScale * limb.TextureScale / spriteSheetZoom;
if (joint.BodyA == limb.body.FarseerBody)
{
joint.LocalAnchorA += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale);
TryUpdateJointParam(joint, "limb1anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
{
foreach (var j in selectedJoints)
{
j.LocalAnchorA = joint.LocalAnchorA;
TryUpdateJointParam(j, "limb1anchor", transformedValue);
}
}
}
else if (joint.BodyB == limb.body.FarseerBody)
{
joint.LocalAnchorB += input;
Vector2 transformedValue = ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale);
TryUpdateJointParam(joint, "limb2anchor", transformedValue);
// Snap all selected joints to the first selected
if (copyJointSettings)
{
foreach (var j in selectedJoints)
{
j.LocalAnchorB = joint.LocalAnchorB;
TryUpdateJointParam(j, "limb2anchor", transformedValue);
}
}
}
if (limbPairEditing)
{
UpdateOtherJoints(limb, (otherLimb, otherJoint) =>
{
if (joint.BodyA == limb.body.FarseerBody && otherJoint.BodyA == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorA = joint.LocalAnchorA;
TryUpdateJointParam(otherJoint, "limb1anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorA / RagdollParams.JointScale));
}
else if (joint.BodyB == limb.body.FarseerBody && otherJoint.BodyB == otherLimb.body.FarseerBody)
{
otherJoint.LocalAnchorB = joint.LocalAnchorB;
TryUpdateJointParam(otherJoint, "limb2anchor", ConvertUnits.ToDisplayUnits(joint.LocalAnchorB / RagdollParams.JointScale));
}
});
}
}
}
}
}
private void DrawJointLimitWidgets(SpriteBatch spriteBatch, Limb limb, LimbJoint joint, Vector2 drawPos, bool autoFreeze, bool allowPairEditing, float rotationOffset = 0)
{
rotationOffset += MathHelper.ToRadians(RagdollParams.SpritesheetOrientation);
Color angleColor = joint.UpperLimit - joint.LowerLimit > 0 ? Color.LightGreen * 0.5f : Color.Red;
DrawRadialWidget(spriteBatch, drawPos, MathHelper.ToDegrees(joint.UpperLimit), $"joint.jointParams.Name {GetCharacterEditorTranslation("UpperLimit")}", Color.Cyan, angle =>
{
joint.UpperLimit = MathHelper.ToRadians(angle);
ValidateJoint(joint);
angle = MathHelper.ToDegrees(joint.UpperLimit);
TryUpdateJointParam(joint, "upperlimit", angle);
if (copyJointSettings)
{
foreach (var j in selectedJoints)
{
if (j.LimitEnabled != joint.LimitEnabled)
{
j.LimitEnabled = joint.LimitEnabled;
TryUpdateJointParam(j, "limitenabled", j.LimitEnabled);
}
j.UpperLimit = joint.UpperLimit;
TryUpdateJointParam(j, "upperlimit", angle);
}
}
if (allowPairEditing && limbPairEditing)
{
UpdateOtherJoints(limb, (otherLimb, otherJoint) =>
{
if (IsMatchingLimb(limb, otherLimb, joint, otherJoint))
{
if (otherJoint.LimitEnabled != joint.LimitEnabled)
{
otherJoint.LimitEnabled = otherJoint.LimitEnabled;
TryUpdateJointParam(otherJoint, "limitenabled", otherJoint.LimitEnabled);
}
otherJoint.UpperLimit = joint.UpperLimit;
TryUpdateJointParam(otherJoint, "upperlimit", angle);
}
});
}
DrawAngle(20, angleColor, 4);
DrawAngle(40, Color.Cyan);
GUI.DrawString(spriteBatch, drawPos, angle.FormatZeroDecimal(), Color.Black, backgroundColor: Color.Cyan, font: GUI.SmallFont);
}, circleRadius: 40, rotationOffset: rotationOffset, displayAngle: false, clockWise: false);
DrawRadialWidget(spriteBatch, drawPos, MathHelper.ToDegrees(joint.LowerLimit), $"joint.jointParams.Name {GetCharacterEditorTranslation("LowerLimit")}", Color.Yellow, angle =>
{
joint.LowerLimit = MathHelper.ToRadians(angle);
ValidateJoint(joint);
angle = MathHelper.ToDegrees(joint.LowerLimit);
TryUpdateJointParam(joint, "lowerlimit", angle);
if (copyJointSettings)
{
foreach (var j in selectedJoints)
{
if (j.LimitEnabled != joint.LimitEnabled)
{
j.LimitEnabled = joint.LimitEnabled;
TryUpdateJointParam(j, "limitenabled", j.LimitEnabled);
}
j.LowerLimit = joint.LowerLimit;
TryUpdateJointParam(j, "lowerlimit", angle);
}
}
if (allowPairEditing && limbPairEditing)
{
UpdateOtherJoints(limb, (otherLimb, otherJoint) =>
{
if (IsMatchingLimb(limb, otherLimb, joint, otherJoint))
{
if (otherJoint.LimitEnabled != joint.LimitEnabled)
{
otherJoint.LimitEnabled = otherJoint.LimitEnabled;
TryUpdateJointParam(otherJoint, "limitenabled", otherJoint.LimitEnabled);
}
otherJoint.LowerLimit = joint.LowerLimit;
TryUpdateJointParam(otherJoint, "lowerlimit", angle);
}
});
}
DrawAngle(20, angleColor, 4);
DrawAngle(25, Color.Yellow);
GUI.DrawString(spriteBatch, drawPos, angle.FormatZeroDecimal(), Color.Black, backgroundColor: Color.Yellow, font: GUI.SmallFont);
}, circleRadius: 25, rotationOffset: rotationOffset, displayAngle: false, clockWise: false);
void DrawAngle(float radius, Color color, float thickness = 5)
{
float angle = joint.UpperLimit - joint.LowerLimit;
ShapeExtensions.DrawSector(spriteBatch, drawPos, radius, angle, 40, color,
offset: -rotationOffset - joint.UpperLimit + MathHelper.PiOver2, thickness: thickness);
}
}
#endregion
#region Widgets as methods
private void DrawRadialWidget(SpriteBatch spriteBatch, Vector2 drawPos, float value, string toolTip, Color color, Action<float> onClick,
float circleRadius = 30, int widgetSize = 10, float rotationOffset = 0, bool clockWise = true, bool displayAngle = true, bool? autoFreeze = null, bool wrapAnglePi = false)
{
var angle = value;
if (!MathUtils.IsValid(angle))
{
angle = 0;
}
float drawAngle = clockWise ? -angle : angle;
var widgetDrawPos = drawPos + VectorExtensions.ForwardFlipped(MathHelper.ToRadians(drawAngle) + rotationOffset, circleRadius);
GUI.DrawLine(spriteBatch, drawPos, widgetDrawPos, color);
DrawWidget(spriteBatch, widgetDrawPos, WidgetType.Rectangle, widgetSize, color, toolTip, () =>
{
GUI.DrawLine(spriteBatch, drawPos, widgetDrawPos, color, width: 3);
ShapeExtensions.DrawCircle(spriteBatch, drawPos, circleRadius, 40, color, thickness: 1);
Vector2 d = PlayerInput.MousePosition - drawPos;
float newAngle = clockWise
? MathUtils.VectorToAngle(d) - MathHelper.PiOver2 + rotationOffset
: -MathUtils.VectorToAngle(d) + MathHelper.PiOver2 - rotationOffset;
angle = MathHelper.ToDegrees(wrapAnglePi ? MathUtils.WrapAnglePi(newAngle) : MathUtils.WrapAngleTwoPi(newAngle));
if (displayAngle)
{
GUI.DrawString(spriteBatch, drawPos, angle.FormatZeroDecimal(), Color.Black, backgroundColor: color, font: GUI.SmallFont);
}
onClick(angle);
var zeroPos = drawPos + VectorExtensions.ForwardFlipped(rotationOffset, circleRadius);
GUI.DrawLine(spriteBatch, drawPos, zeroPos, Color.Red, width: 3);
}, autoFreeze, onHovered: () =>
{
if (!PlayerInput.LeftButtonHeld())
{
GUI.DrawString(spriteBatch, new Vector2(drawPos.X + 5, drawPos.Y - widgetSize / 2),
$"{toolTip} ({angle.FormatZeroDecimal()})", color, Color.Black * 0.5f);
}
});
}
private enum WidgetType { Rectangle, Circle }
private void DrawWidget(SpriteBatch spriteBatch, Vector2 drawPos, WidgetType widgetType, int size, Color color, string toolTip, Action onPressed, bool ? autoFreeze = null, Action onHovered = null)
{
var drawRect = new Rectangle((int)drawPos.X - size / 2, (int)drawPos.Y - size / 2, size, size);
var inputRect = drawRect;
inputRect.Inflate(size * 0.75f, size * 0.75f);
bool isMouseOn = inputRect.Contains(PlayerInput.MousePosition);
bool isSelected = isMouseOn && GUI.MouseOn == null && Widget.selectedWidgets.None();
switch (widgetType)
{
case WidgetType.Rectangle:
if (isSelected)
{
var rect = drawRect;
rect.Inflate(size * 0.3f, size * 0.3f);
GUI.DrawRectangle(spriteBatch, rect, color, thickness: 3, isFilled: PlayerInput.LeftButtonHeld());
}
else
{
GUI.DrawRectangle(spriteBatch, drawRect, color, thickness: 1, isFilled: false);
}
break;
case WidgetType.Circle:
if (isSelected)
{
ShapeExtensions.DrawCircle(spriteBatch, drawPos, size * 0.7f, 40, color, thickness: 3);
}
else
{
ShapeExtensions.DrawCircle(spriteBatch, drawPos, size * 0.5f, 40, color, thickness: 1);
}
break;
default: throw new NotImplementedException(widgetType.ToString());
}
if (isSelected)
{
// Label/tooltip
if (onHovered == null)
{
GUI.DrawString(spriteBatch, new Vector2(drawRect.Right + 5, drawRect.Y - drawRect.Height / 2), toolTip, color, Color.Black);
}
else
{
onHovered();
}
if (PlayerInput.LeftButtonHeld())
{
if (autoFreeze ?? this.autoFreeze)
{
isFreezed = true;
}
onPressed();
}
else
{
isFreezed = freezeToggle.Selected;
}
// Might not be entirely reliable, since the method is used inside the draw loop.
if (PlayerInput.LeftButtonClicked())
{
SaveSnapshot();
}
}
}
#endregion
#region Widgets as classes
private Dictionary<string, Widget> animationWidgets = new Dictionary<string, Widget>();
private Dictionary<string, Widget> jointSelectionWidgets = new Dictionary<string, Widget>();
private Dictionary<string, Widget> limbEditWidgets = new Dictionary<string, Widget>();
private Widget GetAnimationWidget(string name, Color color, int size = 10, float sizeMultiplier = 2, Widget.Shape shape = Widget.Shape.Rectangle, Action<Widget> initMethod = null)
{
string id = $"{character.SpeciesName}_{character.AnimController.CurrentAnimationParams.AnimationType.ToString()}_{name}";
if (!animationWidgets.TryGetValue(id, out Widget widget))
{
int selectedSize = (int)Math.Round(size * sizeMultiplier);
widget = new Widget(id, size, shape)
{
tooltipOffset = new Vector2(selectedSize / 2 + 5, -10),
data = character.AnimController.CurrentAnimationParams
};
widget.MouseUp += () => CurrentAnimation.CreateSnapshot();
widget.color = color;
widget.PreUpdate += dTime =>
{
widget.Enabled = editAnimations;
if (widget.Enabled)
{
AnimationParams data = widget.data as AnimationParams;
widget.Enabled = data.AnimationType == character.AnimController.CurrentAnimationParams.AnimationType;
}
};
widget.PostUpdate += dTime =>
{
widget.inputAreaMargin = widget.IsControlled ? 1000 : 0;
widget.size = widget.IsSelected ? selectedSize : size;
widget.isFilled = widget.IsControlled;
};
widget.PreDraw += (sp, dTime) =>
{
if (!widget.IsControlled)
{
widget.refresh();
}
};
animationWidgets.Add(id, widget);
initMethod?.Invoke(widget);
}
return widget;
}
private Widget GetJointSelectionWidget(string id, LimbJoint joint, string linkedId = null)
{
// Handle widget linking and create the widgets
if (!jointSelectionWidgets.TryGetValue(id, out Widget jointWidget))
{
jointWidget = CreateJointSelectionWidget(id, joint);
if (linkedId != null)
{
if (!jointSelectionWidgets.TryGetValue(linkedId, out Widget linkedWidget))
{
linkedWidget = CreateJointSelectionWidget(linkedId, joint);
}
jointWidget.linkedWidget = linkedWidget;
linkedWidget.linkedWidget = jointWidget;
}
}
return jointWidget;
// Widget creation method
Widget CreateJointSelectionWidget(string ID, LimbJoint j)
{
int normalSize = 10;
int selectedSize = 20;
var widget = new Widget(ID, normalSize, Widget.Shape.Circle)
{
tooltipOffset = new Vector2(selectedSize / 2 + 5, -10)
};
widget.refresh = () =>
{
widget.showTooltip = !selectedJoints.Contains(joint);
widget.color = selectedJoints.Contains(joint) ? Color.Yellow : Color.Red;
};
widget.refresh();
widget.PreUpdate += dTime => widget.Enabled = editJoints;
widget.PostUpdate += dTime =>
{
widget.inputAreaMargin = widget.IsControlled ? 1000 : 0;
widget.size = widget.IsSelected ? selectedSize : normalSize;
};
widget.MouseDown += () =>
{
if (!selectedJoints.Contains(joint))
{
if (!Widget.EnableMultiSelect)
{
selectedJoints.Clear();
}
selectedJoints.Add(joint);
}
else if (Widget.EnableMultiSelect)
{
selectedJoints.Remove(joint);
}
foreach (var w in jointSelectionWidgets.Values)
{
w.refresh();
w.linkedWidget?.refresh();
}
ResetParamsEditor();
};
widget.MouseUp += () => RagdollParams.CreateSnapshot();
widget.tooltip = joint.jointParams.Name;
jointSelectionWidgets.Add(ID, widget);
return widget;
}
}
private Widget GetLimbEditWidget(string ID, Limb limb, int size = 5, Widget.Shape shape = Widget.Shape.Rectangle, Action < Widget> initMethod = null)
{
if (!limbEditWidgets.TryGetValue(ID, out Widget widget))
{
widget = CreateLimbEditWidget();
limbEditWidgets.Add(ID, widget);
}
return widget;
Widget CreateLimbEditWidget()
{
int normalSize = size;
int selectedSize = (int)Math.Round(size * 1.5f);
var w = new Widget(ID, size, shape)
{
tooltipOffset = new Vector2(selectedSize / 2 + 5, -10),
data = limb,
color = Color.Yellow,
secondaryColor = Color.Gray,
textColor = Color.Yellow
};
w.PreUpdate += dTime => w.Enabled = editLimbs && selectedLimbs.Contains(limb);
w.PostUpdate += dTime =>
{
w.inputAreaMargin = w.IsControlled ? 1000 : 0;
w.size = w.IsSelected ? selectedSize : normalSize;
w.isFilled = w.IsControlled;
};
w.MouseUp += () => RagdollParams.CreateSnapshot();
initMethod?.Invoke(w);
return w;
}
}
#endregion
#region Character Wizard
private class Wizard
{
// Ragdoll data
private string name = string.Empty;
private bool isHumanoid = false;
private bool canEnterSubmarine = true;
private string texturePath;
private string xmlPath;
private ContentPackage contentPackage;
private Dictionary<string, XElement> limbXElements = new Dictionary<string, XElement>();
private List<GUIComponent> limbGUIElements = new List<GUIComponent>();
private List<XElement> jointXElements = new List<XElement>();
private List<GUIComponent> jointGUIElements = new List<GUIComponent>();
public static Wizard instance;
public static Wizard Instance
{
get
{
if (instance == null)
{
instance = new Wizard();
}
return instance;
}
}
public void Reset()
{
CharacterView.Get().Release();
RagdollView.Get().Release();
instance = null;
}
public enum Tab { None, Character, Ragdoll }
private View activeView;
private Tab currentTab;
public void SelectTab(Tab tab)
{
currentTab = tab;
activeView?.Box.Close();
switch (currentTab)
{
case Tab.Character:
activeView = CharacterView.Get();
break;
case Tab.Ragdoll:
activeView = RagdollView.Get();
break;
case Tab.None:
default:
Reset();
break;
}
}
public void AddToGUIUpdateList()
{
activeView?.Box.AddToGUIUpdateList();
}
private class CharacterView : View
{
private static CharacterView instance;
public static CharacterView Get() => Get(ref instance);
public override void Release() => instance = null;
protected override GUIMessageBox Create()
{
var box = new GUIMessageBox(GetCharacterEditorTranslation("CreateNewCharacter"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Next") }, new Vector2(0.5f, 1.0f));
box.Header.Font = GUI.LargeFont;
box.Content.ChildAnchor = Anchor.TopCenter;
box.Content.AbsoluteSpacing = 20;
int elementSize = 30;
var listBox = new GUIListBox(new RectTransform(new Vector2(1, 0.9f), box.Content.RectTransform));
var topGroup = new GUILayoutGroup(new RectTransform(Vector2.One, listBox.Content.RectTransform)) { AbsoluteSpacing = 2 };
var fields = new List<GUIComponent>();
GUITextBox texturePathElement = null;
GUITextBox xmlPathElement = null;
GUIDropDown contentPackageDropDown = null;
bool updateTexturePath = true;
void UpdatePaths()
{
string pathBase = ContentPackage == GameMain.VanillaContent ? $"Content/Characters/{Name}/{Name}"
: $"Mods/{(ContentPackage != null ? ContentPackage.Name + "/" : string.Empty)}Characters/{Name}/{Name}";
XMLPath = $"{pathBase}.xml";
xmlPathElement.Text = XMLPath;
if (updateTexturePath)
{
TexturePath = $"{pathBase}.png";
texturePathElement.Text = TexturePath;
}
}
for (int i = 0; i < 6; i++)
{
var mainElement = new GUIFrame(new RectTransform(new Point(topGroup.RectTransform.Rect.Width, elementSize), topGroup.RectTransform), style: null, color: Color.Gray * 0.25f);
fields.Add(mainElement);
RectTransform leftElement = new RectTransform(new Vector2(0.5f, 1), mainElement.RectTransform, Anchor.TopLeft);
RectTransform rightElement = new RectTransform(new Vector2(0.5f, 1), mainElement.RectTransform, Anchor.TopRight);
switch (i)
{
case 0:
new GUITextBlock(leftElement, TextManager.Get("Name"));
var nameField = new GUITextBox(rightElement, GetCharacterEditorTranslation("DefaultName")) { CaretColor = Color.White };
string ProcessText(string text) => text.RemoveWhitespace().CapitaliseFirstInvariant();
Name = ProcessText(nameField.Text);
nameField.OnTextChanged += (tb, text) =>
{
Name = ProcessText(text);
UpdatePaths();
return true;
};
break;
case 1:
new GUITextBlock(leftElement, GetCharacterEditorTranslation("IsHumanoid"))
{
TextColor = Color.White * 0.3f
};
new GUITickBox(rightElement, string.Empty)
{
Selected = IsHumanoid,
OnSelected = (tB) => IsHumanoid = tB.Selected,
Enabled = false
};
break;
case 2:
new GUITextBlock(leftElement, GetCharacterEditorTranslation("CanEnterSubmarines"));
new GUITickBox(rightElement, string.Empty)
{
Selected = CanEnterSubmarine,
OnSelected = (tB) => CanEnterSubmarine = tB.Selected
};
break;
case 3:
new GUITextBlock(leftElement, GetCharacterEditorTranslation("ConfigFileOutput"));
xmlPathElement = new GUITextBox(rightElement, string.Empty)
{
CaretColor = Color.White
};
xmlPathElement.OnTextChanged += (tb, text) =>
{
XMLPath = text;
return true;
};
break;
case 4:
new GUITextBlock(leftElement, GetCharacterEditorTranslation("TexturePath"));
texturePathElement = new GUITextBox(rightElement, string.Empty)
{
CaretColor = Color.White,
};
texturePathElement.OnTextChanged += (tb, text) =>
{
updateTexturePath = false;
TexturePath = text;
return true;
};
break;
case 5:
mainElement.RectTransform.NonScaledSize = new Point(
mainElement.RectTransform.NonScaledSize.X,
mainElement.RectTransform.NonScaledSize.Y * 2);
new GUITextBlock(leftElement, TextManager.Get("ContentPackage"));
var rightContainer = new GUIFrame(rightElement, style: null);
contentPackageDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.5f), rightContainer.RectTransform, Anchor.TopRight));
foreach (ContentPackage cp in ContentPackage.List)
{
#if !DEBUG
if (cp == GameMain.VanillaContent) { continue; }
#endif
contentPackageDropDown.AddItem(cp.Name, userData: cp, toolTip: cp.Path);
}
contentPackageDropDown.OnSelected = (obj, userdata) =>
{
ContentPackage = userdata as ContentPackage;
updateTexturePath = true;
UpdatePaths();
return true;
};
var contentPackageNameElement = new GUITextBox(new RectTransform(new Vector2(0.7f, 0.5f), rightContainer.RectTransform, Anchor.BottomLeft),
TextManager.Get("name"))
{
CaretColor = Color.White,
};
var createNewPackageButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), rightContainer.RectTransform, Anchor.BottomRight), TextManager.Get("CreateNew"))
{
OnClicked = (btn, userdata) =>
{
if (string.IsNullOrEmpty(contentPackageNameElement.Text))
{
contentPackageNameElement.Flash();
return false;
}
if (ContentPackage.List.Any(cp => cp.Name.ToLower() == contentPackageNameElement.Text.ToLower()))
{
new GUIMessageBox("", TextManager.Get("charactereditor.contentpackagenameinuse", fallBackTag: "leveleditorlevelobjnametaken"));
return false;
}
string fileName = ToolBox.RemoveInvalidFileNameChars(contentPackageNameElement.Text);
ContentPackage = ContentPackage.CreatePackage(
contentPackageNameElement.Text,
Path.Combine(ContentPackage.Folder, $"{fileName}.xml"), false);
ContentPackage.List.Add(ContentPackage);
GameMain.Config.SelectedContentPackages.Add(ContentPackage);
contentPackageDropDown.AddItem(ContentPackage.Name, ContentPackage, ContentPackage.Path);
contentPackageDropDown.SelectItem(ContentPackage);
contentPackageNameElement.Text = "";
return true;
},
Enabled = false
};
Color textColor = contentPackageNameElement.TextColor;
contentPackageNameElement.TextColor *= 0.6f;
contentPackageNameElement.OnSelected += (sender, key) =>
{
contentPackageNameElement.Text = "";
};
contentPackageNameElement.OnTextChanged += (textBox, text) =>
{
textBox.TextColor = textColor;
createNewPackageButton.Enabled = !string.IsNullOrWhiteSpace(text);
return true;
};
break;
}
}
UpdatePaths();
//var codeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.5f), listBox.Content.RectTransform), style: null) { CanBeFocused = false };
//new GUITextBlock(new RectTransform(new Vector2(1, 0.05f), codeArea.RectTransform), "Custom code:");
//var inputBox = new GUITextBox(new RectTransform(new Vector2(1, 1 - 0.05f), codeArea.RectTransform, Anchor.BottomLeft), string.Empty, textAlignment: Alignment.TopLeft);
// Cancel
box.Buttons[0].OnClicked += (b, d) =>
{
Wizard.Instance.SelectTab(Tab.None);
return true;
};
// Next
box.Buttons[1].OnClicked += (b, d) =>
{
if (ContentPackage == null)
{
contentPackageDropDown.Flash();
return false;
}
if (!File.Exists(TexturePath))
{
GUI.AddMessage(GetCharacterEditorTranslation("TextureDoesNotExist"), Color.Red);
texturePathElement.Flash(Color.Red);
return false;
}
var path = Path.GetFileName(TexturePath);
if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
{
GUI.AddMessage(TextManager.Get("WrongFileType"), Color.Red);
texturePathElement.Flash(Color.Red);
return false;
}
Wizard.Instance.SelectTab(Tab.Ragdoll);
return true;
};
return box;
}
}
private class RagdollView : View
{
private static RagdollView instance;
public static RagdollView Get() => Get(ref instance);
public override void Release() => instance = null;
protected override GUIMessageBox Create()
{
var box = new GUIMessageBox(GetCharacterEditorTranslation("DefineRagdoll"), string.Empty, new string[] { TextManager.Get("Previous"), TextManager.Get("Create") }, new Vector2(0.5f, 1.0f));
box.Header.Font = GUI.LargeFont;
box.Content.ChildAnchor = Anchor.TopCenter;
box.Content.AbsoluteSpacing = 20;
int elementSize = 30;
var topGroup = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.05f), box.Content.RectTransform)) { AbsoluteSpacing = 2 };
var bottomGroup = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.75f), box.Content.RectTransform)) { AbsoluteSpacing = 10 };
// HTML
GUIMessageBox htmlBox = null;
var loadHtmlButton = new GUIButton(new RectTransform(new Point(topGroup.RectTransform.Rect.Width, elementSize), topGroup.RectTransform), GetCharacterEditorTranslation("LoadFromHTML"));
// Limbs
var limbsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), bottomGroup.RectTransform), style: null) { CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), limbsElement.RectTransform), $"{GetCharacterEditorTranslation("Limbs")}: ");
var limbButtonElement = new GUIFrame(new RectTransform(new Vector2(0.8f, 1f), limbsElement.RectTransform)
{ RelativeOffset = new Vector2(0.1f, 0) }, style: null) { CanBeFocused = false };
var limbEditLayout = new GUILayoutGroup(new RectTransform(Vector2.One, limbButtonElement.RectTransform), isHorizontal: true) { AbsoluteSpacing = 10 };
var limbsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), bottomGroup.RectTransform));
var removeLimbButton = new GUIButton(new RectTransform(new Point(limbButtonElement.Rect.Height, limbButtonElement.Rect.Height), limbEditLayout.RectTransform), "-")
{
OnClicked = (b, d) =>
{
var element = LimbGUIElements.LastOrDefault();
if (element == null) { return false; }
element.RectTransform.Parent = null;
LimbGUIElements.Remove(element);
return true;
}
};
var addLimbButton = new GUIButton(new RectTransform(new Point(limbButtonElement.Rect.Height, limbButtonElement.Rect.Height), limbEditLayout.RectTransform), "+")
{
OnClicked = (b, d) =>
{
LimbType limbType = LimbType.None;
switch (LimbGUIElements.Count)
{
case 0:
limbType = LimbType.Torso;
break;
case 1:
limbType = LimbType.Head;
break;
}
CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize, id: LimbGUIElements.Count, limbType: limbType);
return true;
}
};
int x = 1, y = 1, w = 100, h = 100;
int otherElements = limbButtonElement.Rect.Width / 4 + 10 + limbButtonElement.Rect.Height * 2 + 10 + limbButtonElement.RectTransform.AbsoluteOffset.X;
var frame = new GUIFrame(new RectTransform(new Point(limbEditLayout.Rect.Width - otherElements, limbButtonElement.Rect.Height), limbEditLayout.RectTransform), color: Color.Transparent);
var inputArea = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
{
Stretch = true,
RelativeSpacing = 0.01f
};
for (int i = 3; i >= 0; i--)
{
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);
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), GUI.rectComponentLabels[i], font: GUI.SmallFont, textAlignment: Alignment.CenterLeft);
GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight), GUINumberInput.NumberType.Int)
{
Font = GUI.SmallFont
};
switch (i)
{
case 0:
case 1:
numberInput.IntValue = 1;
numberInput.MinValueInt = 1;
numberInput.MaxValueInt = 100;
break;
case 2:
case 3:
numberInput.IntValue = 100;
numberInput.MinValueInt = 0;
numberInput.MaxValueInt = 999;
break;
}
int comp = i;
numberInput.OnValueChanged += (numInput) =>
{
switch (comp)
{
case 0:
x = numInput.IntValue;
break;
case 1:
y = numInput.IntValue;
break;
case 2:
w = numInput.IntValue;
break;
case 3:
h = numInput.IntValue;
break;
}
};
}
new GUIButton(new RectTransform(new Point(limbButtonElement.Rect.Width / 4, limbButtonElement.Rect.Height), limbEditLayout.RectTransform)
, GetCharacterEditorTranslation("AddMultipleLimbsButton"))
{
OnClicked = (b, d) =>
{
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
LimbType limbType = LimbType.None;
switch (LimbGUIElements.Count)
{
case 0:
limbType = LimbType.Torso;
break;
case 1:
limbType = LimbType.Head;
break;
}
CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize, id: LimbGUIElements.Count, limbType: limbType, sourceRect: new Rectangle(i * w, j * h, w, h));
}
}
return true;
}
};
// Joints
new GUIFrame(new RectTransform(new Vector2(1, 0.05f), bottomGroup.RectTransform), style: null) { CanBeFocused = false };
var jointsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), bottomGroup.RectTransform), style: null) { CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), jointsElement.RectTransform), $"{GetCharacterEditorTranslation("Joints")}: ");
var jointButtonElement = new GUIFrame(new RectTransform(new Vector2(0.5f, 1f), jointsElement.RectTransform)
{ RelativeOffset = new Vector2(0.1f, 0) }, style: null) { CanBeFocused = false };
var jointsList = new GUIListBox(new RectTransform(new Vector2(1, 0.45f), bottomGroup.RectTransform));
var removeJointButton = new GUIButton(new RectTransform(new Point(jointButtonElement.Rect.Height, jointButtonElement.Rect.Height), jointButtonElement.RectTransform), "-")
{
OnClicked = (b, d) =>
{
var element = JointGUIElements.LastOrDefault();
if (element == null) { return false; }
element.RectTransform.Parent = null;
JointGUIElements.Remove(element);
return true;
}
};
var addJointButton = new GUIButton(new RectTransform(new Point(jointButtonElement.Rect.Height, jointButtonElement.Rect.Height), jointButtonElement.RectTransform)
{
AbsoluteOffset = new Point(removeJointButton.Rect.Width + 10, 0)
}, "+")
{
OnClicked = (b, d) =>
{
CreateJointGUIElement(jointsList.Content.RectTransform, elementSize);
return true;
}
};
loadHtmlButton.OnClicked = (b, d) =>
{
if (htmlBox == null)
{
htmlBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadHTML"), string.Empty, new string[] { TextManager.Get("Close"), TextManager.Get("Load") }, new Vector2(0.5f, 1.0f));
htmlBox.Header.Font = GUI.LargeFont;
var element = new GUIFrame(new RectTransform(new Vector2(0.8f, 0.05f), htmlBox.Content.RectTransform), style: null, color: Color.Gray * 0.25f);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), element.RectTransform), GetCharacterEditorTranslation("HTMLPath"));
var htmlPathElement = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), element.RectTransform, Anchor.TopRight), $"Content/Characters/{Name}/{Name}.html");
var list = new GUIListBox(new RectTransform(new Vector2(1, 0.8f), htmlBox.Content.RectTransform));
var htmlOutput = new GUITextBlock(new RectTransform(Vector2.One, list.Content.RectTransform), string.Empty) { CanBeFocused = false };
htmlBox.Buttons[0].OnClicked += (_b, _d) =>
{
htmlBox.Close();
return true;
};
htmlBox.Buttons[1].OnClicked += (_b, _d) =>
{
LimbGUIElements.ForEach(l => l.RectTransform.Parent = null);
LimbGUIElements.Clear();
JointGUIElements.ForEach(j => j.RectTransform.Parent = null);
JointGUIElements.Clear();
LimbXElements.Clear();
JointXElements.Clear();
ParseRagdollFromHTML(htmlPathElement.Text, (id, limbName, limbType, rect) =>
{
CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize, id, limbName, limbType, rect);
}, (id1, id2, anchor1, anchor2, jointName) =>
{
CreateJointGUIElement(jointsList.Content.RectTransform, elementSize, id1, id2, anchor1, anchor2, jointName);
});
htmlOutput.Text = new XDocument(new XElement("Ragdoll", new object[]
{
new XAttribute("type", Name), LimbXElements.Values, JointXElements
})).ToString();
htmlOutput.CalculateHeightFromText();
list.UpdateScrollBarSize();
return true;
};
}
else
{
GUIMessageBox.MessageBoxes.Add(htmlBox);
}
return true;
};
//var codeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.5f), listBox.Content.RectTransform), style: null) { CanBeFocused = false };
//new GUITextBlock(new RectTransform(new Vector2(1, 0.05f), codeArea.RectTransform), "Custom code:");
//new GUITextBox(new RectTransform(new Vector2(1, 1 - 0.05f), codeArea.RectTransform, Anchor.BottomLeft), string.Empty, textAlignment: Alignment.TopLeft);
// Previous
box.Buttons[0].OnClicked += (b, d) =>
{
Wizard.Instance.SelectTab(Tab.Character);
return true;
};
// Parse and create
box.Buttons[1].OnClicked += (b, d) =>
{
ParseLimbsFromGUIElements();
ParseJointsFromGUIElements();
var torsoAttributes = LimbXElements.Values.Select(xe => xe.Attribute("type")).Where(a => a.Value.ToLowerInvariant() == "torso");
if (torsoAttributes.Count() != 1)
{
GUI.AddMessage(GetCharacterEditorTranslation("MultipleTorsosDefined"), Color.Red);
return false;
}
XElement torso = torsoAttributes.Single().Parent;
int radius = torso.GetAttributeInt("radius", -1);
int height = torso.GetAttributeInt("height", -1);
int width = torso.GetAttributeInt("width", -1);
int colliderHeight = -1;
if (radius == -1)
{
// the collider is a box -> calculate the capsule
if (width == height)
{
radius = width / 2;
colliderHeight = width - radius * 2;
}
else
{
if (height > width)
{
radius = width / 2;
colliderHeight = height - radius * 2;
}
else
{
radius = height / 2;
colliderHeight = width - radius * 2;
}
}
radius = Math.Max(radius, 1);
}
else if (height > -1 || width > -1)
{
// the collider is a capsule -> use the capsule as it is
colliderHeight = width > height ? width : height;
}
var colliderAttributes = new List<XAttribute>() { new XAttribute("radius", radius) };
if (colliderHeight > -1)
{
colliderHeight = Math.Max(colliderHeight, 1);
if (height > width)
{
colliderAttributes.Add(new XAttribute("height", colliderHeight));
}
else
{
colliderAttributes.Add(new XAttribute("width", colliderHeight));
}
}
var colliderElements = new List<XElement>() { new XElement("collider", colliderAttributes) };
if (IsHumanoid)
{
// For humanoids, we need a secondary, shorter collider for crouching
var secondaryCollider = new XElement("collider", new XAttribute("radius", radius));
if (colliderHeight > -1)
{
colliderHeight = Math.Max(colliderHeight, 1);
if (height > width)
{
secondaryCollider.Add(new XAttribute("height", colliderHeight * 0.75f));
}
else
{
secondaryCollider.Add(new XAttribute("width", colliderHeight * 0.75f));
}
}
colliderElements.Add(secondaryCollider);
}
var ragdollParams = new object[]
{
new XAttribute("type", Name),
new XAttribute("canentersubmarine", CanEnterSubmarine),
colliderElements,
LimbXElements.Values,
JointXElements
};
if (CharacterEditorScreen.instance.CreateCharacter(Name, Path.GetDirectoryName(XMLPath), IsHumanoid, ContentPackage, ragdollParams))
{
GUI.AddMessage(GetCharacterEditorTranslation("CharacterCreated").Replace("[name]", Name), Color.Green, font: GUI.Font);
}
Wizard.Instance.SelectTab(Tab.None);
return true;
};
return box;
}
private void CreateLimbGUIElement(RectTransform parent, int elementSize, int id, string name = "", LimbType limbType = LimbType.None, Rectangle? sourceRect = null)
{
var limbElement = new GUIFrame(new RectTransform(new Point(parent.Rect.Width, elementSize * 5 + 40), parent), style: null, color: Color.Gray * 0.25f)
{
CanBeFocused = false
};
var group = new GUILayoutGroup(new RectTransform(Vector2.One, limbElement.RectTransform)) { AbsoluteSpacing = 2 };
var label = new GUITextBlock(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), name);
var idField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
var nameField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
var limbTypeField = GUI.CreateEnumField(limbType, elementSize, GetCharacterEditorTranslation("LimbType"), group.RectTransform, font: GUI.Font);
var sourceRectField = GUI.CreateRectangleField(sourceRect ?? new Rectangle(0, 100 * LimbGUIElements.Count, 100, 100), elementSize, GetCharacterEditorTranslation("SourceRectangle"), group.RectTransform, font: GUI.Font);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), idField.RectTransform, Anchor.TopLeft), GetCharacterEditorTranslation("ID"));
new GUINumberInput(new RectTransform(new Vector2(0.5f, 1), idField.RectTransform, Anchor.TopRight), GUINumberInput.NumberType.Int)
{
MinValueInt = 0,
MaxValueInt = byte.MaxValue,
IntValue = id,
OnValueChanged = numInput =>
{
id = numInput.IntValue;
string text = nameField.GetChild<GUITextBox>().Text;
string t = string.IsNullOrWhiteSpace(text) ? id.ToString() : text;
label.Text = t;
}
};
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), nameField.RectTransform, Anchor.TopLeft), TextManager.Get("Name"));
var nameInput = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), nameField.RectTransform, Anchor.TopRight), name)
{
CaretColor = Color.White,
};
nameInput.OnTextChanged += (tb, text) =>
{
string t = string.IsNullOrWhiteSpace(text) ? id.ToString() : text;
label.Text = t;
return true;
};
LimbGUIElements.Add(limbElement);
}
private void CreateJointGUIElement(RectTransform parent, int elementSize, int id1 = 0, int id2 = 1, Vector2? anchor1 = null, Vector2? anchor2 = null, string jointName = "")
{
var jointElement = new GUIFrame(new RectTransform(new Point(parent.Rect.Width, elementSize * 6 + 40), parent), style: null, color: Color.Gray * 0.25f)
{
CanBeFocused = false
};
var group = new GUILayoutGroup(new RectTransform(Vector2.One, jointElement.RectTransform)) { AbsoluteSpacing = 2 };
var label = new GUITextBlock(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), jointName);
var nameField = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), nameField.RectTransform, Anchor.TopLeft), TextManager.Get("Name"));
var nameInput = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), nameField.RectTransform, Anchor.TopRight), jointName)
{
CaretColor = Color.White,
};
nameInput.OnTextChanged += (textB, text) =>
{
jointName = text;
label.Text = jointName;
return true;
};
var limb1Field = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), limb1Field.RectTransform, Anchor.TopLeft), GetCharacterEditorTranslation("LimbWithIndex").Replace("[index]", "1"));
var limb1InputField = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1), limb1Field.RectTransform, Anchor.TopRight), GUINumberInput.NumberType.Int)
{
MinValueInt = 0,
MaxValueInt = byte.MaxValue,
IntValue = id1
};
var limb2Field = new GUIFrame(new RectTransform(new Point(group.Rect.Width, elementSize), group.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), limb2Field.RectTransform, Anchor.TopLeft), GetCharacterEditorTranslation("LimbWithIndex").Replace("[index]", "2"));
var limb2InputField = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1), limb2Field.RectTransform, Anchor.TopRight), GUINumberInput.NumberType.Int)
{
MinValueInt = 0,
MaxValueInt = byte.MaxValue,
IntValue = id2
};
GUI.CreateVector2Field(anchor1 ?? Vector2.Zero, elementSize, GetCharacterEditorTranslation("LimbWithIndexAnchor").Replace("[index]", "1"), group.RectTransform, font: GUI.Font, decimalsToDisplay: 2);
GUI.CreateVector2Field(anchor2 ?? Vector2.Zero, elementSize, GetCharacterEditorTranslation("LimbWithIndexAnchor").Replace("[index]", "2"), group.RectTransform, font: GUI.Font, decimalsToDisplay: 2);
label.Text = GetJointName(jointName);
limb1InputField.OnValueChanged += nInput => label.Text = GetJointName(jointName);
limb2InputField.OnValueChanged += nInput => label.Text = GetJointName(jointName);
JointGUIElements.Add(jointElement);
string GetJointName(string n) => string.IsNullOrWhiteSpace(n) ? $"{GetCharacterEditorTranslation("Joint")} {limb1InputField.IntValue} - {limb2InputField.IntValue}" : n;
}
}
private abstract class View
{
// Easy accessors to the common data.
public string Name
{
get => Instance.name;
set => Instance.name = value;
}
public bool IsHumanoid
{
get => Instance.isHumanoid;
set => Instance.isHumanoid = value;
}
public bool CanEnterSubmarine
{
get => Instance.canEnterSubmarine;
set => Instance.canEnterSubmarine = value;
}
public ContentPackage ContentPackage
{
get => Instance.contentPackage;
set => Instance.contentPackage = value;
}
public string TexturePath
{
get => Instance.texturePath;
set => Instance.texturePath = value;
}
public string XMLPath
{
get => Instance.xmlPath;
set => Instance.xmlPath = value;
}
public Dictionary<string, XElement> LimbXElements
{
get => Instance.limbXElements;
set => Instance.limbXElements = value;
}
public List<GUIComponent> LimbGUIElements
{
get => Instance.limbGUIElements;
set => Instance.limbGUIElements = value;
}
public List<XElement> JointXElements
{
get => Instance.jointXElements;
set => Instance.jointXElements = value;
}
public List<GUIComponent> JointGUIElements
{
get => Instance.jointGUIElements;
set => Instance.jointGUIElements = value;
}
private GUIMessageBox box;
public GUIMessageBox Box
{
get
{
if (box == null)
{
box = Create();
}
return box;
}
}
protected abstract GUIMessageBox Create();
protected static T Get<T>(ref T instance) where T : View, new()
{
if (instance == null)
{
instance = new T();
}
return instance;
}
public abstract void Release();
protected void ParseLimbsFromGUIElements()
{
LimbXElements.Clear();
for (int i = 0; i < LimbGUIElements.Count; i++)
{
var limbGUIElement = LimbGUIElements[i];
var allChildren = limbGUIElement.GetAllChildren();
GUITextBlock GetField(string n) => allChildren.First(c => c is GUITextBlock textBlock && textBlock.Text == n) as GUITextBlock;
int id = GetField(GetCharacterEditorTranslation("ID")).Parent.GetChild<GUINumberInput>().IntValue;
string limbName = GetField(TextManager.Get("Name")).Parent.GetChild<GUITextBox>().Text;
LimbType limbType = (LimbType)GetField(GetCharacterEditorTranslation("LimbType")).Parent.GetChild<GUIDropDown>().SelectedData;
// Reverse, because the elements are created from right to left
var rectInputs = GetField(GetCharacterEditorTranslation("SourceRectangle")).Parent.GetAllChildren().Where(c => c is GUINumberInput).Select(c => c as GUINumberInput).Reverse().ToArray();
int width = rectInputs[2].IntValue;
int height = rectInputs[3].IntValue;
var colliderAttributes = new List<XAttribute>();
// Capsules/Circles
//if (width == height)
//{
// colliderAttributes.Add(new XAttribute("radius", (int)(width / 2 * 0.85f)));
//}
//else
//{
// if (height > width)
// {
// colliderAttributes.Add(new XAttribute("radius", (int)(width / 2 * 0.85f)));
// colliderAttributes.Add(new XAttribute("height",(int) (height - width * 0.85f)));
// }
// else
// {
// colliderAttributes.Add(new XAttribute("radius", (int)(height / 2 * 0.85f)));
// colliderAttributes.Add(new XAttribute("width", (int)(width - height * 0.85f)));
// }
//}
// Rectangles
colliderAttributes.Add(new XAttribute("height", (int)(height * 0.85f)));
colliderAttributes.Add(new XAttribute("width", (int)(width * 0.85f)));
idToCodeName.TryGetValue(id, out string notes);
LimbXElements.Add(id.ToString(), new XElement("limb",
new XAttribute("id", id),
new XAttribute("name", limbName),
new XAttribute("type", limbType.ToString()),
colliderAttributes,
new XElement("sprite",
new XAttribute("texture", TexturePath),
new XAttribute("sourcerect", $"{rectInputs[0].IntValue}, {rectInputs[1].IntValue}, {width}, {height}")),
new XAttribute("notes", null ?? string.Empty)
));
}
}
protected void ParseJointsFromGUIElements()
{
JointXElements.Clear();
for (int i = 0; i < JointGUIElements.Count; i++)
{
var jointGUIElement = JointGUIElements[i];
var allChildren = jointGUIElement.GetAllChildren();
GUITextBlock GetField(string n) => allChildren.First(c => c is GUITextBlock textBlock && textBlock.Text == n) as GUITextBlock;
string jointName = GetField(TextManager.Get("Name")).Parent.GetChild<GUITextBox>().Text;
int limb1ID = GetField(GetCharacterEditorTranslation("LimbWithIndex").Replace("[index]", "1")).Parent.GetChild<GUINumberInput>().IntValue;
int limb2ID = GetField(GetCharacterEditorTranslation("LimbWithIndex").Replace("[index]", "2")).Parent.GetChild<GUINumberInput>().IntValue;
// Reverse, because the elements are created from right to left
var anchor1Inputs = GetField(GetCharacterEditorTranslation("LimbWithIndexAnchor").Replace("[index]", "1")).Parent.GetAllChildren().Where(c => c is GUINumberInput).Select(c => c as GUINumberInput).Reverse().ToArray();
var anchor2Inputs = GetField(GetCharacterEditorTranslation("LimbWithIndexAnchor").Replace("[index]", "2")).Parent.GetAllChildren().Where(c => c is GUINumberInput).Select(c => c as GUINumberInput).Reverse().ToArray();
JointXElements.Add(new XElement("joint",
new XAttribute("name", jointName),
new XAttribute("limb1", limb1ID),
new XAttribute("limb2", limb2ID),
new XAttribute("limb1anchor", $"{anchor1Inputs[0].FloatValue.Format(2)}, {anchor1Inputs[1].FloatValue.Format(2)}"),
new XAttribute("limb2anchor", $"{anchor2Inputs[0].FloatValue.Format(2)}, {anchor2Inputs[1].FloatValue.Format(2)}")));
}
}
Dictionary<int, string> idToCodeName = new Dictionary<int, string>();
protected void ParseRagdollFromHTML(string path, Action<int, string, LimbType, Rectangle> limbCallback = null, Action<int, int, Vector2, Vector2, string> jointCallback = null)
{
// TODO: parse as xml?
//XDocument doc = XMLExtensions.TryLoadXml(path);
//var xElements = doc.Elements().ToArray();
string html = string.Empty;
try
{
html = File.ReadAllText(path);
}
catch (Exception e)
{
DebugConsole.ThrowError(GetCharacterEditorTranslation("FailedToReadHTML").Replace("[path]", path), e);
return;
}
var lines = html.Split(new string[] { "<div", "</div>", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => s.Contains("left") && s.Contains("top") && s.Contains("width") && s.Contains("height"));
int id = 0;
Dictionary<string, int> hierarchyToID = new Dictionary<string, int>();
Dictionary<int, string> idToHierarchy = new Dictionary<int, string>();
Dictionary<int, string> idToPositionCode = new Dictionary<int, string>();
Dictionary<int, string> idToName = new Dictionary<int, string>();
idToCodeName.Clear();
foreach (var line in lines)
{
var codeNames = new string(line.SkipWhile(c => c != '>').Skip(1).ToArray()).Split(',');
for (int i = 0; i < codeNames.Length; i++)
{
string codeName = codeNames[i].Trim();
if (string.IsNullOrWhiteSpace(codeName)) { continue; }
idToCodeName.Add(id, codeName);
string limbName = new string(codeName.SkipWhile(c => c != '_').Skip(1).ToArray());
if (string.IsNullOrWhiteSpace(limbName)) { continue; }
idToName.Add(id, limbName);
var parts = line.Split(' ');
int ParseToInt(string selector)
{
string part = parts.First(p => p.Contains(selector));
string s = new string(part.SkipWhile(c => c != ':').Skip(1).TakeWhile(c => char.IsNumber(c)).ToArray());
int.TryParse(s, out int v);
return v;
};
// example: 111311cr -> 111311
string hierarchy = new string(codeName.TakeWhile(c => char.IsNumber(c)).ToArray());
if (hierarchyToID.ContainsKey(hierarchy))
{
DebugConsole.ThrowError(GetCharacterEditorTranslation("MultipleItemsWithSameHierarchy").Replace("[hierarchy]", hierarchy).Replace("[name]", codeName));
return;
}
hierarchyToID.Add(hierarchy, id);
idToHierarchy.Add(id, hierarchy);
string positionCode = new string(codeName.SkipWhile(c => char.IsNumber(c)).TakeWhile(c => c != '_').ToArray());
idToPositionCode.Add(id, positionCode.ToLowerInvariant());
int x = ParseToInt("left");
int y = ParseToInt("top");
int width = ParseToInt("width");
int height = ParseToInt("height");
// This is overridden when the data is loaded from the gui fields.
LimbXElements.Add(hierarchy, new XElement("limb",
new XAttribute("id", id),
new XAttribute("name", limbName),
new XAttribute("type", ParseLimbType(limbName).ToString()),
new XElement("sprite",
new XAttribute("texture", TexturePath),
new XAttribute("sourcerect", $"{x}, {y}, {width}, {height}"))
));
limbCallback?.Invoke(id, limbName, ParseLimbType(limbName), new Rectangle(x, y, width, height));
id++;
}
}
for (int i = 0; i < id; i++)
{
if (idToHierarchy.TryGetValue(i, out string hierarchy))
{
if (hierarchy != "0")
{
// NEW LOGIC: if hierarchy length == 1, parent to 0
// Else parent to the last bone in the current hierarchy (11 is parented to 1, 212 is parented to 21 etc)
string parent = hierarchy.Length > 1 ? hierarchy.Remove(hierarchy.Length - 1, 1) : "0";
if (hierarchyToID.TryGetValue(parent, out int parentID))
{
Vector2 anchor1 = Vector2.Zero;
Vector2 anchor2 = Vector2.Zero;
idToName.TryGetValue(parentID, out string parentName);
idToName.TryGetValue(i, out string limbName);
string jointName = $"{GetCharacterEditorTranslation("Joint")} {parentName} - {limbName}";
if (idToPositionCode.TryGetValue(i, out string positionCode))
{
float scalar = 0.8f;
if (LimbXElements.TryGetValue(parent, out XElement parentElement))
{
Rectangle parentSourceRect = parentElement.Element("sprite").GetAttributeRect("sourcerect", Rectangle.Empty);
float parentWidth = parentSourceRect.Width / 2 * scalar;
float parentHeight = parentSourceRect.Height / 2 * scalar;
switch (positionCode)
{
case "tl": // -1, 1
anchor1 = new Vector2(-parentWidth, parentHeight);
break;
case "tc": // 0, 1
anchor1 = new Vector2(0, parentHeight);
break;
case "tr": // -1, 1
anchor1 = new Vector2(-parentWidth, parentHeight);
break;
case "cl": // -1, 0
anchor1 = new Vector2(-parentWidth, 0);
break;
case "cr": // 1, 0
anchor1 = new Vector2(parentWidth, 0);
break;
case "bl": // -1, -1
anchor1 = new Vector2(-parentWidth, -parentHeight);
break;
case "bc": // 0, -1
anchor1 = new Vector2(0, -parentHeight);
break;
case "br": // 1, -1
anchor1 = new Vector2(parentWidth, -parentHeight);
break;
}
if (LimbXElements.TryGetValue(hierarchy, out XElement element))
{
Rectangle sourceRect = element.Element("sprite").GetAttributeRect("sourcerect", Rectangle.Empty);
float width = sourceRect.Width / 2 * scalar;
float height = sourceRect.Height / 2 * scalar;
switch (positionCode)
{
// Inverse
case "tl":
// br
anchor2 = new Vector2(-width, -height);
break;
case "tc":
// bc
anchor2 = new Vector2(0, -height);
break;
case "tr":
// bl
anchor2 = new Vector2(-width, -height);
break;
case "cl":
// cr
anchor2 = new Vector2(width, 0);
break;
case "cr":
// cl
anchor2 = new Vector2(-width, 0);
break;
case "bl":
// tr
anchor2 = new Vector2(-width, height);
break;
case "bc":
// tc
anchor2 = new Vector2(0, height);
break;
case "br":
// tl
anchor2 = new Vector2(-width, height);
break;
}
}
}
}
// This is overridden when the data is loaded from the gui fields.
JointXElements.Add(new XElement("joint",
new XAttribute("name", jointName),
new XAttribute("limb1", parentID),
new XAttribute("limb2", i),
new XAttribute("limb1anchor", $"{anchor1.X.Format(2)}, {anchor1.Y.Format(2)}"),
new XAttribute("limb2anchor", $"{anchor2.X.Format(2)}, {anchor2.Y.Format(2)}")
));
jointCallback?.Invoke(parentID, i, anchor1, anchor2, jointName);
}
}
}
}
}
protected LimbType ParseLimbType(string limbName)
{
var limbType = LimbType.None;
string n = limbName.ToLowerInvariant();
switch (n)
{
case "head":
limbType = LimbType.Head;
break;
case "torso":
limbType = LimbType.Torso;
break;
case "waist":
case "pelvis":
limbType = LimbType.Waist;
break;
case "tail":
limbType = LimbType.Tail;
break;
}
if (limbType == LimbType.None)
{
if (n.Contains("tail"))
{
limbType = LimbType.Tail;
}
else if (n.Contains("arm") && !n.Contains("lower"))
{
if (n.Contains("right"))
{
limbType = LimbType.RightArm;
}
else if (n.Contains("left"))
{
limbType = LimbType.LeftArm;
}
}
else if (n.Contains("hand") || n.Contains("palm"))
{
if (n.Contains("right"))
{
limbType = LimbType.RightHand;
}
else if (n.Contains("left"))
{
limbType = LimbType.LeftHand;
}
}
else if (n.Contains("thigh") || n.Contains("upperleg"))
{
if (n.Contains("right"))
{
limbType = LimbType.RightThigh;
}
else if (n.Contains("left"))
{
limbType = LimbType.LeftThigh;
}
}
else if (n.Contains("shin") || n.Contains("lowerleg"))
{
if (n.Contains("right"))
{
limbType = LimbType.RightLeg;
}
else if (n.Contains("left"))
{
limbType = LimbType.LeftLeg;
}
}
else if (n.Contains("foot"))
{
if (n.Contains("right"))
{
limbType = LimbType.RightFoot;
}
else if (n.Contains("left"))
{
limbType = LimbType.LeftFoot;
}
}
}
return limbType;
}
}
}
#endregion
}
}