From 317c1594761865b94164df0fb0fb12f63e95c18f Mon Sep 17 00:00:00 2001 From: AsY!um- <377468+AsYlum-@users.noreply.github.com> Date: Thu, 12 Nov 2026 00:14:59 +0100 Subject: [PATCH 1/2] Add single-action .vd import/export and copy action to animation edit (#83) Actions export as a .vd with 5 direction entries (header type 3); copying keeps frames and palette unchanged and refuses targets outside the body's idx block. --- Ultima/AnimationEdit.cs | 140 ++++++++++++++- .../Forms/AnimationCopyActionForm.Designer.cs | 159 ++++++++++++++++++ .../Forms/AnimationCopyActionForm.cs | 83 +++++++++ .../Forms/AnimationCopyActionForm.resx | 60 +++++++ .../Forms/AnimationEditForm.Designer.cs | 49 +++++- UoFiddler.Controls/Forms/AnimationEditForm.cs | 159 ++++++++++++++++++ 6 files changed, 639 insertions(+), 11 deletions(-) create mode 100644 UoFiddler.Controls/Forms/AnimationCopyActionForm.Designer.cs create mode 100644 UoFiddler.Controls/Forms/AnimationCopyActionForm.cs create mode 100644 UoFiddler.Controls/Forms/AnimationCopyActionForm.resx diff --git a/Ultima/AnimationEdit.cs b/Ultima/AnimationEdit.cs index 86f0125c..3fe862cd 100644 --- a/Ultima/AnimationEdit.cs +++ b/Ultima/AnimationEdit.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; @@ -313,6 +314,107 @@ public static void ExportToVD(int fileType, int body, string file) } } + /// + /// .vd header type of a file that holds one action (5 directions). Body files use 0, 1 and 2. + /// + public const short ActionVdType = 3; + + /// + /// True when of has idx records in the given anim file. + /// + public static bool IsActionInRange(int fileType, int body, int action) + { + if (body < 0 || action < 0 || action >= Animations.GetActionCapacity(body, fileType)) + { + return false; + } + + AnimIdx[] cache = GetCache(fileType); + if (cache == null) + { + return false; + } + + GetFileIndex(body, fileType, action, 4, out FileIndex _, out int index); + + return index < cache.Length; + } + + /// + /// Writes one action as a .vd file: the body .vd layout with 5 index entries, one per direction. + /// + public static void ExportActionToVD(int fileType, int body, int action, string file) + { + using (var fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Write)) + using (var bin = new BinaryWriter(fs)) + { + bin.Write((short)6); + bin.Write(ActionVdType); + long indexPos = bin.BaseStream.Position; + long animPos = bin.BaseStream.Position + (12 * 5); + for (int dir = 0; dir < 5; ++dir) + { + GetAnimation(fileType, body, action, dir).ExportToVD(bin, ref indexPos, ref animPos); + } + } + } + + /// + /// Reads a single-action .vd, positioned after its 4-byte header, into the given action. + /// Directions the file leaves empty are cleared. Nothing changes if the file can't be read. + /// + public static void LoadActionFromVD(int fileType, int body, int action, BinaryReader bin) + { + var entries = new Entry3D[5]; + for (int dir = 0; dir < 5; ++dir) + { + entries[dir].Lookup = bin.ReadInt32(); + entries[dir].Length = bin.ReadInt32(); + entries[dir].Extra = bin.ReadInt32(); + } + + var loaded = new AnimIdx[5]; + for (int dir = 0; dir < 5; ++dir) + { + Entry3D entry = entries[dir]; + if ((entry.Lookup > 0) && (entry.Lookup < bin.BaseStream.Length) && (entry.Length > 0)) + { + bin.BaseStream.Seek(entry.Lookup, SeekOrigin.Begin); + loaded[dir] = new AnimIdx(bin, entry.Extra); + } + } + + for (int dir = 0; dir < 5; ++dir) + { + AnimIdx target = GetAnimation(fileType, body, action, dir); + if (loaded[dir] != null) + { + target.CopyFrom(loaded[dir]); + } + else + { + target.ClearFrames(); + } + } + } + + /// + /// Copies the frames and palettes of all 5 directions of an action to another action, unchanged. + /// + public static void CopyAction(int fileType, int sourceBody, int sourceAction, int targetBody, int targetAction) + { + if (sourceBody == targetBody && sourceAction == targetAction) + { + return; + } + + for (int dir = 0; dir < 5; ++dir) + { + AnimIdx source = GetAnimation(fileType, sourceBody, sourceAction, dir); + GetAnimation(fileType, targetBody, targetAction, dir).CopyFrom(source); + } + } + public static void Save(int fileType, string path) { string filename; @@ -392,7 +494,7 @@ public sealed class AnimIdx { public readonly int PaletteCapacity = 0x100; - private readonly int _idxExtra; + private int _idxExtra; public ushort[] Palette { get; private set; } public List Frames { get; private set; } @@ -563,6 +665,21 @@ public void ClearFrames() Frames?.Clear(); } + /// + /// Makes this a copy of : palette, frames and idx extra. Nothing is shared. + /// + public void CopyFrom(AnimIdx source) + { + if (ReferenceEquals(source, this)) + { + return; + } + + Array.Copy(source.Palette, Palette, PaletteCapacity); + Frames = source.Frames?.ConvertAll(frame => new FrameEdit(frame)); + _idxExtra = source._idxExtra; + } + public void ExportPalette(string filename, int type) { switch (type) @@ -758,6 +875,25 @@ public FrameEdit(BinaryReader bin) Center = new Point(xCenter, yCenter); } + public FrameEdit(FrameEdit source) + { + Center = source.Center; + Width = source.Width; + Height = source.Height; + + if (source.RawData == null) + { + return; + } + + RawData = new Raw[source.RawData.Length]; + for (int i = 0; i < RawData.Length; ++i) + { + RawData[i] = source.RawData[i]; + RawData[i].data = (byte[])source.RawData[i].data.Clone(); + } + } + public unsafe FrameEdit(Bitmap bit, ushort[] palette, int centerX, int centerY) { Center = new Point(centerX, centerY); diff --git a/UoFiddler.Controls/Forms/AnimationCopyActionForm.Designer.cs b/UoFiddler.Controls/Forms/AnimationCopyActionForm.Designer.cs new file mode 100644 index 00000000..f57c2c0c --- /dev/null +++ b/UoFiddler.Controls/Forms/AnimationCopyActionForm.Designer.cs @@ -0,0 +1,159 @@ +/*************************************************************************** + * + * $Author: Turley + * + * "THE BEER-WARE LICENSE" + * As long as you retain this notice you can do whatever you want with + * this stuff. If we meet some day, and you think this stuff is worth it, + * you can buy me a beer in return. + * + ***************************************************************************/ + +namespace UoFiddler.Controls.Forms +{ + partial class AnimationCopyActionForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + SourceLabel = new System.Windows.Forms.Label(); + BodyLabel = new System.Windows.Forms.Label(); + BodyNumericUpDown = new System.Windows.Forms.NumericUpDown(); + ActionLabel = new System.Windows.Forms.Label(); + ActionNumericUpDown = new System.Windows.Forms.NumericUpDown(); + CopyButton = new System.Windows.Forms.Button(); + CloseButton = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)BodyNumericUpDown).BeginInit(); + ((System.ComponentModel.ISupportInitialize)ActionNumericUpDown).BeginInit(); + SuspendLayout(); + // + // SourceLabel + // + SourceLabel.AutoSize = true; + SourceLabel.Location = new System.Drawing.Point(14, 10); + SourceLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + SourceLabel.Name = "SourceLabel"; + SourceLabel.Size = new System.Drawing.Size(73, 15); + SourceLabel.TabIndex = 0; + SourceLabel.Text = "Copy action"; + // + // BodyLabel + // + BodyLabel.AutoSize = true; + BodyLabel.Location = new System.Drawing.Point(14, 44); + BodyLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + BodyLabel.Name = "BodyLabel"; + BodyLabel.Size = new System.Drawing.Size(34, 15); + BodyLabel.TabIndex = 1; + BodyLabel.Text = "Body"; + // + // BodyNumericUpDown + // + BodyNumericUpDown.Location = new System.Drawing.Point(74, 42); + BodyNumericUpDown.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + BodyNumericUpDown.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + BodyNumericUpDown.Name = "BodyNumericUpDown"; + BodyNumericUpDown.Size = new System.Drawing.Size(200, 23); + BodyNumericUpDown.TabIndex = 2; + // + // ActionLabel + // + ActionLabel.AutoSize = true; + ActionLabel.Location = new System.Drawing.Point(14, 76); + ActionLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + ActionLabel.Name = "ActionLabel"; + ActionLabel.Size = new System.Drawing.Size(42, 15); + ActionLabel.TabIndex = 3; + ActionLabel.Text = "Action"; + // + // ActionNumericUpDown + // + ActionNumericUpDown.Location = new System.Drawing.Point(74, 74); + ActionNumericUpDown.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + ActionNumericUpDown.Maximum = new decimal(new int[] { 34, 0, 0, 0 }); + ActionNumericUpDown.Name = "ActionNumericUpDown"; + ActionNumericUpDown.Size = new System.Drawing.Size(200, 23); + ActionNumericUpDown.TabIndex = 4; + // + // CopyButton + // + CopyButton.Location = new System.Drawing.Point(74, 110); + CopyButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + CopyButton.Name = "CopyButton"; + CopyButton.Size = new System.Drawing.Size(96, 27); + CopyButton.TabIndex = 5; + CopyButton.Text = "Copy"; + CopyButton.UseVisualStyleBackColor = true; + CopyButton.Click += OnClickCopy; + // + // CloseButton + // + CloseButton.Location = new System.Drawing.Point(178, 110); + CloseButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + CloseButton.Name = "CloseButton"; + CloseButton.Size = new System.Drawing.Size(96, 27); + CloseButton.TabIndex = 6; + CloseButton.Text = "Cancel"; + CloseButton.UseVisualStyleBackColor = true; + CloseButton.Click += OnClickCancel; + // + // AnimationCopyActionForm + // + AcceptButton = CopyButton; + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + CancelButton = CloseButton; + ClientSize = new System.Drawing.Size(290, 150); + Controls.Add(CloseButton); + Controls.Add(CopyButton); + Controls.Add(ActionNumericUpDown); + Controls.Add(ActionLabel); + Controls.Add(BodyNumericUpDown); + Controls.Add(BodyLabel); + Controls.Add(SourceLabel); + DoubleBuffered = true; + FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + Name = "AnimationCopyActionForm"; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Text = "Copy Action"; + ((System.ComponentModel.ISupportInitialize)BodyNumericUpDown).EndInit(); + ((System.ComponentModel.ISupportInitialize)ActionNumericUpDown).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Label SourceLabel; + private System.Windows.Forms.Label BodyLabel; + private System.Windows.Forms.NumericUpDown BodyNumericUpDown; + private System.Windows.Forms.Label ActionLabel; + private System.Windows.Forms.NumericUpDown ActionNumericUpDown; + private System.Windows.Forms.Button CopyButton; + private System.Windows.Forms.Button CloseButton; + } +} diff --git a/UoFiddler.Controls/Forms/AnimationCopyActionForm.cs b/UoFiddler.Controls/Forms/AnimationCopyActionForm.cs new file mode 100644 index 00000000..fb6d24b6 --- /dev/null +++ b/UoFiddler.Controls/Forms/AnimationCopyActionForm.cs @@ -0,0 +1,83 @@ +/*************************************************************************** + * + * $Author: Turley + * + * "THE BEER-WARE LICENSE" + * As long as you retain this notice you can do whatever you want with + * this stuff. If we meet some day, and you think this stuff is worth it, + * you can buy me a beer in return. + * + ***************************************************************************/ + +using System; +using System.Windows.Forms; +using Ultima; +using UoFiddler.Controls.Classes; + +namespace UoFiddler.Controls.Forms +{ + /// + /// Picks the body and action to copy an action to. Closes with OK only for a valid target. + /// + public partial class AnimationCopyActionForm : Form + { + private readonly int _fileType; + private readonly int _sourceBody; + private readonly int _sourceAction; + + public AnimationCopyActionForm(int fileType, int sourceBody, int sourceAction) + { + InitializeComponent(); + Icon = Options.GetFiddlerIcon(); + + _fileType = fileType; + _sourceBody = sourceBody; + _sourceAction = sourceAction; + + SourceLabel.Text = $"Copy body {sourceBody}, action {sourceAction} (all directions) to:"; + BodyNumericUpDown.Maximum = Math.Max(0, Animations.GetAnimCount(fileType) - 1); + BodyNumericUpDown.Value = Math.Min(sourceBody, BodyNumericUpDown.Maximum); + ActionNumericUpDown.Value = sourceAction; + } + + public int TargetBody => (int)BodyNumericUpDown.Value; + + public int TargetAction => (int)ActionNumericUpDown.Value; + + private void OnClickCopy(object sender, EventArgs e) + { + int body = TargetBody; + int action = TargetAction; + + if (body == _sourceBody && action == _sourceAction) + { + MessageBox.Show("Choose a different body or action than the source.", "Copy Action", + MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + if (!AnimationEdit.IsActionInRange(_fileType, body, action)) + { + MessageBox.Show($"Body {body} has no action {action} in this anim file.", "Copy Action", + MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + if (AnimationEdit.IsActionDefined(_fileType, body, action) && + MessageBox.Show($"Action {action} of body {body} already has frames. Replace them?", "Copy Action", + MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) != DialogResult.Yes) + { + return; + } + + DialogResult = DialogResult.OK; + Close(); + } + + private void OnClickCancel(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + } +} diff --git a/UoFiddler.Controls/Forms/AnimationCopyActionForm.resx b/UoFiddler.Controls/Forms/AnimationCopyActionForm.resx new file mode 100644 index 00000000..6dae11dd --- /dev/null +++ b/UoFiddler.Controls/Forms/AnimationCopyActionForm.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs b/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs index b0a1642f..83c0ffda 100644 --- a/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs +++ b/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs @@ -42,6 +42,9 @@ private void InitializeComponent() asPngToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); importToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); fromvdToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + actionFromVdToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + actionToVdToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + copyActionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); exportToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); tovdToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); AnimationFileToolStrip = new System.Windows.Forms.ToolStrip(); @@ -243,9 +246,10 @@ private void InitializeComponent() // ContextMenuStripTreeView // ContextMenuStripTreeView.ImageScalingSize = new System.Drawing.Size(20, 20); - ContextMenuStripTreeView.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { addToolStripMenuItem, removeToolStripMenuItem, extractImagesToolStripMenuItem1, importToolStripMenuItem1, exportToolStripMenuItem1 }); + ContextMenuStripTreeView.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { addToolStripMenuItem, removeToolStripMenuItem, copyActionToolStripMenuItem, extractImagesToolStripMenuItem1, importToolStripMenuItem1, exportToolStripMenuItem1 }); ContextMenuStripTreeView.Name = "contextMenuStrip2"; - ContextMenuStripTreeView.Size = new System.Drawing.Size(158, 114); + ContextMenuStripTreeView.Size = new System.Drawing.Size(158, 136); + ContextMenuStripTreeView.Opening += OnTreeViewContextMenuOpening; // // addToolStripMenuItem // @@ -260,7 +264,15 @@ private void InitializeComponent() removeToolStripMenuItem.Size = new System.Drawing.Size(157, 22); removeToolStripMenuItem.Text = "Remove"; removeToolStripMenuItem.Click += OnClickRemoveAction; - // + // + // copyActionToolStripMenuItem + // + copyActionToolStripMenuItem.Name = "copyActionToolStripMenuItem"; + copyActionToolStripMenuItem.Size = new System.Drawing.Size(157, 22); + copyActionToolStripMenuItem.Text = "Copy Action To.."; + copyActionToolStripMenuItem.ToolTipText = "Copy the selected action, all directions, to another body or action"; + copyActionToolStripMenuItem.Click += OnClickCopyAction; + // // extractImagesToolStripMenuItem1 // extractImagesToolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { asBmpToolStripMenuItem, asTiffToolStripMenuItem, asJpgToolStripMenuItem, asPngToolStripMenuItem }); @@ -302,7 +314,7 @@ private void InitializeComponent() // // importToolStripMenuItem1 // - importToolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { fromvdToolStripMenuItem }); + importToolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { fromvdToolStripMenuItem, actionFromVdToolStripMenuItem }); importToolStripMenuItem1.Name = "importToolStripMenuItem1"; importToolStripMenuItem1.Size = new System.Drawing.Size(157, 22); importToolStripMenuItem1.Text = "Import.."; @@ -310,13 +322,21 @@ private void InitializeComponent() // fromvdToolStripMenuItem // fromvdToolStripMenuItem.Name = "fromvdToolStripMenuItem"; - fromvdToolStripMenuItem.Size = new System.Drawing.Size(121, 22); - fromvdToolStripMenuItem.Text = "From .vd"; + fromvdToolStripMenuItem.Size = new System.Drawing.Size(165, 22); + fromvdToolStripMenuItem.Text = "Body from .vd"; fromvdToolStripMenuItem.Click += OnClickImportFromVD; + // + // actionFromVdToolStripMenuItem + // + actionFromVdToolStripMenuItem.Name = "actionFromVdToolStripMenuItem"; + actionFromVdToolStripMenuItem.Size = new System.Drawing.Size(165, 22); + actionFromVdToolStripMenuItem.Text = "Action from .vd"; + actionFromVdToolStripMenuItem.ToolTipText = "Replace the selected action, all directions, with a single-action .vd"; + actionFromVdToolStripMenuItem.Click += OnClickImportActionFromVD; // // exportToolStripMenuItem1 // - exportToolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { tovdToolStripMenuItem }); + exportToolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { tovdToolStripMenuItem, actionToVdToolStripMenuItem }); exportToolStripMenuItem1.Name = "exportToolStripMenuItem1"; exportToolStripMenuItem1.Size = new System.Drawing.Size(157, 22); exportToolStripMenuItem1.Text = "Export.."; @@ -324,9 +344,17 @@ private void InitializeComponent() // tovdToolStripMenuItem // tovdToolStripMenuItem.Name = "tovdToolStripMenuItem"; - tovdToolStripMenuItem.Size = new System.Drawing.Size(105, 22); - tovdToolStripMenuItem.Text = "To .vd"; + tovdToolStripMenuItem.Size = new System.Drawing.Size(149, 22); + tovdToolStripMenuItem.Text = "Body to .vd"; tovdToolStripMenuItem.Click += OnClickExportToVD; + // + // actionToVdToolStripMenuItem + // + actionToVdToolStripMenuItem.Name = "actionToVdToolStripMenuItem"; + actionToVdToolStripMenuItem.Size = new System.Drawing.Size(149, 22); + actionToVdToolStripMenuItem.Text = "Action to .vd"; + actionToVdToolStripMenuItem.ToolTipText = "Export the selected action, all directions, as a single-action .vd"; + actionToVdToolStripMenuItem.Click += OnClickExportActionToVD; // // AnimationFileToolStrip // @@ -1681,6 +1709,9 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem fromImageToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem fromTxtToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem fromvdToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem actionFromVdToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem actionToVdToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem copyActionToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem1; private System.Windows.Forms.Label BackgroundRedLabel; diff --git a/UoFiddler.Controls/Forms/AnimationEditForm.cs b/UoFiddler.Controls/Forms/AnimationEditForm.cs index 89006bfc..44fdff17 100644 --- a/UoFiddler.Controls/Forms/AnimationEditForm.cs +++ b/UoFiddler.Controls/Forms/AnimationEditForm.cs @@ -1708,6 +1708,13 @@ private void OnClickImportFromVD(object sender, EventArgs e) return; } + if (animType == AnimationEdit.ActionVdType) + { + MessageBox.Show("This .vd holds a single action. Use Import.. > Action from .vd on an action instead.", + "Import", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + if (animType != currentType) { MessageBox.Show("Wrong Anim Id ( Type )", "Import", MessageBoxButtons.OK, @@ -1759,6 +1766,158 @@ private void OnClickExportToVD(object sender, EventArgs e) FileSavedDialog.Show(FindForm(), Options.OutputPath, "Animation saved successfully."); } + private void OnTreeViewContextMenuOpening(object sender, System.ComponentModel.CancelEventArgs e) + { + bool actionSelected = _fileType != 0 && TryGetSelectedAction(out _, out _); + copyActionToolStripMenuItem.Enabled = actionSelected; + actionFromVdToolStripMenuItem.Enabled = actionSelected; + actionToVdToolStripMenuItem.Enabled = actionSelected; + } + + private bool TryGetSelectedAction(out int body, out int action) + { + TreeNode node = AnimationListTreeView.SelectedNode; + if (node?.Parent == null) + { + body = -1; + action = -1; + return false; + } + + body = (int)node.Parent.Tag; + action = (int)node.Tag; + return true; + } + + private void OnClickExportActionToVD(object sender, EventArgs e) + { + if (_fileType == 0 || !TryGetSelectedAction(out int body, out int action)) + { + return; + } + + string fileName = Path.Combine(Options.OutputPath, $"anim{_fileType}_{Utils.FormatExportId(body)}_action{action}.vd"); + AnimationEdit.ExportActionToVD(_fileType, body, action, fileName); + + FileSavedDialog.Show(FindForm(), Options.OutputPath, "Action saved successfully."); + } + + private void OnClickImportActionFromVD(object sender, EventArgs e) + { + if (_fileType == 0 || !TryGetSelectedAction(out int body, out int action)) + { + return; + } + + using (OpenFileDialog dialog = new OpenFileDialog()) + { + dialog.Multiselect = false; + dialog.Title = $"Choose a single-action .vd to replace action {action} of body {body}"; + dialog.CheckFileExists = true; + dialog.Filter = "vd files (*.vd)|*.vd"; + if (dialog.ShowDialog() != DialogResult.OK) + { + return; + } + + using (FileStream fs = new FileStream(dialog.FileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (BinaryReader bin = new BinaryReader(fs)) + { + int fileType = bin.ReadInt16(); + int animType = bin.ReadInt16(); + if (fileType != 6) + { + MessageBox.Show("Not an Anim File.", "Import", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + if (animType != AnimationEdit.ActionVdType) + { + MessageBox.Show("This .vd holds a whole body. Use Import.. > Body from .vd on the body instead.", + "Import", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + if (AnimationEdit.IsActionDefined(_fileType, body, action) && + MessageBox.Show($"Action {action} of body {body} already has frames. Replace them?", "Import", + MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) != DialogResult.Yes) + { + return; + } + + try + { + AnimationEdit.LoadActionFromVD(_fileType, body, action, bin); + } + catch (Exception ex) when (ex is IOException || ex is ArgumentException || ex is OverflowException) + { + MessageBox.Show($"Could not read {Path.GetFileName(dialog.FileName)}: {ex.Message}", "Import", + MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + } + } + + UpdateBodyNode(body); + Options.ChangedUltimaClass["Animations"] = true; + AfterSelectTreeView(this, null); + } + + private void OnClickCopyAction(object sender, EventArgs e) + { + if (_fileType == 0 || !TryGetSelectedAction(out int body, out int action)) + { + return; + } + + using (var form = new AnimationCopyActionForm(_fileType, body, action)) + { + if (form.ShowDialog(this) != DialogResult.OK) + { + return; + } + + AnimationEdit.CopyAction(_fileType, body, action, form.TargetBody, form.TargetAction); + UpdateBodyNode(form.TargetBody); + } + + Options.ChangedUltimaClass["Animations"] = true; + AfterSelectTreeView(this, null); + } + + /// + /// Recolours a body's tree nodes after its actions changed. + /// + private void UpdateBodyNode(int body) + { + TreeNode node = _showOnlyValid + ? AnimationListTreeView.Nodes.Cast().FirstOrDefault(n => (int)n.Tag == body) + : body < AnimationListTreeView.Nodes.Count ? AnimationListTreeView.Nodes[body] : null; + if (node == null) + { + return; + } + + bool valid = false; + foreach (TreeNode child in node.Nodes) + { + if (AnimationEdit.IsActionDefined(_fileType, body, (int)child.Tag)) + { + valid = true; + if (child.ForeColor == _invalidColor) + { + child.ForeColor = Color.Empty; + } + } + else + { + child.ForeColor = _invalidColor; + } + } + + node.ForeColor = valid ? Color.Empty : _invalidColor; + } + private void OnClickShowOnlyValid(object sender, EventArgs e) { _showOnlyValid = !_showOnlyValid; From e0d0c1bdf9a810ca1d7c60907c97f91b369df8c1 Mon Sep 17 00:00:00 2001 From: AsY!um- <377468+AsYlum-@users.noreply.github.com> Date: Thu, 12 Nov 2026 00:17:56 +0100 Subject: [PATCH 2/2] Remove unimplemented Replace item from animation tree menu (#81) Replacing a body or action is done through Import.. > Body/Action from .vd. --- .../Forms/AnimationEditForm.Designer.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs b/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs index 83c0ffda..bab1b61d 100644 --- a/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs +++ b/UoFiddler.Controls/Forms/AnimationEditForm.Designer.cs @@ -33,7 +33,6 @@ private void InitializeComponent() MainSplitContainer = new System.Windows.Forms.SplitContainer(); AnimationListTreeView = new System.Windows.Forms.TreeView(); ContextMenuStripTreeView = new System.Windows.Forms.ContextMenuStrip(components); - addToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); extractImagesToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); asBmpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -246,18 +245,11 @@ private void InitializeComponent() // ContextMenuStripTreeView // ContextMenuStripTreeView.ImageScalingSize = new System.Drawing.Size(20, 20); - ContextMenuStripTreeView.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { addToolStripMenuItem, removeToolStripMenuItem, copyActionToolStripMenuItem, extractImagesToolStripMenuItem1, importToolStripMenuItem1, exportToolStripMenuItem1 }); + ContextMenuStripTreeView.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { removeToolStripMenuItem, copyActionToolStripMenuItem, extractImagesToolStripMenuItem1, importToolStripMenuItem1, exportToolStripMenuItem1 }); ContextMenuStripTreeView.Name = "contextMenuStrip2"; ContextMenuStripTreeView.Size = new System.Drawing.Size(158, 136); ContextMenuStripTreeView.Opening += OnTreeViewContextMenuOpening; // - // addToolStripMenuItem - // - addToolStripMenuItem.Name = "addToolStripMenuItem"; - addToolStripMenuItem.Size = new System.Drawing.Size(157, 22); - addToolStripMenuItem.Text = "Replace"; - addToolStripMenuItem.Visible = false; - // // removeToolStripMenuItem // removeToolStripMenuItem.Name = "removeToolStripMenuItem"; @@ -1687,7 +1679,6 @@ private void InitializeComponent() #endregion private System.Windows.Forms.ToolStripMenuItem addDirectionsAddWithCanvasUniqueImageToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem addToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem addToolStripMenuItem1; private System.Windows.Forms.ToolStripMenuItem addWithCanvasToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem allDirectionsAddToolStripMenuItem;