From f8e21b17c63c14022b87148200973e73d35fb760 Mon Sep 17 00:00:00 2001 From: Tald0r <47738492+Tald0r@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:51:19 +0100 Subject: [PATCH 1/5] feat(large-scale): add alternate map source option in copy/move tool Add ability to use different map sources for copy/move operations with auto-detection of map dimensions. Introduces new UI elements including: - Checkbox to enable alternate source map - File selection for map/staidx/statics - Configuration fields for map dimensions - Automatic dimension detection based on map number - Path auto-completion for static files when selecting map Enables copying tiles between different map formats and installations. --- .../Tools/LargeScale/Operations/CopyMove.cs | 182 +++++++++++++++++- Client/Map/LargeScaleOperation.cs | 52 ++++- Server/Map/LargeScaleOperations.cs | 20 ++ Server/Map/ServerLandscape.cs | 53 +++++ Server/Map/ServerLandscapePacketHandlers.cs | 83 ++++++-- 5 files changed, 368 insertions(+), 22 deletions(-) diff --git a/CentrED/Tools/LargeScale/Operations/CopyMove.cs b/CentrED/Tools/LargeScale/Operations/CopyMove.cs index 2c10d276..6f250c13 100644 --- a/CentrED/Tools/LargeScale/Operations/CopyMove.cs +++ b/CentrED/Tools/LargeScale/Operations/CopyMove.cs @@ -26,6 +26,13 @@ public class CopyMove : RemoteLargeScaleTool // New: current area for conversions private RectU16 _currentArea; private bool _hasArea = false; + + private bool useAlternateSource = false; + private string alternateMapPath = ""; + private string alternateStaIdxPath = ""; + private string alternateStaticsPath = ""; + private int alternateMapWidth = 768; // Default to Map0 dimensions + private int alternateMapHeight = 512; public void SetArea(RectU16 area) { @@ -36,14 +43,121 @@ public void SetArea(RectU16 area) public override bool DrawUI() { var changed = false; - - // Copy / Move + + // Existing Copy/Move radio buttons changed |= ImGui.RadioButton(LangManager.Get(COPY), ref copyMove_type, (int)LSO.CopyMove.Copy); ImGui.SameLine(); changed |= ImGui.RadioButton(LangManager.Get(MOVE), ref copyMove_type, (int)LSO.CopyMove.Move); ImGui.Separator(); - + + // NEW: Checkbox for alternate source + changed |= ImGui.Checkbox("Use Different Source Map", ref useAlternateSource); + + if (useAlternateSource) + { + ImGui.Text("Source Map Configuration:"); + ImGui.Separator(); + + // File paths + ImGui.InputText("Map File##alt", ref alternateMapPath, 256); + ImGui.SameLine(); + if (ImGui.Button("Browse...##map")) + { + TinyFileDialogs.TryOpenFile + ("Select Map File", Environment.CurrentDirectory, ["*.mul"], null, false, out var result); + if (!string.IsNullOrEmpty(result)) + { + alternateMapPath = result; + // Auto-populate related files + var basePath = alternateMapPath.Replace(".mul", ""); + var mapNumber = basePath[basePath.Length - 1]; // Get map number + var directory = Path.GetDirectoryName(alternateMapPath); + var baseFileName = Path.GetFileNameWithoutExtension(alternateMapPath) + .Replace("Map", "").Replace("map", ""); + + alternateStaIdxPath = Path.Combine(directory, $"Staidx{mapNumber}.mul"); + alternateStaticsPath = Path.Combine(directory, $"Statics{mapNumber}.mul"); + + // Set default dimensions based on common UO map sizes + switch(mapNumber) + { + case '0': // OLD Felucca/Trammel + alternateMapWidth = 6144; + alternateMapHeight = 4096; + break; + case '1': // Felucca/Trammel + alternateMapWidth = 7168; + alternateMapHeight = 4096; + break; + case '2': // Ilshenar + alternateMapWidth = 2304; + alternateMapHeight = 1600; + break; + case '3': // Malas + alternateMapWidth = 2560; + alternateMapHeight = 2048; + break; + case '4': // Tokuno + alternateMapWidth = 1448; + alternateMapHeight = 1448; + break; + case '5': // TerMur + alternateMapWidth = 1280; + alternateMapHeight = 4096; + break; + case '6': // Custom + alternateMapWidth = 1280; + alternateMapHeight = 4096; + break; + default: + // Keep current values + break; + } + } + } + + ImGui.InputText("StaIdx File##alt", ref alternateStaIdxPath, 256); + ImGui.InputText("Statics File##alt", ref alternateStaticsPath, 256); + + ImGui.Separator(); + + ImGui.Text("Map Dimensions (in tiles):"); + changed |= ImGuiEx.DragInt("Width##alt", ref alternateMapWidth, 1, 64, 8192); + ImGui.SameLine(); + ImGui.TextDisabled("(?)"); + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Width of the source map in tiles.\nMap0: 7168, Map1: 2304"); + } + + changed |= ImGuiEx.DragInt("Height##alt", ref alternateMapHeight, 1, 64, 8192); + ImGui.SameLine(); + ImGui.TextDisabled("(?)"); + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Height of the source map in tiles.\nMap0: 4096, Map1: 1600"); + } + + ImGui.Separator(); + + // Validation warning + if (!string.IsNullOrEmpty(alternateMapPath) && !File.Exists(alternateMapPath)) + { + ImGui.TextColored(new System.Numerics.Vector4(1, 0, 0, 1), "Warning: Map file not found!"); + } + if (!string.IsNullOrEmpty(alternateStaIdxPath) && !File.Exists(alternateStaIdxPath)) + { + ImGui.TextColored(new System.Numerics.Vector4(1, 0.5f, 0, 1), "Warning: StaIdx file not found!"); + } + if (!string.IsNullOrEmpty(alternateStaticsPath) && !File.Exists(alternateStaticsPath)) + { + ImGui.TextColored(new System.Numerics.Vector4(1, 0.5f, 0, 1), "Warning: Statics file not found!"); + } + } + + ImGui.Separator(); + // Relative / Absolute toggle with auto-conversion int prevMode = copyMove_coordMode; changed |= ImGui.RadioButton(LangManager.Get(COORD_MODE_RELATIVE), ref copyMove_coordMode, 0); @@ -104,7 +218,7 @@ public override bool DrawUI() public override bool CanSubmit(RectU16 area) { - // Ensure offsets computed from current inputs/mode/area + // Calculate offsets if (copyMove_coordMode == 1) { copyMove_offsetX = copyMove_inputX - area.X1; @@ -116,7 +230,40 @@ public override bool CanSubmit(RectU16 area) copyMove_offsetY = copyMove_inputY; } - // Existing bounds checks + // Validation for alternate source map dimensions + if (useAlternateSource) + { + // Check if source area exists within alternate map bounds + if (area.X1 >= alternateMapWidth || area.X2 >= alternateMapWidth) + { + _submitStatus = $"Source area exceeds alternate map width ({alternateMapWidth})"; + return false; + } + if (area.Y1 >= alternateMapHeight || area.Y2 >= alternateMapHeight) + { + _submitStatus = $"Source area exceeds alternate map height ({alternateMapHeight})"; + return false; + } + + // Check if files exist + if (!File.Exists(alternateMapPath)) + { + _submitStatus = "Alternate map file not found"; + return false; + } + if (!File.Exists(alternateStaIdxPath)) + { + _submitStatus = "Alternate StaIdx file not found"; + return false; + } + if (!File.Exists(alternateStaticsPath)) + { + _submitStatus = "Alternate Statics file not found"; + return false; + } + } + + // Existing destination validation if (copyMove_offsetX < 0 && copyMove_offsetX + area.X1 < 0) { _submitStatus = LangManager.Get(INVALID_OFFSET_X); @@ -142,7 +289,28 @@ public override bool CanSubmit(RectU16 area) protected override ILargeScaleOperation SubmitLSO() { - // Still submit offsets; absolute entries were converted above - return new LSOCopyMove((LSO.CopyMove)copyMove_type, copyMove_erase, copyMove_offsetX, copyMove_offsetY); + if (useAlternateSource) + { + return new LSOCopyMove( + (LSO.CopyMove)copyMove_type, + copyMove_erase, + copyMove_offsetX, + copyMove_offsetY, + alternateMapPath, + alternateStaIdxPath, + alternateStaticsPath, + alternateMapWidth, + alternateMapHeight + ); + } + else + { + return new LSOCopyMove( + (LSO.CopyMove)copyMove_type, + copyMove_erase, + copyMove_offsetX, + copyMove_offsetY + ); + } } } \ No newline at end of file diff --git a/Client/Map/LargeScaleOperation.cs b/Client/Map/LargeScaleOperation.cs index e0cfcbe5..d90caa5c 100644 --- a/Client/Map/LargeScaleOperation.cs +++ b/Client/Map/LargeScaleOperation.cs @@ -13,15 +13,53 @@ public class LSOCopyMove : ILargeScaleOperation private readonly int offsetX; private readonly int offsetY; private readonly bool erase; - + + // NEW: Alternate map source fields + private readonly bool useAlternateSource; + private readonly string alternateMapPath; + private readonly string alternateStaIdxPath; + private readonly string alternateStaticsPath; + private readonly int alternateMapWidth; + private readonly int alternateMapHeight; + + // Original constructor (backwards compatible) public LSOCopyMove(CopyMove type, bool erase, int offsetX, int offsetY) { this.type = type; this.erase = erase; this.offsetX = offsetX; this.offsetY = offsetY; + this.useAlternateSource = false; + this.alternateMapPath = string.Empty; + this.alternateStaIdxPath = string.Empty; + this.alternateStaticsPath = string.Empty; + this.alternateMapWidth = 0; + this.alternateMapHeight = 0; } + // NEW: Extended constructor with alternate map support + public LSOCopyMove( + CopyMove type, + bool erase, + int offsetX, + int offsetY, + string alternateMapPath, + string alternateStaIdxPath, + string alternateStaticsPath, + int alternateMapWidth, + int alternateMapHeight) + { + this.type = type; + this.erase = erase; + this.offsetX = offsetX; + this.offsetY = offsetY; + this.useAlternateSource = true; + this.alternateMapPath = alternateMapPath ?? string.Empty; + this.alternateStaIdxPath = alternateStaIdxPath ?? string.Empty; + this.alternateStaticsPath = alternateStaticsPath ?? string.Empty; + this.alternateMapWidth = alternateMapWidth; + this.alternateMapHeight = alternateMapHeight; + } public void Write(BinaryWriter writer) { @@ -29,6 +67,18 @@ public void Write(BinaryWriter writer) writer.Write(offsetX); writer.Write(offsetY); writer.Write(erase); + + // NEW: Write alternate source flag and data + writer.Write(useAlternateSource); + + if (useAlternateSource) + { + writer.Write(alternateMapPath); + writer.Write(alternateStaIdxPath); + writer.Write(alternateStaticsPath); + writer.Write(alternateMapWidth); + writer.Write(alternateMapHeight); + } } } diff --git a/Server/Map/LargeScaleOperations.cs b/Server/Map/LargeScaleOperations.cs index f2f491af..221c7acc 100644 --- a/Server/Map/LargeScaleOperations.cs +++ b/Server/Map/LargeScaleOperations.cs @@ -15,12 +15,32 @@ public class LsCopyMove : LargeScaleOperation public int OffsetY; public bool Erase; + // NEW: Alternate map source fields + public bool UseAlternateSource; + public string AlternateMapPath; + public string AlternateStaIdxPath; + public string AlternateStaticsPath; + public ushort AlternateMapWidth; + public ushort AlternateMapHeight; + public LsCopyMove(ref SpanReader reader) { Type = (CopyMove)reader.ReadByte(); OffsetX = reader.ReadInt32(); OffsetY = reader.ReadInt32(); Erase = reader.ReadBoolean(); + + // NEW: Read alternate source flag and data + UseAlternateSource = reader.ReadBoolean(); + + if (UseAlternateSource) + { + AlternateMapPath = reader.ReadString(); + AlternateStaIdxPath = reader.ReadString(); + AlternateStaticsPath = reader.ReadString(); + AlternateMapWidth = reader.ReadUInt16(); + AlternateMapHeight = reader.ReadUInt16(); + } } } diff --git a/Server/Map/ServerLandscape.cs b/Server/Map/ServerLandscape.cs index 14698c19..d0836a16 100644 --- a/Server/Map/ServerLandscape.cs +++ b/Server/Map/ServerLandscape.cs @@ -99,6 +99,59 @@ Logger logger BlockCache.Resize(Math.Max(config.Map.Width, config.Map.Height) + 1); } + // NEW: Constructor for loading alternate map sources (read-only) + public ServerLandscape( + string mapPath, + string staIdxPath, + string staticsPath, + ushort width, + ushort height, + TileDataProvider tileDataProvider + ) : base((ushort)(width / 8), (ushort)(height / 8)) // Convert tiles to blocks + { + _logger = new Logger(); // Create a stub logger or pass null if allowed + + var mapFile = new FileInfo(mapPath); + if (!mapFile.Exists) + throw new Exception($"Alternate map file not found: {mapPath}"); + + _map = mapFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); + _mapReader = new BinaryReader(_map, Encoding.UTF8); + _mapWriter = null!; // Read-only, no writer needed + + IsUop = mapFile.Extension.Equals(".uop", StringComparison.OrdinalIgnoreCase); + if (IsUop) + { + string uopPattern = mapFile.Name.Replace(mapFile.Extension, "").ToLowerInvariant(); + ReadUopFiles(uopPattern); + } + + var staidxFile = new FileInfo(staIdxPath); + if (!staidxFile.Exists) + throw new Exception($"Alternate StaIdx file not found: {staIdxPath}"); + + _staidx = staidxFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); + _staidxReader = new BinaryReader(_staidx, Encoding.UTF8); + _staidxWriter = null!; // Read-only + + var staticsFile = new FileInfo(staticsPath); + if (!staticsFile.Exists) + throw new Exception($"Alternate Statics file not found: {staticsPath}"); + + _statics = staticsFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); + _staticsReader = new BinaryReader(_statics, Encoding.UTF8); + _staticsWriter = null!; // Read-only + + // Use the provided TileDataProvider from the main landscape + TileDataProvider = tileDataProvider; + + // Skip radar map initialization for read-only alternate sources + _radarMap = null!; + + // Don't need to validate or create cache for temporary read-only landscape + // Skip BlockCache setup since we're only reading blocks on-demand + } + private void InitMap(FileInfo map) { using var mapFile = map.Open(FileMode.CreateNew, FileAccess.Write); diff --git a/Server/Map/ServerLandscapePacketHandlers.cs b/Server/Map/ServerLandscapePacketHandlers.cs index 9615c734..264a0d80 100644 --- a/Server/Map/ServerLandscapePacketHandlers.cs +++ b/Server/Map/ServerLandscapePacketHandlers.cs @@ -434,30 +434,85 @@ private void LsoApply(LargeScaleOperation lso, LandTile landTile, IEnumerable Date: Sat, 22 Nov 2025 21:23:26 +0100 Subject: [PATCH 2/5] chore: add debug logging for alternate map source handling Added detailed debug logs to LsCopyMove deserialization and ServerLandscape alternate map initialization. This helps track alternate map file loading parameters and validation during large-scale operations by logging: - UseAlternateSource flag state - Alternate file paths and dimensions - File existence checks and sizes - UOP format detection Support troubleshooting map loading issues in development environments. --- Server/Map/LargeScaleOperations.cs | 9 ++ Server/Map/ServerLandscape.cs | 39 +++++-- Server/Map/ServerLandscapePacketHandlers.cs | 111 ++++++++++++++------ 3 files changed, 117 insertions(+), 42 deletions(-) diff --git a/Server/Map/LargeScaleOperations.cs b/Server/Map/LargeScaleOperations.cs index 221c7acc..916c4788 100644 --- a/Server/Map/LargeScaleOperations.cs +++ b/Server/Map/LargeScaleOperations.cs @@ -33,6 +33,9 @@ public LsCopyMove(ref SpanReader reader) // NEW: Read alternate source flag and data UseAlternateSource = reader.ReadBoolean(); + // DEBUG LOG + Console.WriteLine($"[LsCopyMove] UseAlternateSource: {UseAlternateSource}"); + if (UseAlternateSource) { AlternateMapPath = reader.ReadString(); @@ -40,6 +43,12 @@ public LsCopyMove(ref SpanReader reader) AlternateStaticsPath = reader.ReadString(); AlternateMapWidth = reader.ReadUInt16(); AlternateMapHeight = reader.ReadUInt16(); + + // DEBUG LOG + Console.WriteLine($"[LsCopyMove] Alternate Map: {AlternateMapPath}"); + Console.WriteLine($"[LsCopyMove] Alternate StaIdx: {AlternateStaIdxPath}"); + Console.WriteLine($"[LsCopyMove] Alternate Statics: {AlternateStaticsPath}"); + Console.WriteLine($"[LsCopyMove] Alternate Dimensions: {AlternateMapWidth}x{AlternateMapHeight}"); } } } diff --git a/Server/Map/ServerLandscape.cs b/Server/Map/ServerLandscape.cs index d0836a16..b7584c22 100644 --- a/Server/Map/ServerLandscape.cs +++ b/Server/Map/ServerLandscape.cs @@ -107,49 +107,66 @@ public ServerLandscape( ushort width, ushort height, TileDataProvider tileDataProvider - ) : base((ushort)(width / 8), (ushort)(height / 8)) // Convert tiles to blocks - { - _logger = new Logger(); // Create a stub logger or pass null if allowed + ) : base((ushort)(width / 8), (ushort)(height / 8)) + { + // DEBUG LOG + Console.WriteLine($"[ServerLandscape] Creating alternate landscape from:"); + Console.WriteLine($"[ServerLandscape] Map: {mapPath}"); + Console.WriteLine($"[ServerLandscape] StaIdx: {staIdxPath}"); + Console.WriteLine($"[ServerLandscape] Statics: {staticsPath}"); + Console.WriteLine($"[ServerLandscape] Dimensions (tiles): {width}x{height}"); + Console.WriteLine($"[ServerLandscape] Dimensions (blocks): {width/8}x{height/8}"); + + _logger = new Logger(); var mapFile = new FileInfo(mapPath); if (!mapFile.Exists) + { + Console.WriteLine($"[ServerLandscape] ERROR: Map file not found!"); throw new Exception($"Alternate map file not found: {mapPath}"); + } + Console.WriteLine($"[ServerLandscape] Map file exists: {mapFile.Length} bytes"); _map = mapFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); _mapReader = new BinaryReader(_map, Encoding.UTF8); - _mapWriter = null!; // Read-only, no writer needed + _mapWriter = null!; IsUop = mapFile.Extension.Equals(".uop", StringComparison.OrdinalIgnoreCase); if (IsUop) { + Console.WriteLine($"[ServerLandscape] UOP format detected"); string uopPattern = mapFile.Name.Replace(mapFile.Extension, "").ToLowerInvariant(); ReadUopFiles(uopPattern); } var staidxFile = new FileInfo(staIdxPath); if (!staidxFile.Exists) + { + Console.WriteLine($"[ServerLandscape] ERROR: StaIdx file not found!"); throw new Exception($"Alternate StaIdx file not found: {staIdxPath}"); + } + Console.WriteLine($"[ServerLandscape] StaIdx file exists: {staidxFile.Length} bytes"); _staidx = staidxFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); _staidxReader = new BinaryReader(_staidx, Encoding.UTF8); - _staidxWriter = null!; // Read-only + _staidxWriter = null!; var staticsFile = new FileInfo(staticsPath); if (!staticsFile.Exists) + { + Console.WriteLine($"[ServerLandscape] ERROR: Statics file not found!"); throw new Exception($"Alternate Statics file not found: {staticsPath}"); + } + Console.WriteLine($"[ServerLandscape] Statics file exists: {staticsFile.Length} bytes"); _statics = staticsFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); _staticsReader = new BinaryReader(_statics, Encoding.UTF8); - _staticsWriter = null!; // Read-only + _staticsWriter = null!; - // Use the provided TileDataProvider from the main landscape TileDataProvider = tileDataProvider; - - // Skip radar map initialization for read-only alternate sources _radarMap = null!; - // Don't need to validate or create cache for temporary read-only landscape - // Skip BlockCache setup since we're only reading blocks on-demand + Console.WriteLine($"[ServerLandscape] Alternate landscape initialized successfully"); } private void InitMap(FileInfo map) diff --git a/Server/Map/ServerLandscapePacketHandlers.cs b/Server/Map/ServerLandscapePacketHandlers.cs index 264a0d80..336928be 100644 --- a/Server/Map/ServerLandscapePacketHandlers.cs +++ b/Server/Map/ServerLandscapePacketHandlers.cs @@ -424,6 +424,11 @@ private void LsoApply(LargeScaleOperation lso, LandTile landTile, IEnumerable ({x},{y})"); + Console.WriteLine($"[LsoApply] UseAlternateSource: {copyMove.UseAlternateSource}"); + var targetLandTile = GetLandTile(x, y); var targetStaticsBlock = GetStaticBlock((ushort)(x / 8), (ushort)(y / 8)); if (copyMove.Erase) @@ -437,55 +442,99 @@ private void LsoApply(LargeScaleOperation lso, LandTile landTile, IEnumerable Date: Sat, 22 Nov 2025 22:11:11 +0100 Subject: [PATCH 3/5] feat(copy-move): enhance alternate source handling and logging - Added detailed debug logging for alternate source parameters in CopyMove.cs - Refactored LSOCopyMove serialization to use WriteStringNull for paths and cast dimensions to ushort - Improved server-side LsCopyMove deserialization with: - Try-catch block for error handling - Enhanced debug logging for each alternate source parameter - Structured reading of map dimensions as ushort values These changes improve debugging capabilities and ensure proper data serialization when using alternate map sources for copy/move operations. --- .../Tools/LargeScale/Operations/CopyMove.cs | 8 +++ Client/Map/LargeScaleOperation.cs | 13 +++-- Server/Map/LargeScaleOperations.cs | 56 ++++++++++++------- Server/Map/ServerLandscapePacketHandlers.cs | 14 ++++- 4 files changed, 62 insertions(+), 29 deletions(-) diff --git a/CentrED/Tools/LargeScale/Operations/CopyMove.cs b/CentrED/Tools/LargeScale/Operations/CopyMove.cs index 6f250c13..f17e206f 100644 --- a/CentrED/Tools/LargeScale/Operations/CopyMove.cs +++ b/CentrED/Tools/LargeScale/Operations/CopyMove.cs @@ -291,6 +291,13 @@ protected override ILargeScaleOperation SubmitLSO() { if (useAlternateSource) { + Console.WriteLine($"[CopyMove.SubmitLSO] Creating LSOCopyMove with alternate source:"); + Console.WriteLine($"[CopyMove.SubmitLSO] alternateMapPath: '{alternateMapPath}'"); + Console.WriteLine($"[CopyMove.SubmitLSO] alternateStaIdxPath: '{alternateStaIdxPath}'"); + Console.WriteLine($"[CopyMove.SubmitLSO] alternateStaticsPath: '{alternateStaticsPath}'"); + Console.WriteLine($"[CopyMove.SubmitLSO] alternateMapWidth: {alternateMapWidth}"); + Console.WriteLine($"[CopyMove.SubmitLSO] alternateMapHeight: {alternateMapHeight}"); + return new LSOCopyMove( (LSO.CopyMove)copyMove_type, copyMove_erase, @@ -305,6 +312,7 @@ protected override ILargeScaleOperation SubmitLSO() } else { + Console.WriteLine($"[CopyMove.SubmitLSO] Creating LSOCopyMove without alternate source"); return new LSOCopyMove( (LSO.CopyMove)copyMove_type, copyMove_erase, diff --git a/Client/Map/LargeScaleOperation.cs b/Client/Map/LargeScaleOperation.cs index d90caa5c..fe869775 100644 --- a/Client/Map/LargeScaleOperation.cs +++ b/Client/Map/LargeScaleOperation.cs @@ -1,4 +1,5 @@ -using static CentrED.Network.LSO; +using CentrED.Utility; +using static CentrED.Network.LSO; namespace CentrED.Client.Map; @@ -73,11 +74,11 @@ public void Write(BinaryWriter writer) if (useAlternateSource) { - writer.Write(alternateMapPath); - writer.Write(alternateStaIdxPath); - writer.Write(alternateStaticsPath); - writer.Write(alternateMapWidth); - writer.Write(alternateMapHeight); + writer.WriteStringNull(alternateMapPath); + writer.WriteStringNull(alternateStaIdxPath); + writer.WriteStringNull(alternateStaticsPath); + writer.Write((ushort)alternateMapWidth); + writer.Write((ushort)alternateMapHeight); } } } diff --git a/Server/Map/LargeScaleOperations.cs b/Server/Map/LargeScaleOperations.cs index 916c4788..b235c1b2 100644 --- a/Server/Map/LargeScaleOperations.cs +++ b/Server/Map/LargeScaleOperations.cs @@ -25,30 +25,44 @@ public class LsCopyMove : LargeScaleOperation public LsCopyMove(ref SpanReader reader) { - Type = (CopyMove)reader.ReadByte(); - OffsetX = reader.ReadInt32(); - OffsetY = reader.ReadInt32(); - Erase = reader.ReadBoolean(); - - // NEW: Read alternate source flag and data - UseAlternateSource = reader.ReadBoolean(); - - // DEBUG LOG - Console.WriteLine($"[LsCopyMove] UseAlternateSource: {UseAlternateSource}"); - - if (UseAlternateSource) + try { - AlternateMapPath = reader.ReadString(); - AlternateStaIdxPath = reader.ReadString(); - AlternateStaticsPath = reader.ReadString(); - AlternateMapWidth = reader.ReadUInt16(); - AlternateMapHeight = reader.ReadUInt16(); + Type = (CopyMove)reader.ReadByte(); + OffsetX = reader.ReadInt32(); + OffsetY = reader.ReadInt32(); + Erase = reader.ReadBoolean(); + + // NEW: Read alternate source flag and data + UseAlternateSource = reader.ReadBoolean(); // DEBUG LOG - Console.WriteLine($"[LsCopyMove] Alternate Map: {AlternateMapPath}"); - Console.WriteLine($"[LsCopyMove] Alternate StaIdx: {AlternateStaIdxPath}"); - Console.WriteLine($"[LsCopyMove] Alternate Statics: {AlternateStaticsPath}"); - Console.WriteLine($"[LsCopyMove] Alternate Dimensions: {AlternateMapWidth}x{AlternateMapHeight}"); + Console.WriteLine($"[LsCopyMove] UseAlternateSource: {UseAlternateSource}"); + + if (UseAlternateSource) + { + AlternateMapPath = reader.ReadString(); + Console.WriteLine($"[LsCopyMove] Read AlternateMapPath: '{AlternateMapPath}'"); + + AlternateStaIdxPath = reader.ReadString(); + Console.WriteLine($"[LsCopyMove] Read AlternateStaIdxPath: '{AlternateStaIdxPath}'"); + + AlternateStaticsPath = reader.ReadString(); + Console.WriteLine($"[LsCopyMove] Read AlternateStaticsPath: '{AlternateStaticsPath}'"); + + AlternateMapWidth = reader.ReadUInt16(); + Console.WriteLine($"[LsCopyMove] Read AlternateMapWidth: {AlternateMapWidth}"); + + AlternateMapHeight = reader.ReadUInt16(); + Console.WriteLine($"[LsCopyMove] Read AlternateMapHeight: {AlternateMapHeight}"); + + Console.WriteLine($"[LsCopyMove] All alternate source fields read successfully"); + } + } + catch (Exception ex) + { + Console.WriteLine($"[LsCopyMove] ERROR reading from SpanReader: {ex.Message}"); + Console.WriteLine($"[LsCopyMove] Stack trace: {ex.StackTrace}"); + throw; } } } diff --git a/Server/Map/ServerLandscapePacketHandlers.cs b/Server/Map/ServerLandscapePacketHandlers.cs index 336928be..0d25dc2f 100644 --- a/Server/Map/ServerLandscapePacketHandlers.cs +++ b/Server/Map/ServerLandscapePacketHandlers.cs @@ -271,6 +271,9 @@ private void OnLargeScaleCommandPacket(SpanReader reader, NetState ns ns.LogInfo(logMsg); ns.Parent.Send(new ServerStatePacket(ServerState.Other, logMsg)); ns.Parent.Flush(); + + bool radarUpdateStarted = false; // NEW: Track if radar update was started + try { var affectedBlocks = new bool[Width * Height]; @@ -335,14 +338,17 @@ private void OnLargeScaleCommandPacket(SpanReader reader, NetState ns if (reader.ReadBoolean()) operations.Add(new LsDeleteStatics(ref reader)); if (reader.ReadBoolean()) - operations.Add(new LsInsertStatics(ref reader)); + operations.Add(new LsInsertStatics(ref reader)); //We have read everything, now we can validate foreach (var operation in operations) { operation.Validate(this); } + // NEW: Only start radar update AFTER all reading/validation is successful _radarMap.BeginUpdate(); + radarUpdateStarted = true; // NEW: Mark that we started the update + foreach (ushort blockX in xBlockRange) { foreach (ushort blockY in yBlockRange) @@ -411,7 +417,11 @@ private void OnLargeScaleCommandPacket(SpanReader reader, NetState ns } finally { - _radarMap.EndUpdate(ns); + // NEW: Only end radar update if it was actually started + if (radarUpdateStarted) + { + _radarMap.EndUpdate(ns); + } ns.Parent.Send(new ServerStatePacket(ServerState.Running)); } ns.LogInfo("Large scale operation ended."); From 0d9083b0b76e9787ab99d57c9af714d934bed68d Mon Sep 17 00:00:00 2001 From: Tald0r <47738492+Tald0r@users.noreply.github.com> Date: Sat, 22 Nov 2025 22:47:15 +0100 Subject: [PATCH 4/5] fix: enforce Copy mode and Absolute coords for alternate source When using an alternate source map: - Force Copy mode (disable Move option) with explanatory tooltip - Enforce Absolute coordinate mode (disable Relative) with tooltip - Expand source map dimension tooltips to include multiple maps - Update coordinate system switching logic to handle new constraints - Provide clear UI feedback when operations are restricted --- .../Tools/LargeScale/Operations/CopyMove.cs | 101 ++++++++++++++---- Server/Map/ServerLandscape.cs | 22 ++-- 2 files changed, 91 insertions(+), 32 deletions(-) diff --git a/CentrED/Tools/LargeScale/Operations/CopyMove.cs b/CentrED/Tools/LargeScale/Operations/CopyMove.cs index f17e206f..50f41bf3 100644 --- a/CentrED/Tools/LargeScale/Operations/CopyMove.cs +++ b/CentrED/Tools/LargeScale/Operations/CopyMove.cs @@ -44,10 +44,39 @@ public override bool DrawUI() { var changed = false; - // Existing Copy/Move radio buttons - changed |= ImGui.RadioButton(LangManager.Get(COPY), ref copyMove_type, (int)LSO.CopyMove.Copy); - ImGui.SameLine(); - changed |= ImGui.RadioButton(LangManager.Get(MOVE), ref copyMove_type, (int)LSO.CopyMove.Move); + // Copy/Move radio buttons - DISABLE Move when using alternate source + if (useAlternateSource) + { + // Force Copy mode when using alternate source + if (copyMove_type != (int)LSO.CopyMove.Copy) + { + copyMove_type = (int)LSO.CopyMove.Copy; + changed = true; + } + + // Draw Copy as enabled + changed |= ImGui.RadioButton(LangManager.Get(COPY), ref copyMove_type, (int)LSO.CopyMove.Copy); + + // Draw Move as disabled + ImGui.BeginDisabled(); + ImGui.SameLine(); + int disabledMove = (int)LSO.CopyMove.Move; + ImGui.RadioButton(LangManager.Get(MOVE), ref disabledMove, (int)LSO.CopyMove.Move); + ImGui.EndDisabled(); + + // Show tooltip on disabled Move button + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + { + ImGui.SetTooltip("Move is not supported when using a different source map.\nOnly Copy is available."); + } + } + else + { + // Normal mode - both Copy and Move enabled + changed |= ImGui.RadioButton(LangManager.Get(COPY), ref copyMove_type, (int)LSO.CopyMove.Copy); + ImGui.SameLine(); + changed |= ImGui.RadioButton(LangManager.Get(MOVE), ref copyMove_type, (int)LSO.CopyMove.Move); + } ImGui.Separator(); @@ -128,7 +157,7 @@ public override bool DrawUI() ImGui.TextDisabled("(?)"); if (ImGui.IsItemHovered()) { - ImGui.SetTooltip("Width of the source map in tiles.\nMap0: 7168, Map1: 2304"); + ImGui.SetTooltip("Width of the source map in tiles.\nMap0: 6144, Map1: 7168, Map2: 2304, etc..."); } changed |= ImGuiEx.DragInt("Height##alt", ref alternateMapHeight, 1, 64, 8192); @@ -136,7 +165,7 @@ public override bool DrawUI() ImGui.TextDisabled("(?)"); if (ImGui.IsItemHovered()) { - ImGui.SetTooltip("Height of the source map in tiles.\nMap0: 4096, Map1: 1600"); + ImGui.SetTooltip("Height of the source map in tiles.\nMap0: 4096, Map1: 4096, Map2: 1600, etc..."); } ImGui.Separator(); @@ -158,25 +187,55 @@ public override bool DrawUI() ImGui.Separator(); - // Relative / Absolute toggle with auto-conversion - int prevMode = copyMove_coordMode; - changed |= ImGui.RadioButton(LangManager.Get(COORD_MODE_RELATIVE), ref copyMove_coordMode, 0); - ImGui.SameLine(); - changed |= ImGui.RadioButton(LangManager.Get(COORD_MODE_ABSOLUTE), ref copyMove_coordMode, 1); - - if (prevMode != copyMove_coordMode && _hasArea) + // DESTINATION COORDINATES SECTION + // When using alternate source, force Absolute mode and disable Relative + if (useAlternateSource) { - if (copyMove_coordMode == 1) + // Force Absolute mode + if (copyMove_coordMode != 1) + { + copyMove_coordMode = 1; + changed = true; + } + + // Draw Relative as disabled + ImGui.BeginDisabled(); + int disabledRelative = 0; + ImGui.RadioButton(LangManager.Get(COORD_MODE_RELATIVE), ref disabledRelative, 0); + ImGui.EndDisabled(); + + // Show tooltip on disabled Relative button + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { - // Relative -> Absolute - copyMove_inputX = _currentArea.X1 + copyMove_inputX; - copyMove_inputY = _currentArea.Y1 + copyMove_inputY; + ImGui.SetTooltip("Relative coordinates are not supported when using a different source map.\nOnly Absolute coordinates are available."); } - else + + ImGui.SameLine(); + // Draw Absolute as enabled + changed |= ImGui.RadioButton(LangManager.Get(COORD_MODE_ABSOLUTE), ref copyMove_coordMode, 1); + } + else + { + // Normal mode - both Relative and Absolute enabled + int prevMode = copyMove_coordMode; + changed |= ImGui.RadioButton(LangManager.Get(COORD_MODE_RELATIVE), ref copyMove_coordMode, 0); + ImGui.SameLine(); + changed |= ImGui.RadioButton(LangManager.Get(COORD_MODE_ABSOLUTE), ref copyMove_coordMode, 1); + + if (prevMode != copyMove_coordMode && _hasArea) { - // Absolute -> Relative - copyMove_inputX = copyMove_inputX - _currentArea.X1; - copyMove_inputY = copyMove_inputY - _currentArea.Y1; + if (copyMove_coordMode == 1) + { + // Relative -> Absolute + copyMove_inputX = _currentArea.X1 + copyMove_inputX; + copyMove_inputY = _currentArea.Y1 + copyMove_inputY; + } + else + { + // Absolute -> Relative + copyMove_inputX = copyMove_inputX - _currentArea.X1; + copyMove_inputY = copyMove_inputY - _currentArea.Y1; + } } } diff --git a/Server/Map/ServerLandscape.cs b/Server/Map/ServerLandscape.cs index b7584c22..bd691232 100644 --- a/Server/Map/ServerLandscape.cs +++ b/Server/Map/ServerLandscape.cs @@ -117,7 +117,7 @@ TileDataProvider tileDataProvider Console.WriteLine($"[ServerLandscape] Dimensions (tiles): {width}x{height}"); Console.WriteLine($"[ServerLandscape] Dimensions (blocks): {width/8}x{height/8}"); - _logger = new Logger(); + _logger = null!; // Set to null for alternate sources var mapFile = new FileInfo(mapPath); if (!mapFile.Exists) @@ -164,7 +164,7 @@ TileDataProvider tileDataProvider _staticsWriter = null!; TileDataProvider = tileDataProvider; - _radarMap = null!; + _radarMap = null!; // No radar map for alternate sources Console.WriteLine($"[ServerLandscape] Alternate landscape initialized successfully"); } @@ -661,15 +661,15 @@ private void Dispose(bool disposing) { if (disposing) { - _map.Dispose(); - _statics.Dispose(); - _staidx.Dispose(); - _mapReader.Dispose(); - _staticsReader.Dispose(); - _staidxReader.Dispose(); - _mapWriter.Dispose(); - _staticsWriter.Dispose(); - _staidxWriter.Dispose(); + _map?.Dispose(); + _statics?.Dispose(); + _staidx?.Dispose(); + _mapReader?.Dispose(); + _staticsReader?.Dispose(); + _staidxReader?.Dispose(); + _mapWriter?.Dispose(); + _staticsWriter?.Dispose(); + _staidxWriter?.Dispose(); } } From 0f0da50c928c1b6129274b1082b6ca66584a0d51 Mon Sep 17 00:00:00 2001 From: Tald0r <47738492+Tald0r@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:08:26 +0100 Subject: [PATCH 5/5] feat: update default alternate map dimensions and remove debug logs - Update default alternate map dimensions from 768x512 to 7168x4096 in CopyMove class to support larger map sizes. - Deleted debug logs that are no longer needed. --- .../Tools/LargeScale/Operations/CopyMove.cs | 11 +---- Server/Map/LargeScaleOperations.cs | 14 ------- Server/Map/ServerLandscape.cs | 13 ------ Server/Map/ServerLandscapePacketHandlers.cs | 41 ++----------------- 4 files changed, 6 insertions(+), 73 deletions(-) diff --git a/CentrED/Tools/LargeScale/Operations/CopyMove.cs b/CentrED/Tools/LargeScale/Operations/CopyMove.cs index 50f41bf3..6189c842 100644 --- a/CentrED/Tools/LargeScale/Operations/CopyMove.cs +++ b/CentrED/Tools/LargeScale/Operations/CopyMove.cs @@ -31,8 +31,8 @@ public class CopyMove : RemoteLargeScaleTool private string alternateMapPath = ""; private string alternateStaIdxPath = ""; private string alternateStaticsPath = ""; - private int alternateMapWidth = 768; // Default to Map0 dimensions - private int alternateMapHeight = 512; + private int alternateMapWidth = 7168; // Default to Map0 dimensions + private int alternateMapHeight = 4096; public void SetArea(RectU16 area) { @@ -350,12 +350,6 @@ protected override ILargeScaleOperation SubmitLSO() { if (useAlternateSource) { - Console.WriteLine($"[CopyMove.SubmitLSO] Creating LSOCopyMove with alternate source:"); - Console.WriteLine($"[CopyMove.SubmitLSO] alternateMapPath: '{alternateMapPath}'"); - Console.WriteLine($"[CopyMove.SubmitLSO] alternateStaIdxPath: '{alternateStaIdxPath}'"); - Console.WriteLine($"[CopyMove.SubmitLSO] alternateStaticsPath: '{alternateStaticsPath}'"); - Console.WriteLine($"[CopyMove.SubmitLSO] alternateMapWidth: {alternateMapWidth}"); - Console.WriteLine($"[CopyMove.SubmitLSO] alternateMapHeight: {alternateMapHeight}"); return new LSOCopyMove( (LSO.CopyMove)copyMove_type, @@ -371,7 +365,6 @@ protected override ILargeScaleOperation SubmitLSO() } else { - Console.WriteLine($"[CopyMove.SubmitLSO] Creating LSOCopyMove without alternate source"); return new LSOCopyMove( (LSO.CopyMove)copyMove_type, copyMove_erase, diff --git a/Server/Map/LargeScaleOperations.cs b/Server/Map/LargeScaleOperations.cs index b235c1b2..bd0187be 100644 --- a/Server/Map/LargeScaleOperations.cs +++ b/Server/Map/LargeScaleOperations.cs @@ -35,27 +35,13 @@ public LsCopyMove(ref SpanReader reader) // NEW: Read alternate source flag and data UseAlternateSource = reader.ReadBoolean(); - // DEBUG LOG - Console.WriteLine($"[LsCopyMove] UseAlternateSource: {UseAlternateSource}"); - if (UseAlternateSource) { AlternateMapPath = reader.ReadString(); - Console.WriteLine($"[LsCopyMove] Read AlternateMapPath: '{AlternateMapPath}'"); - AlternateStaIdxPath = reader.ReadString(); - Console.WriteLine($"[LsCopyMove] Read AlternateStaIdxPath: '{AlternateStaIdxPath}'"); - AlternateStaticsPath = reader.ReadString(); - Console.WriteLine($"[LsCopyMove] Read AlternateStaticsPath: '{AlternateStaticsPath}'"); - AlternateMapWidth = reader.ReadUInt16(); - Console.WriteLine($"[LsCopyMove] Read AlternateMapWidth: {AlternateMapWidth}"); - AlternateMapHeight = reader.ReadUInt16(); - Console.WriteLine($"[LsCopyMove] Read AlternateMapHeight: {AlternateMapHeight}"); - - Console.WriteLine($"[LsCopyMove] All alternate source fields read successfully"); } } catch (Exception ex) diff --git a/Server/Map/ServerLandscape.cs b/Server/Map/ServerLandscape.cs index bd691232..cbb4afda 100644 --- a/Server/Map/ServerLandscape.cs +++ b/Server/Map/ServerLandscape.cs @@ -109,14 +109,6 @@ public ServerLandscape( TileDataProvider tileDataProvider ) : base((ushort)(width / 8), (ushort)(height / 8)) { - // DEBUG LOG - Console.WriteLine($"[ServerLandscape] Creating alternate landscape from:"); - Console.WriteLine($"[ServerLandscape] Map: {mapPath}"); - Console.WriteLine($"[ServerLandscape] StaIdx: {staIdxPath}"); - Console.WriteLine($"[ServerLandscape] Statics: {staticsPath}"); - Console.WriteLine($"[ServerLandscape] Dimensions (tiles): {width}x{height}"); - Console.WriteLine($"[ServerLandscape] Dimensions (blocks): {width/8}x{height/8}"); - _logger = null!; // Set to null for alternate sources var mapFile = new FileInfo(mapPath); @@ -125,7 +117,6 @@ TileDataProvider tileDataProvider Console.WriteLine($"[ServerLandscape] ERROR: Map file not found!"); throw new Exception($"Alternate map file not found: {mapPath}"); } - Console.WriteLine($"[ServerLandscape] Map file exists: {mapFile.Length} bytes"); _map = mapFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); _mapReader = new BinaryReader(_map, Encoding.UTF8); @@ -145,7 +136,6 @@ TileDataProvider tileDataProvider Console.WriteLine($"[ServerLandscape] ERROR: StaIdx file not found!"); throw new Exception($"Alternate StaIdx file not found: {staIdxPath}"); } - Console.WriteLine($"[ServerLandscape] StaIdx file exists: {staidxFile.Length} bytes"); _staidx = staidxFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); _staidxReader = new BinaryReader(_staidx, Encoding.UTF8); @@ -157,7 +147,6 @@ TileDataProvider tileDataProvider Console.WriteLine($"[ServerLandscape] ERROR: Statics file not found!"); throw new Exception($"Alternate Statics file not found: {staticsPath}"); } - Console.WriteLine($"[ServerLandscape] Statics file exists: {staticsFile.Length} bytes"); _statics = staticsFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); _staticsReader = new BinaryReader(_statics, Encoding.UTF8); @@ -165,8 +154,6 @@ TileDataProvider tileDataProvider TileDataProvider = tileDataProvider; _radarMap = null!; // No radar map for alternate sources - - Console.WriteLine($"[ServerLandscape] Alternate landscape initialized successfully"); } private void InitMap(FileInfo map) diff --git a/Server/Map/ServerLandscapePacketHandlers.cs b/Server/Map/ServerLandscapePacketHandlers.cs index 0d25dc2f..6d6c5085 100644 --- a/Server/Map/ServerLandscapePacketHandlers.cs +++ b/Server/Map/ServerLandscapePacketHandlers.cs @@ -435,10 +435,6 @@ private void LsoApply(LargeScaleOperation lso, LandTile landTile, IEnumerable ({x},{y})"); - Console.WriteLine($"[LsoApply] UseAlternateSource: {copyMove.UseAlternateSource}"); - var targetLandTile = GetLandTile(x, y); var targetStaticsBlock = GetStaticBlock((ushort)(x / 8), (ushort)(y / 8)); if (copyMove.Erase) @@ -452,11 +448,6 @@ private void LsoApply(LargeScaleOperation lso, LandTile landTile, IEnumerable