From 810691e2c027346aef2e633dd41de869476962f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:40:04 +0000 Subject: [PATCH 01/36] Initial plan From ca26b612205a6ca34e5bfae2a93d691301c7180b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:46:52 +0000 Subject: [PATCH 02/36] Add WayPointInstance class with auto-naming, property form, and editor integration Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Command.cs | 5 + .../ContextMenus/SectorContextMenu.cs | 5 + .../SelectedGeometryContextMenu.cs | 5 + TombEditor/EditorActions.cs | 7 + TombEditor/EditorCommands.cs | 5 + TombEditor/Forms/FormWayPoint.Designer.cs | 329 ++++++++++++++++++ TombEditor/Forms/FormWayPoint.cs | 54 +++ .../Rendering/ServiceObjectTextures.cs | 2 + .../ServiceObjectTextures/waypoint.png | Bin 0 -> 2034 bytes .../TombLib.Rendering.csproj | 1 + TombLib/TombLib.Test/WayPointInstanceTests.cs | 139 ++++++++ .../LevelData/Instances/WayPointInstance.cs | 141 ++++++++ 12 files changed, 693 insertions(+) create mode 100644 TombEditor/Forms/FormWayPoint.Designer.cs create mode 100644 TombEditor/Forms/FormWayPoint.cs create mode 100644 TombLib/TombLib.Rendering/Rendering/ServiceObjectTextures/waypoint.png create mode 100644 TombLib/TombLib.Test/WayPointInstanceTests.cs create mode 100644 TombLib/TombLib/LevelData/Instances/WayPointInstance.cs diff --git a/TombEditor/Command.cs b/TombEditor/Command.cs index 55457428f..168de67a5 100644 --- a/TombEditor/Command.cs +++ b/TombEditor/Command.cs @@ -1152,6 +1152,11 @@ static CommandHandler() args.Editor.Action = new EditorActionPlace(false, (l, r) => new FlybyCameraInstance(args.Editor.SelectedObject)); }); + AddCommand("AddWayPoint", "Add waypoint", CommandType.Objects, delegate (CommandArgs args) + { + args.Editor.Action = new EditorActionPlace(false, (l, r) => new WayPointInstance(args.Editor.SelectedObject)); + }); + AddCommand("AddSink", "Add sink", CommandType.Objects, delegate (CommandArgs args) { args.Editor.Action = new EditorActionPlace(false, (l, r) => new SinkInstance()); diff --git a/TombEditor/Controls/ContextMenus/SectorContextMenu.cs b/TombEditor/Controls/ContextMenus/SectorContextMenu.cs index 652b98726..edce1b34a 100644 --- a/TombEditor/Controls/ContextMenus/SectorContextMenu.cs +++ b/TombEditor/Controls/ContextMenus/SectorContextMenu.cs @@ -49,6 +49,11 @@ public SectorContextMenu(Editor editor, IWin32Window owner, Room targetRoom, Vec EditorActions.PlaceObject(targetRoom, targetSector, new FlybyCameraInstance(editor.SelectedObject)); })); + Items.Add(new ToolStripMenuItem("Add waypoint", Properties.Resources.objects_movie_projector_16, (o, e) => + { + EditorActions.PlaceObject(targetRoom, targetSector, new WayPointInstance(editor.SelectedObject)); + })); + Items.Add(new ToolStripMenuItem("Add sink", Properties.Resources.objects_tornado_16, (o, e) => { EditorActions.PlaceObject(targetRoom, targetSector, new SinkInstance()); diff --git a/TombEditor/Controls/ContextMenus/SelectedGeometryContextMenu.cs b/TombEditor/Controls/ContextMenus/SelectedGeometryContextMenu.cs index abd8da4a9..0184aa30b 100644 --- a/TombEditor/Controls/ContextMenus/SelectedGeometryContextMenu.cs +++ b/TombEditor/Controls/ContextMenus/SelectedGeometryContextMenu.cs @@ -56,6 +56,11 @@ public SelectedGeometryContextMenu(Editor editor, IWin32Window owner, Room targe EditorActions.PlaceObject(targetRoom, targetSector, new FlybyCameraInstance(editor.SelectedObject)); })); + Items.Add(new ToolStripMenuItem("Add waypoint", Properties.Resources.objects_movie_projector_16, (o, e) => + { + EditorActions.PlaceObject(targetRoom, targetSector, new WayPointInstance(editor.SelectedObject)); + })); + Items.Add(new ToolStripMenuItem("Add sink", Properties.Resources.objects_tornado_16, (o, e) => { EditorActions.PlaceObject(targetRoom, targetSector, new SinkInstance()); diff --git a/TombEditor/EditorActions.cs b/TombEditor/EditorActions.cs index b3ea287e3..7c54d769f 100644 --- a/TombEditor/EditorActions.cs +++ b/TombEditor/EditorActions.cs @@ -1129,6 +1129,13 @@ public static void EditObject(ObjectInstance instance, IWin32Window owner) return; _editor.ObjectChange(instance, ObjectChangeType.Change); } + else if (instance is WayPointInstance) + { + using (var formWayPoint = GetObjectSetupWindow((WayPointInstance)instance)) + if (formWayPoint.ShowDialog(owner) != DialogResult.OK) + return; + _editor.ObjectChange(instance, ObjectChangeType.Change); + } else if (instance is CameraInstance) { using (var formCamera = GetObjectSetupWindow((CameraInstance)instance)) diff --git a/TombEditor/EditorCommands.cs b/TombEditor/EditorCommands.cs index a2c624dc2..0fceca166 100644 --- a/TombEditor/EditorCommands.cs +++ b/TombEditor/EditorCommands.cs @@ -777,6 +777,11 @@ private void AddCommands() _editor.Action = new EditorActionPlace(false, (l, r) => new FlybyCameraInstance()); }); + AddCommand("AddWayPoint", "Add waypoint", CommandType.Objects, delegate () + { + _editor.Action = new EditorActionPlace(false, (l, r) => new WayPointInstance()); + }); + AddCommand("AddSink", "Add sink", CommandType.Objects, delegate () { _editor.Action = new EditorActionPlace(false, (l, r) => new SinkInstance()); diff --git a/TombEditor/Forms/FormWayPoint.Designer.cs b/TombEditor/Forms/FormWayPoint.Designer.cs new file mode 100644 index 000000000..d56b3624e --- /dev/null +++ b/TombEditor/Forms/FormWayPoint.Designer.cs @@ -0,0 +1,329 @@ +using DarkUI.Controls; + +namespace TombEditor.Forms +{ + partial class FormWayPoint + { + /// + /// 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() + { + this.butCancel = new DarkUI.Controls.DarkButton(); + this.butOK = new DarkUI.Controls.DarkButton(); + this.lblName = new DarkUI.Controls.DarkLabel(); + this.lblSequence = new DarkUI.Controls.DarkLabel(); + this.lblNumber = new DarkUI.Controls.DarkLabel(); + this.lblPathType = new DarkUI.Controls.DarkLabel(); + this.lblRotationX = new DarkUI.Controls.DarkLabel(); + this.lblRotationY = new DarkUI.Controls.DarkLabel(); + this.lblRoll = new DarkUI.Controls.DarkLabel(); + this.txtName = new DarkUI.Controls.DarkTextBox(); + this.numSequence = new DarkUI.Controls.DarkNumericUpDown(); + this.numNumber = new DarkUI.Controls.DarkNumericUpDown(); + this.cmbPathType = new DarkUI.Controls.DarkComboBox(); + this.numRotationX = new DarkUI.Controls.DarkNumericUpDown(); + this.numRotationY = new DarkUI.Controls.DarkNumericUpDown(); + this.numRoll = new DarkUI.Controls.DarkNumericUpDown(); + ((System.ComponentModel.ISupportInitialize)(this.numSequence)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numNumber)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRotationX)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRotationY)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRoll)).BeginInit(); + this.SuspendLayout(); + // + // butCancel + // + this.butCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.butCancel.Checked = false; + this.butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.butCancel.Location = new System.Drawing.Point(239, 220); + this.butCancel.Name = "butCancel"; + this.butCancel.Size = new System.Drawing.Size(80, 23); + this.butCancel.TabIndex = 8; + this.butCancel.Text = "Cancel"; + this.butCancel.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.butCancel.Click += new System.EventHandler(this.butCancel_Click); + // + // butOK + // + this.butOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.butOK.Checked = false; + this.butOK.Location = new System.Drawing.Point(153, 220); + this.butOK.Name = "butOK"; + this.butOK.Size = new System.Drawing.Size(80, 23); + this.butOK.TabIndex = 7; + this.butOK.Text = "OK"; + this.butOK.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + this.butOK.Click += new System.EventHandler(this.butOK_Click); + // + // lblName + // + this.lblName.AutoSize = true; + this.lblName.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblName.Location = new System.Drawing.Point(12, 15); + this.lblName.Name = "lblName"; + this.lblName.Size = new System.Drawing.Size(39, 13); + this.lblName.TabIndex = 0; + this.lblName.Text = "Name:"; + // + // lblSequence + // + this.lblSequence.AutoSize = true; + this.lblSequence.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblSequence.Location = new System.Drawing.Point(12, 41); + this.lblSequence.Name = "lblSequence"; + this.lblSequence.Size = new System.Drawing.Size(60, 13); + this.lblSequence.TabIndex = 2; + this.lblSequence.Text = "Sequence:"; + // + // lblNumber + // + this.lblNumber.AutoSize = true; + this.lblNumber.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblNumber.Location = new System.Drawing.Point(12, 67); + this.lblNumber.Name = "lblNumber"; + this.lblNumber.Size = new System.Drawing.Size(51, 13); + this.lblNumber.TabIndex = 4; + this.lblNumber.Text = "Number:"; + // + // lblPathType + // + this.lblPathType.AutoSize = true; + this.lblPathType.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblPathType.Location = new System.Drawing.Point(12, 93); + this.lblPathType.Name = "lblPathType"; + this.lblPathType.Size = new System.Drawing.Size(59, 13); + this.lblPathType.TabIndex = 6; + this.lblPathType.Text = "Path Type:"; + // + // lblRotationX + // + this.lblRotationX.AutoSize = true; + this.lblRotationX.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblRotationX.Location = new System.Drawing.Point(12, 119); + this.lblRotationX.Name = "lblRotationX"; + this.lblRotationX.Size = new System.Drawing.Size(64, 13); + this.lblRotationX.TabIndex = 8; + this.lblRotationX.Text = "Rotation X:"; + // + // lblRotationY + // + this.lblRotationY.AutoSize = true; + this.lblRotationY.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblRotationY.Location = new System.Drawing.Point(12, 145); + this.lblRotationY.Name = "lblRotationY"; + this.lblRotationY.Size = new System.Drawing.Size(63, 13); + this.lblRotationY.TabIndex = 10; + this.lblRotationY.Text = "Rotation Y:"; + // + // lblRoll + // + this.lblRoll.AutoSize = true; + this.lblRoll.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblRoll.Location = new System.Drawing.Point(12, 171); + this.lblRoll.Name = "lblRoll"; + this.lblRoll.Size = new System.Drawing.Size(30, 13); + this.lblRoll.TabIndex = 12; + this.lblRoll.Text = "Roll:"; + // + // txtName + // + this.txtName.Location = new System.Drawing.Point(82, 12); + this.txtName.Name = "txtName"; + this.txtName.Size = new System.Drawing.Size(237, 20); + this.txtName.TabIndex = 1; + // + // numSequence + // + this.numSequence.IncrementAlternate = new decimal(new int[] { + 10, + 0, + 0, + 65536}); + this.numSequence.Location = new System.Drawing.Point(82, 38); + this.numSequence.LoopValues = false; + this.numSequence.Maximum = new decimal(new int[] { + 65535, + 0, + 0, + 0}); + this.numSequence.Name = "numSequence"; + this.numSequence.Size = new System.Drawing.Size(237, 22); + this.numSequence.TabIndex = 3; + // + // numNumber + // + this.numNumber.IncrementAlternate = new decimal(new int[] { + 10, + 0, + 0, + 65536}); + this.numNumber.Location = new System.Drawing.Point(82, 64); + this.numNumber.LoopValues = false; + this.numNumber.Maximum = new decimal(new int[] { + 65535, + 0, + 0, + 0}); + this.numNumber.Name = "numNumber"; + this.numNumber.Size = new System.Drawing.Size(237, 22); + this.numNumber.TabIndex = 5; + // + // cmbPathType + // + this.cmbPathType.FormattingEnabled = true; + this.cmbPathType.Items.AddRange(new object[] { + "Linear", + "Curved", + "Bezier"}); + this.cmbPathType.Location = new System.Drawing.Point(82, 90); + this.cmbPathType.Name = "cmbPathType"; + this.cmbPathType.Size = new System.Drawing.Size(237, 23); + this.cmbPathType.TabIndex = 7; + // + // numRotationX + // + this.numRotationX.DecimalPlaces = 2; + this.numRotationX.IncrementAlternate = new decimal(new int[] { + 10, + 0, + 0, + 65536}); + this.numRotationX.Location = new System.Drawing.Point(82, 116); + this.numRotationX.LoopValues = false; + this.numRotationX.Maximum = new decimal(new int[] { + 90, + 0, + 0, + 0}); + this.numRotationX.Minimum = new decimal(new int[] { + 90, + 0, + 0, + -2147483648}); + this.numRotationX.Name = "numRotationX"; + this.numRotationX.Size = new System.Drawing.Size(237, 22); + this.numRotationX.TabIndex = 9; + // + // numRotationY + // + this.numRotationY.DecimalPlaces = 2; + this.numRotationY.IncrementAlternate = new decimal(new int[] { + 10, + 0, + 0, + 65536}); + this.numRotationY.Location = new System.Drawing.Point(82, 142); + this.numRotationY.LoopValues = false; + this.numRotationY.Maximum = new decimal(new int[] { + 360, + 0, + 0, + 0}); + this.numRotationY.Name = "numRotationY"; + this.numRotationY.Size = new System.Drawing.Size(237, 22); + this.numRotationY.TabIndex = 11; + // + // numRoll + // + this.numRoll.DecimalPlaces = 2; + this.numRoll.IncrementAlternate = new decimal(new int[] { + 10, + 0, + 0, + 65536}); + this.numRoll.Location = new System.Drawing.Point(82, 168); + this.numRoll.LoopValues = false; + this.numRoll.Maximum = new decimal(new int[] { + 360, + 0, + 0, + 0}); + this.numRoll.Name = "numRoll"; + this.numRoll.Size = new System.Drawing.Size(237, 22); + this.numRoll.TabIndex = 13; + // + // FormWayPoint + // + this.AcceptButton = this.butOK; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.butCancel; + this.ClientSize = new System.Drawing.Size(331, 255); + this.Controls.Add(this.numRoll); + this.Controls.Add(this.numRotationY); + this.Controls.Add(this.numRotationX); + this.Controls.Add(this.cmbPathType); + this.Controls.Add(this.numNumber); + this.Controls.Add(this.numSequence); + this.Controls.Add(this.txtName); + this.Controls.Add(this.lblRoll); + this.Controls.Add(this.lblRotationY); + this.Controls.Add(this.lblRotationX); + this.Controls.Add(this.lblPathType); + this.Controls.Add(this.lblNumber); + this.Controls.Add(this.lblSequence); + this.Controls.Add(this.lblName); + this.Controls.Add(this.butCancel); + this.Controls.Add(this.butOK); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FormWayPoint"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "WayPoint"; + this.Load += new System.EventHandler(this.FormWayPoint_Load); + ((System.ComponentModel.ISupportInitialize)(this.numSequence)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numNumber)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRotationX)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRotationY)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRoll)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private DarkButton butOK; + private DarkButton butCancel; + private DarkLabel lblName; + private DarkLabel lblSequence; + private DarkLabel lblNumber; + private DarkLabel lblPathType; + private DarkLabel lblRotationX; + private DarkLabel lblRotationY; + private DarkLabel lblRoll; + private DarkTextBox txtName; + private DarkNumericUpDown numSequence; + private DarkNumericUpDown numNumber; + private DarkComboBox cmbPathType; + private DarkNumericUpDown numRotationX; + private DarkNumericUpDown numRotationY; + private DarkNumericUpDown numRoll; + } +} diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs new file mode 100644 index 000000000..a2bc65dde --- /dev/null +++ b/TombEditor/Forms/FormWayPoint.cs @@ -0,0 +1,54 @@ +using System; +using System.Windows.Forms; +using DarkUI.Forms; +using TombLib.LevelData; + +namespace TombEditor.Forms +{ + public partial class FormWayPoint : DarkForm + { + public bool IsNew { get; set; } + + private readonly WayPointInstance _wayPoint; + private readonly Editor _editor; + + public FormWayPoint(WayPointInstance wayPoint) + { + _wayPoint = wayPoint; + _editor = Editor.Instance; + + InitializeComponent(); + } + + private void butCancel_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + + private void FormWayPoint_Load(object sender, EventArgs e) + { + txtName.Text = _wayPoint.Name; + numSequence.Value = _wayPoint.Sequence; + numNumber.Value = _wayPoint.Number; + cmbPathType.SelectedIndex = (int)_wayPoint.PathType; + numRotationX.Value = (decimal)_wayPoint.RotationX; + numRotationY.Value = (decimal)_wayPoint.RotationY; + numRoll.Value = (decimal)_wayPoint.Roll; + } + + private void butOK_Click(object sender, EventArgs e) + { + _wayPoint.Name = txtName.Text; + _wayPoint.Sequence = (ushort)numSequence.Value; + _wayPoint.Number = (ushort)numNumber.Value; + _wayPoint.PathType = (PathType)cmbPathType.SelectedIndex; + _wayPoint.RotationX = (float)numRotationX.Value; + _wayPoint.RotationY = (float)numRotationY.Value; + _wayPoint.Roll = (float)numRoll.Value; + + DialogResult = DialogResult.OK; + Close(); + } + } +} diff --git a/TombLib/TombLib.Rendering/Rendering/ServiceObjectTextures.cs b/TombLib/TombLib.Rendering/Rendering/ServiceObjectTextures.cs index a439c0665..c8829746e 100644 --- a/TombLib/TombLib.Rendering/Rendering/ServiceObjectTextures.cs +++ b/TombLib/TombLib.Rendering/Rendering/ServiceObjectTextures.cs @@ -14,6 +14,7 @@ public enum ServiceObjectTexture { camera, flyby_camera, + waypoint, imp_geo, sink, sound_source, @@ -134,6 +135,7 @@ public static ServiceObjectTexture GetType(ISpatial instance) else if (instance is VolumeInstance) type = ServiceObjectTexture.volume; else if (instance is GhostBlockInstance) type = ServiceObjectTexture.ghost_block; else if (instance is FlybyCameraInstance) type = ServiceObjectTexture.flyby_camera; + else if (instance is WayPointInstance) type = ServiceObjectTexture.waypoint; else if (instance is SoundSourceInstance) type = ServiceObjectTexture.sound_source; else if (instance is ImportedGeometryInstance) type = ServiceObjectTexture.imp_geo; else type = ServiceObjectTexture.unknown; diff --git a/TombLib/TombLib.Rendering/Rendering/ServiceObjectTextures/waypoint.png b/TombLib/TombLib.Rendering/Rendering/ServiceObjectTextures/waypoint.png new file mode 100644 index 0000000000000000000000000000000000000000..84e9fb1e0a0e2d11f061b5d0550f12ca3dfcf964 GIT binary patch literal 2034 zcmV_bktLya)kRPjUV9-y-js+Jp$4TiYUj~RqL{KsqmT}xW|}jOGt8*7pZDVX+OOMF zBX3)sas6Sz-fOSZ{<+q(*Lv0(B}4=!aplJlFvtut6NAhkuO-PAdLWTV*tBDbA|eUv z$;@0Z$V_6`Y#YM;I6wz|_#=w5 zUOw&1moJf>osAzqe(-ToQBgR0^eAFtVxUr~;BYwb_U&6VH#g(UmoI#}(9lq{w6q{0 zAwg6tFxv~a(9X!R_2lmEZaQ(|1Z~{7ao*t`5fMS>&z~oY#X@$woe~lf1kcZ$IYTCs zY2LC;CKDAG7YowI#Kh3Z$jI6;7x(Vnqs^N)FKSbNhr@>tQ&CZoAg-dK!lQc1%E|=! zu3fuE9LM=SU2y*=+W0$iu_Kl$e;vm*MB$n)Z5$3r*S%+)Y;id`T6;C8m&|+sj;zfNp&_h zHj+}QBmg9pN~x)-$+scjx^+tc`0CZG3#PSLEL2!nD5x_wHkO8lhL%+4;NT#|#>Vnx zR904sW->g5Eb5By{{8#>wN9r)d3pJQY1L{qDk>@v6&1zDg@uJ7BqU@>b%ut9A}lP7 zzqi?JaJ${UyR3fy{yl%K*XyBDsTNJYYu7F$Cnxjw9LFtr>NsO0^3yC&H$1%(^4Qqe z|JLp@83F?X|GdVR$q*hM&fgCX4#I3UFR4zm*^GgK0sda2(LgGd`Yz<(egux=V7J>n z%44xu1S8?$!-q?%^TC4${0LGg6oLhgPx>y7967@0&&kR8;|J*b_wN)P9WAIgF)@*B zHk(KF*=#o2w{IVx_u#>UG&(x!+p~-7*RS*W6$%AaS645ZzO=M--nnu?LBStaZzCfk zR8UYL$amw$4c}k!O-xKsT3Q-khFYzr>gsCx`SYhB-KS5V=B(CchxGJxG8harF)=Zx z4uio!dc9td{?esOtHwbjsz2FkwF;(SR;v{yB_(KRXh3ReDgpumFf=rTwzf9(^zO6h7GXUY%rNjXl-r9e{bHhhTnC{<#MX4tMh0vVzF4Lrly86Gc#$=o;}`UF4SuEvICwSE?l@k zQ&Uq5w(;G&cT`?pPVw>aynkAyQhATLShsE+tX3-|5(y$BBe7-67QwLo+mcG9;5ZI_ zeSNT6t^71@_JYQ2Hsi^YC#bEh#fJ|c@a@|-!E=p9<2`$Petv%Zi$; zYNoHEzrP1qDD zsi_I|_4R_d-zH(_&Yd`U@+3~5K8=Qk20VZMeC-|y_%E86m=O3zA|oS_nwpA&f&wHb zCnF#r007`{I7BmLJ`WiHf`fyxd-rbS=H?P2RnVFd@3V2OGw%hG2yd;OoUAK@9T@{ zf?X57i{I+*?nZZaHv$6#k(HH&^z?LOWo02aI2a)zA-wadAja zPKHvcIusQZAulfvw{PFZ=g*%-gS;#~SuU3g4grd-;NW2X`q;5!C@CpH zPfrhOYHINK@nZ}R5A)BBMk9vya2yAh%O&1a$`sHhb`5^(wQ zWtyIzUa*ZWmy2G#dPP-LRg{#JL;y&m(Rk01fQuI|qN=J24u@meXA&-#3vqFA$j{GT z^aPPYp@3Gag;uM@@#DwQ)zyU;FJ5?$x$t#)YA%?Ai1{uUWF`igL1vJd7-R;Si9u$N z8Du5~nL%cdnZ%aI{^XVZpAoDlGcm{v@*0qYZ7Z>!%pfx{$P6-r%=B0F55A`$8008h Q1ONa407*qoM6N<$f}*wGVE_OC literal 0 HcmV?d00001 diff --git a/TombLib/TombLib.Rendering/TombLib.Rendering.csproj b/TombLib/TombLib.Rendering/TombLib.Rendering.csproj index eaaed83bd..d3631a1a8 100644 --- a/TombLib/TombLib.Rendering/TombLib.Rendering.csproj +++ b/TombLib/TombLib.Rendering/TombLib.Rendering.csproj @@ -151,6 +151,7 @@ Always + diff --git a/TombLib/TombLib.Test/WayPointInstanceTests.cs b/TombLib/TombLib.Test/WayPointInstanceTests.cs new file mode 100644 index 000000000..895945d86 --- /dev/null +++ b/TombLib/TombLib.Test/WayPointInstanceTests.cs @@ -0,0 +1,139 @@ +using TombLib.LevelData; + +namespace TombLib.Test +{ + [TestClass] + public class WayPointInstanceTests + { + [TestMethod] + public void WayPoint_AutoNaming_DefaultName() + { + // Arrange & Act + var wayPoint = new WayPointInstance(); + + // Assert + Assert.AreEqual("WayPoint_0", wayPoint.Name, "Default WayPoint name should be 'WayPoint_0'"); + } + + [TestMethod] + public void WayPoint_AutoNaming_CustomBaseName() + { + // Arrange + var wayPoint = new WayPointInstance(); + + // Act + wayPoint.Name = "Camera"; + wayPoint.Number = 0; + + // Assert + Assert.AreEqual("Camera_0", wayPoint.Name, "Custom name should be 'Camera_0'"); + } + + [TestMethod] + public void WayPoint_AutoNaming_SequenceChange() + { + // Arrange + var wayPoint = new WayPointInstance(); + wayPoint.Name = "MyPath"; + + // Act + wayPoint.Number = 5; + + // Assert + Assert.AreEqual("MyPath_5", wayPoint.Name, "Name should update to 'MyPath_5' when number changes"); + } + + [TestMethod] + public void WayPoint_AutoNaming_NumberChange() + { + // Arrange + var wayPoint = new WayPointInstance(); + wayPoint.Name = "Camera"; + wayPoint.Number = 3; + + // Act + wayPoint.Number = 7; + + // Assert + Assert.AreEqual("Camera_7", wayPoint.Name, "Name should update to 'Camera_7' when number changes to 7"); + } + + [TestMethod] + public void WayPoint_AutoNaming_PreservesBaseName() + { + // Arrange + var wayPoint = new WayPointInstance(); + wayPoint.Name = "PatrolPath"; + wayPoint.Number = 0; + + // Act + wayPoint.Number = 10; + + // Assert + Assert.AreEqual("PatrolPath_10", wayPoint.Name, "Base name 'PatrolPath' should be preserved"); + } + + [TestMethod] + public void WayPoint_DefaultPathType() + { + // Arrange & Act + var wayPoint = new WayPointInstance(); + + // Assert + Assert.AreEqual(PathType.Linear, wayPoint.PathType, "Default PathType should be Linear"); + } + + [TestMethod] + public void WayPoint_PathTypeCanBeSet() + { + // Arrange + var wayPoint = new WayPointInstance(); + + // Act + wayPoint.PathType = PathType.Bezier; + + // Assert + Assert.AreEqual(PathType.Bezier, wayPoint.PathType, "PathType should be settable to Bezier"); + } + + [TestMethod] + public void WayPoint_RotationXClamping() + { + // Arrange + var wayPoint = new WayPointInstance(); + + // Act & Assert + wayPoint.RotationX = 100; + Assert.AreEqual(90, wayPoint.RotationX, "RotationX should be clamped to 90"); + + wayPoint.RotationX = -100; + Assert.AreEqual(-90, wayPoint.RotationX, "RotationX should be clamped to -90"); + } + + [TestMethod] + public void WayPoint_RotationYWrapping() + { + // Arrange + var wayPoint = new WayPointInstance(); + + // Act + wayPoint.RotationY = 400; + + // Assert + Assert.AreEqual(40, wayPoint.RotationY, 0.01, "RotationY should wrap around 360 degrees"); + } + + [TestMethod] + public void WayPoint_RollWrapping() + { + // Arrange + var wayPoint = new WayPointInstance(); + + // Act + wayPoint.Roll = 720; + + // Assert + Assert.AreEqual(0, wayPoint.Roll, 0.01, "Roll should wrap around 360 degrees"); + } + } +} diff --git a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs new file mode 100644 index 000000000..fca8984b4 --- /dev/null +++ b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs @@ -0,0 +1,141 @@ +using System; +using System.Linq; + +namespace TombLib.LevelData +{ + public enum PathType + { + Linear, + Curved, + Bezier + } + + public class WayPointInstance : PositionAndScriptBasedObjectInstance, IRotateableYXRoll + { + private string _baseName = "WayPoint"; + private ushort _sequence; + private ushort _number; + + public string Name + { + get { return _baseName + "_" + _number; } + set + { + // When setting name, extract the base name (strip trailing _number if present) + if (!string.IsNullOrEmpty(value)) + { + int lastUnderscore = value.LastIndexOf('_'); + if (lastUnderscore >= 0) + { + string suffix = value.Substring(lastUnderscore + 1); + if (ushort.TryParse(suffix, out _)) + { + _baseName = value.Substring(0, lastUnderscore); + } + else + { + _baseName = value; + } + } + else + { + _baseName = value; + } + } + else + { + _baseName = "WayPoint"; + } + } + } + + public ushort Sequence + { + get { return _sequence; } + set { _sequence = value; } + } + + public ushort Number + { + get { return _number; } + set { _number = value; } + } + + public PathType PathType { get; set; } = PathType.Linear; + + private float _rotationX { get; set; } + private float _rotationY { get; set; } + private float _roll { get; set; } + + public WayPointInstance(ObjectInstance selectedObject = null) + { + if (selectedObject != null && selectedObject is WayPointInstance) + { + var prevWayPoint = (WayPointInstance)selectedObject; + var currSeq = prevWayPoint.Sequence; + var currNum = (ushort)(prevWayPoint.Number + 1); + + // Push next waypoints in sequence forward + var level = selectedObject.Room.Level; + foreach (var room in level.ExistingRooms) + foreach (var instance in room.Objects.OfType()) + if (instance.Sequence == currSeq && instance.Number >= currNum) + instance.Number++; + + Sequence = currSeq; + Number = currNum; + _baseName = prevWayPoint._baseName; + PathType = prevWayPoint.PathType; + + // Additionally copy last waypoint parameters + RotationX = prevWayPoint.RotationX; + RotationY = prevWayPoint.RotationY; + Roll = prevWayPoint.Roll; + } + } + + /// Degrees in the range [-90, 90] + public float RotationX + { + get { return _rotationX; } + set { _rotationX = Math.Max(-90, Math.Min(90, value)); } + } + + /// Degrees in the range [0, 360) + public float RotationY + { + get { return _rotationY; } + set { _rotationY = (float)(value - Math.Floor(value / 360.0) * 360.0); } + } + + /// Degrees in the range [0, 360) + public float Roll + { + get { return _roll; } + set { _roll = (float)(value - Math.Floor(value / 360.0) * 360.0); } + } + + public override bool CopyToAlternateRooms => false; + + public override string ToString() + { + return "WayPoint " + + ", Name = " + Name + + ", Sequence = " + Sequence + + ", Number = " + Number + + ", PathType = " + PathType + + " (" + (Room?.ToString() ?? "NULL") + ")" + + ", X = " + SectorPosition.X + + ", Z = " + SectorPosition.Y + + GetScriptIDOrName(false); + } + + public string ShortName() => "WayPoint (" + Sequence + ":" + Number + ")" + GetScriptIDOrName() + " (" + (Room?.ToString() ?? "NULL") + ")"; + + public override void CopyDependentLevelSettings(Room.CopyDependentLevelSettingsArgs args) + { + base.CopyDependentLevelSettings(args); + Sequence = args.ReassociateFlyBySequence(Sequence); + } + } +} From 2bdb2c921062b9ce58fdd1a16fb703646aa640a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:48:28 +0000 Subject: [PATCH 03/36] Add WayPoint serialization support for save/load functionality Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombLib/TombLib/LevelData/IO/Prj2Chunks.cs | 1 + TombLib/TombLib/LevelData/IO/Prj2Loader.cs | 14 ++++++++++++++ TombLib/TombLib/LevelData/IO/Prj2Writer.cs | 15 +++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/TombLib/TombLib/LevelData/IO/Prj2Chunks.cs b/TombLib/TombLib/LevelData/IO/Prj2Chunks.cs index ba995a2cc..af88f211a 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Chunks.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Chunks.cs @@ -190,6 +190,7 @@ internal static class Prj2Chunks /**********/public static readonly ChunkId ObjectFlyBy = ChunkId.FromString("TeFly"); /**********/public static readonly ChunkId ObjectFlyBy2 = ChunkId.FromString("TeFly2"); /**********/public static readonly ChunkId ObjectFlyBy2LuaScript = ChunkId.FromString("TeFly2Lua"); + /**********/public static readonly ChunkId ObjectWayPoint = ChunkId.FromString("TeWayPt"); /**********/public static readonly ChunkId ObjectMemo = ChunkId.FromString("TeMemo"); /**********/public static readonly ChunkId ObjectMemo2 = ChunkId.FromString("TeMemo2"); /**********/public static readonly ChunkId ObjectSink = ChunkId.FromString("TeSin"); diff --git a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs index 0613f0545..f6096319a 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs @@ -1402,6 +1402,20 @@ private static bool LoadObjects(ChunkReader chunkIO, ChunkId idOuter, LevelSetti addObject(instance); newObjects.TryAdd(objectID, instance); } + else if (id3 == Prj2Chunks.ObjectWayPoint) + { + var instance = new WayPointInstance(); + instance.Position = chunkIO.Raw.ReadVector3(); + instance.SetArbitaryRotationsYX(chunkIO.Raw.ReadSingle(), chunkIO.Raw.ReadSingle()); + instance.Roll = chunkIO.Raw.ReadSingle(); + instance.ScriptId = ReadOptionalLEB128Int(chunkIO.Raw); + instance.Name = chunkIO.Raw.ReadStringUTF8(); + instance.Number = LEB128.ReadUShort(chunkIO.Raw); + instance.Sequence = LEB128.ReadUShort(chunkIO.Raw); + instance.PathType = (PathType)LEB128.ReadInt(chunkIO.Raw); + addObject(instance); + newObjects.TryAdd(objectID, instance); + } else if (id3 == Prj2Chunks.ObjectFlyBy2) // Obsolete; LuaScript is unused with new script concept. { var instance = new FlybyCameraInstance(); diff --git a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs index 01ab4e49c..9bc23f11c 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs @@ -679,6 +679,21 @@ private static void WriteObjects(ChunkWriter chunkIO, IEnumerable + { + var instance = (WayPointInstance)o; + LEB128.Write(chunkIO.Raw, objectInstanceLookup.TryGetOrDefault(instance, -1)); + chunkIO.Raw.Write(instance.Position); + chunkIO.Raw.Write(instance.RotationY); + chunkIO.Raw.Write(instance.RotationX); + chunkIO.Raw.Write(instance.Roll); + LEB128.Write(chunkIO.Raw, ((long?)instance.ScriptId ?? -1)); + chunkIO.Raw.WriteStringUTF8(instance.Name); + LEB128.Write(chunkIO.Raw, instance.Number); + LEB128.Write(chunkIO.Raw, instance.Sequence); + LEB128.Write(chunkIO.Raw, (int)instance.PathType); + }); else if (o is MemoInstance) using (var chunk = chunkIO.WriteChunk(Prj2Chunks.ObjectMemo2, LEB128.MaximumSize3Byte)) { From 4fe8bc69d9e5f0dc208475a7baecf9617b96ff54 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:50:38 +0000 Subject: [PATCH 04/36] Add WayPoint rendering support with path visualization Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3D.cs | 2 + TombEditor/Controls/Panel3D/Panel3DDraw.cs | 45 ++++++++++ TombEditor/Controls/Panel3D/Panel3DHelpers.cs | 84 +++++++++++++++++++ 3 files changed, 131 insertions(+) diff --git a/TombEditor/Controls/Panel3D/Panel3D.cs b/TombEditor/Controls/Panel3D/Panel3D.cs index 61eca6e9f..11d0f60dd 100644 --- a/TombEditor/Controls/Panel3D/Panel3D.cs +++ b/TombEditor/Controls/Panel3D/Panel3D.cs @@ -134,6 +134,7 @@ public bool DisablePickingForHiddenRooms private bool _drawHeightLine; private Buffer _objectHeightLineVertexBuffer; private Buffer _flybyPathVertexBuffer; + private Buffer _wayPointPathVertexBuffer; private Buffer _ghostBlockVertexBuffer; private Buffer _boxVertexBuffer; @@ -217,6 +218,7 @@ protected override void Dispose(bool disposing) _rasterizerWireframe?.Dispose(); _objectHeightLineVertexBuffer?.Dispose(); _flybyPathVertexBuffer?.Dispose(); + _wayPointPathVertexBuffer?.Dispose(); _gizmo?.Dispose(); _sphere?.Dispose(); _cone?.Dispose(); diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index a29d32b1d..7c86503ce 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -196,6 +196,19 @@ private void DrawFlybyPath(Effect effect) effect.CurrentTechnique.Passes[0].Apply(); _legacyDevice.Draw(PrimitiveType.TriangleList, _flybyPathVertexBuffer.ElementCount); } + + // Add the path of waypoints + if (_editor.SelectedObject is WayPointInstance && + AddWayPointPath(((WayPointInstance)_editor.SelectedObject).Sequence)) + { + _legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullNone); + _legacyDevice.SetVertexBuffer(_wayPointPathVertexBuffer); + _legacyDevice.SetVertexInputLayout(VertexInputLayout.FromBuffer(0, _wayPointPathVertexBuffer)); + effect.Parameters["ModelViewProjection"].SetValue(_viewProjection.ToSharpDX()); + effect.Parameters["Color"].SetValue(new Vector4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange for waypoints + effect.CurrentTechnique.Passes[0].Apply(); + _legacyDevice.Draw(PrimitiveType.TriangleList, _wayPointPathVertexBuffer.ElementCount); + } } private void DrawSectorSplitHighlights(Effect effect) @@ -1138,6 +1151,38 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis DrawOrQueueServiceObject(instance, _littleCube, color, effect, sprites); } + if (group.Key == typeof(WayPointInstance)) + foreach (WayPointInstance instance in group) + { + _legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullBack); + + var color = new Vector4(1.0f, 0.5f, 0.0f, 1.0f); // Orange color for waypoints + + if (_editor.SelectedObject is WayPointInstance && (_editor.SelectedObject as WayPointInstance).Sequence == instance.Sequence) + color = MathC.GetRandomColorByIndex(instance.Sequence, 32, 0.7f); + + if (_highlightedObjects.Contains(instance)) + { + color = _editor.Configuration.UI_ColorScheme.ColorSelection; + _legacyDevice.SetRasterizerState(_rasterizerWireframe); + + if (_editor.SelectedObject == instance) + { + // Add text message + textToDraw.Add(CreateTextTagForObject( + instance.RotationPositionMatrix * _viewProjection, + "WayPoint (" + instance.Sequence + ":" + instance.Number + ") " + + instance.GetScriptIDOrName() + "\n" + + GetObjectPositionString(instance.Room, instance) + GetObjectTriggerString(instance))); + + // Add the line height of the object + AddObjectHeightLine(instance.Room, instance.Position); + } + } + + DrawOrQueueServiceObject(instance, _littleCube, color, effect, sprites); + } + if (group.Key == typeof(MemoInstance)) foreach (MemoInstance instance in group) { diff --git a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs index 4f61f9d47..91610f911 100644 --- a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs +++ b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs @@ -183,6 +183,90 @@ private bool AddFlybyPath(int sequence) return true; } + private bool AddWayPointPath(int sequence) + { + // Collect all waypoints + var wayPoints = new List(); + + foreach (var room in _editor.Level.ExistingRooms) + foreach (var instance in room.Objects.OfType()) + { + if (instance.Sequence == sequence) + wayPoints.Add(instance); + } + + // Is it actually necessary to show the path? + if (wayPoints.Count < 2) + return false; + + // Sort waypoints + wayPoints.Sort((x, y) => x.Number.CompareTo(y.Number)); + + // Initialize variables for vertex buffer preparation + var vertices = new List(); + var startColor = MathC.GetRandomColorByIndex(sequence, 32, 0.7f); + var endColor = MathC.GetRandomColorByIndex(sequence, 32, 0.3f); + + float th = _flybyPathThickness; + + // Process waypoints to calculate paths + var pointList = new List(); + for (int i = 0; i < wayPoints.Count; i++) + { + var wp = wayPoints[i]; + pointList.Add(wp.Position + wp.Room.WorldPos); + } + + // Calculate the spline path based on PathType + List interpolatedPoints; + if (wayPoints[0].PathType == PathType.Linear) + { + // For linear, just use the points as-is + interpolatedPoints = pointList; + } + else + { + // For Curved and Bezier, use spline interpolation + interpolatedPoints = Spline.Calculate(pointList, pointList.Count * _flybyPathSmoothness); + } + + // Add vertices for the path + for (int j = 0; j < interpolatedPoints.Count - 1; j++) + { + var color = Vector4.Lerp(startColor, endColor, j / (float)interpolatedPoints.Count); + var points = new List() + { + new Vector3[] + { + interpolatedPoints[j], + new Vector3(interpolatedPoints[j].X + th, interpolatedPoints[j].Y + th, interpolatedPoints[j].Z + th), + new Vector3(interpolatedPoints[j].X - th, interpolatedPoints[j].Y + th, interpolatedPoints[j].Z + th) + }, + new Vector3[] + { + interpolatedPoints[j + 1], + new Vector3(interpolatedPoints[j + 1].X + th, interpolatedPoints[j + 1].Y + th, interpolatedPoints[j + 1].Z + th), + new Vector3(interpolatedPoints[j + 1].X - th, interpolatedPoints[j + 1].Y + th, interpolatedPoints[j + 1].Z + th) + } + }; + + for (int k = 0; k < _flybyPathIndices.Count; k++) + { + var v = new SolidVertex(); + v.Position = points[_flybyPathIndices[k].Y][_flybyPathIndices[k].X]; + v.Color = color; + vertices.Add(v); + } + } + + // Prepare the Vertex Buffer + if (_wayPointPathVertexBuffer != null) + _wayPointPathVertexBuffer.Dispose(); + _wayPointPathVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, vertices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Dynamic); + + return true; + } + private class Comparer : IComparer, IComparer, IComparer { public int Compare(StaticInstance x, StaticInstance y) From 1391f22eb398f678a8717e217d8fe5a72f7fa1ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:52:01 +0000 Subject: [PATCH 05/36] Address code review feedback: improve pattern matching, disposal, and PathType consistency Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DHelpers.cs | 12 +++++++++--- .../TombLib/LevelData/Instances/WayPointInstance.cs | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs index 91610f911..adba12355 100644 --- a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs +++ b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs @@ -209,6 +209,12 @@ private bool AddWayPointPath(int sequence) float th = _flybyPathThickness; + // Determine the PathType to use for the sequence (use the most common one) + var pathTypeCounts = wayPoints.GroupBy(wp => wp.PathType) + .OrderByDescending(g => g.Count()) + .ToList(); + var pathType = pathTypeCounts.FirstOrDefault()?.Key ?? PathType.Linear; + // Process waypoints to calculate paths var pointList = new List(); for (int i = 0; i < wayPoints.Count; i++) @@ -219,7 +225,7 @@ private bool AddWayPointPath(int sequence) // Calculate the spline path based on PathType List interpolatedPoints; - if (wayPoints[0].PathType == PathType.Linear) + if (pathType == PathType.Linear) { // For linear, just use the points as-is interpolatedPoints = pointList; @@ -260,8 +266,8 @@ private bool AddWayPointPath(int sequence) } // Prepare the Vertex Buffer - if (_wayPointPathVertexBuffer != null) - _wayPointPathVertexBuffer.Dispose(); + _wayPointPathVertexBuffer?.Dispose(); + _wayPointPathVertexBuffer = null; _wayPointPathVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, vertices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Dynamic); return true; diff --git a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs index fca8984b4..a3c783dea 100644 --- a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs +++ b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs @@ -69,9 +69,8 @@ public ushort Number public WayPointInstance(ObjectInstance selectedObject = null) { - if (selectedObject != null && selectedObject is WayPointInstance) + if (selectedObject is WayPointInstance prevWayPoint) { - var prevWayPoint = (WayPointInstance)selectedObject; var currSeq = prevWayPoint.Sequence; var currNum = (ushort)(prevWayPoint.Number + 1); From 46bca0fd9f5c183d84f5c2085e18727f3a7dd9ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 13:19:33 +0000 Subject: [PATCH 06/36] Restrict WayPoint to TombEngine and add compiler support Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Command.cs | 2 + .../ContextMenus/SectorContextMenu.cs | 1 + .../SelectedGeometryContextMenu.cs | 1 + TombEditor/EditorCommands.cs | 2 + .../TombEngine/LevelCompilerTombEngine.cs | 51 +++++++++++++++++++ .../LevelData/Compilers/TombEngine/Structs.cs | 17 +++++++ .../Compilers/TombEngine/TombEngine.cs | 17 +++++++ 7 files changed, 91 insertions(+) diff --git a/TombEditor/Command.cs b/TombEditor/Command.cs index 168de67a5..76a09aed1 100644 --- a/TombEditor/Command.cs +++ b/TombEditor/Command.cs @@ -1154,6 +1154,8 @@ static CommandHandler() AddCommand("AddWayPoint", "Add waypoint", CommandType.Objects, delegate (CommandArgs args) { + if (!EditorActions.VersionCheck(args.Editor.Level.IsTombEngine, "WayPoint")) + return; args.Editor.Action = new EditorActionPlace(false, (l, r) => new WayPointInstance(args.Editor.SelectedObject)); }); diff --git a/TombEditor/Controls/ContextMenus/SectorContextMenu.cs b/TombEditor/Controls/ContextMenus/SectorContextMenu.cs index edce1b34a..29b6f8ffa 100644 --- a/TombEditor/Controls/ContextMenus/SectorContextMenu.cs +++ b/TombEditor/Controls/ContextMenus/SectorContextMenu.cs @@ -49,6 +49,7 @@ public SectorContextMenu(Editor editor, IWin32Window owner, Room targetRoom, Vec EditorActions.PlaceObject(targetRoom, targetSector, new FlybyCameraInstance(editor.SelectedObject)); })); + if (editor.Level.IsTombEngine) Items.Add(new ToolStripMenuItem("Add waypoint", Properties.Resources.objects_movie_projector_16, (o, e) => { EditorActions.PlaceObject(targetRoom, targetSector, new WayPointInstance(editor.SelectedObject)); diff --git a/TombEditor/Controls/ContextMenus/SelectedGeometryContextMenu.cs b/TombEditor/Controls/ContextMenus/SelectedGeometryContextMenu.cs index 0184aa30b..d6eb36da0 100644 --- a/TombEditor/Controls/ContextMenus/SelectedGeometryContextMenu.cs +++ b/TombEditor/Controls/ContextMenus/SelectedGeometryContextMenu.cs @@ -56,6 +56,7 @@ public SelectedGeometryContextMenu(Editor editor, IWin32Window owner, Room targe EditorActions.PlaceObject(targetRoom, targetSector, new FlybyCameraInstance(editor.SelectedObject)); })); + if (editor.Level.IsTombEngine) Items.Add(new ToolStripMenuItem("Add waypoint", Properties.Resources.objects_movie_projector_16, (o, e) => { EditorActions.PlaceObject(targetRoom, targetSector, new WayPointInstance(editor.SelectedObject)); diff --git a/TombEditor/EditorCommands.cs b/TombEditor/EditorCommands.cs index 0fceca166..8635e6ac1 100644 --- a/TombEditor/EditorCommands.cs +++ b/TombEditor/EditorCommands.cs @@ -779,6 +779,8 @@ private void AddCommands() AddCommand("AddWayPoint", "Add waypoint", CommandType.Objects, delegate () { + if (!VersionCheck(_editor.Level.IsTombEngine, "WayPoint")) + return; _editor.Action = new EditorActionPlace(false, (l, r) => new WayPointInstance()); }); diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs index 1b5835b98..446db8593 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs @@ -56,6 +56,7 @@ public int Compare(TombEngineBucket x, TombEngineBucket y) private readonly List _cameras = new List(); private readonly List _sinks = new List(); private readonly List _flyByCameras = new List(); + private readonly List _wayPoints = new List(); private readonly List _soundSources = new List(); private List _boxes = new List(); private List _overlaps = new List(); @@ -74,6 +75,7 @@ public int Compare(TombEngineBucket x, TombEngineBucket y) private Dictionary _aiObjectsTable; private Dictionary _soundSourcesTable; private Dictionary _flybyTable; + private Dictionary _wayPointTable; // Collected game limits private Dictionary _limits; @@ -228,10 +230,12 @@ private void BuildCamerasAndSinks() int sinkID = 0; int camID = 0; int flybyID = 0; + int wayPointID = 0; _cameraTable = new Dictionary(new ReferenceEqualityComparer()); _sinkTable = new Dictionary(new ReferenceEqualityComparer()); _flybyTable = new Dictionary(new ReferenceEqualityComparer()); + _wayPointTable = new Dictionary(new ReferenceEqualityComparer()); foreach (var room in _level.ExistingRooms) { @@ -241,6 +245,8 @@ private void BuildCamerasAndSinks() _flybyTable.Add(obj, flybyID++); foreach (var obj in room.Objects.OfType()) _sinkTable.Add(obj, sinkID++); + foreach (var obj in room.Objects.OfType()) + _wayPointTable.Add(obj, wayPointID++); } } @@ -332,8 +338,53 @@ private void BuildCamerasAndSinks() lastIndex = _flyByCameras[i].Index; } + // Collect waypoints + foreach (var instance in _wayPointTable.Keys) + { + Vector3 position = instance.Room.WorldPos + instance.Position; + _wayPoints.Add(new TombEngineWayPoint + { + X = (int)Math.Round(position.X), + Y = (int)Math.Round(-position.Y), + Z = (int)Math.Round(position.Z), + Room = _roomRemapping[instance.Room], + RotationX = instance.RotationX, + RotationY = instance.RotationY, + Roll = instance.Roll, + Sequence = instance.Sequence, + Number = instance.Number, + PathType = (int)instance.PathType, + Name = instance.Name, + LuaName = instance.ScriptId.HasValue ? (_scriptingIdsTable.TryGetOrDefault(instance.ScriptId.Value, string.Empty) ?? string.Empty) : string.Empty + }); + } + _wayPoints.Sort((x, y) => + { + int seqCompare = x.Sequence.CompareTo(y.Sequence); + if (seqCompare != 0) return seqCompare; + return x.Number.CompareTo(y.Number); + }); + + // Check waypoint duplicates + lastSeq = -1; + lastIndex = -1; + + for (int i = 0; i < _wayPoints.Count; i++) + { + if(_wayPoints[i].Sequence != lastSeq) + { + lastSeq = _wayPoints[i].Sequence; + lastIndex = -1; + } + + if (_wayPoints[i].Number == lastIndex && _wayPoints[i].Sequence == lastSeq) + _progressReporter.ReportWarn("Warning: waypoint sequence " + _wayPoints[i].Sequence + " has duplicated waypoint with ID " + lastIndex); + lastIndex = _wayPoints[i].Number; + } + ReportProgress(47, " Number of cameras: " + _cameraTable.Count); ReportProgress(47, " Number of flyby cameras: " + _flyByCameras.Count); + ReportProgress(47, " Number of waypoints: " + _wayPointTable.Count); ReportProgress(47, " Number of sinks: " + _sinkTable.Count); } diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs index a00161107..e8f4e2541 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs @@ -585,6 +585,23 @@ public struct TombEngineSink public string LuaName; } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TombEngineWayPoint + { + public int X; + public int Y; + public int Z; + public int Room; + public float RotationX; + public float RotationY; + public float Roll; + public ushort Sequence; + public ushort Number; + public int PathType; + public string Name; + public string LuaName; + } + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TombEngineSoundSource { diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs index 67650844a..630977c73 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs @@ -77,6 +77,23 @@ private void WriteLevelTombEngine() writer.Write((uint)_flyByCameras.Count); writer.WriteBlockArray(_flyByCameras); + writer.Write((uint)_wayPoints.Count); + foreach (var waypoint in _wayPoints) + { + writer.Write(waypoint.X); + writer.Write(waypoint.Y); + writer.Write(waypoint.Z); + writer.Write(waypoint.Room); + writer.Write(waypoint.RotationX); + writer.Write(waypoint.RotationY); + writer.Write(waypoint.Roll); + writer.Write(waypoint.Sequence); + writer.Write(waypoint.Number); + writer.Write(waypoint.PathType); + writer.Write(waypoint.Name); + writer.Write(waypoint.LuaName); + } + writer.Write((uint)_sinks.Count); foreach (var sink in _sinks) { From 4a60133c481ac566134913934eac303d3959d974 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 13:21:04 +0000 Subject: [PATCH 07/36] Fix code formatting in waypoint duplicate detection Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- .../LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs index 446db8593..f4cac0a05 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs @@ -371,14 +371,14 @@ private void BuildCamerasAndSinks() for (int i = 0; i < _wayPoints.Count; i++) { - if(_wayPoints[i].Sequence != lastSeq) + if (_wayPoints[i].Sequence != lastSeq) { lastSeq = _wayPoints[i].Sequence; lastIndex = -1; } if (_wayPoints[i].Number == lastIndex && _wayPoints[i].Sequence == lastSeq) - _progressReporter.ReportWarn("Warning: waypoint sequence " + _wayPoints[i].Sequence + " has duplicated waypoint with ID " + lastIndex); + _progressReporter.ReportWarn($"Warning: waypoint sequence {_wayPoints[i].Sequence} has duplicated waypoint with ID {lastIndex}"); lastIndex = _wayPoints[i].Number; } From 34ba61fb7626d7296d3dfe79b243691d4d3689e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 13:46:46 +0000 Subject: [PATCH 08/36] Add Circle/Ellipse shape support with Radius1 and Radius2 properties Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Forms/FormWayPoint.Designer.cs | 148 ++++++++++++++++-- TombEditor/Forms/FormWayPoint.cs | 20 +++ .../TombEngine/LevelCompilerTombEngine.cs | 3 + .../LevelData/Compilers/TombEngine/Structs.cs | 3 + .../Compilers/TombEngine/TombEngine.cs | 3 + TombLib/TombLib/LevelData/IO/Prj2Loader.cs | 3 + TombLib/TombLib/LevelData/IO/Prj2Writer.cs | 3 + .../LevelData/Instances/WayPointInstance.cs | 13 ++ 8 files changed, 183 insertions(+), 13 deletions(-) diff --git a/TombEditor/Forms/FormWayPoint.Designer.cs b/TombEditor/Forms/FormWayPoint.Designer.cs index d56b3624e..830977278 100644 --- a/TombEditor/Forms/FormWayPoint.Designer.cs +++ b/TombEditor/Forms/FormWayPoint.Designer.cs @@ -36,6 +36,9 @@ private void InitializeComponent() this.lblSequence = new DarkUI.Controls.DarkLabel(); this.lblNumber = new DarkUI.Controls.DarkLabel(); this.lblPathType = new DarkUI.Controls.DarkLabel(); + this.lblShape = new DarkUI.Controls.DarkLabel(); + this.lblRadius1 = new DarkUI.Controls.DarkLabel(); + this.lblRadius2 = new DarkUI.Controls.DarkLabel(); this.lblRotationX = new DarkUI.Controls.DarkLabel(); this.lblRotationY = new DarkUI.Controls.DarkLabel(); this.lblRoll = new DarkUI.Controls.DarkLabel(); @@ -43,11 +46,16 @@ private void InitializeComponent() this.numSequence = new DarkUI.Controls.DarkNumericUpDown(); this.numNumber = new DarkUI.Controls.DarkNumericUpDown(); this.cmbPathType = new DarkUI.Controls.DarkComboBox(); + this.cmbShape = new DarkUI.Controls.DarkComboBox(); + this.numRadius1 = new DarkUI.Controls.DarkNumericUpDown(); + this.numRadius2 = new DarkUI.Controls.DarkNumericUpDown(); this.numRotationX = new DarkUI.Controls.DarkNumericUpDown(); this.numRotationY = new DarkUI.Controls.DarkNumericUpDown(); this.numRoll = new DarkUI.Controls.DarkNumericUpDown(); ((System.ComponentModel.ISupportInitialize)(this.numSequence)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numNumber)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRadius1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRadius2)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numRotationX)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numRotationY)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numRoll)).BeginInit(); @@ -58,10 +66,10 @@ private void InitializeComponent() this.butCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.butCancel.Checked = false; this.butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.butCancel.Location = new System.Drawing.Point(239, 220); + this.butCancel.Location = new System.Drawing.Point(239, 298); this.butCancel.Name = "butCancel"; this.butCancel.Size = new System.Drawing.Size(80, 23); - this.butCancel.TabIndex = 8; + this.butCancel.TabIndex = 12; this.butCancel.Text = "Cancel"; this.butCancel.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; this.butCancel.Click += new System.EventHandler(this.butCancel_Click); @@ -70,10 +78,10 @@ private void InitializeComponent() // this.butOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.butOK.Checked = false; - this.butOK.Location = new System.Drawing.Point(153, 220); + this.butOK.Location = new System.Drawing.Point(153, 298); this.butOK.Name = "butOK"; this.butOK.Size = new System.Drawing.Size(80, 23); - this.butOK.TabIndex = 7; + this.butOK.TabIndex = 11; this.butOK.Text = "OK"; this.butOK.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; this.butOK.Click += new System.EventHandler(this.butOK_Click); @@ -118,34 +126,64 @@ private void InitializeComponent() this.lblPathType.TabIndex = 6; this.lblPathType.Text = "Path Type:"; // + // lblShape + // + this.lblShape.AutoSize = true; + this.lblShape.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblShape.Location = new System.Drawing.Point(12, 119); + this.lblShape.Name = "lblShape"; + this.lblShape.Size = new System.Drawing.Size(42, 13); + this.lblShape.TabIndex = 8; + this.lblShape.Text = "Shape:"; + // + // lblRadius1 + // + this.lblRadius1.AutoSize = true; + this.lblRadius1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblRadius1.Location = new System.Drawing.Point(12, 145); + this.lblRadius1.Name = "lblRadius1"; + this.lblRadius1.Size = new System.Drawing.Size(52, 13); + this.lblRadius1.TabIndex = 10; + this.lblRadius1.Text = "Radius 1:"; + // + // lblRadius2 + // + this.lblRadius2.AutoSize = true; + this.lblRadius2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblRadius2.Location = new System.Drawing.Point(12, 171); + this.lblRadius2.Name = "lblRadius2"; + this.lblRadius2.Size = new System.Drawing.Size(52, 13); + this.lblRadius2.TabIndex = 12; + this.lblRadius2.Text = "Radius 2:"; + // // lblRotationX // this.lblRotationX.AutoSize = true; this.lblRotationX.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRotationX.Location = new System.Drawing.Point(12, 119); + this.lblRotationX.Location = new System.Drawing.Point(12, 197); this.lblRotationX.Name = "lblRotationX"; this.lblRotationX.Size = new System.Drawing.Size(64, 13); - this.lblRotationX.TabIndex = 8; + this.lblRotationX.TabIndex = 14; this.lblRotationX.Text = "Rotation X:"; // // lblRotationY // this.lblRotationY.AutoSize = true; this.lblRotationY.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRotationY.Location = new System.Drawing.Point(12, 145); + this.lblRotationY.Location = new System.Drawing.Point(12, 223); this.lblRotationY.Name = "lblRotationY"; this.lblRotationY.Size = new System.Drawing.Size(63, 13); - this.lblRotationY.TabIndex = 10; + this.lblRotationY.TabIndex = 16; this.lblRotationY.Text = "Rotation Y:"; // // lblRoll // this.lblRoll.AutoSize = true; this.lblRoll.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRoll.Location = new System.Drawing.Point(12, 171); + this.lblRoll.Location = new System.Drawing.Point(12, 249); this.lblRoll.Name = "lblRoll"; this.lblRoll.Size = new System.Drawing.Size(30, 13); - this.lblRoll.TabIndex = 12; + this.lblRoll.TabIndex = 18; this.lblRoll.Text = "Roll:"; // // txtName @@ -203,6 +241,76 @@ private void InitializeComponent() this.cmbPathType.Size = new System.Drawing.Size(237, 23); this.cmbPathType.TabIndex = 7; // + // cmbShape + // + this.cmbShape.FormattingEnabled = true; + this.cmbShape.Items.AddRange(new object[] { + "Circle", + "Ellipse"}); + this.cmbShape.Location = new System.Drawing.Point(82, 116); + this.cmbShape.Name = "cmbShape"; + this.cmbShape.Size = new System.Drawing.Size(237, 23); + this.cmbShape.TabIndex = 9; + this.cmbShape.SelectedIndexChanged += new System.EventHandler(this.cmbShape_SelectedIndexChanged); + // + // numRadius1 + // + this.numRadius1.DecimalPlaces = 2; + this.numRadius1.IncrementAlternate = new decimal(new int[] { + 100, + 0, + 0, + 0}); + this.numRadius1.Location = new System.Drawing.Point(82, 142); + this.numRadius1.LoopValues = false; + this.numRadius1.Maximum = new decimal(new int[] { + 100000, + 0, + 0, + 0}); + this.numRadius1.Minimum = new decimal(new int[] { + 0, + 0, + 0, + 0}); + this.numRadius1.Name = "numRadius1"; + this.numRadius1.Size = new System.Drawing.Size(237, 22); + this.numRadius1.TabIndex = 11; + this.numRadius1.Value = new decimal(new int[] { + 1024, + 0, + 0, + 0}); + // + // numRadius2 + // + this.numRadius2.DecimalPlaces = 2; + this.numRadius2.IncrementAlternate = new decimal(new int[] { + 100, + 0, + 0, + 0}); + this.numRadius2.Location = new System.Drawing.Point(82, 168); + this.numRadius2.LoopValues = false; + this.numRadius2.Maximum = new decimal(new int[] { + 100000, + 0, + 0, + 0}); + this.numRadius2.Minimum = new decimal(new int[] { + 0, + 0, + 0, + 0}); + this.numRadius2.Name = "numRadius2"; + this.numRadius2.Size = new System.Drawing.Size(237, 22); + this.numRadius2.TabIndex = 13; + this.numRadius2.Value = new decimal(new int[] { + 1024, + 0, + 0, + 0}); + // // numRotationX // this.numRotationX.DecimalPlaces = 2; @@ -211,7 +319,7 @@ private void InitializeComponent() 0, 0, 65536}); - this.numRotationX.Location = new System.Drawing.Point(82, 116); + this.numRotationX.Location = new System.Drawing.Point(82, 194); this.numRotationX.LoopValues = false; this.numRotationX.Maximum = new decimal(new int[] { 90, @@ -225,7 +333,7 @@ private void InitializeComponent() -2147483648}); this.numRotationX.Name = "numRotationX"; this.numRotationX.Size = new System.Drawing.Size(237, 22); - this.numRotationX.TabIndex = 9; + this.numRotationX.TabIndex = 15; // // numRotationY // @@ -235,7 +343,7 @@ private void InitializeComponent() 0, 0, 65536}); - this.numRotationY.Location = new System.Drawing.Point(82, 142); + this.numRotationY.Location = new System.Drawing.Point(82, 220); this.numRotationY.LoopValues = false; this.numRotationY.Maximum = new decimal(new int[] { 360, @@ -275,6 +383,9 @@ private void InitializeComponent() this.Controls.Add(this.numRoll); this.Controls.Add(this.numRotationY); this.Controls.Add(this.numRotationX); + this.Controls.Add(this.numRadius2); + this.Controls.Add(this.numRadius1); + this.Controls.Add(this.cmbShape); this.Controls.Add(this.cmbPathType); this.Controls.Add(this.numNumber); this.Controls.Add(this.numSequence); @@ -282,6 +393,9 @@ private void InitializeComponent() this.Controls.Add(this.lblRoll); this.Controls.Add(this.lblRotationY); this.Controls.Add(this.lblRotationX); + this.Controls.Add(this.lblRadius2); + this.Controls.Add(this.lblRadius1); + this.Controls.Add(this.lblShape); this.Controls.Add(this.lblPathType); this.Controls.Add(this.lblNumber); this.Controls.Add(this.lblSequence); @@ -299,6 +413,8 @@ private void InitializeComponent() this.Load += new System.EventHandler(this.FormWayPoint_Load); ((System.ComponentModel.ISupportInitialize)(this.numSequence)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numNumber)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRadius1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numRadius2)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numRotationX)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numRotationY)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numRoll)).EndInit(); @@ -315,6 +431,9 @@ private void InitializeComponent() private DarkLabel lblSequence; private DarkLabel lblNumber; private DarkLabel lblPathType; + private DarkLabel lblShape; + private DarkLabel lblRadius1; + private DarkLabel lblRadius2; private DarkLabel lblRotationX; private DarkLabel lblRotationY; private DarkLabel lblRoll; @@ -322,6 +441,9 @@ private void InitializeComponent() private DarkNumericUpDown numSequence; private DarkNumericUpDown numNumber; private DarkComboBox cmbPathType; + private DarkComboBox cmbShape; + private DarkNumericUpDown numRadius1; + private DarkNumericUpDown numRadius2; private DarkNumericUpDown numRotationX; private DarkNumericUpDown numRotationY; private DarkNumericUpDown numRoll; diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index a2bc65dde..3989021bc 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -32,9 +32,26 @@ private void FormWayPoint_Load(object sender, EventArgs e) numSequence.Value = _wayPoint.Sequence; numNumber.Value = _wayPoint.Number; cmbPathType.SelectedIndex = (int)_wayPoint.PathType; + cmbShape.SelectedIndex = (int)_wayPoint.Shape; + numRadius1.Value = (decimal)_wayPoint.Radius1; + numRadius2.Value = (decimal)_wayPoint.Radius2; numRotationX.Value = (decimal)_wayPoint.RotationX; numRotationY.Value = (decimal)_wayPoint.RotationY; numRoll.Value = (decimal)_wayPoint.Roll; + + UpdateRadiusVisibility(); + } + + private void cmbShape_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateRadiusVisibility(); + } + + private void UpdateRadiusVisibility() + { + bool isEllipse = cmbShape.SelectedIndex == (int)WayPointShape.Ellipse; + lblRadius2.Visible = isEllipse; + numRadius2.Visible = isEllipse; } private void butOK_Click(object sender, EventArgs e) @@ -43,6 +60,9 @@ private void butOK_Click(object sender, EventArgs e) _wayPoint.Sequence = (ushort)numSequence.Value; _wayPoint.Number = (ushort)numNumber.Value; _wayPoint.PathType = (PathType)cmbPathType.SelectedIndex; + _wayPoint.Shape = (WayPointShape)cmbShape.SelectedIndex; + _wayPoint.Radius1 = (float)numRadius1.Value; + _wayPoint.Radius2 = (float)numRadius2.Value; _wayPoint.RotationX = (float)numRotationX.Value; _wayPoint.RotationY = (float)numRotationY.Value; _wayPoint.Roll = (float)numRoll.Value; diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs index f4cac0a05..4cf6800af 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs @@ -354,6 +354,9 @@ private void BuildCamerasAndSinks() Sequence = instance.Sequence, Number = instance.Number, PathType = (int)instance.PathType, + Shape = (int)instance.Shape, + Radius1 = instance.Radius1, + Radius2 = instance.Radius2, Name = instance.Name, LuaName = instance.ScriptId.HasValue ? (_scriptingIdsTable.TryGetOrDefault(instance.ScriptId.Value, string.Empty) ?? string.Empty) : string.Empty }); diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs index e8f4e2541..fff6b1cc0 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs @@ -598,6 +598,9 @@ public struct TombEngineWayPoint public ushort Sequence; public ushort Number; public int PathType; + public int Shape; + public float Radius1; + public float Radius2; public string Name; public string LuaName; } diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs index 630977c73..49256c392 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs @@ -90,6 +90,9 @@ private void WriteLevelTombEngine() writer.Write(waypoint.Sequence); writer.Write(waypoint.Number); writer.Write(waypoint.PathType); + writer.Write(waypoint.Shape); + writer.Write(waypoint.Radius1); + writer.Write(waypoint.Radius2); writer.Write(waypoint.Name); writer.Write(waypoint.LuaName); } diff --git a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs index f6096319a..62dd479b3 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs @@ -1413,6 +1413,9 @@ private static bool LoadObjects(ChunkReader chunkIO, ChunkId idOuter, LevelSetti instance.Number = LEB128.ReadUShort(chunkIO.Raw); instance.Sequence = LEB128.ReadUShort(chunkIO.Raw); instance.PathType = (PathType)LEB128.ReadInt(chunkIO.Raw); + instance.Shape = (WayPointShape)LEB128.ReadInt(chunkIO.Raw); + instance.Radius1 = chunkIO.Raw.ReadSingle(); + instance.Radius2 = chunkIO.Raw.ReadSingle(); addObject(instance); newObjects.TryAdd(objectID, instance); } diff --git a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs index 9bc23f11c..61156f106 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs @@ -693,6 +693,9 @@ private static void WriteObjects(ChunkWriter chunkIO, IEnumerable Date: Sat, 24 Jan 2026 19:52:29 +0000 Subject: [PATCH 09/36] Fix compilation error: use LuaName property instead of TryGetOrDefault Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- .../LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs index 4cf6800af..e69ab2e21 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs @@ -358,7 +358,7 @@ private void BuildCamerasAndSinks() Radius1 = instance.Radius1, Radius2 = instance.Radius2, Name = instance.Name, - LuaName = instance.ScriptId.HasValue ? (_scriptingIdsTable.TryGetOrDefault(instance.ScriptId.Value, string.Empty) ?? string.Empty) : string.Empty + LuaName = instance.LuaName ?? string.Empty }); } _wayPoints.Sort((x, y) => From c25a5c21954da969dc6767cc926e8b38510b638b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:05:11 +0000 Subject: [PATCH 10/36] Refactor WayPoint to use single Type enum and update UI/serialization Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DHelpers.cs | 13 ++- TombEditor/Forms/FormWayPoint.Designer.cs | 109 +++++++----------- TombEditor/Forms/FormWayPoint.cs | 49 +++++--- .../TombEngine/LevelCompilerTombEngine.cs | 3 +- .../LevelData/Compilers/TombEngine/Structs.cs | 3 +- .../Compilers/TombEngine/TombEngine.cs | 3 +- TombLib/TombLib/LevelData/IO/Prj2Loader.cs | 5 +- TombLib/TombLib/LevelData/IO/Prj2Writer.cs | 5 +- .../LevelData/Instances/WayPointInstance.cs | 99 +++++++++++----- 9 files changed, 164 insertions(+), 125 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs index adba12355..6d6d14095 100644 --- a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs +++ b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs @@ -195,6 +195,9 @@ private bool AddWayPointPath(int sequence) wayPoints.Add(instance); } + // Filter to only multi-point types (Linear, Bezier) + wayPoints = wayPoints.Where(wp => wp.Type == WayPointType.Linear || wp.Type == WayPointType.Bezier).ToList(); + // Is it actually necessary to show the path? if (wayPoints.Count < 2) return false; @@ -209,11 +212,11 @@ private bool AddWayPointPath(int sequence) float th = _flybyPathThickness; - // Determine the PathType to use for the sequence (use the most common one) - var pathTypeCounts = wayPoints.GroupBy(wp => wp.PathType) + // Determine the Type to use for the sequence (use the most common one) + var typeCounts = wayPoints.GroupBy(wp => wp.Type) .OrderByDescending(g => g.Count()) .ToList(); - var pathType = pathTypeCounts.FirstOrDefault()?.Key ?? PathType.Linear; + var wpType = typeCounts.FirstOrDefault()?.Key ?? WayPointType.Linear; // Process waypoints to calculate paths var pointList = new List(); @@ -223,9 +226,9 @@ private bool AddWayPointPath(int sequence) pointList.Add(wp.Position + wp.Room.WorldPos); } - // Calculate the spline path based on PathType + // Calculate the spline path based on Type List interpolatedPoints; - if (pathType == PathType.Linear) + if (wpType == WayPointType.Linear) { // For linear, just use the points as-is interpolatedPoints = pointList; diff --git a/TombEditor/Forms/FormWayPoint.Designer.cs b/TombEditor/Forms/FormWayPoint.Designer.cs index 830977278..c72e6506e 100644 --- a/TombEditor/Forms/FormWayPoint.Designer.cs +++ b/TombEditor/Forms/FormWayPoint.Designer.cs @@ -35,8 +35,7 @@ private void InitializeComponent() this.lblName = new DarkUI.Controls.DarkLabel(); this.lblSequence = new DarkUI.Controls.DarkLabel(); this.lblNumber = new DarkUI.Controls.DarkLabel(); - this.lblPathType = new DarkUI.Controls.DarkLabel(); - this.lblShape = new DarkUI.Controls.DarkLabel(); + this.lblType = new DarkUI.Controls.DarkLabel(); this.lblRadius1 = new DarkUI.Controls.DarkLabel(); this.lblRadius2 = new DarkUI.Controls.DarkLabel(); this.lblRotationX = new DarkUI.Controls.DarkLabel(); @@ -45,8 +44,7 @@ private void InitializeComponent() this.txtName = new DarkUI.Controls.DarkTextBox(); this.numSequence = new DarkUI.Controls.DarkNumericUpDown(); this.numNumber = new DarkUI.Controls.DarkNumericUpDown(); - this.cmbPathType = new DarkUI.Controls.DarkComboBox(); - this.cmbShape = new DarkUI.Controls.DarkComboBox(); + this.cmbType = new DarkUI.Controls.DarkComboBox(); this.numRadius1 = new DarkUI.Controls.DarkNumericUpDown(); this.numRadius2 = new DarkUI.Controls.DarkNumericUpDown(); this.numRotationX = new DarkUI.Controls.DarkNumericUpDown(); @@ -116,74 +114,64 @@ private void InitializeComponent() this.lblNumber.TabIndex = 4; this.lblNumber.Text = "Number:"; // - // lblPathType + // lblType // - this.lblPathType.AutoSize = true; - this.lblPathType.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblPathType.Location = new System.Drawing.Point(12, 93); - this.lblPathType.Name = "lblPathType"; - this.lblPathType.Size = new System.Drawing.Size(59, 13); - this.lblPathType.TabIndex = 6; - this.lblPathType.Text = "Path Type:"; - // - // lblShape - // - this.lblShape.AutoSize = true; - this.lblShape.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblShape.Location = new System.Drawing.Point(12, 119); - this.lblShape.Name = "lblShape"; - this.lblShape.Size = new System.Drawing.Size(42, 13); - this.lblShape.TabIndex = 8; - this.lblShape.Text = "Shape:"; + this.lblType.AutoSize = true; + this.lblType.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblType.Location = new System.Drawing.Point(12, 93); + this.lblType.Name = "lblType"; + this.lblType.Size = new System.Drawing.Size(34, 13); + this.lblType.TabIndex = 6; + this.lblType.Text = "Type:"; // // lblRadius1 // this.lblRadius1.AutoSize = true; this.lblRadius1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRadius1.Location = new System.Drawing.Point(12, 145); + this.lblRadius1.Location = new System.Drawing.Point(12, 119); this.lblRadius1.Name = "lblRadius1"; this.lblRadius1.Size = new System.Drawing.Size(52, 13); - this.lblRadius1.TabIndex = 10; + this.lblRadius1.TabIndex = 8; this.lblRadius1.Text = "Radius 1:"; // // lblRadius2 // this.lblRadius2.AutoSize = true; this.lblRadius2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRadius2.Location = new System.Drawing.Point(12, 171); + this.lblRadius2.Location = new System.Drawing.Point(12, 145); this.lblRadius2.Name = "lblRadius2"; this.lblRadius2.Size = new System.Drawing.Size(52, 13); - this.lblRadius2.TabIndex = 12; + this.lblRadius2.TabIndex = 10; this.lblRadius2.Text = "Radius 2:"; // // lblRotationX // this.lblRotationX.AutoSize = true; this.lblRotationX.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRotationX.Location = new System.Drawing.Point(12, 197); + this.lblRotationX.Location = new System.Drawing.Point(12, 171); this.lblRotationX.Name = "lblRotationX"; this.lblRotationX.Size = new System.Drawing.Size(64, 13); - this.lblRotationX.TabIndex = 14; + this.lblRotationX.TabIndex = 12; this.lblRotationX.Text = "Rotation X:"; // // lblRotationY // this.lblRotationY.AutoSize = true; this.lblRotationY.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRotationY.Location = new System.Drawing.Point(12, 223); + this.lblRotationY.Location = new System.Drawing.Point(12, 197); this.lblRotationY.Name = "lblRotationY"; this.lblRotationY.Size = new System.Drawing.Size(63, 13); - this.lblRotationY.TabIndex = 16; + this.lblRotationY.TabIndex = 14; this.lblRotationY.Text = "Rotation Y:"; // // lblRoll // this.lblRoll.AutoSize = true; this.lblRoll.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRoll.Location = new System.Drawing.Point(12, 249); + this.lblRoll.Location = new System.Drawing.Point(12, 223); this.lblRoll.Name = "lblRoll"; this.lblRoll.Size = new System.Drawing.Size(30, 13); - this.lblRoll.TabIndex = 18; + this.lblRoll.TabIndex = 16; this.lblRoll.Text = "Roll:"; // // txtName @@ -229,29 +217,22 @@ private void InitializeComponent() this.numNumber.Size = new System.Drawing.Size(237, 22); this.numNumber.TabIndex = 5; // - // cmbPathType + // cmbType // - this.cmbPathType.FormattingEnabled = true; - this.cmbPathType.Items.AddRange(new object[] { + this.cmbType.FormattingEnabled = true; + this.cmbType.Items.AddRange(new object[] { + "Point", + "Circle", + "Ellipse", + "Square", + "Rectangle", "Linear", - "Curved", "Bezier"}); - this.cmbPathType.Location = new System.Drawing.Point(82, 90); - this.cmbPathType.Name = "cmbPathType"; - this.cmbPathType.Size = new System.Drawing.Size(237, 23); - this.cmbPathType.TabIndex = 7; - // - // cmbShape - // - this.cmbShape.FormattingEnabled = true; - this.cmbShape.Items.AddRange(new object[] { - "Circle", - "Ellipse"}); - this.cmbShape.Location = new System.Drawing.Point(82, 116); - this.cmbShape.Name = "cmbShape"; - this.cmbShape.Size = new System.Drawing.Size(237, 23); - this.cmbShape.TabIndex = 9; - this.cmbShape.SelectedIndexChanged += new System.EventHandler(this.cmbShape_SelectedIndexChanged); + this.cmbType.Location = new System.Drawing.Point(82, 90); + this.cmbType.Name = "cmbType"; + this.cmbType.Size = new System.Drawing.Size(237, 23); + this.cmbType.TabIndex = 7; + this.cmbType.SelectedIndexChanged += new System.EventHandler(this.cmbType_SelectedIndexChanged); // // numRadius1 // @@ -261,7 +242,7 @@ private void InitializeComponent() 0, 0, 0}); - this.numRadius1.Location = new System.Drawing.Point(82, 142); + this.numRadius1.Location = new System.Drawing.Point(82, 116); this.numRadius1.LoopValues = false; this.numRadius1.Maximum = new decimal(new int[] { 100000, @@ -275,7 +256,7 @@ private void InitializeComponent() 0}); this.numRadius1.Name = "numRadius1"; this.numRadius1.Size = new System.Drawing.Size(237, 22); - this.numRadius1.TabIndex = 11; + this.numRadius1.TabIndex = 9; this.numRadius1.Value = new decimal(new int[] { 1024, 0, @@ -290,7 +271,7 @@ private void InitializeComponent() 0, 0, 0}); - this.numRadius2.Location = new System.Drawing.Point(82, 168); + this.numRadius2.Location = new System.Drawing.Point(82, 142); this.numRadius2.LoopValues = false; this.numRadius2.Maximum = new decimal(new int[] { 100000, @@ -304,7 +285,7 @@ private void InitializeComponent() 0}); this.numRadius2.Name = "numRadius2"; this.numRadius2.Size = new System.Drawing.Size(237, 22); - this.numRadius2.TabIndex = 13; + this.numRadius2.TabIndex = 11; this.numRadius2.Value = new decimal(new int[] { 1024, 0, @@ -319,7 +300,7 @@ private void InitializeComponent() 0, 0, 65536}); - this.numRotationX.Location = new System.Drawing.Point(82, 194); + this.numRotationX.Location = new System.Drawing.Point(82, 168); this.numRotationX.LoopValues = false; this.numRotationX.Maximum = new decimal(new int[] { 90, @@ -333,7 +314,7 @@ private void InitializeComponent() -2147483648}); this.numRotationX.Name = "numRotationX"; this.numRotationX.Size = new System.Drawing.Size(237, 22); - this.numRotationX.TabIndex = 15; + this.numRotationX.TabIndex = 13; // // numRotationY // @@ -385,8 +366,7 @@ private void InitializeComponent() this.Controls.Add(this.numRotationX); this.Controls.Add(this.numRadius2); this.Controls.Add(this.numRadius1); - this.Controls.Add(this.cmbShape); - this.Controls.Add(this.cmbPathType); + this.Controls.Add(this.cmbType); this.Controls.Add(this.numNumber); this.Controls.Add(this.numSequence); this.Controls.Add(this.txtName); @@ -395,8 +375,7 @@ private void InitializeComponent() this.Controls.Add(this.lblRotationX); this.Controls.Add(this.lblRadius2); this.Controls.Add(this.lblRadius1); - this.Controls.Add(this.lblShape); - this.Controls.Add(this.lblPathType); + this.Controls.Add(this.lblType); this.Controls.Add(this.lblNumber); this.Controls.Add(this.lblSequence); this.Controls.Add(this.lblName); @@ -430,8 +409,7 @@ private void InitializeComponent() private DarkLabel lblName; private DarkLabel lblSequence; private DarkLabel lblNumber; - private DarkLabel lblPathType; - private DarkLabel lblShape; + private DarkLabel lblType; private DarkLabel lblRadius1; private DarkLabel lblRadius2; private DarkLabel lblRotationX; @@ -440,8 +418,7 @@ private void InitializeComponent() private DarkTextBox txtName; private DarkNumericUpDown numSequence; private DarkNumericUpDown numNumber; - private DarkComboBox cmbPathType; - private DarkComboBox cmbShape; + private DarkComboBox cmbType; private DarkNumericUpDown numRadius1; private DarkNumericUpDown numRadius2; private DarkNumericUpDown numRotationX; diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index 3989021bc..442042e65 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -28,39 +28,62 @@ private void butCancel_Click(object sender, EventArgs e) private void FormWayPoint_Load(object sender, EventArgs e) { - txtName.Text = _wayPoint.Name; + txtName.Text = _wayPoint.BaseName; numSequence.Value = _wayPoint.Sequence; numNumber.Value = _wayPoint.Number; - cmbPathType.SelectedIndex = (int)_wayPoint.PathType; - cmbShape.SelectedIndex = (int)_wayPoint.Shape; + cmbType.SelectedIndex = (int)_wayPoint.Type; numRadius1.Value = (decimal)_wayPoint.Radius1; numRadius2.Value = (decimal)_wayPoint.Radius2; numRotationX.Value = (decimal)_wayPoint.RotationX; numRotationY.Value = (decimal)_wayPoint.RotationY; numRoll.Value = (decimal)_wayPoint.Roll; - UpdateRadiusVisibility(); + UpdateFieldVisibility(); } - private void cmbShape_SelectedIndexChanged(object sender, EventArgs e) + private void cmbType_SelectedIndexChanged(object sender, EventArgs e) { - UpdateRadiusVisibility(); + UpdateFieldVisibility(); } - private void UpdateRadiusVisibility() + private void UpdateFieldVisibility() { - bool isEllipse = cmbShape.SelectedIndex == (int)WayPointShape.Ellipse; - lblRadius2.Visible = isEllipse; - numRadius2.Visible = isEllipse; + WayPointType type = (WayPointType)cmbType.SelectedIndex; + + // Check if this is a singular type + bool isSingular = type == WayPointType.Point || + type == WayPointType.Circle || + type == WayPointType.Ellipse || + type == WayPointType.Square || + type == WayPointType.Rectangle; + + // Number field only for multi-point types + lblNumber.Visible = !isSingular; + numNumber.Visible = !isSingular; + + // Radius fields only for shape types + bool requiresRadius = type == WayPointType.Circle || + type == WayPointType.Ellipse || + type == WayPointType.Square || + type == WayPointType.Rectangle; + + lblRadius1.Visible = requiresRadius; + numRadius1.Visible = requiresRadius; + + // Radius2 only for ellipse and rectangle + bool requiresTwoRadii = type == WayPointType.Ellipse || + type == WayPointType.Rectangle; + + lblRadius2.Visible = requiresTwoRadii; + numRadius2.Visible = requiresTwoRadii; } private void butOK_Click(object sender, EventArgs e) { - _wayPoint.Name = txtName.Text; + _wayPoint.BaseName = txtName.Text; _wayPoint.Sequence = (ushort)numSequence.Value; _wayPoint.Number = (ushort)numNumber.Value; - _wayPoint.PathType = (PathType)cmbPathType.SelectedIndex; - _wayPoint.Shape = (WayPointShape)cmbShape.SelectedIndex; + _wayPoint.Type = (WayPointType)cmbType.SelectedIndex; _wayPoint.Radius1 = (float)numRadius1.Value; _wayPoint.Radius2 = (float)numRadius2.Value; _wayPoint.RotationX = (float)numRotationX.Value; diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs index e69ab2e21..00975b1a6 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs @@ -353,8 +353,7 @@ private void BuildCamerasAndSinks() Roll = instance.Roll, Sequence = instance.Sequence, Number = instance.Number, - PathType = (int)instance.PathType, - Shape = (int)instance.Shape, + Type = (int)instance.Type, Radius1 = instance.Radius1, Radius2 = instance.Radius2, Name = instance.Name, diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs index fff6b1cc0..69a833cba 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs @@ -597,8 +597,7 @@ public struct TombEngineWayPoint public float Roll; public ushort Sequence; public ushort Number; - public int PathType; - public int Shape; + public int Type; public float Radius1; public float Radius2; public string Name; diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs index 49256c392..b0afe65e8 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs @@ -89,8 +89,7 @@ private void WriteLevelTombEngine() writer.Write(waypoint.Roll); writer.Write(waypoint.Sequence); writer.Write(waypoint.Number); - writer.Write(waypoint.PathType); - writer.Write(waypoint.Shape); + writer.Write(waypoint.Type); writer.Write(waypoint.Radius1); writer.Write(waypoint.Radius2); writer.Write(waypoint.Name); diff --git a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs index 62dd479b3..fc79e3504 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs @@ -1409,11 +1409,10 @@ private static bool LoadObjects(ChunkReader chunkIO, ChunkId idOuter, LevelSetti instance.SetArbitaryRotationsYX(chunkIO.Raw.ReadSingle(), chunkIO.Raw.ReadSingle()); instance.Roll = chunkIO.Raw.ReadSingle(); instance.ScriptId = ReadOptionalLEB128Int(chunkIO.Raw); - instance.Name = chunkIO.Raw.ReadStringUTF8(); + instance.BaseName = chunkIO.Raw.ReadStringUTF8(); instance.Number = LEB128.ReadUShort(chunkIO.Raw); instance.Sequence = LEB128.ReadUShort(chunkIO.Raw); - instance.PathType = (PathType)LEB128.ReadInt(chunkIO.Raw); - instance.Shape = (WayPointShape)LEB128.ReadInt(chunkIO.Raw); + instance.Type = (WayPointType)LEB128.ReadInt(chunkIO.Raw); instance.Radius1 = chunkIO.Raw.ReadSingle(); instance.Radius2 = chunkIO.Raw.ReadSingle(); addObject(instance); diff --git a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs index 61156f106..1e31a1cd9 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs @@ -689,11 +689,10 @@ private static void WriteObjects(ChunkWriter chunkIO, IEnumerable= 0) + if (lastUnderscore >= 0 && !IsSingularType()) { string suffix = value.Substring(lastUnderscore + 1); if (ushort.TryParse(suffix, out _)) @@ -67,15 +79,42 @@ public ushort Number set { _number = value; } } - public PathType PathType { get; set; } = PathType.Linear; - public WayPointShape Shape { get; set; } = WayPointShape.Circle; + public WayPointType Type + { + get { return _type; } + set { _type = value; } + } + public float Radius1 { get; set; } = 1024.0f; // Default radius in units - public float Radius2 { get; set; } = 1024.0f; // Default radius in units (same as Radius1 for circle) + public float Radius2 { get; set; } = 1024.0f; // Default radius in units private float _rotationX { get; set; } private float _rotationY { get; set; } private float _roll { get; set; } + public bool IsSingularType() + { + return _type == WayPointType.Point || + _type == WayPointType.Circle || + _type == WayPointType.Ellipse || + _type == WayPointType.Square || + _type == WayPointType.Rectangle; + } + + public bool RequiresRadius() + { + return _type == WayPointType.Circle || + _type == WayPointType.Ellipse || + _type == WayPointType.Square || + _type == WayPointType.Rectangle; + } + + public bool RequiresTwoRadii() + { + return _type == WayPointType.Ellipse || + _type == WayPointType.Rectangle; + } + public WayPointInstance(ObjectInstance selectedObject = null) { if (selectedObject is WayPointInstance prevWayPoint) @@ -83,18 +122,21 @@ public WayPointInstance(ObjectInstance selectedObject = null) var currSeq = prevWayPoint.Sequence; var currNum = (ushort)(prevWayPoint.Number + 1); - // Push next waypoints in sequence forward - var level = selectedObject.Room.Level; - foreach (var room in level.ExistingRooms) - foreach (var instance in room.Objects.OfType()) - if (instance.Sequence == currSeq && instance.Number >= currNum) - instance.Number++; + // Only push forward if it's a multi-point type + if (!prevWayPoint.IsSingularType()) + { + // Push next waypoints in sequence forward + var level = selectedObject.Room.Level; + foreach (var room in level.ExistingRooms) + foreach (var instance in room.Objects.OfType()) + if (instance.Sequence == currSeq && instance.Number >= currNum) + instance.Number++; + } Sequence = currSeq; - Number = currNum; + Number = prevWayPoint.IsSingularType() ? (ushort)0 : currNum; _baseName = prevWayPoint._baseName; - PathType = prevWayPoint.PathType; - Shape = prevWayPoint.Shape; + Type = prevWayPoint.Type; Radius1 = prevWayPoint.Radius1; Radius2 = prevWayPoint.Radius2; @@ -133,16 +175,15 @@ public override string ToString() return "WayPoint " + ", Name = " + Name + ", Sequence = " + Sequence + - ", Number = " + Number + - ", PathType = " + PathType + - ", Shape = " + Shape + + (IsSingularType() ? "" : ", Number = " + Number) + + ", Type = " + Type + " (" + (Room?.ToString() ?? "NULL") + ")" + ", X = " + SectorPosition.X + ", Z = " + SectorPosition.Y + GetScriptIDOrName(false); } - public string ShortName() => "WayPoint (" + Sequence + ":" + Number + ")" + GetScriptIDOrName() + " (" + (Room?.ToString() ?? "NULL") + ")"; + public string ShortName() => "WayPoint " + (IsSingularType() ? "" : "(" + Sequence + ":" + Number + ") ") + GetScriptIDOrName() + " (" + (Room?.ToString() ?? "NULL") + ")"; public override void CopyDependentLevelSettings(Room.CopyDependentLevelSettingsArgs args) { From c6e14266684457e1ecfc2a6c95333f7886364f01 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:06:03 +0000 Subject: [PATCH 11/36] Add circle, ellipse, square, and rectangle rendering for WayPoints Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 95 ++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 7c86503ce..18eb276f9 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1181,6 +1181,12 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis } DrawOrQueueServiceObject(instance, _littleCube, color, effect, sprites); + + // Draw shape for shape types (Circle, Ellipse, Square, Rectangle) + if (instance.RequiresRadius()) + { + DrawWayPointShape(instance, color); + } } if (group.Key == typeof(MemoInstance)) @@ -1469,6 +1475,95 @@ private void DrawOrQueueServiceObject(ISpatial instance, GeometricPrimitive prim _legacyDevice.DrawIndexed(PrimitiveType.TriangleList, primitive.IndexBuffer.ElementCount); } + private void DrawWayPointShape(WayPointInstance instance, Vector4 color) + { + // Get world position + Vector3 position = instance.Position + instance.Room.WorldPos; + + // Create transformation matrix for the shape orientation + Matrix4x4 rotation = Matrix4x4.CreateRotationY(instance.RotationY * (float)Math.PI / 180.0f) * + Matrix4x4.CreateRotationZ(instance.Roll * (float)Math.PI / 180.0f); + + // Number of segments for circles/ellipses + int segments = 32; + var vertices = new List(); + + switch (instance.Type) + { + case WayPointType.Circle: + // Draw circle with Radius1 + for (int i = 0; i <= segments; i++) + { + float angle = (i / (float)segments) * 2.0f * (float)Math.PI; + float x = (float)Math.Cos(angle) * instance.Radius1; + float z = (float)Math.Sin(angle) * instance.Radius1; + Vector3 point = Vector3.Transform(new Vector3(x, 0, z), rotation) + position; + vertices.Add(point); + } + break; + + case WayPointType.Ellipse: + // Draw ellipse with Radius1 and Radius2 + for (int i = 0; i <= segments; i++) + { + float angle = (i / (float)segments) * 2.0f * (float)Math.PI; + float x = (float)Math.Cos(angle) * instance.Radius1; + float z = (float)Math.Sin(angle) * instance.Radius2; + Vector3 point = Vector3.Transform(new Vector3(x, 0, z), rotation) + position; + vertices.Add(point); + } + break; + + case WayPointType.Square: + // Draw square with Radius1 + { + float r = instance.Radius1; + Vector3[] corners = new Vector3[] + { + new Vector3(-r, 0, -r), + new Vector3(r, 0, -r), + new Vector3(r, 0, r), + new Vector3(-r, 0, r), + new Vector3(-r, 0, -r) // Close the loop + }; + foreach (var corner in corners) + { + vertices.Add(Vector3.Transform(corner, rotation) + position); + } + } + break; + + case WayPointType.Rectangle: + // Draw rectangle with Radius1 and Radius2 + { + float r1 = instance.Radius1; + float r2 = instance.Radius2; + Vector3[] corners = new Vector3[] + { + new Vector3(-r1, 0, -r2), + new Vector3(r1, 0, -r2), + new Vector3(r1, 0, r2), + new Vector3(-r1, 0, r2), + new Vector3(-r1, 0, -r2) // Close the loop + }; + foreach (var corner in corners) + { + vertices.Add(Vector3.Transform(corner, rotation) + position); + } + } + break; + } + + // Draw the shape using line rendering + if (vertices.Count > 1) + { + for (int i = 0; i < vertices.Count - 1; i++) + { + AddLine(vertices[i], vertices[i + 1], color.To4()); + } + } + } + private void DrawCardinalDirections(List textToDraw) { string[] messages; From 0957e3e827a64a9ec05e20232245dd3d20bcc51f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:11:32 +0000 Subject: [PATCH 12/36] Fix compilation errors: update DrawWayPointShape rendering and tests, rename Roll to Z Axis, fix form layout Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 62 ++++++++++++-- TombEditor/Forms/FormWayPoint.Designer.cs | 14 ++-- TombLib/TombLib.Test/WayPointInstanceTests.cs | 84 +++++++++++++------ 3 files changed, 119 insertions(+), 41 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 18eb276f9..a58a74d32 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1486,7 +1486,7 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) // Number of segments for circles/ellipses int segments = 32; - var vertices = new List(); + var points = new List(); switch (instance.Type) { @@ -1498,7 +1498,7 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) float x = (float)Math.Cos(angle) * instance.Radius1; float z = (float)Math.Sin(angle) * instance.Radius1; Vector3 point = Vector3.Transform(new Vector3(x, 0, z), rotation) + position; - vertices.Add(point); + points.Add(point); } break; @@ -1510,7 +1510,7 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) float x = (float)Math.Cos(angle) * instance.Radius1; float z = (float)Math.Sin(angle) * instance.Radius2; Vector3 point = Vector3.Transform(new Vector3(x, 0, z), rotation) + position; - vertices.Add(point); + points.Add(point); } break; @@ -1528,7 +1528,7 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) }; foreach (var corner in corners) { - vertices.Add(Vector3.Transform(corner, rotation) + position); + points.Add(Vector3.Transform(corner, rotation) + position); } } break; @@ -1548,18 +1548,62 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) }; foreach (var corner in corners) { - vertices.Add(Vector3.Transform(corner, rotation) + position); + points.Add(Vector3.Transform(corner, rotation) + position); } } break; } - // Draw the shape using line rendering - if (vertices.Count > 1) + // Convert points to line vertices using the same approach as flyby paths + if (points.Count > 1) { - for (int i = 0; i < vertices.Count - 1; i++) + var vertices = new List(); + float th = 4.0f; // Line thickness + + for (int i = 0; i < points.Count - 1; i++) { - AddLine(vertices[i], vertices[i + 1], color.To4()); + var linePoints = new List() + { + new Vector3[] + { + points[i], + new Vector3(points[i].X + th, points[i].Y + th, points[i].Z + th), + new Vector3(points[i].X - th, points[i].Y + th, points[i].Z + th) + }, + new Vector3[] + { + points[i + 1], + new Vector3(points[i + 1].X + th, points[i + 1].Y + th, points[i + 1].Z + th), + new Vector3(points[i + 1].X - th, points[i + 1].Y + th, points[i + 1].Z + th) + } + }; + + // Add triangles to form the line segment + for (int k = 0; k < _flybyPathIndices.Count; k++) + { + var v = new SolidVertex(); + v.Position = linePoints[_flybyPathIndices[k].Y][_flybyPathIndices[k].X]; + v.Color = color; + vertices.Add(v); + } + } + + // Create temporary vertex buffer for this shape + if (vertices.Count > 0) + { + var shapeBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, + vertices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Dynamic); + + // Draw the shape + var effect = _legacyDevice.Extensions.DeviceManager.___LegacyEffects["Solid"]; + effect.Parameters["ModelViewProjection"].SetValue(_viewProjection.ToSharpDX()); + effect.Parameters["Color"].SetValue(color); + effect.Techniques[0].Passes[0].Apply(); + _legacyDevice.SetVertexBuffer(shapeBuffer); + _legacyDevice.SetVertexInputLayout(VertexInputLayout.FromBuffer(0, shapeBuffer)); + _legacyDevice.Draw(PrimitiveType.TriangleList, vertices.Count); + + shapeBuffer.Dispose(); } } } diff --git a/TombEditor/Forms/FormWayPoint.Designer.cs b/TombEditor/Forms/FormWayPoint.Designer.cs index c72e6506e..6074ccc6d 100644 --- a/TombEditor/Forms/FormWayPoint.Designer.cs +++ b/TombEditor/Forms/FormWayPoint.Designer.cs @@ -170,9 +170,9 @@ private void InitializeComponent() this.lblRoll.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); this.lblRoll.Location = new System.Drawing.Point(12, 223); this.lblRoll.Name = "lblRoll"; - this.lblRoll.Size = new System.Drawing.Size(30, 13); + this.lblRoll.Size = new System.Drawing.Size(42, 13); this.lblRoll.TabIndex = 16; - this.lblRoll.Text = "Roll:"; + this.lblRoll.Text = "Z Axis:"; // // txtName // @@ -324,7 +324,7 @@ private void InitializeComponent() 0, 0, 65536}); - this.numRotationY.Location = new System.Drawing.Point(82, 220); + this.numRotationY.Location = new System.Drawing.Point(82, 194); this.numRotationY.LoopValues = false; this.numRotationY.Maximum = new decimal(new int[] { 360, @@ -333,7 +333,7 @@ private void InitializeComponent() 0}); this.numRotationY.Name = "numRotationY"; this.numRotationY.Size = new System.Drawing.Size(237, 22); - this.numRotationY.TabIndex = 11; + this.numRotationY.TabIndex = 15; // // numRoll // @@ -343,7 +343,7 @@ private void InitializeComponent() 0, 0, 65536}); - this.numRoll.Location = new System.Drawing.Point(82, 168); + this.numRoll.Location = new System.Drawing.Point(82, 220); this.numRoll.LoopValues = false; this.numRoll.Maximum = new decimal(new int[] { 360, @@ -352,7 +352,7 @@ private void InitializeComponent() 0}); this.numRoll.Name = "numRoll"; this.numRoll.Size = new System.Drawing.Size(237, 22); - this.numRoll.TabIndex = 13; + this.numRoll.TabIndex = 17; // // FormWayPoint // @@ -360,7 +360,7 @@ private void InitializeComponent() this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.butCancel; - this.ClientSize = new System.Drawing.Size(331, 255); + this.ClientSize = new System.Drawing.Size(331, 333); this.Controls.Add(this.numRoll); this.Controls.Add(this.numRotationY); this.Controls.Add(this.numRotationX); diff --git a/TombLib/TombLib.Test/WayPointInstanceTests.cs b/TombLib/TombLib.Test/WayPointInstanceTests.cs index 895945d86..5f7cd2320 100644 --- a/TombLib/TombLib.Test/WayPointInstanceTests.cs +++ b/TombLib/TombLib.Test/WayPointInstanceTests.cs @@ -6,49 +6,60 @@ namespace TombLib.Test public class WayPointInstanceTests { [TestMethod] - public void WayPoint_AutoNaming_DefaultName() + public void WayPoint_DefaultType() { // Arrange & Act var wayPoint = new WayPointInstance(); // Assert - Assert.AreEqual("WayPoint_0", wayPoint.Name, "Default WayPoint name should be 'WayPoint_0'"); + Assert.AreEqual(WayPointType.Point, wayPoint.Type, "Default Type should be Point"); } [TestMethod] - public void WayPoint_AutoNaming_CustomBaseName() + public void WayPoint_TypeCanBeSet() { // Arrange var wayPoint = new WayPointInstance(); // Act - wayPoint.Name = "Camera"; - wayPoint.Number = 0; + wayPoint.Type = WayPointType.Bezier; // Assert - Assert.AreEqual("Camera_0", wayPoint.Name, "Custom name should be 'Camera_0'"); + Assert.AreEqual(WayPointType.Bezier, wayPoint.Type, "Type should be settable to Bezier"); } [TestMethod] - public void WayPoint_AutoNaming_SequenceChange() + public void WayPoint_AutoNaming_SingularType() { - // Arrange + // Arrange & Act var wayPoint = new WayPointInstance(); - wayPoint.Name = "MyPath"; + wayPoint.Type = WayPointType.Circle; + wayPoint.BaseName = "Patrol"; - // Act + // Assert + Assert.AreEqual("Patrol", wayPoint.Name, "Singular type should use base name only"); + } + + [TestMethod] + public void WayPoint_AutoNaming_MultiPointType() + { + // Arrange & Act + var wayPoint = new WayPointInstance(); + wayPoint.Type = WayPointType.Linear; + wayPoint.BaseName = "Path"; wayPoint.Number = 5; // Assert - Assert.AreEqual("MyPath_5", wayPoint.Name, "Name should update to 'MyPath_5' when number changes"); + Assert.AreEqual("Path_5", wayPoint.Name, "Multi-point type should use BaseName_Number format"); } [TestMethod] - public void WayPoint_AutoNaming_NumberChange() + public void WayPoint_AutoNaming_NumberChangeLinear() { // Arrange var wayPoint = new WayPointInstance(); - wayPoint.Name = "Camera"; + wayPoint.Type = WayPointType.Linear; + wayPoint.BaseName = "Camera"; wayPoint.Number = 3; // Act @@ -59,41 +70,64 @@ public void WayPoint_AutoNaming_NumberChange() } [TestMethod] - public void WayPoint_AutoNaming_PreservesBaseName() + public void WayPoint_AutoNaming_TypeChangeToSingular() { // Arrange var wayPoint = new WayPointInstance(); - wayPoint.Name = "PatrolPath"; - wayPoint.Number = 0; + wayPoint.Type = WayPointType.Bezier; + wayPoint.BaseName = "Target"; + wayPoint.Number = 3; + Assert.AreEqual("Target_3", wayPoint.Name, "Initially should be Target_3"); // Act - wayPoint.Number = 10; + wayPoint.Type = WayPointType.Ellipse; // Assert - Assert.AreEqual("PatrolPath_10", wayPoint.Name, "Base name 'PatrolPath' should be preserved"); + Assert.AreEqual("Target", wayPoint.Name, "After changing to singular type, name should be just 'Target'"); } [TestMethod] - public void WayPoint_DefaultPathType() + public void WayPoint_RequiresRadius_Circle() { // Arrange & Act var wayPoint = new WayPointInstance(); + wayPoint.Type = WayPointType.Circle; // Assert - Assert.AreEqual(PathType.Linear, wayPoint.PathType, "Default PathType should be Linear"); + Assert.IsTrue(wayPoint.RequiresRadius(), "Circle should require radius"); } [TestMethod] - public void WayPoint_PathTypeCanBeSet() + public void WayPoint_RequiresTwoRadii_Ellipse() { - // Arrange + // Arrange & Act var wayPoint = new WayPointInstance(); + wayPoint.Type = WayPointType.Ellipse; - // Act - wayPoint.PathType = PathType.Bezier; + // Assert + Assert.IsTrue(wayPoint.RequiresTwoRadii(), "Ellipse should require two radii"); + } + + [TestMethod] + public void WayPoint_IsSingularType_Point() + { + // Arrange & Act + var wayPoint = new WayPointInstance(); + wayPoint.Type = WayPointType.Point; + + // Assert + Assert.IsTrue(wayPoint.IsSingularType(), "Point should be a singular type"); + } + + [TestMethod] + public void WayPoint_IsSingularType_Linear() + { + // Arrange & Act + var wayPoint = new WayPointInstance(); + wayPoint.Type = WayPointType.Linear; // Assert - Assert.AreEqual(PathType.Bezier, wayPoint.PathType, "PathType should be settable to Bezier"); + Assert.IsFalse(wayPoint.IsSingularType(), "Linear should not be a singular type"); } [TestMethod] From 3e533e8bdc3ef2ba9172e0e436184605fc79191c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:12:39 +0000 Subject: [PATCH 13/36] Implement batch type updates and selective compilation for WayPoints Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Forms/FormWayPoint.cs | 24 +++++++- .../TombEngine/LevelCompilerTombEngine.cs | 59 +++++++++++++------ 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index 442042e65..b99f05d1b 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Windows.Forms; using DarkUI.Forms; using TombLib.LevelData; @@ -80,16 +81,35 @@ private void UpdateFieldVisibility() private void butOK_Click(object sender, EventArgs e) { + var oldType = _wayPoint.Type; + var newType = (WayPointType)cmbType.SelectedIndex; + var sequence = (ushort)numSequence.Value; + _wayPoint.BaseName = txtName.Text; - _wayPoint.Sequence = (ushort)numSequence.Value; + _wayPoint.Sequence = sequence; _wayPoint.Number = (ushort)numNumber.Value; - _wayPoint.Type = (WayPointType)cmbType.SelectedIndex; + _wayPoint.Type = newType; _wayPoint.Radius1 = (float)numRadius1.Value; _wayPoint.Radius2 = (float)numRadius2.Value; _wayPoint.RotationX = (float)numRotationX.Value; _wayPoint.RotationY = (float)numRotationY.Value; _wayPoint.Roll = (float)numRoll.Value; + // Batch type update: if type changed, update all waypoints in the same sequence + if (oldType != newType && _editor?.Level != null) + { + foreach (var room in _editor.Level.ExistingRooms) + { + foreach (var obj in room.Objects.OfType()) + { + if (obj.Sequence == sequence && obj != _wayPoint) + { + obj.Type = newType; + } + } + } + } + DialogResult = DialogResult.OK; Close(); } diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs index 00975b1a6..f66213e93 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs @@ -338,27 +338,50 @@ private void BuildCamerasAndSinks() lastIndex = _flyByCameras[i].Index; } - // Collect waypoints + // Collect waypoints with selective compilation logic + // If any waypoint in a sequence is singular type, only compile that one + var waypointsBySequence = new Dictionary>(); foreach (var instance in _wayPointTable.Keys) { - Vector3 position = instance.Room.WorldPos + instance.Position; - _wayPoints.Add(new TombEngineWayPoint + if (!waypointsBySequence.ContainsKey(instance.Sequence)) + waypointsBySequence[instance.Sequence] = new List(); + waypointsBySequence[instance.Sequence].Add(instance); + } + + foreach (var sequencePair in waypointsBySequence) + { + var waypoints = sequencePair.Value; + + // Check if any waypoint in this sequence is singular type + bool hasSingularType = waypoints.Any(wp => wp.IsSingularType()); + + // If there's a singular type, only compile singular type waypoints + // Otherwise, compile all waypoints + var waypointsToCompile = hasSingularType + ? waypoints.Where(wp => wp.IsSingularType()).ToList() + : waypoints; + + foreach (var instance in waypointsToCompile) { - X = (int)Math.Round(position.X), - Y = (int)Math.Round(-position.Y), - Z = (int)Math.Round(position.Z), - Room = _roomRemapping[instance.Room], - RotationX = instance.RotationX, - RotationY = instance.RotationY, - Roll = instance.Roll, - Sequence = instance.Sequence, - Number = instance.Number, - Type = (int)instance.Type, - Radius1 = instance.Radius1, - Radius2 = instance.Radius2, - Name = instance.Name, - LuaName = instance.LuaName ?? string.Empty - }); + Vector3 position = instance.Room.WorldPos + instance.Position; + _wayPoints.Add(new TombEngineWayPoint + { + X = (int)Math.Round(position.X), + Y = (int)Math.Round(-position.Y), + Z = (int)Math.Round(position.Z), + Room = _roomRemapping[instance.Room], + RotationX = instance.RotationX, + RotationY = instance.RotationY, + Roll = instance.Roll, + Sequence = instance.Sequence, + Number = instance.Number, + Type = (int)instance.Type, + Radius1 = instance.Radius1, + Radius2 = instance.Radius2, + Name = instance.Name, + LuaName = instance.LuaName ?? string.Empty + }); + } } _wayPoints.Sort((x, y) => { From 0286387494ff58ecd3ae5c32985cf587d7a68796 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:18:32 +0000 Subject: [PATCH 14/36] Fix GraphicsDevice.Extensions compilation error in DrawWayPointShape Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index a58a74d32..47138e874 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1595,7 +1595,7 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) vertices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Dynamic); // Draw the shape - var effect = _legacyDevice.Extensions.DeviceManager.___LegacyEffects["Solid"]; + var effect = DeviceManager.DefaultDeviceManager.___LegacyEffects["Solid"]; effect.Parameters["ModelViewProjection"].SetValue(_viewProjection.ToSharpDX()); effect.Parameters["Color"].SetValue(color); effect.Techniques[0].Passes[0].Apply(); From eae8208ea62927b3089cae36f0a2e786177245e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:31:06 +0000 Subject: [PATCH 15/36] Major refactor: Remove Sequence, use Name as identifier, disable fields instead of hiding, rename Radius to Dimension, add name validation Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Forms/FormWayPoint.Designer.cs | 210 ++++++++---------- TombEditor/Forms/FormWayPoint.cs | 134 +++++++++-- TombLib/TombLib.Test/WayPointInstanceTests.cs | 10 +- .../TombEngine/LevelCompilerTombEngine.cs | 66 ++++-- .../LevelData/Compilers/TombEngine/Structs.cs | 1 - .../Compilers/TombEngine/TombEngine.cs | 1 - TombLib/TombLib/LevelData/IO/Prj2Loader.cs | 7 +- TombLib/TombLib/LevelData/IO/Prj2Writer.cs | 19 +- .../LevelData/Instances/WayPointInstance.cs | 47 ++-- 9 files changed, 287 insertions(+), 208 deletions(-) diff --git a/TombEditor/Forms/FormWayPoint.Designer.cs b/TombEditor/Forms/FormWayPoint.Designer.cs index 6074ccc6d..692793127 100644 --- a/TombEditor/Forms/FormWayPoint.Designer.cs +++ b/TombEditor/Forms/FormWayPoint.Designer.cs @@ -33,27 +33,24 @@ private void InitializeComponent() this.butCancel = new DarkUI.Controls.DarkButton(); this.butOK = new DarkUI.Controls.DarkButton(); this.lblName = new DarkUI.Controls.DarkLabel(); - this.lblSequence = new DarkUI.Controls.DarkLabel(); this.lblNumber = new DarkUI.Controls.DarkLabel(); this.lblType = new DarkUI.Controls.DarkLabel(); - this.lblRadius1 = new DarkUI.Controls.DarkLabel(); - this.lblRadius2 = new DarkUI.Controls.DarkLabel(); + this.lblDimension1 = new DarkUI.Controls.DarkLabel(); + this.lblDimension2 = new DarkUI.Controls.DarkLabel(); this.lblRotationX = new DarkUI.Controls.DarkLabel(); this.lblRotationY = new DarkUI.Controls.DarkLabel(); this.lblRoll = new DarkUI.Controls.DarkLabel(); this.txtName = new DarkUI.Controls.DarkTextBox(); - this.numSequence = new DarkUI.Controls.DarkNumericUpDown(); this.numNumber = new DarkUI.Controls.DarkNumericUpDown(); this.cmbType = new DarkUI.Controls.DarkComboBox(); - this.numRadius1 = new DarkUI.Controls.DarkNumericUpDown(); - this.numRadius2 = new DarkUI.Controls.DarkNumericUpDown(); + this.numDimension1 = new DarkUI.Controls.DarkNumericUpDown(); + this.numDimension2 = new DarkUI.Controls.DarkNumericUpDown(); this.numRotationX = new DarkUI.Controls.DarkNumericUpDown(); this.numRotationY = new DarkUI.Controls.DarkNumericUpDown(); this.numRoll = new DarkUI.Controls.DarkNumericUpDown(); - ((System.ComponentModel.ISupportInitialize)(this.numSequence)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numNumber)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRadius1)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRadius2)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numDimension1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numDimension2)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numRotationX)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numRotationY)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numRoll)).BeginInit(); @@ -64,10 +61,10 @@ private void InitializeComponent() this.butCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.butCancel.Checked = false; this.butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.butCancel.Location = new System.Drawing.Point(239, 298); + this.butCancel.Location = new System.Drawing.Point(239, 222); this.butCancel.Name = "butCancel"; this.butCancel.Size = new System.Drawing.Size(80, 23); - this.butCancel.TabIndex = 12; + this.butCancel.TabIndex = 17; this.butCancel.Text = "Cancel"; this.butCancel.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; this.butCancel.Click += new System.EventHandler(this.butCancel_Click); @@ -76,10 +73,10 @@ private void InitializeComponent() // this.butOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.butOK.Checked = false; - this.butOK.Location = new System.Drawing.Point(153, 298); + this.butOK.Location = new System.Drawing.Point(153, 222); this.butOK.Name = "butOK"; this.butOK.Size = new System.Drawing.Size(80, 23); - this.butOK.TabIndex = 11; + this.butOK.TabIndex = 16; this.butOK.Text = "OK"; this.butOK.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; this.butOK.Click += new System.EventHandler(this.butOK_Click); @@ -90,115 +87,87 @@ private void InitializeComponent() this.lblName.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); this.lblName.Location = new System.Drawing.Point(12, 15); this.lblName.Name = "lblName"; - this.lblName.Size = new System.Drawing.Size(39, 13); + this.lblName.Size = new System.Drawing.Point(39, 13); this.lblName.TabIndex = 0; this.lblName.Text = "Name:"; // - // lblSequence - // - this.lblSequence.AutoSize = true; - this.lblSequence.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblSequence.Location = new System.Drawing.Point(12, 41); - this.lblSequence.Name = "lblSequence"; - this.lblSequence.Size = new System.Drawing.Size(60, 13); - this.lblSequence.TabIndex = 2; - this.lblSequence.Text = "Sequence:"; - // // lblNumber // this.lblNumber.AutoSize = true; this.lblNumber.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblNumber.Location = new System.Drawing.Point(12, 67); + this.lblNumber.Location = new System.Drawing.Point(12, 41); this.lblNumber.Name = "lblNumber"; this.lblNumber.Size = new System.Drawing.Size(51, 13); - this.lblNumber.TabIndex = 4; + this.lblNumber.TabIndex = 2; this.lblNumber.Text = "Number:"; // // lblType // this.lblType.AutoSize = true; this.lblType.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblType.Location = new System.Drawing.Point(12, 93); + this.lblType.Location = new System.Drawing.Point(12, 67); this.lblType.Name = "lblType"; this.lblType.Size = new System.Drawing.Size(34, 13); - this.lblType.TabIndex = 6; + this.lblType.TabIndex = 4; this.lblType.Text = "Type:"; // - // lblRadius1 + // lblDimension1 // - this.lblRadius1.AutoSize = true; - this.lblRadius1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRadius1.Location = new System.Drawing.Point(12, 119); - this.lblRadius1.Name = "lblRadius1"; - this.lblRadius1.Size = new System.Drawing.Size(52, 13); - this.lblRadius1.TabIndex = 8; - this.lblRadius1.Text = "Radius 1:"; + this.lblDimension1.AutoSize = true; + this.lblDimension1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblDimension1.Location = new System.Drawing.Point(12, 93); + this.lblDimension1.Name = "lblDimension1"; + this.lblDimension1.Size = new System.Drawing.Size(71, 13); + this.lblDimension1.TabIndex = 6; + this.lblDimension1.Text = "Dimension 1:"; // - // lblRadius2 + // lblDimension2 // - this.lblRadius2.AutoSize = true; - this.lblRadius2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRadius2.Location = new System.Drawing.Point(12, 145); - this.lblRadius2.Name = "lblRadius2"; - this.lblRadius2.Size = new System.Drawing.Size(52, 13); - this.lblRadius2.TabIndex = 10; - this.lblRadius2.Text = "Radius 2:"; + this.lblDimension2.AutoSize = true; + this.lblDimension2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.lblDimension2.Location = new System.Drawing.Point(12, 119); + this.lblDimension2.Name = "lblDimension2"; + this.lblDimension2.Size = new System.Drawing.Size(71, 13); + this.lblDimension2.TabIndex = 8; + this.lblDimension2.Text = "Dimension 2:"; // // lblRotationX // this.lblRotationX.AutoSize = true; this.lblRotationX.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRotationX.Location = new System.Drawing.Point(12, 171); + this.lblRotationX.Location = new System.Drawing.Point(12, 145); this.lblRotationX.Name = "lblRotationX"; this.lblRotationX.Size = new System.Drawing.Size(64, 13); - this.lblRotationX.TabIndex = 12; + this.lblRotationX.TabIndex = 10; this.lblRotationX.Text = "Rotation X:"; // // lblRotationY // this.lblRotationY.AutoSize = true; this.lblRotationY.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRotationY.Location = new System.Drawing.Point(12, 197); + this.lblRotationY.Location = new System.Drawing.Point(12, 171); this.lblRotationY.Name = "lblRotationY"; this.lblRotationY.Size = new System.Drawing.Size(63, 13); - this.lblRotationY.TabIndex = 14; + this.lblRotationY.TabIndex = 12; this.lblRotationY.Text = "Rotation Y:"; // // lblRoll // this.lblRoll.AutoSize = true; this.lblRoll.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRoll.Location = new System.Drawing.Point(12, 223); + this.lblRoll.Location = new System.Drawing.Point(12, 197); this.lblRoll.Name = "lblRoll"; this.lblRoll.Size = new System.Drawing.Size(42, 13); - this.lblRoll.TabIndex = 16; + this.lblRoll.TabIndex = 14; this.lblRoll.Text = "Z Axis:"; // // txtName // - this.txtName.Location = new System.Drawing.Point(82, 12); + this.txtName.Location = new System.Drawing.Point(92, 12); this.txtName.Name = "txtName"; - this.txtName.Size = new System.Drawing.Size(237, 20); + this.txtName.Size = new System.Drawing.Size(227, 20); this.txtName.TabIndex = 1; // - // numSequence - // - this.numSequence.IncrementAlternate = new decimal(new int[] { - 10, - 0, - 0, - 65536}); - this.numSequence.Location = new System.Drawing.Point(82, 38); - this.numSequence.LoopValues = false; - this.numSequence.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.numSequence.Name = "numSequence"; - this.numSequence.Size = new System.Drawing.Size(237, 22); - this.numSequence.TabIndex = 3; - // // numNumber // this.numNumber.IncrementAlternate = new decimal(new int[] { @@ -206,7 +175,7 @@ private void InitializeComponent() 0, 0, 65536}); - this.numNumber.Location = new System.Drawing.Point(82, 64); + this.numNumber.Location = new System.Drawing.Point(92, 38); this.numNumber.LoopValues = false; this.numNumber.Maximum = new decimal(new int[] { 65535, @@ -214,8 +183,8 @@ private void InitializeComponent() 0, 0}); this.numNumber.Name = "numNumber"; - this.numNumber.Size = new System.Drawing.Size(237, 22); - this.numNumber.TabIndex = 5; + this.numNumber.Size = new System.Drawing.Size(227, 22); + this.numNumber.TabIndex = 3; // // cmbType // @@ -228,65 +197,65 @@ private void InitializeComponent() "Rectangle", "Linear", "Bezier"}); - this.cmbType.Location = new System.Drawing.Point(82, 90); + this.cmbType.Location = new System.Drawing.Point(92, 64); this.cmbType.Name = "cmbType"; - this.cmbType.Size = new System.Drawing.Size(237, 23); - this.cmbType.TabIndex = 7; + this.cmbType.Size = new System.Drawing.Size(227, 23); + this.cmbType.TabIndex = 5; this.cmbType.SelectedIndexChanged += new System.EventHandler(this.cmbType_SelectedIndexChanged); // - // numRadius1 + // numDimension1 // - this.numRadius1.DecimalPlaces = 2; - this.numRadius1.IncrementAlternate = new decimal(new int[] { + this.numDimension1.DecimalPlaces = 2; + this.numDimension1.IncrementAlternate = new decimal(new int[] { 100, 0, 0, 0}); - this.numRadius1.Location = new System.Drawing.Point(82, 116); - this.numRadius1.LoopValues = false; - this.numRadius1.Maximum = new decimal(new int[] { + this.numDimension1.Location = new System.Drawing.Point(92, 90); + this.numDimension1.LoopValues = false; + this.numDimension1.Maximum = new decimal(new int[] { 100000, 0, 0, 0}); - this.numRadius1.Minimum = new decimal(new int[] { + this.numDimension1.Minimum = new decimal(new int[] { 0, 0, 0, 0}); - this.numRadius1.Name = "numRadius1"; - this.numRadius1.Size = new System.Drawing.Size(237, 22); - this.numRadius1.TabIndex = 9; - this.numRadius1.Value = new decimal(new int[] { + this.numDimension1.Name = "numDimension1"; + this.numDimension1.Size = new System.Drawing.Size(227, 22); + this.numDimension1.TabIndex = 7; + this.numDimension1.Value = new decimal(new int[] { 1024, 0, 0, 0}); // - // numRadius2 + // numDimension2 // - this.numRadius2.DecimalPlaces = 2; - this.numRadius2.IncrementAlternate = new decimal(new int[] { + this.numDimension2.DecimalPlaces = 2; + this.numDimension2.IncrementAlternate = new decimal(new int[] { 100, 0, 0, 0}); - this.numRadius2.Location = new System.Drawing.Point(82, 142); - this.numRadius2.LoopValues = false; - this.numRadius2.Maximum = new decimal(new int[] { + this.numDimension2.Location = new System.Drawing.Point(92, 116); + this.numDimension2.LoopValues = false; + this.numDimension2.Maximum = new decimal(new int[] { 100000, 0, 0, 0}); - this.numRadius2.Minimum = new decimal(new int[] { + this.numDimension2.Minimum = new decimal(new int[] { 0, 0, 0, 0}); - this.numRadius2.Name = "numRadius2"; - this.numRadius2.Size = new System.Drawing.Size(237, 22); - this.numRadius2.TabIndex = 11; - this.numRadius2.Value = new decimal(new int[] { + this.numDimension2.Name = "numDimension2"; + this.numDimension2.Size = new System.Drawing.Size(227, 22); + this.numDimension2.TabIndex = 9; + this.numDimension2.Value = new decimal(new int[] { 1024, 0, 0, @@ -300,7 +269,7 @@ private void InitializeComponent() 0, 0, 65536}); - this.numRotationX.Location = new System.Drawing.Point(82, 168); + this.numRotationX.Location = new System.Drawing.Point(92, 142); this.numRotationX.LoopValues = false; this.numRotationX.Maximum = new decimal(new int[] { 90, @@ -313,8 +282,8 @@ private void InitializeComponent() 0, -2147483648}); this.numRotationX.Name = "numRotationX"; - this.numRotationX.Size = new System.Drawing.Size(237, 22); - this.numRotationX.TabIndex = 13; + this.numRotationX.Size = new System.Drawing.Size(227, 22); + this.numRotationX.TabIndex = 11; // // numRotationY // @@ -324,7 +293,7 @@ private void InitializeComponent() 0, 0, 65536}); - this.numRotationY.Location = new System.Drawing.Point(82, 194); + this.numRotationY.Location = new System.Drawing.Point(92, 168); this.numRotationY.LoopValues = false; this.numRotationY.Maximum = new decimal(new int[] { 360, @@ -332,8 +301,8 @@ private void InitializeComponent() 0, 0}); this.numRotationY.Name = "numRotationY"; - this.numRotationY.Size = new System.Drawing.Size(237, 22); - this.numRotationY.TabIndex = 15; + this.numRotationY.Size = new System.Drawing.Size(227, 22); + this.numRotationY.TabIndex = 13; // // numRoll // @@ -343,7 +312,7 @@ private void InitializeComponent() 0, 0, 65536}); - this.numRoll.Location = new System.Drawing.Point(82, 220); + this.numRoll.Location = new System.Drawing.Point(92, 194); this.numRoll.LoopValues = false; this.numRoll.Maximum = new decimal(new int[] { 360, @@ -351,8 +320,8 @@ private void InitializeComponent() 0, 0}); this.numRoll.Name = "numRoll"; - this.numRoll.Size = new System.Drawing.Size(237, 22); - this.numRoll.TabIndex = 17; + this.numRoll.Size = new System.Drawing.Size(227, 22); + this.numRoll.TabIndex = 15; // // FormWayPoint // @@ -360,24 +329,22 @@ private void InitializeComponent() this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.butCancel; - this.ClientSize = new System.Drawing.Size(331, 333); + this.ClientSize = new System.Drawing.Size(331, 255); this.Controls.Add(this.numRoll); this.Controls.Add(this.numRotationY); this.Controls.Add(this.numRotationX); - this.Controls.Add(this.numRadius2); - this.Controls.Add(this.numRadius1); + this.Controls.Add(this.numDimension2); + this.Controls.Add(this.numDimension1); this.Controls.Add(this.cmbType); this.Controls.Add(this.numNumber); - this.Controls.Add(this.numSequence); this.Controls.Add(this.txtName); this.Controls.Add(this.lblRoll); this.Controls.Add(this.lblRotationY); this.Controls.Add(this.lblRotationX); - this.Controls.Add(this.lblRadius2); - this.Controls.Add(this.lblRadius1); + this.Controls.Add(this.lblDimension2); + this.Controls.Add(this.lblDimension1); this.Controls.Add(this.lblType); this.Controls.Add(this.lblNumber); - this.Controls.Add(this.lblSequence); this.Controls.Add(this.lblName); this.Controls.Add(this.butCancel); this.Controls.Add(this.butOK); @@ -390,10 +357,9 @@ private void InitializeComponent() this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "WayPoint"; this.Load += new System.EventHandler(this.FormWayPoint_Load); - ((System.ComponentModel.ISupportInitialize)(this.numSequence)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numNumber)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRadius1)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRadius2)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numDimension1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numDimension2)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numRotationX)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numRotationY)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numRoll)).EndInit(); @@ -407,20 +373,18 @@ private void InitializeComponent() private DarkButton butOK; private DarkButton butCancel; private DarkLabel lblName; - private DarkLabel lblSequence; private DarkLabel lblNumber; private DarkLabel lblType; - private DarkLabel lblRadius1; - private DarkLabel lblRadius2; + private DarkLabel lblDimension1; + private DarkLabel lblDimension2; private DarkLabel lblRotationX; private DarkLabel lblRotationY; private DarkLabel lblRoll; private DarkTextBox txtName; - private DarkNumericUpDown numSequence; private DarkNumericUpDown numNumber; private DarkComboBox cmbType; - private DarkNumericUpDown numRadius1; - private DarkNumericUpDown numRadius2; + private DarkNumericUpDown numDimension1; + private DarkNumericUpDown numDimension2; private DarkNumericUpDown numRotationX; private DarkNumericUpDown numRotationY; private DarkNumericUpDown numRoll; diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index b99f05d1b..e82e8bb85 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -29,12 +29,26 @@ private void butCancel_Click(object sender, EventArgs e) private void FormWayPoint_Load(object sender, EventArgs e) { - txtName.Text = _wayPoint.BaseName; - numSequence.Value = _wayPoint.Sequence; + // Extract base name from full name (remove _number suffix if present) + string baseName = _wayPoint.Name; + if (!_wayPoint.IsSingularType()) + { + int lastUnderscore = baseName.LastIndexOf('_'); + if (lastUnderscore >= 0) + { + string suffix = baseName.Substring(lastUnderscore + 1); + if (ushort.TryParse(suffix, out _)) + { + baseName = baseName.Substring(0, lastUnderscore); + } + } + } + + txtName.Text = baseName; numNumber.Value = _wayPoint.Number; cmbType.SelectedIndex = (int)_wayPoint.Type; - numRadius1.Value = (decimal)_wayPoint.Radius1; - numRadius2.Value = (decimal)_wayPoint.Radius2; + numDimension1.Value = (decimal)_wayPoint.Radius1; + numDimension2.Value = (decimal)_wayPoint.Radius2; numRotationX.Value = (decimal)_wayPoint.RotationX; numRotationY.Value = (decimal)_wayPoint.RotationY; numRoll.Value = (decimal)_wayPoint.Roll; @@ -58,53 +72,127 @@ private void UpdateFieldVisibility() type == WayPointType.Square || type == WayPointType.Rectangle; - // Number field only for multi-point types - lblNumber.Visible = !isSingular; - numNumber.Visible = !isSingular; + // Number field only for multi-point types - disable instead of hide + numNumber.Enabled = !isSingular; - // Radius fields only for shape types - bool requiresRadius = type == WayPointType.Circle || + // Dimension fields only for shape types - disable instead of hide + bool requiresDimension = type == WayPointType.Circle || type == WayPointType.Ellipse || type == WayPointType.Square || type == WayPointType.Rectangle; - lblRadius1.Visible = requiresRadius; - numRadius1.Visible = requiresRadius; + numDimension1.Enabled = requiresDimension; - // Radius2 only for ellipse and rectangle - bool requiresTwoRadii = type == WayPointType.Ellipse || + // Dimension2 only for ellipse and rectangle - disable instead of hide + bool requiresTwoDimensions = type == WayPointType.Ellipse || type == WayPointType.Rectangle; - lblRadius2.Visible = requiresTwoRadii; - numRadius2.Visible = requiresTwoRadii; + numDimension2.Enabled = requiresTwoDimensions; } private void butOK_Click(object sender, EventArgs e) { + // Validate name is not empty + string newName = txtName.Text.Trim(); + if (string.IsNullOrEmpty(newName)) + { + DarkMessageBox.Show(this, "WayPoint name cannot be empty.", "Validation Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + var oldType = _wayPoint.Type; var newType = (WayPointType)cmbType.SelectedIndex; - var sequence = (ushort)numSequence.Value; + + // Extract old base name for comparison + string oldBaseName = _wayPoint.Name; + if (!_wayPoint.IsSingularType()) + { + int lastUnderscore = oldBaseName.LastIndexOf('_'); + if (lastUnderscore >= 0) + { + string suffix = oldBaseName.Substring(lastUnderscore + 1); + if (ushort.TryParse(suffix, out _)) + { + oldBaseName = oldBaseName.Substring(0, lastUnderscore); + } + } + } + + // Check for duplicate names only if name changed + bool nameChanged = oldBaseName != newName; + if (nameChanged && _editor?.Level != null) + { + foreach (var room in _editor.Level.ExistingRooms) + { + foreach (var obj in room.Objects.OfType()) + { + if (obj != _wayPoint) + { + // Extract base name from existing waypoint + string existingBaseName = obj.Name; + if (!obj.IsSingularType()) + { + int lastUnderscore = existingBaseName.LastIndexOf('_'); + if (lastUnderscore >= 0) + { + string suffix = existingBaseName.Substring(lastUnderscore + 1); + if (ushort.TryParse(suffix, out _)) + { + existingBaseName = existingBaseName.Substring(0, lastUnderscore); + } + } + } + + if (existingBaseName == newName) + { + DarkMessageBox.Show(this, $"A WayPoint with the name '{newName}' already exists.", "Duplicate Name", + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + } + } + } + } - _wayPoint.BaseName = txtName.Text; - _wayPoint.Sequence = sequence; + // Update waypoint properties + _wayPoint.Name = newName; _wayPoint.Number = (ushort)numNumber.Value; _wayPoint.Type = newType; - _wayPoint.Radius1 = (float)numRadius1.Value; - _wayPoint.Radius2 = (float)numRadius2.Value; + _wayPoint.Radius1 = (float)numDimension1.Value; + _wayPoint.Radius2 = (float)numDimension2.Value; _wayPoint.RotationX = (float)numRotationX.Value; _wayPoint.RotationY = (float)numRotationY.Value; _wayPoint.Roll = (float)numRoll.Value; - // Batch type update: if type changed, update all waypoints in the same sequence + // Batch type update: if type changed, update all waypoints with the same name if (oldType != newType && _editor?.Level != null) { foreach (var room in _editor.Level.ExistingRooms) { foreach (var obj in room.Objects.OfType()) { - if (obj.Sequence == sequence && obj != _wayPoint) + if (obj != _wayPoint) { - obj.Type = newType; + // Extract base name + string objBaseName = obj.Name; + if (!obj.IsSingularType()) + { + int lastUnderscore = objBaseName.LastIndexOf('_'); + if (lastUnderscore >= 0) + { + string suffix = objBaseName.Substring(lastUnderscore + 1); + if (ushort.TryParse(suffix, out _)) + { + objBaseName = objBaseName.Substring(0, lastUnderscore); + } + } + } + + if (objBaseName == newName) + { + obj.Type = newType; + } } } } diff --git a/TombLib/TombLib.Test/WayPointInstanceTests.cs b/TombLib/TombLib.Test/WayPointInstanceTests.cs index 5f7cd2320..7f19491db 100644 --- a/TombLib/TombLib.Test/WayPointInstanceTests.cs +++ b/TombLib/TombLib.Test/WayPointInstanceTests.cs @@ -34,7 +34,7 @@ public void WayPoint_AutoNaming_SingularType() // Arrange & Act var wayPoint = new WayPointInstance(); wayPoint.Type = WayPointType.Circle; - wayPoint.BaseName = "Patrol"; + wayPoint.Name = "Patrol"; // Assert Assert.AreEqual("Patrol", wayPoint.Name, "Singular type should use base name only"); @@ -46,11 +46,11 @@ public void WayPoint_AutoNaming_MultiPointType() // Arrange & Act var wayPoint = new WayPointInstance(); wayPoint.Type = WayPointType.Linear; - wayPoint.BaseName = "Path"; + wayPoint.Name = "Path"; wayPoint.Number = 5; // Assert - Assert.AreEqual("Path_5", wayPoint.Name, "Multi-point type should use BaseName_Number format"); + Assert.AreEqual("Path_5", wayPoint.Name, "Multi-point type should use Name_Number format"); } [TestMethod] @@ -59,7 +59,7 @@ public void WayPoint_AutoNaming_NumberChangeLinear() // Arrange var wayPoint = new WayPointInstance(); wayPoint.Type = WayPointType.Linear; - wayPoint.BaseName = "Camera"; + wayPoint.Name = "Camera"; wayPoint.Number = 3; // Act @@ -75,7 +75,7 @@ public void WayPoint_AutoNaming_TypeChangeToSingular() // Arrange var wayPoint = new WayPointInstance(); wayPoint.Type = WayPointType.Bezier; - wayPoint.BaseName = "Target"; + wayPoint.Name = "Target"; wayPoint.Number = 3; Assert.AreEqual("Target_3", wayPoint.Name, "Initially should be Target_3"); diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs index f66213e93..6fd2041c5 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs @@ -338,21 +338,40 @@ private void BuildCamerasAndSinks() lastIndex = _flyByCameras[i].Index; } - // Collect waypoints with selective compilation logic - // If any waypoint in a sequence is singular type, only compile that one - var waypointsBySequence = new Dictionary>(); + // Collect waypoints with name-based grouping and selective compilation logic + // Skip waypoints without a name + // If any waypoint with a name is singular type, only compile singular types with that name + var waypointsByName = new Dictionary>(); foreach (var instance in _wayPointTable.Keys) { - if (!waypointsBySequence.ContainsKey(instance.Sequence)) - waypointsBySequence[instance.Sequence] = new List(); - waypointsBySequence[instance.Sequence].Add(instance); + // Extract base name + string baseName = instance.Name; + if (string.IsNullOrEmpty(baseName)) + continue; // Skip waypoints without a name + + if (!instance.IsSingularType()) + { + int lastUnderscore = baseName.LastIndexOf('_'); + if (lastUnderscore >= 0) + { + string suffix = baseName.Substring(lastUnderscore + 1); + if (ushort.TryParse(suffix, out _)) + { + baseName = baseName.Substring(0, lastUnderscore); + } + } + } + + if (!waypointsByName.ContainsKey(baseName)) + waypointsByName[baseName] = new List(); + waypointsByName[baseName].Add(instance); } - foreach (var sequencePair in waypointsBySequence) + foreach (var namePair in waypointsByName) { - var waypoints = sequencePair.Value; + var waypoints = namePair.Value; - // Check if any waypoint in this sequence is singular type + // Check if any waypoint with this name is singular type bool hasSingularType = waypoints.Any(wp => wp.IsSingularType()); // If there's a singular type, only compile singular type waypoints @@ -373,7 +392,6 @@ private void BuildCamerasAndSinks() RotationX = instance.RotationX, RotationY = instance.RotationY, Roll = instance.Roll, - Sequence = instance.Sequence, Number = instance.Number, Type = (int)instance.Type, Radius1 = instance.Radius1, @@ -383,27 +401,41 @@ private void BuildCamerasAndSinks() }); } } + + // Sort by name, then number _wayPoints.Sort((x, y) => { - int seqCompare = x.Sequence.CompareTo(y.Sequence); - if (seqCompare != 0) return seqCompare; + int nameCompare = string.Compare(x.Name, y.Name, StringComparison.Ordinal); + if (nameCompare != 0) return nameCompare; return x.Number.CompareTo(y.Number); }); // Check waypoint duplicates - lastSeq = -1; + string lastName = ""; lastIndex = -1; for (int i = 0; i < _wayPoints.Count; i++) { - if (_wayPoints[i].Sequence != lastSeq) + // Extract base name for comparison + string baseName = _wayPoints[i].Name; + if (baseName.Contains("_")) + { + int lastUnderscore = baseName.LastIndexOf('_'); + string suffix = baseName.Substring(lastUnderscore + 1); + if (ushort.TryParse(suffix, out _)) + { + baseName = baseName.Substring(0, lastUnderscore); + } + } + + if (baseName != lastName) { - lastSeq = _wayPoints[i].Sequence; + lastName = baseName; lastIndex = -1; } - if (_wayPoints[i].Number == lastIndex && _wayPoints[i].Sequence == lastSeq) - _progressReporter.ReportWarn($"Warning: waypoint sequence {_wayPoints[i].Sequence} has duplicated waypoint with ID {lastIndex}"); + if (_wayPoints[i].Number == lastIndex && baseName == lastName) + _progressReporter.ReportWarn($"Warning: waypoint '{baseName}' has duplicated waypoint with number {lastIndex}"); lastIndex = _wayPoints[i].Number; } diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs index 69a833cba..cc1b805a8 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs @@ -595,7 +595,6 @@ public struct TombEngineWayPoint public float RotationX; public float RotationY; public float Roll; - public ushort Sequence; public ushort Number; public int Type; public float Radius1; diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs index b0afe65e8..6bcd879ec 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs @@ -87,7 +87,6 @@ private void WriteLevelTombEngine() writer.Write(waypoint.RotationX); writer.Write(waypoint.RotationY); writer.Write(waypoint.Roll); - writer.Write(waypoint.Sequence); writer.Write(waypoint.Number); writer.Write(waypoint.Type); writer.Write(waypoint.Radius1); diff --git a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs index fc79e3504..ff7aed0ea 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs @@ -1409,12 +1409,15 @@ private static bool LoadObjects(ChunkReader chunkIO, ChunkId idOuter, LevelSetti instance.SetArbitaryRotationsYX(chunkIO.Raw.ReadSingle(), chunkIO.Raw.ReadSingle()); instance.Roll = chunkIO.Raw.ReadSingle(); instance.ScriptId = ReadOptionalLEB128Int(chunkIO.Raw); - instance.BaseName = chunkIO.Raw.ReadStringUTF8(); + string baseName = chunkIO.Raw.ReadStringUTF8(); instance.Number = LEB128.ReadUShort(chunkIO.Raw); - instance.Sequence = LEB128.ReadUShort(chunkIO.Raw); instance.Type = (WayPointType)LEB128.ReadInt(chunkIO.Raw); instance.Radius1 = chunkIO.Raw.ReadSingle(); instance.Radius2 = chunkIO.Raw.ReadSingle(); + + // Set the name (will auto-format with _number if multi-point type) + instance.Name = baseName; + addObject(instance); newObjects.TryAdd(objectID, instance); } diff --git a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs index 1e31a1cd9..88a4aa7e0 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs @@ -689,9 +689,24 @@ private static void WriteObjects(ChunkWriter chunkIO, IEnumerable= 0) + { + string suffix = baseName.Substring(lastUnderscore + 1); + if (ushort.TryParse(suffix, out _)) + { + baseName = baseName.Substring(0, lastUnderscore); + } + } + } + + chunkIO.Raw.WriteStringUTF8(baseName); LEB128.Write(chunkIO.Raw, instance.Number); - LEB128.Write(chunkIO.Raw, instance.Sequence); LEB128.Write(chunkIO.Raw, (int)instance.Type); chunkIO.Raw.Write(instance.Radius1); chunkIO.Raw.Write(instance.Radius2); diff --git a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs index 33fd4c653..33860f763 100644 --- a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs +++ b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs @@ -16,26 +16,19 @@ public enum WayPointType public class WayPointInstance : PositionAndScriptBasedObjectInstance, IRotateableYXRoll { - private string _baseName = "WayPoint"; - private ushort _sequence; + private string _name = ""; private ushort _number; private WayPointType _type = WayPointType.Point; - public string BaseName - { - get { return _baseName; } - set { _baseName = value ?? "WayPoint"; } - } - public string Name { get { - // Singular types don't have number suffix + // Singular types use name as-is if (IsSingularType()) - return _baseName; + return _name; else - return _baseName + "_" + _number; + return _name + "_" + _number; } set { @@ -48,31 +41,25 @@ public string Name string suffix = value.Substring(lastUnderscore + 1); if (ushort.TryParse(suffix, out _)) { - _baseName = value.Substring(0, lastUnderscore); + _name = value.Substring(0, lastUnderscore); } else { - _baseName = value; + _name = value; } } else { - _baseName = value; + _name = value; } } else { - _baseName = "WayPoint"; + _name = ""; } } } - public ushort Sequence - { - get { return _sequence; } - set { _sequence = value; } - } - public ushort Number { get { return _number; } @@ -119,23 +106,22 @@ public WayPointInstance(ObjectInstance selectedObject = null) { if (selectedObject is WayPointInstance prevWayPoint) { - var currSeq = prevWayPoint.Sequence; var currNum = (ushort)(prevWayPoint.Number + 1); // Only push forward if it's a multi-point type if (!prevWayPoint.IsSingularType()) { - // Push next waypoints in sequence forward + // Push next waypoints with same name forward var level = selectedObject.Room.Level; + var prevName = prevWayPoint._name; foreach (var room in level.ExistingRooms) foreach (var instance in room.Objects.OfType()) - if (instance.Sequence == currSeq && instance.Number >= currNum) + if (instance._name == prevName && instance.Number >= currNum) instance.Number++; } - Sequence = currSeq; Number = prevWayPoint.IsSingularType() ? (ushort)0 : currNum; - _baseName = prevWayPoint._baseName; + _name = prevWayPoint._name; Type = prevWayPoint.Type; Radius1 = prevWayPoint.Radius1; Radius2 = prevWayPoint.Radius2; @@ -174,7 +160,6 @@ public override string ToString() { return "WayPoint " + ", Name = " + Name + - ", Sequence = " + Sequence + (IsSingularType() ? "" : ", Number = " + Number) + ", Type = " + Type + " (" + (Room?.ToString() ?? "NULL") + ")" + @@ -183,12 +168,6 @@ public override string ToString() GetScriptIDOrName(false); } - public string ShortName() => "WayPoint " + (IsSingularType() ? "" : "(" + Sequence + ":" + Number + ") ") + GetScriptIDOrName() + " (" + (Room?.ToString() ?? "NULL") + ")"; - - public override void CopyDependentLevelSettings(Room.CopyDependentLevelSettingsArgs args) - { - base.CopyDependentLevelSettings(args); - Sequence = args.ReassociateFlyBySequence(Sequence); - } + public string ShortName() => "WayPoint " + Name + (IsSingularType() ? "" : " (" + Number + ")") + " " + GetScriptIDOrName() + " (" + (Room?.ToString() ?? "NULL") + ")"; } } From 9015a2148ba695bb964edbab913f1b9567ddc9a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:45:14 +0000 Subject: [PATCH 16/36] Fix compilation errors: remove Sequence references and fix Size type in Designer Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 8 ++++---- TombEditor/Controls/Panel3D/Panel3DHelpers.cs | 4 ++-- TombEditor/Forms/FormWayPoint.Designer.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 47138e874..3eb8f41e7 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -198,8 +198,8 @@ private void DrawFlybyPath(Effect effect) } // Add the path of waypoints - if (_editor.SelectedObject is WayPointInstance && - AddWayPointPath(((WayPointInstance)_editor.SelectedObject).Sequence)) + if (_editor.SelectedObject is WayPointInstance waypoint && + AddWayPointPath(waypoint.Name)) { _legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullNone); _legacyDevice.SetVertexBuffer(_wayPointPathVertexBuffer); @@ -1158,8 +1158,8 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis var color = new Vector4(1.0f, 0.5f, 0.0f, 1.0f); // Orange color for waypoints - if (_editor.SelectedObject is WayPointInstance && (_editor.SelectedObject as WayPointInstance).Sequence == instance.Sequence) - color = MathC.GetRandomColorByIndex(instance.Sequence, 32, 0.7f); + if (_editor.SelectedObject is WayPointInstance selectedWaypoint && selectedWaypoint.Name == instance.Name) + color = MathC.GetRandomColorByIndex(instance.Name.GetHashCode(), 32, 0.7f); if (_highlightedObjects.Contains(instance)) { diff --git a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs index 6d6d14095..36c7f1622 100644 --- a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs +++ b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs @@ -183,7 +183,7 @@ private bool AddFlybyPath(int sequence) return true; } - private bool AddWayPointPath(int sequence) + private bool AddWayPointPath(string name) { // Collect all waypoints var wayPoints = new List(); @@ -191,7 +191,7 @@ private bool AddWayPointPath(int sequence) foreach (var room in _editor.Level.ExistingRooms) foreach (var instance in room.Objects.OfType()) { - if (instance.Sequence == sequence) + if (instance.Name == name) wayPoints.Add(instance); } diff --git a/TombEditor/Forms/FormWayPoint.Designer.cs b/TombEditor/Forms/FormWayPoint.Designer.cs index 692793127..49cce114a 100644 --- a/TombEditor/Forms/FormWayPoint.Designer.cs +++ b/TombEditor/Forms/FormWayPoint.Designer.cs @@ -87,7 +87,7 @@ private void InitializeComponent() this.lblName.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); this.lblName.Location = new System.Drawing.Point(12, 15); this.lblName.Name = "lblName"; - this.lblName.Size = new System.Drawing.Point(39, 13); + this.lblName.Size = new System.Drawing.Size(39, 13); this.lblName.TabIndex = 0; this.lblName.Text = "Name:"; // From d1d63c559848f53d4ae20b9358b9ce8cded8497a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:49:33 +0000 Subject: [PATCH 17/36] Fix remaining compilation errors: remove last Sequence reference and fix variable names Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 6 +++++- TombEditor/Controls/Panel3D/Panel3DHelpers.cs | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 3eb8f41e7..795b549aa 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1169,9 +1169,13 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis if (_editor.SelectedObject == instance) { // Add text message + string label = instance.IsSingularType() ? + $"WayPoint ({instance.Name}) " : + $"WayPoint ({instance.Name}:{instance.Number}) "; + textToDraw.Add(CreateTextTagForObject( instance.RotationPositionMatrix * _viewProjection, - "WayPoint (" + instance.Sequence + ":" + instance.Number + ") " + + label + instance.GetScriptIDOrName() + "\n" + GetObjectPositionString(instance.Room, instance) + GetObjectTriggerString(instance))); diff --git a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs index 36c7f1622..31c353137 100644 --- a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs +++ b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs @@ -207,8 +207,10 @@ private bool AddWayPointPath(string name) // Initialize variables for vertex buffer preparation var vertices = new List(); - var startColor = MathC.GetRandomColorByIndex(sequence, 32, 0.7f); - var endColor = MathC.GetRandomColorByIndex(sequence, 32, 0.3f); + // Use name hash for color selection + int nameHash = name.GetHashCode(); + var startColor = MathC.GetRandomColorByIndex(nameHash, 32, 0.7f); + var endColor = MathC.GetRandomColorByIndex(nameHash, 32, 0.3f); float th = _flybyPathThickness; From 0fb2dd5b14649f66e3db7b24ae693ce94d2fdefd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:53:53 +0000 Subject: [PATCH 18/36] Fix crash: ensure name hash is positive and non-zero for GetRandomColorByIndex Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 6 +++++- TombEditor/Controls/Panel3D/Panel3DHelpers.cs | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 795b549aa..4b756f4fb 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1159,7 +1159,11 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis var color = new Vector4(1.0f, 0.5f, 0.0f, 1.0f); // Orange color for waypoints if (_editor.SelectedObject is WayPointInstance selectedWaypoint && selectedWaypoint.Name == instance.Name) - color = MathC.GetRandomColorByIndex(instance.Name.GetHashCode(), 32, 0.7f); + { + int nameHash = Math.Abs(instance.Name?.GetHashCode() ?? 1); + if (nameHash == 0) nameHash = 1; + color = MathC.GetRandomColorByIndex(nameHash, 32, 0.7f); + } if (_highlightedObjects.Contains(instance)) { diff --git a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs index 31c353137..0605cb70f 100644 --- a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs +++ b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs @@ -207,8 +207,9 @@ private bool AddWayPointPath(string name) // Initialize variables for vertex buffer preparation var vertices = new List(); - // Use name hash for color selection - int nameHash = name.GetHashCode(); + // Use name hash for color selection (ensure positive and non-zero) + int nameHash = Math.Abs(name?.GetHashCode() ?? 1); + if (nameHash == 0) nameHash = 1; var startColor = MathC.GetRandomColorByIndex(nameHash, 32, 0.7f); var endColor = MathC.GetRandomColorByIndex(nameHash, 32, 0.3f); From f378c8645f5253cbe63444d1885399e9222f9f62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:02:43 +0000 Subject: [PATCH 19/36] Fix waypoint copy bugs: sync LuaName with Name, improve label format, expose BaseName property Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 6 ++--- .../LevelData/Instances/WayPointInstance.cs | 26 +++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 4b756f4fb..1dcf31f5a 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1172,10 +1172,10 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis if (_editor.SelectedObject == instance) { - // Add text message + // Add text message with format: Name (Number) for multi-point, just Name for singular string label = instance.IsSingularType() ? - $"WayPoint ({instance.Name}) " : - $"WayPoint ({instance.Name}:{instance.Number}) "; + $"{instance.BaseName} " : + $"{instance.BaseName} ({instance.Number}) "; textToDraw.Add(CreateTextTagForObject( instance.RotationPositionMatrix * _viewProjection, diff --git a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs index 33860f763..d325fb13b 100644 --- a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs +++ b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs @@ -20,6 +20,12 @@ public class WayPointInstance : PositionAndScriptBasedObjectInstance, IRotateabl private ushort _number; private WayPointType _type = WayPointType.Point; + // Public property to access the base name (without number suffix) + public string BaseName + { + get { return _name; } + } + public string Name { get @@ -57,19 +63,32 @@ public string Name { _name = ""; } + + // Update LuaName to match Name + LuaName = Name; } } public ushort Number { get { return _number; } - set { _number = value; } + set + { + _number = value; + // Update LuaName when number changes + LuaName = Name; + } } public WayPointType Type { get { return _type; } - set { _type = value; } + set + { + _type = value; + // Update LuaName when type changes (affects Name format) + LuaName = Name; + } } public float Radius1 { get; set; } = 1024.0f; // Default radius in units @@ -130,6 +149,9 @@ public WayPointInstance(ObjectInstance selectedObject = null) RotationX = prevWayPoint.RotationX; RotationY = prevWayPoint.RotationY; Roll = prevWayPoint.Roll; + + // Set LuaName to match Name + LuaName = Name; } } From 1cdc2fcd0db00ce49097ca9cfe944e155aef5476 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:09:06 +0000 Subject: [PATCH 20/36] Fix path preview and batch type update: use BaseName for comparisons Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DHelpers.cs | 2 +- TombEditor/Forms/FormWayPoint.cs | 24 +++---------------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs index 0605cb70f..b699dad9e 100644 --- a/TombEditor/Controls/Panel3D/Panel3DHelpers.cs +++ b/TombEditor/Controls/Panel3D/Panel3DHelpers.cs @@ -191,7 +191,7 @@ private bool AddWayPointPath(string name) foreach (var room in _editor.Level.ExistingRooms) foreach (var instance in room.Objects.OfType()) { - if (instance.Name == name) + if (instance.BaseName == name) wayPoints.Add(instance); } diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index e82e8bb85..aa4c22033 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -165,34 +165,16 @@ private void butOK_Click(object sender, EventArgs e) _wayPoint.RotationY = (float)numRotationY.Value; _wayPoint.Roll = (float)numRoll.Value; - // Batch type update: if type changed, update all waypoints with the same name + // Batch type update: if type changed, update all waypoints with the same original base name if (oldType != newType && _editor?.Level != null) { foreach (var room in _editor.Level.ExistingRooms) { foreach (var obj in room.Objects.OfType()) { - if (obj != _wayPoint) + if (obj != _wayPoint && obj.BaseName == oldBaseName) { - // Extract base name - string objBaseName = obj.Name; - if (!obj.IsSingularType()) - { - int lastUnderscore = objBaseName.LastIndexOf('_'); - if (lastUnderscore >= 0) - { - string suffix = objBaseName.Substring(lastUnderscore + 1); - if (ushort.TryParse(suffix, out _)) - { - objBaseName = objBaseName.Substring(0, lastUnderscore); - } - } - } - - if (objBaseName == newName) - { - obj.Type = newType; - } + obj.Type = newType; } } } From 6246ea565322e65f4f84175c30ef61e7361b039b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:16:36 +0000 Subject: [PATCH 21/36] Increase waypoint shape thickness from 4.0f to 16.0f for better visibility Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 1dcf31f5a..5247c1cb4 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1566,7 +1566,7 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) if (points.Count > 1) { var vertices = new List(); - float th = 4.0f; // Line thickness + float th = 16.0f; // Line thickness (increased for better visibility) for (int i = 0; i < points.Count - 1; i++) { From 7e20ff9eb30424b119836cced0885af336c7ce53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 22:03:24 +0000 Subject: [PATCH 22/36] Fix path preview, duplicate name validation, and add double-sided shape rendering Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 13 ++++++- TombEditor/Forms/FormWayPoint.cs | 44 ++++++++++------------ 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 5247c1cb4..f237f55cb 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -199,7 +199,7 @@ private void DrawFlybyPath(Effect effect) // Add the path of waypoints if (_editor.SelectedObject is WayPointInstance waypoint && - AddWayPointPath(waypoint.Name)) + AddWayPointPath(waypoint.BaseName)) { _legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullNone); _legacyDevice.SetVertexBuffer(_wayPointPathVertexBuffer); @@ -1586,7 +1586,7 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) } }; - // Add triangles to form the line segment + // Add triangles to form the line segment (both sides for double-sided rendering) for (int k = 0; k < _flybyPathIndices.Count; k++) { var v = new SolidVertex(); @@ -1594,6 +1594,15 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) v.Color = color; vertices.Add(v); } + + // Add reversed triangles for the back side + for (int k = _flybyPathIndices.Count - 1; k >= 0; k--) + { + var v = new SolidVertex(); + v.Position = linePoints[_flybyPathIndices[k].Y][_flybyPathIndices[k].X]; + v.Color = color; + vertices.Add(v); + } } // Create temporary vertex buffer for this shape diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index aa4c22033..5aaa5bc51 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -119,37 +119,31 @@ private void butOK_Click(object sender, EventArgs e) } } - // Check for duplicate names only if name changed - bool nameChanged = oldBaseName != newName; - if (nameChanged && _editor?.Level != null) + // Check for duplicate full names (base name + number combination) + // Only check if this creates an actual duplicate, not just same base name + bool nameOrNumberChanged = oldBaseName != newName || _wayPoint.Number != (ushort)numNumber.Value; + if (nameOrNumberChanged && _editor?.Level != null) { + // Build the full name that will be generated + ushort newNumber = (ushort)numNumber.Value; + var newTypeToCheck = (WayPointType)cmbType.SelectedIndex; + bool isSingularNew = newTypeToCheck == WayPointType.Point || + newTypeToCheck == WayPointType.Circle || + newTypeToCheck == WayPointType.Ellipse || + newTypeToCheck == WayPointType.Square || + newTypeToCheck == WayPointType.Rectangle; + + string fullNameToCheck = isSingularNew ? newName : $"{newName}_{newNumber}"; + foreach (var room in _editor.Level.ExistingRooms) { foreach (var obj in room.Objects.OfType()) { - if (obj != _wayPoint) + if (obj != _wayPoint && obj.Name == fullNameToCheck) { - // Extract base name from existing waypoint - string existingBaseName = obj.Name; - if (!obj.IsSingularType()) - { - int lastUnderscore = existingBaseName.LastIndexOf('_'); - if (lastUnderscore >= 0) - { - string suffix = existingBaseName.Substring(lastUnderscore + 1); - if (ushort.TryParse(suffix, out _)) - { - existingBaseName = existingBaseName.Substring(0, lastUnderscore); - } - } - } - - if (existingBaseName == newName) - { - DarkMessageBox.Show(this, $"A WayPoint with the name '{newName}' already exists.", "Duplicate Name", - MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } + DarkMessageBox.Show(this, $"A WayPoint with the name '{fullNameToCheck}' already exists.", "Duplicate Name", + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; } } } From 73638236b9aadbca8c302c2113926137be0c77bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 22:15:39 +0000 Subject: [PATCH 23/36] Enforce unique base names: prevent different types with same name unless numbers differ Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Forms/FormWayPoint.cs | 39 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index 5aaa5bc51..463672ceb 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -119,31 +119,36 @@ private void butOK_Click(object sender, EventArgs e) } } - // Check for duplicate full names (base name + number combination) - // Only check if this creates an actual duplicate, not just same base name - bool nameOrNumberChanged = oldBaseName != newName || _wayPoint.Number != (ushort)numNumber.Value; - if (nameOrNumberChanged && _editor?.Level != null) + // Check for duplicate names with validation rules: + // 1. Base name must be unique across all waypoints + // 2. Exception: same base name allowed ONLY if numbers are different + // (e.g., "test_0" and "test_1" are OK, but "test" Circle and "test" Point are NOT OK) + bool nameChanged = oldBaseName != newName; + bool numberChanged = _wayPoint.Number != (ushort)numNumber.Value; + + if ((nameChanged || numberChanged) && _editor?.Level != null) { - // Build the full name that will be generated ushort newNumber = (ushort)numNumber.Value; - var newTypeToCheck = (WayPointType)cmbType.SelectedIndex; - bool isSingularNew = newTypeToCheck == WayPointType.Point || - newTypeToCheck == WayPointType.Circle || - newTypeToCheck == WayPointType.Ellipse || - newTypeToCheck == WayPointType.Square || - newTypeToCheck == WayPointType.Rectangle; - - string fullNameToCheck = isSingularNew ? newName : $"{newName}_{newNumber}"; foreach (var room in _editor.Level.ExistingRooms) { foreach (var obj in room.Objects.OfType()) { - if (obj != _wayPoint && obj.Name == fullNameToCheck) + if (obj == _wayPoint) + continue; + + // Check if another waypoint uses this base name + if (obj.BaseName == newName) { - DarkMessageBox.Show(this, $"A WayPoint with the name '{fullNameToCheck}' already exists.", "Duplicate Name", - MessageBoxButtons.OK, MessageBoxIcon.Error); - return; + // Same base name found - only allowed if numbers are different + if (obj.Number == newNumber) + { + // Same base name AND same number = duplicate + DarkMessageBox.Show(this, $"A WayPoint with the name '{newName}' and number {newNumber} already exists.", "Duplicate Name", + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + // Different numbers - this is OK } } } From 1e10cdae9234e8c3c5f59c98c1988928d5d7d48e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 22:35:39 +0000 Subject: [PATCH 24/36] Add scale gizmo, fix copy behavior, reset number on type change, and check LuaName conflicts Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Forms/FormWayPoint.cs | 92 ++++++++++++++++++- .../LevelData/Instances/WayPointInstance.cs | 41 ++++++++- 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index 463672ceb..bf0388da7 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -58,6 +58,28 @@ private void FormWayPoint_Load(object sender, EventArgs e) private void cmbType_SelectedIndexChanged(object sender, EventArgs e) { + WayPointType newType = (WayPointType)cmbType.SelectedIndex; + WayPointType oldType = _wayPoint.Type; + + // If type changed to singular, reset number to 0 + bool newIsSingular = newType == WayPointType.Point || + newType == WayPointType.Circle || + newType == WayPointType.Ellipse || + newType == WayPointType.Square || + newType == WayPointType.Rectangle; + + bool oldIsSingular = oldType == WayPointType.Point || + oldType == WayPointType.Circle || + oldType == WayPointType.Ellipse || + oldType == WayPointType.Square || + oldType == WayPointType.Rectangle; + + // Reset number to 0 when changing to singular type from multi-point type + if (newIsSingular && !oldIsSingular) + { + numNumber.Value = 0; + } + UpdateFieldVisibility(); } @@ -154,9 +176,77 @@ private void butOK_Click(object sender, EventArgs e) } } + // Generate the new full name that will be used + string fullNewName = newName; + ushort newNumber = (ushort)numNumber.Value; + + bool newIsSingular = newType == WayPointType.Point || + newType == WayPointType.Circle || + newType == WayPointType.Ellipse || + newType == WayPointType.Square || + newType == WayPointType.Rectangle; + + if (!newIsSingular) + { + fullNewName = newName + "_" + newNumber; + } + + // Check if a LuaName with this value already exists + if (_editor?.Level != null) + { + foreach (var room in _editor.Level.ExistingRooms) + { + foreach (var obj in room.Objects) + { + if (obj == _wayPoint) + continue; + + if (obj is PositionAndScriptBasedObjectInstance scriptObj) + { + if (!string.IsNullOrEmpty(scriptObj.LuaName) && scriptObj.LuaName == fullNewName) + { + // LuaName conflict - clear the LuaName for this waypoint + _wayPoint.LuaName = ""; + DarkMessageBox.Show(this, $"A LuaName '{fullNewName}' already exists for another object. The LuaName for this waypoint has been cleared. Please generate a new LuaName.", "LuaName Conflict", + MessageBoxButtons.OK, MessageBoxIcon.Warning); + + // Update waypoint properties but with cleared LuaName + _wayPoint.Name = newName; + _wayPoint.Number = newNumber; + _wayPoint.Type = newType; + _wayPoint.Radius1 = (float)numDimension1.Value; + _wayPoint.Radius2 = (float)numDimension2.Value; + _wayPoint.RotationX = (float)numRotationX.Value; + _wayPoint.RotationY = (float)numRotationY.Value; + _wayPoint.Roll = (float)numRoll.Value; + + // Batch type update + if (oldType != newType && _editor?.Level != null) + { + foreach (var r in _editor.Level.ExistingRooms) + { + foreach (var o in r.Objects.OfType()) + { + if (o != _wayPoint && o.BaseName == oldBaseName) + { + o.Type = newType; + } + } + } + } + + DialogResult = DialogResult.OK; + Close(); + return; + } + } + } + } + } + // Update waypoint properties _wayPoint.Name = newName; - _wayPoint.Number = (ushort)numNumber.Value; + _wayPoint.Number = newNumber; _wayPoint.Type = newType; _wayPoint.Radius1 = (float)numDimension1.Value; _wayPoint.Radius2 = (float)numDimension2.Value; diff --git a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs index d325fb13b..162020bd6 100644 --- a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs +++ b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs @@ -14,7 +14,7 @@ public enum WayPointType Bezier // Multi-point bezier path } - public class WayPointInstance : PositionAndScriptBasedObjectInstance, IRotateableYXRoll + public class WayPointInstance : PositionAndScriptBasedObjectInstance, IRotateableYXRoll, ISizeable { private string _name = ""; private ushort _number; @@ -94,6 +94,26 @@ public WayPointType Type public float Radius1 { get; set; } = 1024.0f; // Default radius in units public float Radius2 { get; set; } = 1024.0f; // Default radius in units + // ISizeable implementation for scale gizmo support on X and Z axes + public Vector3 DefaultSize => new Vector3(Radius1 * 2, 0, Radius2 * 2); + + public Vector3 Size + { + get => new Vector3(Radius1 * 2, 0, Radius2 * 2); + set + { + // Only allow scaling on X and Z axes for shapes + if (RequiresRadius()) + { + Radius1 = Math.Max(0.01f, value.X / 2); + if (RequiresTwoRadii()) + Radius2 = Math.Max(0.01f, value.Z / 2); + else + Radius2 = Radius1; // Keep them synchronized for single-radius shapes + } + } + } + private float _rotationX { get; set; } private float _rotationY { get; set; } private float _roll { get; set; } @@ -155,6 +175,25 @@ public WayPointInstance(ObjectInstance selectedObject = null) } } + public override ObjectInstance Clone() + { + var clone = (WayPointInstance)base.Clone(); + + // For singular types, clear the name so user must provide a new one + if (IsSingularType()) + { + clone._name = ""; + clone.LuaName = ""; + } + else + { + // For multi-point types, increment the number + clone.Number = (ushort)(Number + 1); + } + + return clone; + } + /// Degrees in the range [-90, 90] public float RotationX { From f08346de46cc299f88b7c0bab5aaaf11320e6a36 Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:39:18 -0500 Subject: [PATCH 25/36] Fix label --- TombEditor/Forms/FormWayPoint.Designer.cs | 480 +++++++++------------- TombEditor/Forms/FormWayPoint.resx | 120 ++++++ 2 files changed, 321 insertions(+), 279 deletions(-) create mode 100644 TombEditor/Forms/FormWayPoint.resx diff --git a/TombEditor/Forms/FormWayPoint.Designer.cs b/TombEditor/Forms/FormWayPoint.Designer.cs index 49cce114a..04b30bc7a 100644 --- a/TombEditor/Forms/FormWayPoint.Designer.cs +++ b/TombEditor/Forms/FormWayPoint.Designer.cs @@ -30,342 +30,264 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.butCancel = new DarkUI.Controls.DarkButton(); - this.butOK = new DarkUI.Controls.DarkButton(); - this.lblName = new DarkUI.Controls.DarkLabel(); - this.lblNumber = new DarkUI.Controls.DarkLabel(); - this.lblType = new DarkUI.Controls.DarkLabel(); - this.lblDimension1 = new DarkUI.Controls.DarkLabel(); - this.lblDimension2 = new DarkUI.Controls.DarkLabel(); - this.lblRotationX = new DarkUI.Controls.DarkLabel(); - this.lblRotationY = new DarkUI.Controls.DarkLabel(); - this.lblRoll = new DarkUI.Controls.DarkLabel(); - this.txtName = new DarkUI.Controls.DarkTextBox(); - this.numNumber = new DarkUI.Controls.DarkNumericUpDown(); - this.cmbType = new DarkUI.Controls.DarkComboBox(); - this.numDimension1 = new DarkUI.Controls.DarkNumericUpDown(); - this.numDimension2 = new DarkUI.Controls.DarkNumericUpDown(); - this.numRotationX = new DarkUI.Controls.DarkNumericUpDown(); - this.numRotationY = new DarkUI.Controls.DarkNumericUpDown(); - this.numRoll = new DarkUI.Controls.DarkNumericUpDown(); - ((System.ComponentModel.ISupportInitialize)(this.numNumber)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numDimension1)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numDimension2)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRotationX)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRotationY)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRoll)).BeginInit(); - this.SuspendLayout(); + butCancel = new DarkButton(); + butOK = new DarkButton(); + lblName = new DarkLabel(); + lblNumber = new DarkLabel(); + lblType = new DarkLabel(); + lblDimension1 = new DarkLabel(); + lblDimension2 = new DarkLabel(); + lblRotationX = new DarkLabel(); + lblRotationY = new DarkLabel(); + lblRoll = new DarkLabel(); + txtName = new DarkTextBox(); + numNumber = new DarkNumericUpDown(); + cmbType = new DarkComboBox(); + numDimension1 = new DarkNumericUpDown(); + numDimension2 = new DarkNumericUpDown(); + numRotationX = new DarkNumericUpDown(); + numRotationY = new DarkNumericUpDown(); + numRoll = new DarkNumericUpDown(); + ((System.ComponentModel.ISupportInitialize)numNumber).BeginInit(); + ((System.ComponentModel.ISupportInitialize)numDimension1).BeginInit(); + ((System.ComponentModel.ISupportInitialize)numDimension2).BeginInit(); + ((System.ComponentModel.ISupportInitialize)numRotationX).BeginInit(); + ((System.ComponentModel.ISupportInitialize)numRotationY).BeginInit(); + ((System.ComponentModel.ISupportInitialize)numRoll).BeginInit(); + SuspendLayout(); // // butCancel // - this.butCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.butCancel.Checked = false; - this.butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.butCancel.Location = new System.Drawing.Point(239, 222); - this.butCancel.Name = "butCancel"; - this.butCancel.Size = new System.Drawing.Size(80, 23); - this.butCancel.TabIndex = 17; - this.butCancel.Text = "Cancel"; - this.butCancel.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; - this.butCancel.Click += new System.EventHandler(this.butCancel_Click); + butCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + butCancel.Checked = false; + butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + butCancel.Location = new System.Drawing.Point(239, 222); + butCancel.Name = "butCancel"; + butCancel.Size = new System.Drawing.Size(80, 23); + butCancel.TabIndex = 17; + butCancel.Text = "Cancel"; + butCancel.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + butCancel.Click += butCancel_Click; // // butOK // - this.butOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.butOK.Checked = false; - this.butOK.Location = new System.Drawing.Point(153, 222); - this.butOK.Name = "butOK"; - this.butOK.Size = new System.Drawing.Size(80, 23); - this.butOK.TabIndex = 16; - this.butOK.Text = "OK"; - this.butOK.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; - this.butOK.Click += new System.EventHandler(this.butOK_Click); + butOK.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + butOK.Checked = false; + butOK.Location = new System.Drawing.Point(153, 222); + butOK.Name = "butOK"; + butOK.Size = new System.Drawing.Size(80, 23); + butOK.TabIndex = 16; + butOK.Text = "OK"; + butOK.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + butOK.Click += butOK_Click; // // lblName // - this.lblName.AutoSize = true; - this.lblName.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblName.Location = new System.Drawing.Point(12, 15); - this.lblName.Name = "lblName"; - this.lblName.Size = new System.Drawing.Size(39, 13); - this.lblName.TabIndex = 0; - this.lblName.Text = "Name:"; + lblName.AutoSize = true; + lblName.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + lblName.Location = new System.Drawing.Point(12, 15); + lblName.Name = "lblName"; + lblName.Size = new System.Drawing.Size(39, 13); + lblName.TabIndex = 0; + lblName.Text = "Name:"; // // lblNumber // - this.lblNumber.AutoSize = true; - this.lblNumber.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblNumber.Location = new System.Drawing.Point(12, 41); - this.lblNumber.Name = "lblNumber"; - this.lblNumber.Size = new System.Drawing.Size(51, 13); - this.lblNumber.TabIndex = 2; - this.lblNumber.Text = "Number:"; + lblNumber.AutoSize = true; + lblNumber.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + lblNumber.Location = new System.Drawing.Point(12, 41); + lblNumber.Name = "lblNumber"; + lblNumber.Size = new System.Drawing.Size(51, 13); + lblNumber.TabIndex = 2; + lblNumber.Text = "Number:"; // // lblType // - this.lblType.AutoSize = true; - this.lblType.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblType.Location = new System.Drawing.Point(12, 67); - this.lblType.Name = "lblType"; - this.lblType.Size = new System.Drawing.Size(34, 13); - this.lblType.TabIndex = 4; - this.lblType.Text = "Type:"; + lblType.AutoSize = true; + lblType.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + lblType.Location = new System.Drawing.Point(12, 67); + lblType.Name = "lblType"; + lblType.Size = new System.Drawing.Size(32, 13); + lblType.TabIndex = 4; + lblType.Text = "Type:"; // // lblDimension1 // - this.lblDimension1.AutoSize = true; - this.lblDimension1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblDimension1.Location = new System.Drawing.Point(12, 93); - this.lblDimension1.Name = "lblDimension1"; - this.lblDimension1.Size = new System.Drawing.Size(71, 13); - this.lblDimension1.TabIndex = 6; - this.lblDimension1.Text = "Dimension 1:"; + lblDimension1.AutoSize = true; + lblDimension1.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + lblDimension1.Location = new System.Drawing.Point(12, 93); + lblDimension1.Name = "lblDimension1"; + lblDimension1.Size = new System.Drawing.Size(74, 13); + lblDimension1.TabIndex = 6; + lblDimension1.Text = "Dimension 1:"; // // lblDimension2 // - this.lblDimension2.AutoSize = true; - this.lblDimension2.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblDimension2.Location = new System.Drawing.Point(12, 119); - this.lblDimension2.Name = "lblDimension2"; - this.lblDimension2.Size = new System.Drawing.Size(71, 13); - this.lblDimension2.TabIndex = 8; - this.lblDimension2.Text = "Dimension 2:"; + lblDimension2.AutoSize = true; + lblDimension2.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + lblDimension2.Location = new System.Drawing.Point(12, 119); + lblDimension2.Name = "lblDimension2"; + lblDimension2.Size = new System.Drawing.Size(74, 13); + lblDimension2.TabIndex = 8; + lblDimension2.Text = "Dimension 2:"; // // lblRotationX // - this.lblRotationX.AutoSize = true; - this.lblRotationX.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRotationX.Location = new System.Drawing.Point(12, 145); - this.lblRotationX.Name = "lblRotationX"; - this.lblRotationX.Size = new System.Drawing.Size(64, 13); - this.lblRotationX.TabIndex = 10; - this.lblRotationX.Text = "Rotation X:"; + lblRotationX.AutoSize = true; + lblRotationX.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + lblRotationX.Location = new System.Drawing.Point(12, 145); + lblRotationX.Name = "lblRotationX"; + lblRotationX.Size = new System.Drawing.Size(64, 13); + lblRotationX.TabIndex = 10; + lblRotationX.Text = "Rotation X:"; // // lblRotationY // - this.lblRotationY.AutoSize = true; - this.lblRotationY.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRotationY.Location = new System.Drawing.Point(12, 171); - this.lblRotationY.Name = "lblRotationY"; - this.lblRotationY.Size = new System.Drawing.Size(63, 13); - this.lblRotationY.TabIndex = 12; - this.lblRotationY.Text = "Rotation Y:"; + lblRotationY.AutoSize = true; + lblRotationY.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + lblRotationY.Location = new System.Drawing.Point(12, 171); + lblRotationY.Name = "lblRotationY"; + lblRotationY.Size = new System.Drawing.Size(63, 13); + lblRotationY.TabIndex = 12; + lblRotationY.Text = "Rotation Y:"; // // lblRoll // - this.lblRoll.AutoSize = true; - this.lblRoll.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); - this.lblRoll.Location = new System.Drawing.Point(12, 197); - this.lblRoll.Name = "lblRoll"; - this.lblRoll.Size = new System.Drawing.Size(42, 13); - this.lblRoll.TabIndex = 14; - this.lblRoll.Text = "Z Axis:"; + lblRoll.AutoSize = true; + lblRoll.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + lblRoll.Location = new System.Drawing.Point(12, 197); + lblRoll.Name = "lblRoll"; + lblRoll.Size = new System.Drawing.Size(64, 13); + lblRoll.TabIndex = 14; + lblRoll.Text = "Rotation Z:"; // // txtName // - this.txtName.Location = new System.Drawing.Point(92, 12); - this.txtName.Name = "txtName"; - this.txtName.Size = new System.Drawing.Size(227, 20); - this.txtName.TabIndex = 1; + txtName.Location = new System.Drawing.Point(92, 12); + txtName.Name = "txtName"; + txtName.Size = new System.Drawing.Size(227, 22); + txtName.TabIndex = 1; // // numNumber // - this.numNumber.IncrementAlternate = new decimal(new int[] { - 10, - 0, - 0, - 65536}); - this.numNumber.Location = new System.Drawing.Point(92, 38); - this.numNumber.LoopValues = false; - this.numNumber.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.numNumber.Name = "numNumber"; - this.numNumber.Size = new System.Drawing.Size(227, 22); - this.numNumber.TabIndex = 3; + numNumber.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); + numNumber.Location = new System.Drawing.Point(92, 38); + numNumber.LoopValues = false; + numNumber.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + numNumber.Name = "numNumber"; + numNumber.Size = new System.Drawing.Size(227, 22); + numNumber.TabIndex = 3; // // cmbType // - this.cmbType.FormattingEnabled = true; - this.cmbType.Items.AddRange(new object[] { - "Point", - "Circle", - "Ellipse", - "Square", - "Rectangle", - "Linear", - "Bezier"}); - this.cmbType.Location = new System.Drawing.Point(92, 64); - this.cmbType.Name = "cmbType"; - this.cmbType.Size = new System.Drawing.Size(227, 23); - this.cmbType.TabIndex = 5; - this.cmbType.SelectedIndexChanged += new System.EventHandler(this.cmbType_SelectedIndexChanged); + cmbType.FormattingEnabled = true; + cmbType.Items.AddRange(new object[] { "Point", "Circle", "Ellipse", "Square", "Rectangle", "Linear", "Bezier" }); + cmbType.Location = new System.Drawing.Point(92, 64); + cmbType.Name = "cmbType"; + cmbType.Size = new System.Drawing.Size(227, 23); + cmbType.TabIndex = 5; + cmbType.SelectedIndexChanged += cmbType_SelectedIndexChanged; // // numDimension1 // - this.numDimension1.DecimalPlaces = 2; - this.numDimension1.IncrementAlternate = new decimal(new int[] { - 100, - 0, - 0, - 0}); - this.numDimension1.Location = new System.Drawing.Point(92, 90); - this.numDimension1.LoopValues = false; - this.numDimension1.Maximum = new decimal(new int[] { - 100000, - 0, - 0, - 0}); - this.numDimension1.Minimum = new decimal(new int[] { - 0, - 0, - 0, - 0}); - this.numDimension1.Name = "numDimension1"; - this.numDimension1.Size = new System.Drawing.Size(227, 22); - this.numDimension1.TabIndex = 7; - this.numDimension1.Value = new decimal(new int[] { - 1024, - 0, - 0, - 0}); + numDimension1.DecimalPlaces = 2; + numDimension1.IncrementAlternate = new decimal(new int[] { 100, 0, 0, 0 }); + numDimension1.Location = new System.Drawing.Point(92, 90); + numDimension1.LoopValues = false; + numDimension1.Maximum = new decimal(new int[] { 100000, 0, 0, 0 }); + numDimension1.Name = "numDimension1"; + numDimension1.Size = new System.Drawing.Size(227, 22); + numDimension1.TabIndex = 7; + numDimension1.Value = new decimal(new int[] { 1024, 0, 0, 0 }); // // numDimension2 // - this.numDimension2.DecimalPlaces = 2; - this.numDimension2.IncrementAlternate = new decimal(new int[] { - 100, - 0, - 0, - 0}); - this.numDimension2.Location = new System.Drawing.Point(92, 116); - this.numDimension2.LoopValues = false; - this.numDimension2.Maximum = new decimal(new int[] { - 100000, - 0, - 0, - 0}); - this.numDimension2.Minimum = new decimal(new int[] { - 0, - 0, - 0, - 0}); - this.numDimension2.Name = "numDimension2"; - this.numDimension2.Size = new System.Drawing.Size(227, 22); - this.numDimension2.TabIndex = 9; - this.numDimension2.Value = new decimal(new int[] { - 1024, - 0, - 0, - 0}); + numDimension2.DecimalPlaces = 2; + numDimension2.IncrementAlternate = new decimal(new int[] { 100, 0, 0, 0 }); + numDimension2.Location = new System.Drawing.Point(92, 116); + numDimension2.LoopValues = false; + numDimension2.Maximum = new decimal(new int[] { 100000, 0, 0, 0 }); + numDimension2.Name = "numDimension2"; + numDimension2.Size = new System.Drawing.Size(227, 22); + numDimension2.TabIndex = 9; + numDimension2.Value = new decimal(new int[] { 1024, 0, 0, 0 }); // // numRotationX // - this.numRotationX.DecimalPlaces = 2; - this.numRotationX.IncrementAlternate = new decimal(new int[] { - 10, - 0, - 0, - 65536}); - this.numRotationX.Location = new System.Drawing.Point(92, 142); - this.numRotationX.LoopValues = false; - this.numRotationX.Maximum = new decimal(new int[] { - 90, - 0, - 0, - 0}); - this.numRotationX.Minimum = new decimal(new int[] { - 90, - 0, - 0, - -2147483648}); - this.numRotationX.Name = "numRotationX"; - this.numRotationX.Size = new System.Drawing.Size(227, 22); - this.numRotationX.TabIndex = 11; + numRotationX.DecimalPlaces = 2; + numRotationX.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); + numRotationX.Location = new System.Drawing.Point(92, 142); + numRotationX.LoopValues = false; + numRotationX.Maximum = new decimal(new int[] { 90, 0, 0, 0 }); + numRotationX.Minimum = new decimal(new int[] { 90, 0, 0, int.MinValue }); + numRotationX.Name = "numRotationX"; + numRotationX.Size = new System.Drawing.Size(227, 22); + numRotationX.TabIndex = 11; // // numRotationY // - this.numRotationY.DecimalPlaces = 2; - this.numRotationY.IncrementAlternate = new decimal(new int[] { - 10, - 0, - 0, - 65536}); - this.numRotationY.Location = new System.Drawing.Point(92, 168); - this.numRotationY.LoopValues = false; - this.numRotationY.Maximum = new decimal(new int[] { - 360, - 0, - 0, - 0}); - this.numRotationY.Name = "numRotationY"; - this.numRotationY.Size = new System.Drawing.Size(227, 22); - this.numRotationY.TabIndex = 13; + numRotationY.DecimalPlaces = 2; + numRotationY.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); + numRotationY.Location = new System.Drawing.Point(92, 168); + numRotationY.LoopValues = false; + numRotationY.Maximum = new decimal(new int[] { 360, 0, 0, 0 }); + numRotationY.Name = "numRotationY"; + numRotationY.Size = new System.Drawing.Size(227, 22); + numRotationY.TabIndex = 13; // // numRoll // - this.numRoll.DecimalPlaces = 2; - this.numRoll.IncrementAlternate = new decimal(new int[] { - 10, - 0, - 0, - 65536}); - this.numRoll.Location = new System.Drawing.Point(92, 194); - this.numRoll.LoopValues = false; - this.numRoll.Maximum = new decimal(new int[] { - 360, - 0, - 0, - 0}); - this.numRoll.Name = "numRoll"; - this.numRoll.Size = new System.Drawing.Size(227, 22); - this.numRoll.TabIndex = 15; + numRoll.DecimalPlaces = 2; + numRoll.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); + numRoll.Location = new System.Drawing.Point(92, 194); + numRoll.LoopValues = false; + numRoll.Maximum = new decimal(new int[] { 360, 0, 0, 0 }); + numRoll.Name = "numRoll"; + numRoll.Size = new System.Drawing.Size(227, 22); + numRoll.TabIndex = 15; // // FormWayPoint // - this.AcceptButton = this.butOK; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.CancelButton = this.butCancel; - this.ClientSize = new System.Drawing.Size(331, 255); - this.Controls.Add(this.numRoll); - this.Controls.Add(this.numRotationY); - this.Controls.Add(this.numRotationX); - this.Controls.Add(this.numDimension2); - this.Controls.Add(this.numDimension1); - this.Controls.Add(this.cmbType); - this.Controls.Add(this.numNumber); - this.Controls.Add(this.txtName); - this.Controls.Add(this.lblRoll); - this.Controls.Add(this.lblRotationY); - this.Controls.Add(this.lblRotationX); - this.Controls.Add(this.lblDimension2); - this.Controls.Add(this.lblDimension1); - this.Controls.Add(this.lblType); - this.Controls.Add(this.lblNumber); - this.Controls.Add(this.lblName); - this.Controls.Add(this.butCancel); - this.Controls.Add(this.butOK); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "FormWayPoint"; - this.ShowIcon = false; - this.ShowInTaskbar = false; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "WayPoint"; - this.Load += new System.EventHandler(this.FormWayPoint_Load); - ((System.ComponentModel.ISupportInitialize)(this.numNumber)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numDimension1)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numDimension2)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRotationX)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRotationY)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numRoll)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - + AcceptButton = butOK; + AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + CancelButton = butCancel; + ClientSize = new System.Drawing.Size(331, 255); + Controls.Add(numRoll); + Controls.Add(numRotationY); + Controls.Add(numRotationX); + Controls.Add(numDimension2); + Controls.Add(numDimension1); + Controls.Add(cmbType); + Controls.Add(numNumber); + Controls.Add(txtName); + Controls.Add(lblRoll); + Controls.Add(lblRotationY); + Controls.Add(lblRotationX); + Controls.Add(lblDimension2); + Controls.Add(lblDimension1); + Controls.Add(lblType); + Controls.Add(lblNumber); + Controls.Add(lblName); + Controls.Add(butCancel); + Controls.Add(butOK); + FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + Name = "FormWayPoint"; + ShowIcon = false; + ShowInTaskbar = false; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Text = "WayPoint"; + Load += FormWayPoint_Load; + ((System.ComponentModel.ISupportInitialize)numNumber).EndInit(); + ((System.ComponentModel.ISupportInitialize)numDimension1).EndInit(); + ((System.ComponentModel.ISupportInitialize)numDimension2).EndInit(); + ((System.ComponentModel.ISupportInitialize)numRotationX).EndInit(); + ((System.ComponentModel.ISupportInitialize)numRotationY).EndInit(); + ((System.ComponentModel.ISupportInitialize)numRoll).EndInit(); + ResumeLayout(false); + PerformLayout(); } #endregion diff --git a/TombEditor/Forms/FormWayPoint.resx b/TombEditor/Forms/FormWayPoint.resx new file mode 100644 index 000000000..8b2ff64a1 --- /dev/null +++ b/TombEditor/Forms/FormWayPoint.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 From 5eaa7509889809ca9dcc468b3c256c246d871302 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 22:42:51 +0000 Subject: [PATCH 26/36] Fix compilation errors: add System.Numerics using and fix variable name conflict Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Forms/FormWayPoint.cs | 8 ++++---- TombLib/TombLib/LevelData/Instances/WayPointInstance.cs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index bf0388da7..9827d6651 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -146,11 +146,11 @@ private void butOK_Click(object sender, EventArgs e) // 2. Exception: same base name allowed ONLY if numbers are different // (e.g., "test_0" and "test_1" are OK, but "test" Circle and "test" Point are NOT OK) bool nameChanged = oldBaseName != newName; - bool numberChanged = _wayPoint.Number != (ushort)numNumber.Value; + ushort currentNumber = (ushort)numNumber.Value; + bool numberChanged = _wayPoint.Number != currentNumber; if ((nameChanged || numberChanged) && _editor?.Level != null) { - ushort newNumber = (ushort)numNumber.Value; foreach (var room in _editor.Level.ExistingRooms) { @@ -163,10 +163,10 @@ private void butOK_Click(object sender, EventArgs e) if (obj.BaseName == newName) { // Same base name found - only allowed if numbers are different - if (obj.Number == newNumber) + if (obj.Number == currentNumber) { // Same base name AND same number = duplicate - DarkMessageBox.Show(this, $"A WayPoint with the name '{newName}' and number {newNumber} already exists.", "Duplicate Name", + DarkMessageBox.Show(this, $"A WayPoint with the name '{newName}' and number {currentNumber} already exists.", "Duplicate Name", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } diff --git a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs index 162020bd6..e94dc2bd2 100644 --- a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs +++ b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Numerics; namespace TombLib.LevelData { From 4aac58f2ce9f98b80ff28c5e5dcb54098a4e193b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 02:21:14 +0000 Subject: [PATCH 27/36] Fix shape rendering: add RotationX to transformation matrix Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index f237f55cb..74ed7681e 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1489,7 +1489,9 @@ private void DrawWayPointShape(WayPointInstance instance, Vector4 color) Vector3 position = instance.Position + instance.Room.WorldPos; // Create transformation matrix for the shape orientation - Matrix4x4 rotation = Matrix4x4.CreateRotationY(instance.RotationY * (float)Math.PI / 180.0f) * + // Apply rotations in order: X, Y, Z (Roll) + Matrix4x4 rotation = Matrix4x4.CreateRotationX(instance.RotationX * (float)Math.PI / 180.0f) * + Matrix4x4.CreateRotationY(instance.RotationY * (float)Math.PI / 180.0f) * Matrix4x4.CreateRotationZ(instance.Roll * (float)Math.PI / 180.0f); // Number of segments for circles/ellipses From b09d92dd36f66eb38dfffb50dbb82fe9f2d1055b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:38:36 +0000 Subject: [PATCH 28/36] Add back Sequence field as alternative waypoint identifier alongside Name Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Forms/FormWayPoint.Designer.cs | 94 ++++++++++++------- TombEditor/Forms/FormWayPoint.cs | 13 ++- .../TombEngine/LevelCompilerTombEngine.cs | 1 + .../LevelData/Compilers/TombEngine/Structs.cs | 1 + .../Compilers/TombEngine/TombEngine.cs | 1 + TombLib/TombLib/LevelData/IO/Prj2Loader.cs | 1 + TombLib/TombLib/LevelData/IO/Prj2Writer.cs | 1 + .../LevelData/Instances/WayPointInstance.cs | 7 ++ 8 files changed, 83 insertions(+), 36 deletions(-) diff --git a/TombEditor/Forms/FormWayPoint.Designer.cs b/TombEditor/Forms/FormWayPoint.Designer.cs index 04b30bc7a..df187b449 100644 --- a/TombEditor/Forms/FormWayPoint.Designer.cs +++ b/TombEditor/Forms/FormWayPoint.Designer.cs @@ -33,6 +33,7 @@ private void InitializeComponent() butCancel = new DarkButton(); butOK = new DarkButton(); lblName = new DarkLabel(); + lblSequence = new DarkLabel(); lblNumber = new DarkLabel(); lblType = new DarkLabel(); lblDimension1 = new DarkLabel(); @@ -41,6 +42,7 @@ private void InitializeComponent() lblRotationY = new DarkLabel(); lblRoll = new DarkLabel(); txtName = new DarkTextBox(); + numSequence = new DarkNumericUpDown(); numNumber = new DarkNumericUpDown(); cmbType = new DarkComboBox(); numDimension1 = new DarkNumericUpDown(); @@ -48,6 +50,7 @@ private void InitializeComponent() numRotationX = new DarkNumericUpDown(); numRotationY = new DarkNumericUpDown(); numRoll = new DarkNumericUpDown(); + ((System.ComponentModel.ISupportInitialize)numSequence).BeginInit(); ((System.ComponentModel.ISupportInitialize)numNumber).BeginInit(); ((System.ComponentModel.ISupportInitialize)numDimension1).BeginInit(); ((System.ComponentModel.ISupportInitialize)numDimension2).BeginInit(); @@ -61,10 +64,10 @@ private void InitializeComponent() butCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; butCancel.Checked = false; butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - butCancel.Location = new System.Drawing.Point(239, 222); + butCancel.Location = new System.Drawing.Point(239, 248); butCancel.Name = "butCancel"; butCancel.Size = new System.Drawing.Size(80, 23); - butCancel.TabIndex = 17; + butCancel.TabIndex = 19; butCancel.Text = "Cancel"; butCancel.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; butCancel.Click += butCancel_Click; @@ -73,10 +76,10 @@ private void InitializeComponent() // butOK.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; butOK.Checked = false; - butOK.Location = new System.Drawing.Point(153, 222); + butOK.Location = new System.Drawing.Point(153, 248); butOK.Name = "butOK"; butOK.Size = new System.Drawing.Size(80, 23); - butOK.TabIndex = 16; + butOK.TabIndex = 18; butOK.Text = "OK"; butOK.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; butOK.Click += butOK_Click; @@ -91,74 +94,84 @@ private void InitializeComponent() lblName.TabIndex = 0; lblName.Text = "Name:"; // + // lblSequence + // + lblSequence.AutoSize = true; + lblSequence.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + lblSequence.Location = new System.Drawing.Point(12, 41); + lblSequence.Name = "lblSequence"; + lblSequence.Size = new System.Drawing.Size(61, 13); + lblSequence.TabIndex = 2; + lblSequence.Text = "Sequence:"; + // // lblNumber // lblNumber.AutoSize = true; lblNumber.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - lblNumber.Location = new System.Drawing.Point(12, 41); + lblNumber.Location = new System.Drawing.Point(12, 67); lblNumber.Name = "lblNumber"; lblNumber.Size = new System.Drawing.Size(51, 13); - lblNumber.TabIndex = 2; + lblNumber.TabIndex = 4; lblNumber.Text = "Number:"; // // lblType // lblType.AutoSize = true; lblType.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - lblType.Location = new System.Drawing.Point(12, 67); + lblType.Location = new System.Drawing.Point(12, 93); lblType.Name = "lblType"; lblType.Size = new System.Drawing.Size(32, 13); - lblType.TabIndex = 4; + lblType.TabIndex = 6; lblType.Text = "Type:"; // // lblDimension1 // lblDimension1.AutoSize = true; lblDimension1.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - lblDimension1.Location = new System.Drawing.Point(12, 93); + lblDimension1.Location = new System.Drawing.Point(12, 119); lblDimension1.Name = "lblDimension1"; lblDimension1.Size = new System.Drawing.Size(74, 13); - lblDimension1.TabIndex = 6; + lblDimension1.TabIndex = 8; lblDimension1.Text = "Dimension 1:"; // // lblDimension2 // lblDimension2.AutoSize = true; lblDimension2.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - lblDimension2.Location = new System.Drawing.Point(12, 119); + lblDimension2.Location = new System.Drawing.Point(12, 145); lblDimension2.Name = "lblDimension2"; lblDimension2.Size = new System.Drawing.Size(74, 13); - lblDimension2.TabIndex = 8; + lblDimension2.TabIndex = 10; lblDimension2.Text = "Dimension 2:"; // // lblRotationX // lblRotationX.AutoSize = true; lblRotationX.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - lblRotationX.Location = new System.Drawing.Point(12, 145); + lblRotationX.Location = new System.Drawing.Point(12, 171); lblRotationX.Name = "lblRotationX"; lblRotationX.Size = new System.Drawing.Size(64, 13); - lblRotationX.TabIndex = 10; + lblRotationX.TabIndex = 12; lblRotationX.Text = "Rotation X:"; // // lblRotationY // lblRotationY.AutoSize = true; lblRotationY.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - lblRotationY.Location = new System.Drawing.Point(12, 171); + lblRotationY.Location = new System.Drawing.Point(12, 197); lblRotationY.Name = "lblRotationY"; lblRotationY.Size = new System.Drawing.Size(63, 13); - lblRotationY.TabIndex = 12; + lblRotationY.TabIndex = 14; lblRotationY.Text = "Rotation Y:"; // // lblRoll // lblRoll.AutoSize = true; lblRoll.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); - lblRoll.Location = new System.Drawing.Point(12, 197); + lblRoll.Location = new System.Drawing.Point(12, 223); lblRoll.Name = "lblRoll"; lblRoll.Size = new System.Drawing.Size(64, 13); - lblRoll.TabIndex = 14; + lblRoll.TabIndex = 16; lblRoll.Text = "Rotation Z:"; // // txtName @@ -168,83 +181,93 @@ private void InitializeComponent() txtName.Size = new System.Drawing.Size(227, 22); txtName.TabIndex = 1; // + // numSequence + // + numSequence.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); + numSequence.Location = new System.Drawing.Point(92, 38); + numSequence.LoopValues = false; + numSequence.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + numSequence.Name = "numSequence"; + numSequence.Size = new System.Drawing.Size(227, 22); + numSequence.TabIndex = 3; + // // numNumber // numNumber.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); - numNumber.Location = new System.Drawing.Point(92, 38); + numNumber.Location = new System.Drawing.Point(92, 64); numNumber.LoopValues = false; numNumber.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); numNumber.Name = "numNumber"; numNumber.Size = new System.Drawing.Size(227, 22); - numNumber.TabIndex = 3; + numNumber.TabIndex = 5; // // cmbType // cmbType.FormattingEnabled = true; cmbType.Items.AddRange(new object[] { "Point", "Circle", "Ellipse", "Square", "Rectangle", "Linear", "Bezier" }); - cmbType.Location = new System.Drawing.Point(92, 64); + cmbType.Location = new System.Drawing.Point(92, 90); cmbType.Name = "cmbType"; cmbType.Size = new System.Drawing.Size(227, 23); - cmbType.TabIndex = 5; + cmbType.TabIndex = 7; cmbType.SelectedIndexChanged += cmbType_SelectedIndexChanged; // // numDimension1 // numDimension1.DecimalPlaces = 2; numDimension1.IncrementAlternate = new decimal(new int[] { 100, 0, 0, 0 }); - numDimension1.Location = new System.Drawing.Point(92, 90); + numDimension1.Location = new System.Drawing.Point(92, 116); numDimension1.LoopValues = false; numDimension1.Maximum = new decimal(new int[] { 100000, 0, 0, 0 }); numDimension1.Name = "numDimension1"; numDimension1.Size = new System.Drawing.Size(227, 22); - numDimension1.TabIndex = 7; + numDimension1.TabIndex = 9; numDimension1.Value = new decimal(new int[] { 1024, 0, 0, 0 }); // // numDimension2 // numDimension2.DecimalPlaces = 2; numDimension2.IncrementAlternate = new decimal(new int[] { 100, 0, 0, 0 }); - numDimension2.Location = new System.Drawing.Point(92, 116); + numDimension2.Location = new System.Drawing.Point(92, 142); numDimension2.LoopValues = false; numDimension2.Maximum = new decimal(new int[] { 100000, 0, 0, 0 }); numDimension2.Name = "numDimension2"; numDimension2.Size = new System.Drawing.Size(227, 22); - numDimension2.TabIndex = 9; + numDimension2.TabIndex = 11; numDimension2.Value = new decimal(new int[] { 1024, 0, 0, 0 }); // // numRotationX // numRotationX.DecimalPlaces = 2; numRotationX.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); - numRotationX.Location = new System.Drawing.Point(92, 142); + numRotationX.Location = new System.Drawing.Point(92, 168); numRotationX.LoopValues = false; numRotationX.Maximum = new decimal(new int[] { 90, 0, 0, 0 }); numRotationX.Minimum = new decimal(new int[] { 90, 0, 0, int.MinValue }); numRotationX.Name = "numRotationX"; numRotationX.Size = new System.Drawing.Size(227, 22); - numRotationX.TabIndex = 11; + numRotationX.TabIndex = 13; // // numRotationY // numRotationY.DecimalPlaces = 2; numRotationY.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); - numRotationY.Location = new System.Drawing.Point(92, 168); + numRotationY.Location = new System.Drawing.Point(92, 194); numRotationY.LoopValues = false; numRotationY.Maximum = new decimal(new int[] { 360, 0, 0, 0 }); numRotationY.Name = "numRotationY"; numRotationY.Size = new System.Drawing.Size(227, 22); - numRotationY.TabIndex = 13; + numRotationY.TabIndex = 15; // // numRoll // numRoll.DecimalPlaces = 2; numRoll.IncrementAlternate = new decimal(new int[] { 10, 0, 0, 65536 }); - numRoll.Location = new System.Drawing.Point(92, 194); + numRoll.Location = new System.Drawing.Point(92, 220); numRoll.LoopValues = false; numRoll.Maximum = new decimal(new int[] { 360, 0, 0, 0 }); numRoll.Name = "numRoll"; numRoll.Size = new System.Drawing.Size(227, 22); - numRoll.TabIndex = 15; + numRoll.TabIndex = 17; // // FormWayPoint // @@ -252,7 +275,7 @@ private void InitializeComponent() AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; CancelButton = butCancel; - ClientSize = new System.Drawing.Size(331, 255); + ClientSize = new System.Drawing.Size(331, 281); Controls.Add(numRoll); Controls.Add(numRotationY); Controls.Add(numRotationX); @@ -260,6 +283,7 @@ private void InitializeComponent() Controls.Add(numDimension1); Controls.Add(cmbType); Controls.Add(numNumber); + Controls.Add(numSequence); Controls.Add(txtName); Controls.Add(lblRoll); Controls.Add(lblRotationY); @@ -268,6 +292,7 @@ private void InitializeComponent() Controls.Add(lblDimension1); Controls.Add(lblType); Controls.Add(lblNumber); + Controls.Add(lblSequence); Controls.Add(lblName); Controls.Add(butCancel); Controls.Add(butOK); @@ -280,6 +305,7 @@ private void InitializeComponent() StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; Text = "WayPoint"; Load += FormWayPoint_Load; + ((System.ComponentModel.ISupportInitialize)numSequence).EndInit(); ((System.ComponentModel.ISupportInitialize)numNumber).EndInit(); ((System.ComponentModel.ISupportInitialize)numDimension1).EndInit(); ((System.ComponentModel.ISupportInitialize)numDimension2).EndInit(); @@ -295,6 +321,7 @@ private void InitializeComponent() private DarkButton butOK; private DarkButton butCancel; private DarkLabel lblName; + private DarkLabel lblSequence; private DarkLabel lblNumber; private DarkLabel lblType; private DarkLabel lblDimension1; @@ -303,6 +330,7 @@ private void InitializeComponent() private DarkLabel lblRotationY; private DarkLabel lblRoll; private DarkTextBox txtName; + private DarkNumericUpDown numSequence; private DarkNumericUpDown numNumber; private DarkComboBox cmbType; private DarkNumericUpDown numDimension1; diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index 9827d6651..2f37a2036 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -45,6 +45,7 @@ private void FormWayPoint_Load(object sender, EventArgs e) } txtName.Text = baseName; + numSequence.Value = _wayPoint.Sequence; numNumber.Value = _wayPoint.Number; cmbType.SelectedIndex = (int)_wayPoint.Type; numDimension1.Value = (decimal)_wayPoint.Radius1; @@ -212,6 +213,7 @@ private void butOK_Click(object sender, EventArgs e) // Update waypoint properties but with cleared LuaName _wayPoint.Name = newName; + _wayPoint.Sequence = (ushort)numSequence.Value; _wayPoint.Number = newNumber; _wayPoint.Type = newType; _wayPoint.Radius1 = (float)numDimension1.Value; @@ -223,11 +225,12 @@ private void butOK_Click(object sender, EventArgs e) // Batch type update if (oldType != newType && _editor?.Level != null) { + var oldSequence = _wayPoint.Sequence; foreach (var r in _editor.Level.ExistingRooms) { foreach (var o in r.Objects.OfType()) { - if (o != _wayPoint && o.BaseName == oldBaseName) + if (o != _wayPoint && (o.BaseName == oldBaseName || o.Sequence == oldSequence)) { o.Type = newType; } @@ -246,6 +249,7 @@ private void butOK_Click(object sender, EventArgs e) // Update waypoint properties _wayPoint.Name = newName; + _wayPoint.Sequence = (ushort)numSequence.Value; _wayPoint.Number = newNumber; _wayPoint.Type = newType; _wayPoint.Radius1 = (float)numDimension1.Value; @@ -254,14 +258,17 @@ private void butOK_Click(object sender, EventArgs e) _wayPoint.RotationY = (float)numRotationY.Value; _wayPoint.Roll = (float)numRoll.Value; - // Batch type update: if type changed, update all waypoints with the same original base name + // Batch type update: if type changed, update all waypoints with either: + // 1. Same original base name OR + // 2. Same sequence number if (oldType != newType && _editor?.Level != null) { + var oldSequence = _wayPoint.Sequence; foreach (var room in _editor.Level.ExistingRooms) { foreach (var obj in room.Objects.OfType()) { - if (obj != _wayPoint && obj.BaseName == oldBaseName) + if (obj != _wayPoint && (obj.BaseName == oldBaseName || obj.Sequence == oldSequence)) { obj.Type = newType; } diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs index 6fd2041c5..a1bdeb9c9 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs @@ -392,6 +392,7 @@ private void BuildCamerasAndSinks() RotationX = instance.RotationX, RotationY = instance.RotationY, Roll = instance.Roll, + Sequence = instance.Sequence, Number = instance.Number, Type = (int)instance.Type, Radius1 = instance.Radius1, diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs index cc1b805a8..69a833cba 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs @@ -595,6 +595,7 @@ public struct TombEngineWayPoint public float RotationX; public float RotationY; public float Roll; + public ushort Sequence; public ushort Number; public int Type; public float Radius1; diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs index 6bcd879ec..b0afe65e8 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs @@ -87,6 +87,7 @@ private void WriteLevelTombEngine() writer.Write(waypoint.RotationX); writer.Write(waypoint.RotationY); writer.Write(waypoint.Roll); + writer.Write(waypoint.Sequence); writer.Write(waypoint.Number); writer.Write(waypoint.Type); writer.Write(waypoint.Radius1); diff --git a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs index ff7aed0ea..85e9aebf6 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs @@ -1410,6 +1410,7 @@ private static bool LoadObjects(ChunkReader chunkIO, ChunkId idOuter, LevelSetti instance.Roll = chunkIO.Raw.ReadSingle(); instance.ScriptId = ReadOptionalLEB128Int(chunkIO.Raw); string baseName = chunkIO.Raw.ReadStringUTF8(); + instance.Sequence = LEB128.ReadUShort(chunkIO.Raw); instance.Number = LEB128.ReadUShort(chunkIO.Raw); instance.Type = (WayPointType)LEB128.ReadInt(chunkIO.Raw); instance.Radius1 = chunkIO.Raw.ReadSingle(); diff --git a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs index 88a4aa7e0..5b01d89a9 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs @@ -706,6 +706,7 @@ private static void WriteObjects(ChunkWriter chunkIO, IEnumerable Date: Mon, 26 Jan 2026 00:50:09 +0000 Subject: [PATCH 29/36] Add WayPoint button to toolbar (TombEngine only) Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Properties/Resources.Designer.cs | 10 ++++++++++ TombEditor/Properties/Resources.resx | 3 +++ .../icons_objects/objects_WayPoint-16.png | Bin 0 -> 2034 bytes TombEditor/ToolWindows/MainView.Designer.cs | 15 ++++++++++++++- TombEditor/ToolWindows/MainView.cs | 1 + 5 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 TombEditor/Resources/icons_objects/objects_WayPoint-16.png diff --git a/TombEditor/Properties/Resources.Designer.cs b/TombEditor/Properties/Resources.Designer.cs index f53d05977..3a7de26cd 100644 --- a/TombEditor/Properties/Resources.Designer.cs +++ b/TombEditor/Properties/Resources.Designer.cs @@ -860,6 +860,16 @@ internal static System.Drawing.Bitmap objects_volume_sphere_16 { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap objects_WayPoint_16 { + get { + object obj = ResourceManager.GetObject("objects_WayPoint_16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/TombEditor/Properties/Resources.resx b/TombEditor/Properties/Resources.resx index f4fa1b3d4..59793f457 100644 --- a/TombEditor/Properties/Resources.resx +++ b/TombEditor/Properties/Resources.resx @@ -247,6 +247,9 @@ ..\Resources\icons_objects\objects_volume-sphere-16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\icons_objects\objects_WayPoint-16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\icons_general\general_animcommand-16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/TombEditor/Resources/icons_objects/objects_WayPoint-16.png b/TombEditor/Resources/icons_objects/objects_WayPoint-16.png new file mode 100644 index 0000000000000000000000000000000000000000..84e9fb1e0a0e2d11f061b5d0550f12ca3dfcf964 GIT binary patch literal 2034 zcmV_bktLya)kRPjUV9-y-js+Jp$4TiYUj~RqL{KsqmT}xW|}jOGt8*7pZDVX+OOMF zBX3)sas6Sz-fOSZ{<+q(*Lv0(B}4=!aplJlFvtut6NAhkuO-PAdLWTV*tBDbA|eUv z$;@0Z$V_6`Y#YM;I6wz|_#=w5 zUOw&1moJf>osAzqe(-ToQBgR0^eAFtVxUr~;BYwb_U&6VH#g(UmoI#}(9lq{w6q{0 zAwg6tFxv~a(9X!R_2lmEZaQ(|1Z~{7ao*t`5fMS>&z~oY#X@$woe~lf1kcZ$IYTCs zY2LC;CKDAG7YowI#Kh3Z$jI6;7x(Vnqs^N)FKSbNhr@>tQ&CZoAg-dK!lQc1%E|=! zu3fuE9LM=SU2y*=+W0$iu_Kl$e;vm*MB$n)Z5$3r*S%+)Y;id`T6;C8m&|+sj;zfNp&_h zHj+}QBmg9pN~x)-$+scjx^+tc`0CZG3#PSLEL2!nD5x_wHkO8lhL%+4;NT#|#>Vnx zR904sW->g5Eb5By{{8#>wN9r)d3pJQY1L{qDk>@v6&1zDg@uJ7BqU@>b%ut9A}lP7 zzqi?JaJ${UyR3fy{yl%K*XyBDsTNJYYu7F$Cnxjw9LFtr>NsO0^3yC&H$1%(^4Qqe z|JLp@83F?X|GdVR$q*hM&fgCX4#I3UFR4zm*^GgK0sda2(LgGd`Yz<(egux=V7J>n z%44xu1S8?$!-q?%^TC4${0LGg6oLhgPx>y7967@0&&kR8;|J*b_wN)P9WAIgF)@*B zHk(KF*=#o2w{IVx_u#>UG&(x!+p~-7*RS*W6$%AaS645ZzO=M--nnu?LBStaZzCfk zR8UYL$amw$4c}k!O-xKsT3Q-khFYzr>gsCx`SYhB-KS5V=B(CchxGJxG8harF)=Zx z4uio!dc9td{?esOtHwbjsz2FkwF;(SR;v{yB_(KRXh3ReDgpumFf=rTwzf9(^zO6h7GXUY%rNjXl-r9e{bHhhTnC{<#MX4tMh0vVzF4Lrly86Gc#$=o;}`UF4SuEvICwSE?l@k zQ&Uq5w(;G&cT`?pPVw>aynkAyQhATLShsE+tX3-|5(y$BBe7-67QwLo+mcG9;5ZI_ zeSNT6t^71@_JYQ2Hsi^YC#bEh#fJ|c@a@|-!E=p9<2`$Petv%Zi$; zYNoHEzrP1qDD zsi_I|_4R_d-zH(_&Yd`U@+3~5K8=Qk20VZMeC-|y_%E86m=O3zA|oS_nwpA&f&wHb zCnF#r007`{I7BmLJ`WiHf`fyxd-rbS=H?P2RnVFd@3V2OGw%hG2yd;OoUAK@9T@{ zf?X57i{I+*?nZZaHv$6#k(HH&^z?LOWo02aI2a)zA-wadAja zPKHvcIusQZAulfvw{PFZ=g*%-gS;#~SuU3g4grd-;NW2X`q;5!C@CpH zPfrhOYHINK@nZ}R5A)BBMk9vya2yAh%O&1a$`sHhb`5^(wQ zWtyIzUa*ZWmy2G#dPP-LRg{#JL;y&m(Rk01fQuI|qN=J24u@meXA&-#3vqFA$j{GT z^aPPYp@3Gag;uM@@#DwQ)zyU;FJ5?$x$t#)YA%?Ai1{uUWF`igL1vJd7-R;Si9u$N z8Du5~nL%cdnZ%aI{^XVZpAoDlGcm{v@*0qYZ7Z>!%pfx{$P6-r%=B0F55A`$8008h Q1ONa407*qoM6N<$f}*wGVE_OC literal 0 HcmV?d00001 diff --git a/TombEditor/ToolWindows/MainView.Designer.cs b/TombEditor/ToolWindows/MainView.Designer.cs index 05032bdee..ce0d33a23 100644 --- a/TombEditor/ToolWindows/MainView.Designer.cs +++ b/TombEditor/ToolWindows/MainView.Designer.cs @@ -61,6 +61,7 @@ private void InitializeComponent() butAddCamera = new System.Windows.Forms.ToolStripButton(); butAddSprite = new System.Windows.Forms.ToolStripButton(); butAddFlybyCamera = new System.Windows.Forms.ToolStripButton(); + butAddWayPoint = new System.Windows.Forms.ToolStripButton(); butAddSink = new System.Windows.Forms.ToolStripButton(); butAddSoundSource = new System.Windows.Forms.ToolStripButton(); butAddImportedGeometry = new System.Windows.Forms.ToolStripButton(); @@ -97,7 +98,7 @@ private void InitializeComponent() toolStrip.BackColor = System.Drawing.Color.FromArgb(60, 63, 65); toolStrip.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); toolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; - toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { but2D, but3D, butFaceEdit, butLightingMode, butUndo, butRedo, butCenterCamera, butDrawPortals, butDrawAllRooms, butDrawHorizon, butDrawRoomNames, butDrawCardinalDirections, butDrawExtraBlendingModes, butHideTransparentFaces, butBilinearFilter, butDrawWhiteLighting, butDrawStaticTint, butDrawIllegalSlopes, butDrawSlideDirections, butDisableGeometryPicking, butDisableHiddenRoomPicking, butDrawObjects, butFlipMap, butCopy, butPaste, butStamp, butOpacityNone, butOpacitySolidFaces, butOpacityTraversableFaces, butMirror, butAddCamera, butAddSprite, butAddFlybyCamera, butAddSink, butAddSoundSource, butAddImportedGeometry, butAddGhostBlock, butAddMemo, butCompileLevel, butCompileLevelAndPlay, butCompileAndPlayPreview, butAddBoxVolume, butAddSphereVolume, butTextureFloor, butTextureCeiling, butTextureWalls, butEditLevelSettings, butToggleFlyMode, butSearch, butSearchAndReplaceObjects }); + toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { but2D, but3D, butFaceEdit, butLightingMode, butUndo, butRedo, butCenterCamera, butDrawPortals, butDrawAllRooms, butDrawHorizon, butDrawRoomNames, butDrawCardinalDirections, butDrawExtraBlendingModes, butHideTransparentFaces, butBilinearFilter, butDrawWhiteLighting, butDrawStaticTint, butDrawIllegalSlopes, butDrawSlideDirections, butDisableGeometryPicking, butDisableHiddenRoomPicking, butDrawObjects, butFlipMap, butCopy, butPaste, butStamp, butOpacityNone, butOpacitySolidFaces, butOpacityTraversableFaces, butMirror, butAddCamera, butAddSprite, butAddFlybyCamera, butAddWayPoint, butAddSink, butAddSoundSource, butAddImportedGeometry, butAddGhostBlock, butAddMemo, butCompileLevel, butCompileLevelAndPlay, butCompileAndPlayPreview, butAddBoxVolume, butAddSphereVolume, butTextureFloor, butTextureCeiling, butTextureWalls, butEditLevelSettings, butToggleFlyMode, butSearch, butSearchAndReplaceObjects }); toolStrip.Location = new System.Drawing.Point(0, 0); toolStrip.Name = "toolStrip"; toolStrip.Padding = new System.Windows.Forms.Padding(6, 0, 1, 0); @@ -567,6 +568,17 @@ private void InitializeComponent() butAddFlybyCamera.Size = new System.Drawing.Size(23, 29); butAddFlybyCamera.Tag = "AddFlybyCamera"; // + // butAddWayPoint + // + butAddWayPoint.BackColor = System.Drawing.Color.FromArgb(60, 63, 65); + butAddWayPoint.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + butAddWayPoint.ForeColor = System.Drawing.Color.FromArgb(220, 220, 220); + butAddWayPoint.Image = Properties.Resources.objects_WayPoint_16; + butAddWayPoint.ImageTransparentColor = System.Drawing.Color.Magenta; + butAddWayPoint.Name = "butAddWayPoint"; + butAddWayPoint.Size = new System.Drawing.Size(23, 29); + butAddWayPoint.Tag = "AddWayPoint"; + // // butAddSink // butAddSink.BackColor = System.Drawing.Color.FromArgb(60, 63, 65); @@ -902,6 +914,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripButton butOpacityTraversableFaces; private System.Windows.Forms.ToolStripButton butAddCamera; private System.Windows.Forms.ToolStripButton butAddFlybyCamera; + private System.Windows.Forms.ToolStripButton butAddWayPoint; private System.Windows.Forms.ToolStripButton butAddSoundSource; private System.Windows.Forms.ToolStripButton butAddSink; private System.Windows.Forms.ToolStripButton butAddGhostBlock; diff --git a/TombEditor/ToolWindows/MainView.cs b/TombEditor/ToolWindows/MainView.cs index 711e19edc..eac3e6375 100644 --- a/TombEditor/ToolWindows/MainView.cs +++ b/TombEditor/ToolWindows/MainView.cs @@ -190,6 +190,7 @@ obj is Editor.LevelChangedEvent || { butAddBoxVolume.Enabled = _editor.Level.IsTombEngine; butAddSphereVolume.Enabled = _editor.Level.IsTombEngine; + butAddWayPoint.Enabled = _editor.Level.IsTombEngine; butDrawVolumes.Enabled = _editor.Level.IsTombEngine; // We may safely hide it because it's not customizable butAddSprite.Enabled = _editor.Level.Settings.GameVersion.Native() <= TRVersion.Game.TR2; From 7525247e0c317311b0eaf399e1e5cb7091dfa307 Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Sun, 25 Jan 2026 19:34:05 -0500 Subject: [PATCH 30/36] UpdateLogo --- .../ServiceObjectTextures/waypoint.png | Bin 2034 -> 4564 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/TombLib/TombLib.Rendering/Rendering/ServiceObjectTextures/waypoint.png b/TombLib/TombLib.Rendering/Rendering/ServiceObjectTextures/waypoint.png index 84e9fb1e0a0e2d11f061b5d0550f12ca3dfcf964..879947ae51df855aaed5ac89f11f0780bf1b4736 100644 GIT binary patch literal 4564 zcmeHKYfuzd79K<%0hy@yg4MDaHAtXmdV1asgFFUd76uTIh%fBv?wRQ@FMAqhKqLrS z#?>Xdt1I}R5@S~58>21@VvLV$j7AZoan0g_f+8D2NsLrh410S(#M)GCQMLKURP}WC zx!?KjIo~<=R^JWjsfhu8!G1g*FF>25$pH5p?(+2p{|o)!Jqm807iBGCGjJzlXKV)A zOhIg+oq{MAZQ$`-cP!}(_~#+t=e?66%f@6VE0)!hirbHU;&vANJ-Il#@Ilzt=RcHe zTkjb>xpU(&^exY@uyKJIo&%KW7R;_8k85&C4g(9lAFg>%WJ$KHo*xJhC+3jV+z= zn`}Z;5!tcPpC9zMML4M^5q?{*P%qp2s>mK0aL=AO9jF zkjJjoTN;wjr%$?=midujvQK2vgiGnWzuP0I+xF&^&*JFt6P+u1nnb3i&3kK=en-4~ z=1n=s7hbKyYW>PueBOPhR2f#6w4ry-x+&u2uG2FgHalG1(J`j%s<)qB;4S=Q6Dtrt z=sBownH;^=W0O8r|L5u}5r)>B+}^Ks`BU3-?yi34R%r$j)DpOXNKr)G3%FOMOMZAd zZMvS)hD3)}BOPTI-dnnTS;_sS%%Ya6kXbvcL-`XnekA-;edV@*ZLS^4J^O>({Z-p1 zUtO~6=G(hZc3;Y!7GVkxI&vd8c1&`Tz-t29x&M7X{{?qs87X@oMSb7;dgRZO{z@_n z&wf!;8l06>F|+UWl54jjQgwCO&yw6s5{wh8-rabV^1 z*$vmP3lh(^$n7heueYgN7@uh+O>^Rz+4JBJ7LC1otz&1;*+#hW(+5`%udcY^10{d5 z_>2dizvl5o>gcM|^+#7(`=6e8*z5VvET$>o%Jp{8dIdCSu|>KRjIddRIBC;ULYKu3 zdXLA8n&-0PL>|RLddf&!)q?K&PX!Q7ss(S!bcoI#PnqbXB8JK=O3fmQ@(2|vm>2CA z<-!1gg<^5YWieYFm`g2i^J3tcGm8X}8^Y$P1&eg)P`r(yAgNF)MBoG$U4ROr{h%m@ zG+-H;`GXYTO)W67tQ`}HoKC0EDG}Njqe!e$sYD1WLQxnXU`L^q#a*z~F^i)Z;LuPG zf}!m!ZL>lgC$6{Uvuc3=j6*Ntv)FaIA$qH0Pz9ie$c5WQVj&{3SVY4;9Be`XAQ=qk z7d;$VV5N#OD2FYdA*h4`%F50fPC*hw{`P#v>`sRyM3kAb0H_1ZDt=|kM6E7;$b*x> zNL%b~FCg|SNR~Fd6zi4PxDj_c!vg{CL%gq`U*zrv1C&mOX>3G37oJw57I5=pq>Z3S z%>9T+5S)<630R7#q_9+lNMOBKWq@%dq1O|L0asAyFeXmYG7)2UGgRaEQ!=vJ;NI)f}D7l=J%U~Jsf~9&y z38Fz2umq9mQI(2Ps8EBOiX^c4HpYU3a?%#uNQvxLqkDi89E(lYss*SJd1*;Ew=_yvoRT8B_E|JMpQVFVj3CgAz2k1qPRg4HFa`y;V7!0HX zq{X>T1q5z8$OemND4ex1SvH$lE#QKJIM1PB9avB#&f*%Jr2r{{$}vQYp|UKo3X@1M znG8l`7%~iRBWXk7|3Y)i2Z|afdJ^pb^B1~J18XXiS~>7G@M@;riwT0<%L2oRffO8g z0X0xAz%?*Lm~g9+0{h2c!@jW7zt9W{${>~@h#aQmQUxrPD+pMrBnen1R-rP5OrjSL zW;Mj_uo+k<&QP&Npd-)M#S(i$poUoWJTPL@gmzO z(SPX?yU>$zVDIjQck`d`UD%Yr7BT`^Q;L+|H zm1{%_j0im1UH><^{9YcWC@c5^a)P7Mf#yjTaLn@7Cnsunjl8YAgPLdim8?>>D5UgvessCNH+8 zT%L8JEn=6VBU?il&i_=gZyrDVr;ES4d-7a+qEQ{QuQDw1&%x>X^ V_e+kS`W}>qr%g!J9En>|_8+Y2h2#JL delta 2020 zcmVJ(xqsHP*Lv0(B}4=!aplJl zFvtut6NAhkuO-PAdLWTV*tBDbA|eUv$;@0Z$V_6`Fn9FB$vx+cAI7fe}8{EckUeZ_4O@V+vZ_3Kg*SZJUKZ@ zPN$RHZuf#|j7B4E-@bj$%gAIhQYw`sl}hJ4+p%K@8I8tO1zEC?cWw60$lvh7xm+%E zb#>vaoqwH;A3uKZaZyoGIC}IbVq#*T zQmNo@IPmuETQoN}R0NQ-6Pl!-o%3QBjc~uA-vCqk789$^`kYUAsmc$N4_w&dyHKXf%Sdwr$%+r%s)s z($Z4O&(EijkdOtT>vXzB?eSG_Z1!!)!^6Xrn3%|y;pgW^6%`fK z+uO^haXOvU+1W|?`T27ityC(hv9WPUbv8COl7CXEBmg9pN~x)-$+scjx^+tc`0CZG z3#PSLEL2!nD5x_wHkO8lhL%+4;NT#|#>VnxR904sW->g5Eb5By{{8#>wN9r)d3pJQ zY1L{qDk>@v6&1zDg@uJ7BqU@>b%ut9A}lP7zqi?JaJ${UyR3fy{yl%K*XyBDsTNJY zYk$`+Bqt~H_Z-J9dFnW0B=XZNPd7Zh67tyC*#Fk zHZQ48v)PP+fdT$rqtQSrmHIB^-+ly+<6yVjJ<4OTSOg>C;lqbZs`J5v2mA<9C=`MP zj!*h7jvP6{=g-N>`Qr!Z`}gk@9UU#GH-9lPk!&`bNA=lkHrlsuAD{Q&!Gkn9I_lfA zi|f~~^Z6AD1yxs9FPgrzv~=FNazR1CA6IW9BO_E$P$0;6EK_sd_*?(%a z3Z`IIs}&_BC1_}9Kx%3#0s;asG&F>^wl?(i^ekFddwV;cJ$r_Xj10uZ#bLvS4Y1j4 zFquqfZEeNmFw3)PHLAvICwSE?l@kQ&Uq5w(;G&cT`?pPVw>aynkAy zQhATLShsE+tX3-|5(y$BBe7-67QwLo+mcG9;5ZI_eSNT6t^71@_JYQ2Hsi^YC#bEh z#fJ|c@a@|-!E=p9<2`$Petv%Zi({Ry&)vOZBbDPg1Ox;iC@5%IUTUVVqQAc%H*emA!C*j3OAG9F zyP#*w<#O!bzaKK03>_UEn3|gMPRK+gs+pLr>FH_yx~ZuN_4W0FxZfsW=gyrtdGaJq zpFWL-h6X%;{(S8o3HUFXn17fM_(mclBaxb#ih_ayBqt{$ARqt$;BYuZGi5#x832NV zgRy(}Zsg|XA~!b|TeoigqxX(MUV;uBIDjixt{@{L!#BNft3jEWnJWr-O+dEW?Q=eQ z46;bBTn5?4#UL}t46;{9CX+!fmovy>YHVx_*$o2L0aDO-)$ji%vR;xus zL3UKj@sH<)YjG_DJcor+1b$PbVy80giQAD>x=1vT@${G-|Ft}Mt65N0s{k) zm6e6`^mJrpWg$2?7$G4cy#L!PZ88yw+EZq0W?gY{aY#;1hEl2I<9_}6g}S;r6crUA zFE0vya2yAh%O&1a$7W{{c0mdF0& zmHwX*tS2)u$PDrtkc4e3v7XEzGcm{vGK0+YSM?9Rryv;QC|d*o0000 Date: Sun, 25 Jan 2026 19:58:44 -0500 Subject: [PATCH 31/36] Add toolbar icon --- .../icons_objects/objects_WayPoint-16.png | Bin 2034 -> 4596 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/TombEditor/Resources/icons_objects/objects_WayPoint-16.png b/TombEditor/Resources/icons_objects/objects_WayPoint-16.png index 84e9fb1e0a0e2d11f061b5d0550f12ca3dfcf964..081f4aaef6a65c0f8869952fe5908fcdf08a7f86 100644 GIT binary patch literal 4596 zcmeHKYfuzd7H*#)DCjB=Ezq`kL<~Lsn0JQ}kr`mbQG{U^)ZK8W`wp}+4|*DAfK{@h zF~n>ERhtlvn>FjBQdU+?bh9z8kHn~zb<0??YFJ}@gn+s+!Dy0QCF|av;aN%57FF>d zQ#I4w=brQ3bH4MPThmPiOLM10%!G9{T!^kym2db&^gHL?BY-X^>+B_RA@Ji~ z$>S6&AuiW?Ar1qg6T<@kbQP?O8aFq*w|jqf;S>oJ+ zui`T5=#;$LcE_&mxo-)pen0Q*>*FT1AJ6Ma&QDx7w%*$N(w5at|2?qu9mDeKm#CZv zlflyy@~`jH?+D~QFn`e*o)npMJ*^=AAO9iKcVGS{vi^ito_%w>&UwpObog`e-krpb ztJAttlW)!|bT;2FTDWJrBz*YE7UM^M+~HuFsLrdeo_&V88Cz<^ew9{?VPiIm+1Ulw z?Ck!6P#N3SwjRzuQ844wvE7R;^CN0nZnV6{byzG3`A^2aw@7^Q;O#Q0Jx(L$6|PE{ zaA|#G^T|Dpn-A=b+PfbAH+DCA>Jyf@w7Z||JV%@w*Y@zU!`)A9tB=ADu3n;j=y+yN zX-?|QxO*FmnVyI_?_*sI!4xfnalW1x_SOGw_dA=vUh!#d!>N`+&CF}loA@RA^cz!d z9J1wi|8-e{BgQ&+!4t2tuV>xJ-FrrLtV;D%de7FmNp04to1QxUQ_8vV@xrZ{BI1gzt|$_{!VgudTd%XZrC+ zlUIqqY&c@c?@d0^ae>G^-lOwYUAfR{x+aCi*Pl^rH&fgtGgw@vD5@ z7i&-78rQO;l)Q?CZ8|P&S-r2mw|=|d{isXs`fl92TImFF^^1RDSX8ly6jowe!toxL z3J4wtR0UjKBs~nv$P9P^zXr;<1D11x!Q`mqYi8nIiC@i@e4}l-LUJY>xzSjY^}UNlQSi zVTlD1c!nf6xk7VJ9|U?b5f!rR-YNWD(0)f)|JHK4%|D688i12+#9C4q7ER#f}N>Dj8D+NkGP_h=*21K{u@%sjs?4k=6$w1+gK}4txMus-(P(fP5pwrKx8C&wKGOeYv5QT zNozTJh`&b=owfhZTNxibBh>Uf(TDc04VpqTRRpU-Poc+3F*r;(9vlk}@SzfXpau%T zJP}rCiLU_ea){24KEd|e#UBxaLjXFB9ymw{fu7Vjd6slQMj&;9hD8m8(4Y&EG=T2& zIAuSOU{*Qe5pjhC8srL}*Vn4F;o0~rpwb6K8A&r_A7xsO(Gt|-FAuPKAn4Gz7z8zo z6m9^d!Kl}ffCr48;hn5n6H?7jTb`nb{_-*?oS=x7kPV&8=%A+t zPWnE+P_B>j(&EuI2%lzaISXQq}JzN+!;Cky_oT1WA`49DS7_o)Z$ps zrVamc(SCa4x9)}JH)nj1xnaCXJX-dB>R(=QM4j*^U7WIdN!8}8iH2|g(7pR%wfWaM z8(4qU%ZD5rB73hThIKR~T#xa@J6mVZ?)i;1_SR?9j>VU^nli3O??`{ExlQ^|jO{oX z6Kgo_oY?Vxb=u{W6(3hTFMEqxj9>0=pV6VRto-~9_gH)JH!;%=u_tu@?yf_XU{=dg^WH^e>;DI@(PIGs literal 2034 zcmV_bktLya)kRPjUV9-y-js+Jp$4TiYUj~RqL{KsqmT}xW|}jOGt8*7pZDVX+OOMF zBX3)sas6Sz-fOSZ{<+q(*Lv0(B}4=!aplJlFvtut6NAhkuO-PAdLWTV*tBDbA|eUv z$;@0Z$V_6`Y#YM;I6wz|_#=w5 zUOw&1moJf>osAzqe(-ToQBgR0^eAFtVxUr~;BYwb_U&6VH#g(UmoI#}(9lq{w6q{0 zAwg6tFxv~a(9X!R_2lmEZaQ(|1Z~{7ao*t`5fMS>&z~oY#X@$woe~lf1kcZ$IYTCs zY2LC;CKDAG7YowI#Kh3Z$jI6;7x(Vnqs^N)FKSbNhr@>tQ&CZoAg-dK!lQc1%E|=! zu3fuE9LM=SU2y*=+W0$iu_Kl$e;vm*MB$n)Z5$3r*S%+)Y;id`T6;C8m&|+sj;zfNp&_h zHj+}QBmg9pN~x)-$+scjx^+tc`0CZG3#PSLEL2!nD5x_wHkO8lhL%+4;NT#|#>Vnx zR904sW->g5Eb5By{{8#>wN9r)d3pJQY1L{qDk>@v6&1zDg@uJ7BqU@>b%ut9A}lP7 zzqi?JaJ${UyR3fy{yl%K*XyBDsTNJYYu7F$Cnxjw9LFtr>NsO0^3yC&H$1%(^4Qqe z|JLp@83F?X|GdVR$q*hM&fgCX4#I3UFR4zm*^GgK0sda2(LgGd`Yz<(egux=V7J>n z%44xu1S8?$!-q?%^TC4${0LGg6oLhgPx>y7967@0&&kR8;|J*b_wN)P9WAIgF)@*B zHk(KF*=#o2w{IVx_u#>UG&(x!+p~-7*RS*W6$%AaS645ZzO=M--nnu?LBStaZzCfk zR8UYL$amw$4c}k!O-xKsT3Q-khFYzr>gsCx`SYhB-KS5V=B(CchxGJxG8harF)=Zx z4uio!dc9td{?esOtHwbjsz2FkwF;(SR;v{yB_(KRXh3ReDgpumFf=rTwzf9(^zO6h7GXUY%rNjXl-r9e{bHhhTnC{<#MX4tMh0vVzF4Lrly86Gc#$=o;}`UF4SuEvICwSE?l@k zQ&Uq5w(;G&cT`?pPVw>aynkAyQhATLShsE+tX3-|5(y$BBe7-67QwLo+mcG9;5ZI_ zeSNT6t^71@_JYQ2Hsi^YC#bEh#fJ|c@a@|-!E=p9<2`$Petv%Zi$; zYNoHEzrP1qDD zsi_I|_4R_d-zH(_&Yd`U@+3~5K8=Qk20VZMeC-|y_%E86m=O3zA|oS_nwpA&f&wHb zCnF#r007`{I7BmLJ`WiHf`fyxd-rbS=H?P2RnVFd@3V2OGw%hG2yd;OoUAK@9T@{ zf?X57i{I+*?nZZaHv$6#k(HH&^z?LOWo02aI2a)zA-wadAja zPKHvcIusQZAulfvw{PFZ=g*%-gS;#~SuU3g4grd-;NW2X`q;5!C@CpH zPfrhOYHINK@nZ}R5A)BBMk9vya2yAh%O&1a$`sHhb`5^(wQ zWtyIzUa*ZWmy2G#dPP-LRg{#JL;y&m(Rk01fQuI|qN=J24u@meXA&-#3vqFA$j{GT z^aPPYp@3Gag;uM@@#DwQ)zyU;FJ5?$x$t#)YA%?Ai1{uUWF`igL1vJd7-R;Si9u$N z8Du5~nL%cdnZ%aI{^XVZpAoDlGcm{v@*0qYZ7Z>!%pfx{$P6-r%=B0F55A`$8008h Q1ONa407*qoM6N<$f}*wGVE_OC From 1c198f42ddbeeaf61a9553ee4744089684ccccec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 01:14:02 +0000 Subject: [PATCH 32/36] Remove LuaName functionality from WayPoints - waypoints now use Name only Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- .../ContextMenus/MaterialObjectContextMenu.cs | 6 +- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 1 - TombEditor/Forms/FormWayPoint.cs | 55 ------------------- .../TombEngine/LevelCompilerTombEngine.cs | 3 +- .../LevelData/Compilers/TombEngine/Structs.cs | 1 - .../Compilers/TombEngine/TombEngine.cs | 1 - .../LevelData/Instances/WayPointInstance.cs | 9 +-- 7 files changed, 5 insertions(+), 71 deletions(-) diff --git a/TombEditor/Controls/ContextMenus/MaterialObjectContextMenu.cs b/TombEditor/Controls/ContextMenus/MaterialObjectContextMenu.cs index 0063c126c..e288719b1 100644 --- a/TombEditor/Controls/ContextMenus/MaterialObjectContextMenu.cs +++ b/TombEditor/Controls/ContextMenus/MaterialObjectContextMenu.cs @@ -9,7 +9,7 @@ class MaterialObjectContextMenu : BaseContextMenu public MaterialObjectContextMenu(Editor editor, IWin32Window owner, ObjectInstance targetObject) : base(editor, owner) { - if (targetObject is IHasScriptID) + if (targetObject is IHasScriptID && !(targetObject is WayPointInstance)) { if (_editor.Level.IsNG && targetObject == editor.SelectedObject) { @@ -33,7 +33,7 @@ public MaterialObjectContextMenu(Editor editor, IWin32Window owner, ObjectInstan } } - if (!(targetObject is LightInstance || targetObject is GhostBlockInstance)) + if (!(targetObject is LightInstance || targetObject is GhostBlockInstance || targetObject is WayPointInstance)) { Items.Add(new ToolStripMenuItem("Edit object", Properties.Resources.general_edit_16, (o, e) => { @@ -137,7 +137,7 @@ public MaterialObjectContextMenu(Editor editor, IWin32Window owner, ObjectInstan })); } - if (targetObject is PositionAndScriptBasedObjectInstance && _editor.Level.Settings.GameVersion == TRVersion.Game.TombEngine) + if (targetObject is PositionAndScriptBasedObjectInstance && !(targetObject is WayPointInstance) && _editor.Level.Settings.GameVersion == TRVersion.Game.TombEngine) { Items.Add(new ToolStripMenuItem("Copy Lua name to clipboard", null, (o, e) => { diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 74ed7681e..d64494a5e 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1180,7 +1180,6 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis textToDraw.Add(CreateTextTagForObject( instance.RotationPositionMatrix * _viewProjection, label + - instance.GetScriptIDOrName() + "\n" + GetObjectPositionString(instance.Room, instance) + GetObjectTriggerString(instance))); // Add the line height of the object diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index 2f37a2036..10592b26d 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -192,61 +192,6 @@ private void butOK_Click(object sender, EventArgs e) fullNewName = newName + "_" + newNumber; } - // Check if a LuaName with this value already exists - if (_editor?.Level != null) - { - foreach (var room in _editor.Level.ExistingRooms) - { - foreach (var obj in room.Objects) - { - if (obj == _wayPoint) - continue; - - if (obj is PositionAndScriptBasedObjectInstance scriptObj) - { - if (!string.IsNullOrEmpty(scriptObj.LuaName) && scriptObj.LuaName == fullNewName) - { - // LuaName conflict - clear the LuaName for this waypoint - _wayPoint.LuaName = ""; - DarkMessageBox.Show(this, $"A LuaName '{fullNewName}' already exists for another object. The LuaName for this waypoint has been cleared. Please generate a new LuaName.", "LuaName Conflict", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - - // Update waypoint properties but with cleared LuaName - _wayPoint.Name = newName; - _wayPoint.Sequence = (ushort)numSequence.Value; - _wayPoint.Number = newNumber; - _wayPoint.Type = newType; - _wayPoint.Radius1 = (float)numDimension1.Value; - _wayPoint.Radius2 = (float)numDimension2.Value; - _wayPoint.RotationX = (float)numRotationX.Value; - _wayPoint.RotationY = (float)numRotationY.Value; - _wayPoint.Roll = (float)numRoll.Value; - - // Batch type update - if (oldType != newType && _editor?.Level != null) - { - var oldSequence = _wayPoint.Sequence; - foreach (var r in _editor.Level.ExistingRooms) - { - foreach (var o in r.Objects.OfType()) - { - if (o != _wayPoint && (o.BaseName == oldBaseName || o.Sequence == oldSequence)) - { - o.Type = newType; - } - } - } - } - - DialogResult = DialogResult.OK; - Close(); - return; - } - } - } - } - } - // Update waypoint properties _wayPoint.Name = newName; _wayPoint.Sequence = (ushort)numSequence.Value; diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs index a1bdeb9c9..fb89588b7 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/LevelCompilerTombEngine.cs @@ -397,8 +397,7 @@ private void BuildCamerasAndSinks() Type = (int)instance.Type, Radius1 = instance.Radius1, Radius2 = instance.Radius2, - Name = instance.Name, - LuaName = instance.LuaName ?? string.Empty + Name = instance.Name }); } } diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs index 69a833cba..af681ed32 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Structs.cs @@ -601,7 +601,6 @@ public struct TombEngineWayPoint public float Radius1; public float Radius2; public string Name; - public string LuaName; } [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs index b0afe65e8..b33e8461a 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/TombEngine.cs @@ -93,7 +93,6 @@ private void WriteLevelTombEngine() writer.Write(waypoint.Radius1); writer.Write(waypoint.Radius2); writer.Write(waypoint.Name); - writer.Write(waypoint.LuaName); } writer.Write((uint)_sinks.Count); diff --git a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs index 5ff1607ec..4812e000f 100644 --- a/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs +++ b/TombLib/TombLib/LevelData/Instances/WayPointInstance.cs @@ -15,7 +15,7 @@ public enum WayPointType Bezier // Multi-point bezier path } - public class WayPointInstance : PositionAndScriptBasedObjectInstance, IRotateableYXRoll, ISizeable + public class WayPointInstance : PositionBasedObjectInstance, IRotateableYXRoll, ISizeable { private string _name = ""; private ushort _sequence; @@ -71,9 +71,6 @@ public string Name { _name = ""; } - - // Update LuaName to match Name - LuaName = Name; } } @@ -83,8 +80,6 @@ public ushort Number set { _number = value; - // Update LuaName when number changes - LuaName = Name; } } @@ -94,8 +89,6 @@ public WayPointType Type set { _type = value; - // Update LuaName when type changes (affects Name format) - LuaName = Name; } } From 026ae2aebe44db3b5c41d2c486c0508afa72b0eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 01:33:00 +0000 Subject: [PATCH 33/36] Fix WayPoint context menu and add rotation display Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- .../Controls/ContextMenus/MaterialObjectContextMenu.cs | 2 +- TombEditor/Controls/Panel3D/Panel3DDraw.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/TombEditor/Controls/ContextMenus/MaterialObjectContextMenu.cs b/TombEditor/Controls/ContextMenus/MaterialObjectContextMenu.cs index e288719b1..d9a31c4e6 100644 --- a/TombEditor/Controls/ContextMenus/MaterialObjectContextMenu.cs +++ b/TombEditor/Controls/ContextMenus/MaterialObjectContextMenu.cs @@ -33,7 +33,7 @@ public MaterialObjectContextMenu(Editor editor, IWin32Window owner, ObjectInstan } } - if (!(targetObject is LightInstance || targetObject is GhostBlockInstance || targetObject is WayPointInstance)) + if (!(targetObject is LightInstance || targetObject is GhostBlockInstance)) { Items.Add(new ToolStripMenuItem("Edit object", Properties.Resources.general_edit_16, (o, e) => { diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index d64494a5e..765d03fc3 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1177,10 +1177,14 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis $"{instance.BaseName} " : $"{instance.BaseName} ({instance.Number}) "; + string rotationInfo = GetObjectRotationString(instance.Room, instance); + if (!string.IsNullOrEmpty(rotationInfo)) + rotationInfo = "\n" + rotationInfo; + textToDraw.Add(CreateTextTagForObject( instance.RotationPositionMatrix * _viewProjection, label + - GetObjectPositionString(instance.Room, instance) + GetObjectTriggerString(instance))); + GetObjectPositionString(instance.Room, instance) + rotationInfo + GetObjectTriggerString(instance))); // Add the line height of the object AddObjectHeightLine(instance.Room, instance.Position); From da368c1a134b42579ce1ad6fbfee7ef4fcab0fc5 Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Sun, 25 Jan 2026 20:41:25 -0500 Subject: [PATCH 34/36] fix bugs --- TombLib/TombLib/LevelData/IO/Prj2Loader.cs | 1 - TombLib/TombLib/LevelData/IO/Prj2Writer.cs | 1 - TombLib/TombLib/LevelData/Instances/WayPointInstance.cs | 8 ++------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs index 85e9aebf6..90ae3ab23 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs @@ -1408,7 +1408,6 @@ private static bool LoadObjects(ChunkReader chunkIO, ChunkId idOuter, LevelSetti instance.Position = chunkIO.Raw.ReadVector3(); instance.SetArbitaryRotationsYX(chunkIO.Raw.ReadSingle(), chunkIO.Raw.ReadSingle()); instance.Roll = chunkIO.Raw.ReadSingle(); - instance.ScriptId = ReadOptionalLEB128Int(chunkIO.Raw); string baseName = chunkIO.Raw.ReadStringUTF8(); instance.Sequence = LEB128.ReadUShort(chunkIO.Raw); instance.Number = LEB128.ReadUShort(chunkIO.Raw); diff --git a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs index 5b01d89a9..d0a4e6025 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs @@ -688,7 +688,6 @@ private static void WriteObjects(ChunkWriter chunkIO, IEnumerable "WayPoint " + Name + (IsSingularType() ? "" : " (" + Number + ")") + " " + " (" + (Room?.ToString() ?? "NULL") + ")"; } } From 6e1457fd8c04a041e4e8d1d58ff94bf89cf0c973 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 01:57:17 +0000 Subject: [PATCH 35/36] Add sequence number uniqueness validation Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEditor/Forms/FormWayPoint.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index 10592b26d..ab95f67cf 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -149,8 +149,10 @@ private void butOK_Click(object sender, EventArgs e) bool nameChanged = oldBaseName != newName; ushort currentNumber = (ushort)numNumber.Value; bool numberChanged = _wayPoint.Number != currentNumber; + ushort currentSequence = (ushort)numSequence.Value; + bool sequenceChanged = _wayPoint.Sequence != currentSequence; - if ((nameChanged || numberChanged) && _editor?.Level != null) + if (((nameChanged || numberChanged) || sequenceChanged) && _editor?.Level != null) { foreach (var room in _editor.Level.ExistingRooms) @@ -173,6 +175,16 @@ private void butOK_Click(object sender, EventArgs e) } // Different numbers - this is OK } + + // Check for duplicate sequence numbers + // Sequence must be unique UNLESS the base name is the same + if (obj.Sequence == currentSequence && obj.BaseName != newName) + { + // Same sequence but different base name = NOT allowed + DarkMessageBox.Show(this, $"A WayPoint with sequence number {currentSequence} already exists with a different name ('{obj.BaseName}'). Sequence numbers must be unique unless waypoints share the same base name.", "Duplicate Sequence", + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } } } } From f0a00891d7a7c618d7c76c0483d483f374dc0ebf Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:53:21 -0500 Subject: [PATCH 36/36] Update Validation code for Sequence/Name and Number --- TombEditor/Forms/FormWayPoint.cs | 72 ++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/TombEditor/Forms/FormWayPoint.cs b/TombEditor/Forms/FormWayPoint.cs index ab95f67cf..992329a77 100644 --- a/TombEditor/Forms/FormWayPoint.cs +++ b/TombEditor/Forms/FormWayPoint.cs @@ -119,14 +119,14 @@ private void butOK_Click(object sender, EventArgs e) string newName = txtName.Text.Trim(); if (string.IsNullOrEmpty(newName)) { - DarkMessageBox.Show(this, "WayPoint name cannot be empty.", "Validation Error", + DarkMessageBox.Show(this, "Waypoint name cannot be empty.", "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var oldType = _wayPoint.Type; var newType = (WayPointType)cmbType.SelectedIndex; - + // Extract old base name for comparison string oldBaseName = _wayPoint.Name; if (!_wayPoint.IsSingularType()) @@ -142,46 +142,57 @@ private void butOK_Click(object sender, EventArgs e) } } - // Check for duplicate names with validation rules: - // 1. Base name must be unique across all waypoints - // 2. Exception: same base name allowed ONLY if numbers are different - // (e.g., "test_0" and "test_1" are OK, but "test" Circle and "test" Point are NOT OK) + // Check for changes bool nameChanged = oldBaseName != newName; ushort currentNumber = (ushort)numNumber.Value; bool numberChanged = _wayPoint.Number != currentNumber; ushort currentSequence = (ushort)numSequence.Value; bool sequenceChanged = _wayPoint.Sequence != currentSequence; - - if (((nameChanged || numberChanged) || sequenceChanged) && _editor?.Level != null) + + // Validation rules: + // 1. Same name + same sequence can only exist if numbers are different + // 2. Same name + different sequence is NOT allowed + // 3. Different name + same sequence is NOT allowed + // 4. Same sequence + same name + same number is NOT allowed + // Always validate if any of these changed (removed the condition check) + if (_editor?.Level != null) { - foreach (var room in _editor.Level.ExistingRooms) { foreach (var obj in room.Objects.OfType()) { if (obj == _wayPoint) continue; - - // Check if another waypoint uses this base name - if (obj.BaseName == newName) + + // Rule 1 & 4: Check for same name + same sequence + if (obj.BaseName == newName && obj.Sequence == currentSequence) { - // Same base name found - only allowed if numbers are different + // Only allowed if numbers are different if (obj.Number == currentNumber) { - // Same base name AND same number = duplicate - DarkMessageBox.Show(this, $"A WayPoint with the name '{newName}' and number {currentNumber} already exists.", "Duplicate Name", + DarkMessageBox.Show(this, + $"A waypoint with name '{newName}', sequence {currentSequence}, and number {currentNumber} already exists.", + "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } - // Different numbers - this is OK + // Different numbers - this is allowed + } + // Rule 2: Check for same name + different sequence + else if (obj.BaseName == newName && obj.Sequence != currentSequence) + { + DarkMessageBox.Show(this, + $"A waypoint with name '{newName}' already exists with a different sequence ({obj.Sequence}). The same name cannot be used with different sequence numbers.", + "Validation Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; } - - // Check for duplicate sequence numbers - // Sequence must be unique UNLESS the base name is the same - if (obj.Sequence == currentSequence && obj.BaseName != newName) + // Rule 3: Check for different name + same sequence + else if (obj.BaseName != newName && obj.Sequence == currentSequence) { - // Same sequence but different base name = NOT allowed - DarkMessageBox.Show(this, $"A WayPoint with sequence number {currentSequence} already exists with a different name ('{obj.BaseName}'). Sequence numbers must be unique unless waypoints share the same base name.", "Duplicate Sequence", + DarkMessageBox.Show(this, + $"A waypoint with sequence {currentSequence} already exists with a different name ('{obj.BaseName}'). The same sequence number cannot be used with different names.", + "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } @@ -189,6 +200,23 @@ private void butOK_Click(object sender, EventArgs e) } } + // Batch type update: if type changed, update all waypoints with the same name/sequence pair + if (oldType != newType && _editor?.Level != null) + { + var oldSequence = _wayPoint.Sequence; + foreach (var room in _editor.Level.ExistingRooms) + { + foreach (var obj in room.Objects.OfType()) + { + // Update all waypoints that share the same base name (which implies same sequence) + if (obj != _wayPoint && obj.BaseName == oldBaseName) + { + obj.Type = newType; + } + } + } + } + // Generate the new full name that will be used string fullNewName = newName; ushort newNumber = (ushort)numNumber.Value;