diff --git a/Godbert/ViewModels/MonstersViewModel.cs b/Godbert/ViewModels/MonstersViewModel.cs index 7b4e9d48..5d3a54f8 100644 --- a/Godbert/ViewModels/MonstersViewModel.cs +++ b/Godbert/ViewModels/MonstersViewModel.cs @@ -178,13 +178,33 @@ private List SearchPaps(string modelPath, int m) * a0002 animations are decided? */ string[] searchPaths = { + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/ability", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/battle", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/blownaway", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/bnpc_gesture", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/bnpc_gimmickjump", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/bnpc_passmove", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/cr_mon", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/emote", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/event", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/event_base", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/gs_roulette/m{0:D4}", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/human_sp/c0101", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/idle_sp", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/lcut", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/loop_sp", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/minion", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/mon_sp/m{0:D4}", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/mon_sp/m{0:D4}/hide", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/mon_sp/m{0:D4}/show", - "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/event", - "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/minion", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/mount_sp/m{0:D4}", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/normal", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/parts_idle_sp", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/pc_contentsaction", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/resident", - "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/specialpop" + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/specialpop", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/trans_sp/m{0:D4}", + "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/warp", }; SaintCoinach.IO.Directory d; @@ -213,22 +233,27 @@ private IComponent CreateModel(Engine engine, Skeleton skeleton, ModelDefinition var component = new AnimatedModel(engine, skeleton, variant, model, ModelQuality.High) {}; var papPath = string.Format(PapPathFormat, m, b); - SaintCoinach.IO.File papFileBase; - if (Parent.Realm.Packs.TryGetFile(papPath, out papFileBase)) { - var anim = new AnimationContainer(skeleton, new PapFile(papFileBase)); - + var allPaps = SearchPaps(model.File.Path, m); + if (allPaps.Any()) { var hasAnim = false; - for(var i = 0; i < DefaultAnimationNames.Length && !hasAnim; ++i) { - var n = DefaultAnimationNames[i]; - if (anim.AnimationNames.Contains(n)) { - component.AnimationPlayer.Animation = anim.Get(n); - hasAnim = true; + + foreach (var pap in allPaps) { + var anim = new AnimationContainer(skeleton, pap); + foreach (var name in anim.AnimationNames) { + component.AnimationPlayer.AddAnimation(anim.Get(name)); + if (!hasAnim && DefaultAnimationNames.Contains(name)) { + hasAnim = true; + component.AnimationPlayer.AnimationIndex = component.AnimationPlayer.Animations.Count - 1; + } } } - - if (!hasAnim) - component.AnimationPlayer.Animation = anim.Get(0); + + if (!hasAnim && component.AnimationPlayer.Animations.Any()) + component.AnimationPlayer.AnimationIndex = 0; + + component.AnimationPlayer.SortAnimationsByName(); } + return component; } diff --git a/SaintCoinach.Graphics.Viewer/AnimatedModel.cs b/SaintCoinach.Graphics.Viewer/AnimatedModel.cs index 4ce5ff2b..44dcb3cb 100644 --- a/SaintCoinach.Graphics.Viewer/AnimatedModel.cs +++ b/SaintCoinach.Graphics.Viewer/AnimatedModel.cs @@ -31,7 +31,7 @@ public AnimatedModel(Engine engine, Skeleton skeleton, ModelVariantIdentifier va : base(engine, variant, definition, quality) { Skeleton = skeleton; - _AnimationPlayer = new AnimationPlayer(); + _AnimationPlayer = new AnimationPlayer(engine); var nameMap = new Dictionary(); for (var i = 0; i < Skeleton.BoneNames.Length; ++i) diff --git a/SaintCoinach.Graphics.Viewer/AnimationPlayer.cs b/SaintCoinach.Graphics.Viewer/AnimationPlayer.cs index dc19273a..5b900fd1 100644 --- a/SaintCoinach.Graphics.Viewer/AnimationPlayer.cs +++ b/SaintCoinach.Graphics.Viewer/AnimationPlayer.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Forms; namespace SaintCoinach.Graphics.Viewer { using SharpDX; @@ -16,21 +17,26 @@ public class AnimationPlayer : IUpdateableComponent { private bool _IsEnabled = true; private bool _IsLooping = true; - private Animation _Animation; + private List _Animations = new List(); + private int _AnimationIndex = -1; private double _CurrentPlaybackPosition = 0f; private bool _IsDirty = true; private Matrix[] _BoneTransformationMatrices; + + private Engine _Engine; #endregion #region Properties public double PlaybackSpeed { get { return _PlaybackSpeed; } set { _PlaybackSpeed = value; } } public bool IsLooping { get { return _IsLooping; } set { _IsLooping = value; _IsAnimating = true; } } - public Animation Animation { - get { return _Animation; } + public IList Animations { get { return _Animations.AsReadOnly(); } } + public Animation Animation { get { return _AnimationIndex == -1 ? null : _Animations[_AnimationIndex]; } } + public int AnimationIndex { + get { return _AnimationIndex; } set { - _Animation = value; + _AnimationIndex = value; Reset(); } } @@ -39,6 +45,11 @@ public Animation Animation { #endregion #region Constructor + public AnimationPlayer(Engine engine) { + _Engine = engine; + + Reset(); + } #endregion #region Update @@ -53,19 +64,73 @@ public Matrix[] GetPose() { return _BoneTransformationMatrices; } + public void Reset() { _IsAnimating = true; CurrentPlaybackPosition = 0f; + PlaybackSpeed = 1; + UpdateTitle(); + } + + public void UpdateTitle() { + if (Animation == null) { + _Engine.Subtitle = ""; + return; + } + + var sb = new StringBuilder(); + sb.Append(_AnimationIndex + 1).Append("/").Append(_Animations.Count); + sb.Append(" - ").Append(Animation.Name); + sb.Append(" - ").AppendFormat("{0:0.00}x", PlaybackSpeed); + sb.Append(" - ").AppendFormat("{0:0.00}s/{1:0.00}s", CurrentPlaybackPosition, Animation.Duration); + sb.Append(" - ").AppendFormat("{0}/{1}", (int)Math.Floor(CurrentPlaybackPosition / Animation.FrameDuration) + 1, Animation.FrameCount); + if (!_IsAnimating) + sb.Append(" - Paused"); + _Engine.Subtitle = sb.ToString(); } public bool IsEnabled { get { return _IsEnabled; } set { _IsEnabled = value; } } public void Update(EngineTime engineTime) { - if (!IsEnabled || !IsAnimating || Animation == null || Math.Abs(PlaybackSpeed) <= float.Epsilon) + if (!Animations.Any()) return; + var elapsedTime = IsAnimating ? engineTime.ElapsedTime.TotalSeconds : 0; + if (_Engine.Keyboard.IsKeyDown(Keys.R)) + Reset(); + if (_Engine.Keyboard.WasKeyPressedRepeatable(Keys.Oemcomma)) { + _AnimationIndex = (_AnimationIndex + _Animations.Count - 1) % _Animations.Count; + elapsedTime = 0; + _IsAnimating = true; + CurrentPlaybackPosition = 0f; + } + if (_Engine.Keyboard.WasKeyPressedRepeatable(Keys.OemPeriod)) { + _AnimationIndex = (_AnimationIndex + 1) % _Animations.Count; + elapsedTime = 0; + _IsAnimating = true; + CurrentPlaybackPosition = 0f; + } + if (_Engine.Keyboard.WasKeyPressedRepeatable(Keys.OemQuestion)) { + _IsAnimating = !_IsAnimating; + elapsedTime = 0; + } + if (_Engine.Keyboard.WasKeyPressedRepeatable(Keys.N)) { + _IsAnimating = false; + elapsedTime = 0; + _CurrentPlaybackPosition = (int)Math.Round((_CurrentPlaybackPosition + Animation.Duration - Animation.FrameDuration) / Animation.FrameDuration) % Animation.FrameCount * Animation.FrameDuration; + } + if (_Engine.Keyboard.WasKeyPressedRepeatable(Keys.M)) { + _IsAnimating = false; + elapsedTime = 0; + _CurrentPlaybackPosition = (int)Math.Round((_CurrentPlaybackPosition + Animation.FrameDuration) / Animation.FrameDuration) % Animation.FrameCount * Animation.FrameDuration; + } + if (_Engine.Keyboard.WasKeyPressedRepeatable(Keys.K)) + PlaybackSpeed = Math.Max(1, Math.Round(PlaybackSpeed * 20) - 1) / 20; + if (_Engine.Keyboard.WasKeyPressedRepeatable(Keys.L)) + PlaybackSpeed = Math.Min(80, Math.Round(PlaybackSpeed * 20) + 1) / 20; + _IsDirty = true; - _CurrentPlaybackPosition += engineTime.ElapsedTime.TotalSeconds * PlaybackSpeed; + _CurrentPlaybackPosition += elapsedTime * PlaybackSpeed; if (IsLooping) { while (_CurrentPlaybackPosition < 0) _CurrentPlaybackPosition += Animation.Duration; @@ -73,6 +138,20 @@ public void Update(EngineTime engineTime) { _CurrentPlaybackPosition -= Animation.Duration; } else if (_CurrentPlaybackPosition <= 0 || _CurrentPlaybackPosition > Animation.Duration) _IsAnimating = false; + + UpdateTitle(); + } + #endregion + + #region Misc + public void AddAnimation(Animation animation) { + _Animations.Add(animation); + } + + public void SortAnimationsByName() { + var prev = Animation; + _Animations.Sort((x, y) => (x.Name.CompareTo(y.Name))); + AnimationIndex = Animations.IndexOf(prev); } #endregion } diff --git a/SaintCoinach.Graphics.Viewer/Camera.cs b/SaintCoinach.Graphics.Viewer/Camera.cs index 411c4f17..b8500caf 100644 --- a/SaintCoinach.Graphics.Viewer/Camera.cs +++ b/SaintCoinach.Graphics.Viewer/Camera.cs @@ -163,14 +163,20 @@ public void Update(EngineTime time) { if (_Engine.Keyboard.IsKeyDown(Keys.Down)) _Pitch -= RotationSpeed * amount * 2; - if (_CurrentMouseState.LeftButton) { + if (_CurrentMouseState.LeftButton || _CurrentMouseState.RightButton) { + var mouseMove = _CurrentMouseState.AbsolutePosition - _PreviousMouseState.AbsolutePosition; + if (_CurrentMouseState.LeftButton) { + _Yaw -= mouseMove.X * MouseRotationSpeedYaw; + _Pitch -= mouseMove.Y * MouseRotationSpeedPitch; + } if (_CurrentMouseState.RightButton) + moveVector += new Vector3(mouseMove.X / -16f, mouseMove.Y / 16f, 0); + if (_CurrentMouseState.LeftButton && _CurrentMouseState.RightButton) moveVector += new Vector3(0, 0, -1); - var mouseMove = _CurrentMouseState.AbsolutePosition - _PreviousMouseState.AbsolutePosition; - _Yaw -= mouseMove.X * MouseRotationSpeedYaw; - _Pitch -= mouseMove.Y * MouseRotationSpeedPitch; } + moveVector += new Vector3(0, 0, _CurrentMouseState.WheelDelta / -12f); + AddToCameraPosition(moveVector * amount); } diff --git a/SaintCoinach.Graphics.Viewer/Engine.cs b/SaintCoinach.Graphics.Viewer/Engine.cs index 4072cff8..2f431af5 100644 --- a/SaintCoinach.Graphics.Viewer/Engine.cs +++ b/SaintCoinach.Graphics.Viewer/Engine.cs @@ -50,6 +50,7 @@ public abstract class Engine : IDisposable { private MaterialFactory _MaterialFactory; private Size2 _ViewportSize; + private string _Subtitle; #endregion #region Properties @@ -72,6 +73,7 @@ public abstract class Engine : IDisposable { public MaterialFactory MaterialFactory { get { return _MaterialFactory; } } public abstract IInputService InputService { get; } public virtual Size2 ViewportSize { get { return _ViewportSize; } } + public virtual string Subtitle { get { return _Subtitle; } set { _Subtitle = value; } } public abstract bool IsActive { get; } public DepthStencilState DepthStencilState { diff --git a/SaintCoinach.Graphics.Viewer/FormEngine.cs b/SaintCoinach.Graphics.Viewer/FormEngine.cs index af952cff..eddd6e9f 100644 --- a/SaintCoinach.Graphics.Viewer/FormEngine.cs +++ b/SaintCoinach.Graphics.Viewer/FormEngine.cs @@ -45,6 +45,15 @@ public override IInputService InputService { get { return _InputService; } } public RenderForm Form { get { return _Form; } } + public override string Subtitle { + get { + return base.Subtitle; + } + set { + base.Subtitle = value; + _Form.Text = string.IsNullOrEmpty(value) ? _Title : _Title + " - " + value; + } + } public override bool IsActive { get { return Form.ContainsFocus; } } diff --git a/SaintCoinach.Graphics.Viewer/FormInputService.cs b/SaintCoinach.Graphics.Viewer/FormInputService.cs index 5d58083c..ae5940ff 100644 --- a/SaintCoinach.Graphics.Viewer/FormInputService.cs +++ b/SaintCoinach.Graphics.Viewer/FormInputService.cs @@ -14,6 +14,7 @@ public class FormInputService : IInputService { #region Fields private List _DownKeys = new List(); private Point _MousePosition; + private int _MouseDelta; private List _DownMouseButtons = new List(); #endregion @@ -25,6 +26,7 @@ public FormInputService(Form form) { form.KeyUp += Form_KeyUp; form.MouseMove += Form_MouseMove; form.MouseDown += Form_MouseDown; + form.MouseWheel += Form_MouseWheel; form.MouseUp += Form_MouseUp; form.LostFocus += Form_LostFocus; } @@ -43,6 +45,11 @@ public IEnumerable GetDownMouseButtons() { buttons = _DownMouseButtons.ToArray(); return buttons; } + public int GetNextWheelDelta() { + var v = _MouseDelta; + _MouseDelta = 0; + return v; + } #endregion #region Form events @@ -67,6 +74,11 @@ void Form_MouseDown(object sender, MouseEventArgs e) { void Form_MouseMove(object sender, MouseEventArgs e) { _MousePosition = new Point(e.X, e.Y); } + + void Form_MouseWheel(object sender, MouseEventArgs e) { + _MouseDelta += e.Delta; + } + void Form_KeyUp(object sender, KeyEventArgs e) { lock (_DownKeys) _DownKeys.Remove(e.KeyCode); diff --git a/SaintCoinach.Graphics.Viewer/IInputService.cs b/SaintCoinach.Graphics.Viewer/IInputService.cs index 357447c9..200be1fb 100644 --- a/SaintCoinach.Graphics.Viewer/IInputService.cs +++ b/SaintCoinach.Graphics.Viewer/IInputService.cs @@ -13,5 +13,6 @@ public interface IInputService { Point MousePosition { get; } IEnumerable GetDownKeys(); IEnumerable GetDownMouseButtons(); + int GetNextWheelDelta(); } } diff --git a/SaintCoinach.Graphics.Viewer/ImageRenderer.cs b/SaintCoinach.Graphics.Viewer/ImageRenderer.cs index 0d28f013..52c8bde9 100644 --- a/SaintCoinach.Graphics.Viewer/ImageRenderer.cs +++ b/SaintCoinach.Graphics.Viewer/ImageRenderer.cs @@ -27,6 +27,10 @@ public SharpDX.Point MousePosition { yield break; } + public int GetNextWheelDelta() { + return 0; + } + #endregion } #endregion diff --git a/SaintCoinach.Graphics.Viewer/Keyboard.cs b/SaintCoinach.Graphics.Viewer/Keyboard.cs index 6300b127..539614c1 100644 --- a/SaintCoinach.Graphics.Viewer/Keyboard.cs +++ b/SaintCoinach.Graphics.Viewer/Keyboard.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; - +using System.Windows.Forms; using SharpDX.Multimedia; using Keys = System.Windows.Forms.Keys; @@ -14,6 +14,9 @@ public class Keyboard : IUpdateableComponent { private List _ReleasedKeys = new List(); private List _PressedKeys = new List(); private List _DownKeys = new List(); + private Dictionary _FirstRepeatKeys = new Dictionary(); + private Dictionary _SubsequentRepeatKeys = new Dictionary(); + private List _RepeatKeys = new List(); private Engine _Engine; #endregion @@ -37,6 +40,9 @@ public bool WasKeyReleased(Keys key) { public bool WasKeyPressed(Keys key) { return _PressedKeys.Contains(key); } + public bool WasKeyPressedRepeatable(Keys key) { + return _RepeatKeys.Contains(key); + } #endregion #region IUpdateable Members @@ -46,14 +52,40 @@ public bool WasKeyPressed(Keys key) { public void Update(EngineTime time) { _ReleasedKeys.Clear(); _PressedKeys.Clear(); + _RepeatKeys.Clear(); var downKeys = _Engine.InputService.GetDownKeys(); _PressedKeys.AddRange(downKeys.Except(_DownKeys)); _ReleasedKeys.AddRange(_DownKeys.Except(downKeys)); _DownKeys.AddRange(_PressedKeys); - foreach (var k in _ReleasedKeys) + foreach (var k in _ReleasedKeys) { _DownKeys.Remove(k); + _FirstRepeatKeys.Remove(k); + _SubsequentRepeatKeys.Remove(k); + } + + var firstRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay); + var subsequentRepeatDelay = (int)(1000 / (2.5 + 27.5 * SystemInformation.KeyboardSpeed / 31)); + + foreach (var key in downKeys.Except(_SubsequentRepeatKeys.Keys)) { + if (_FirstRepeatKeys.TryGetValue(key, out var next)) { + if (next <= Environment.TickCount) { + _FirstRepeatKeys.Remove(key); + _SubsequentRepeatKeys[key] = Environment.TickCount + subsequentRepeatDelay; + _RepeatKeys.Add(key); + } + } else { + _FirstRepeatKeys[key] = Environment.TickCount + firstRepeatDelay; + _RepeatKeys.Add(key); + } + } + foreach (var pair in _SubsequentRepeatKeys.ToArray()) { + if (pair.Value <= Environment.TickCount) { + _SubsequentRepeatKeys[pair.Key] = Environment.TickCount + subsequentRepeatDelay; + _RepeatKeys.Add(pair.Key); + } + } } #endregion } diff --git a/SaintCoinach.Graphics.Viewer/Mouse.cs b/SaintCoinach.Graphics.Viewer/Mouse.cs index c3cd0ba6..e087cf36 100644 --- a/SaintCoinach.Graphics.Viewer/Mouse.cs +++ b/SaintCoinach.Graphics.Viewer/Mouse.cs @@ -43,6 +43,7 @@ public void Update(EngineTime engineTime) { _CurrentState.LeftButton = buttons.Contains(System.Windows.Forms.MouseButtons.Left); _CurrentState.RightButton = buttons.Contains(System.Windows.Forms.MouseButtons.Right); _CurrentState.MiddleButton = buttons.Contains(System.Windows.Forms.MouseButtons.Middle); + _CurrentState.WheelDelta = _Engine.InputService.GetNextWheelDelta(); } #endregion diff --git a/SaintCoinach.Graphics.Viewer/MouseState.cs b/SaintCoinach.Graphics.Viewer/MouseState.cs index 9cb6f435..c9ee4f78 100644 --- a/SaintCoinach.Graphics.Viewer/MouseState.cs +++ b/SaintCoinach.Graphics.Viewer/MouseState.cs @@ -11,6 +11,7 @@ public struct MouseState { public bool LeftButton { get; internal set; } public bool RightButton { get; internal set; } public bool MiddleButton { get; internal set; } + public int WheelDelta { get; internal set; } public Vector2 AbsolutePosition { get; internal set; } public Vector2 RelativePosition { get; internal set; }