From 32c27edf736ba8a2b7cb8b2078db4c8065619a25 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 22:23:38 +0000 Subject: [PATCH 1/3] feat(admin-panel): add a terrain editor for the maps and their terrain variants The new page /terrain-editor shows the terrain of a map and lets you paint its attributes (safezone, blocked, no ground, water) with a brush, a rectangle or a fill tool, adding, removing or replacing the selected attributes. It edits the default terrain and the terrain variants of a map, supports zoom, panning, a tile grid, undo and redo. Terrain files can be imported (server *.att or client EncTerrain*.att) and downloaded in both formats, so that the changed terrain can also be distributed to the game client. The page logic which loads the maps, saves and asks before discarding changes is shared with the map editor page through a common base class. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KESqfok2EbBMX41efgZUvw --- docs-website/docs/admin-panel/map-editor.md | 49 +- docs-website/docs/admin-panel/overview.md | 2 +- .../Components/Layout/ConfigNavMenu.razor | 3 + src/Web/AdminPanel/Pages/EditMap.cs | 273 +-------- src/Web/AdminPanel/Pages/EditTerrain.cs | 21 + src/Web/AdminPanel/Pages/MapEditorPageBase.cs | 295 ++++++++++ .../Properties/Resources.Designer.cs | 9 + .../AdminPanel/Properties/Resources.de.resx | 3 + src/Web/AdminPanel/Properties/Resources.resx | 3 + .../Properties/Resources.zh-CN.resx | 3 + .../TerrainEditor/TerrainEditMode.cs | 26 + .../TerrainEditor/TerrainEditor.razor | 153 +++++ .../TerrainEditor/TerrainEditor.razor.cs | 549 ++++++++++++++++++ .../TerrainEditor/TerrainEditor.razor.js | 524 +++++++++++++++++ .../TerrainEditor/TerrainFileConverter.cs | 178 ++++++ .../Components/TerrainEditor/TerrainTool.cs | 26 + src/Web/Shared/Exports.cs | 1 + .../Shared/Properties/Resources.Designer.cs | 93 +++ src/Web/Shared/Properties/Resources.de.resx | 93 +++ src/Web/Shared/Properties/Resources.resx | 93 +++ .../Shared/Properties/Resources.zh-CN.resx | 93 +++ src/Web/Shared/wwwroot/css/terrain-editor.css | 87 +++ .../TerrainEditor/TerrainEditorTests.cs | 115 ++++ .../TerrainFileConverterTests.cs | 118 ++++ 24 files changed, 2538 insertions(+), 272 deletions(-) create mode 100644 src/Web/AdminPanel/Pages/EditTerrain.cs create mode 100644 src/Web/AdminPanel/Pages/MapEditorPageBase.cs create mode 100644 src/Web/Shared/Components/TerrainEditor/TerrainEditMode.cs create mode 100644 src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor create mode 100644 src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.cs create mode 100644 src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.js create mode 100644 src/Web/Shared/Components/TerrainEditor/TerrainFileConverter.cs create mode 100644 src/Web/Shared/Components/TerrainEditor/TerrainTool.cs create mode 100644 src/Web/Shared/wwwroot/css/terrain-editor.css create mode 100644 tests/MUnique.OpenMU.Web.Tests/TerrainEditor/TerrainEditorTests.cs create mode 100644 tests/MUnique.OpenMU.Web.Tests/TerrainEditor/TerrainFileConverterTests.cs diff --git a/docs-website/docs/admin-panel/map-editor.md b/docs-website/docs/admin-panel/map-editor.md index 18f7abfa3..b9295ec3d 100644 --- a/docs-website/docs/admin-panel/map-editor.md +++ b/docs-website/docs/admin-panel/map-editor.md @@ -1,7 +1,7 @@ --- title: Map editor sidebar_position: 10 -description: Edit monster spawn areas and gates of a map graphically. +description: Edit monster spawn areas, gates and the terrain of a map graphically. --- # Map editor @@ -67,3 +67,50 @@ This is how you Import replaces the objects of the currently selected map, so export first if you want to be able to go back. + +## Terrain editor + +**Navigation:** *Configuration → Terrain editor* — route `/terrain-editor`. + +The terrain defines for every tile of a map whether it can be walked on, is a +safezone, a wall, a hole or water. The terrain editor lets you paint these +attributes directly on the map. + +Select the map, and in the second drop-down the terrain to edit: the **default +terrain**, or one of the **terrain variants** which replace it in certain states +of the map, e.g. the peace, occupied and war terrains of the Crywolf fortress. + +The tools: + +| Tool | Edits | +|---|---| +| **Brush** | The tiles under the cursor while the left mouse button is pressed; the brush size sets the width | +| **Rectangle** | All tiles of the dragged rectangle | +| **Fill** | All connected tiles which have the same attributes as the clicked one | + +The **mode** decides what happens to the selected **attributes**: *add* them to the +tiles, *remove* them, or *replace* all attributes of the tiles with them. Replace +without any selected attribute makes the tiles plain walkable ground. + +Use the mouse wheel or the zoom buttons to zoom, and drag with the right mouse +button to pan. Below the terrain you see the coordinates and the attributes of +the tile under the cursor. **Undo** and **Redo** work per terrain until you switch +to another one; **Save** writes the changes of all terrains to the database. + +### Terrain files + +* **Import terrain file** reads a terrain file into the selected terrain. It + accepts both the `*.att` file of the original server and the encrypted + `EncTerrain*.att` file of the game client. You can undo an import. +* **Server terrain file** downloads the terrain in the `*.att` format of the + original server. +* **Client terrain file** downloads the encrypted terrain file of the game + client. The client loads it from `Data\World\EncTerrain.att`, where `` + is the map number plus one. + +:::warning[The client needs the terrain, too] +The game servers use a changed terrain after they reloaded their configuration. +The game client, however, has its own copy of the terrain: if walls, safezones or +water changed, distribute the client terrain file to your players as well. +Otherwise characters get stuck or walk through walls on their screen. +::: diff --git a/docs-website/docs/admin-panel/overview.md b/docs-website/docs/admin-panel/overview.md index dd1186143..24fffdaf9 100644 --- a/docs-website/docs/admin-panel/overview.md +++ b/docs-website/docs/admin-panel/overview.md @@ -55,7 +55,7 @@ with `-adminpanel:disabled`. | [Game configuration](game-configuration.md) | Everything about the game world: system settings, items, monsters, skills, maps, drops, … | | [Plugins](plugins.md) | Activate, deactivate and configure plugins | | [Chat commands](chat-commands.md) | Which in-game commands exist and who may use them | -| [Map editor](map-editor.md) | Edit monster spawn areas and gates graphically | +| [Map editor](map-editor.md) | Edit monster spawn areas, gates and the [terrain](map-editor.md#terrain-editor) graphically | | [Live map](live-map.md) | Watch what happens on a map in real time | | [Logs and monitoring](logs-and-monitoring.md) | Log files, Grafana with logs, metrics and traces | | [Users](users.md) | The users which may log into the admin panel | diff --git a/src/Web/AdminPanel/Components/Layout/ConfigNavMenu.razor b/src/Web/AdminPanel/Components/Layout/ConfigNavMenu.razor index a2097ccc5..e00204b48 100644 --- a/src/Web/AdminPanel/Components/Layout/ConfigNavMenu.razor +++ b/src/Web/AdminPanel/Components/Layout/ConfigNavMenu.razor @@ -39,6 +39,9 @@ @Resources.MapEditor + + @Resources.TerrainEditor + @Resources.FullConfiguration diff --git a/src/Web/AdminPanel/Pages/EditMap.cs b/src/Web/AdminPanel/Pages/EditMap.cs index 9700e1617..1a7cb19a6 100644 --- a/src/Web/AdminPanel/Pages/EditMap.cs +++ b/src/Web/AdminPanel/Pages/EditMap.cs @@ -4,285 +4,18 @@ namespace MUnique.OpenMU.Web.AdminPanel.Pages; -using System.Reflection; -using System.Threading; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.AspNetCore.Components.Routing; -using Microsoft.Extensions.Logging; -using Microsoft.JSInterop; using MUnique.OpenMU.DataModel.Configuration; -using MUnique.OpenMU.Persistence; using MUnique.OpenMU.Web.AdminPanel.Properties; -using MUnique.OpenMU.Web.Shared; -using MUnique.OpenMU.Web.Shared.Components; using MUnique.OpenMU.Web.Shared.Components.MapEditor; -using MUnique.OpenMU.Web.Shared.Components.Modal; -using MUnique.OpenMU.Web.Shared.Components.Toast; -using MUnique.OpenMU.Web.Shared.Services; /// /// A page, which shows an for all . /// [Route("/map-editor")] [Route("/map-editor/{SelectedMapId:guid}")] -public sealed class EditMap : ComponentBase, IDisposable +public sealed class EditMap : MapEditorPageBase { - private List? _maps; - private CancellationTokenSource? _disposeCts; - private IContext? _context; - private IDisposable? _navigationLockDisposable; - - /// - /// Gets or sets the selected map identifier. - /// - [Parameter] - public Guid SelectedMapId { get; set; } - - /// - /// Gets or sets the modal service. - /// - [Inject] - private IModalService ModalService { get; set; } = null!; - - /// - /// Gets or sets the loading overlay service. - /// - [Inject] - private LoadingOverlayService LoadingOverlay { get; set; } = null!; - - /// - /// Gets or sets the toast service. - /// - [Inject] - private IToastService ToastService { get; set; } = null!; - - /// - /// Gets or sets the game configuration source. - /// - [Inject] - private IDataSource GameConfigurationSource { get; set; } = null!; - - /// - /// Gets or sets the logger. - /// - [Inject] - private ILogger Logger { get; set; } = null!; - - /// - /// Gets or sets the navigation manager. - /// - [Inject] - private NavigationManager NavigationManager { get; set; } = null!; - - /// - /// Gets or sets the JavaScript runtime. - /// - [Inject] - private IJSRuntime JavaScript { get; set; } = null!; - - /// - public void Dispose() - { - this._disposeCts?.Cancel(); - this._disposeCts?.Dispose(); - this._disposeCts = null; - - this._navigationLockDisposable?.Dispose(); - this._navigationLockDisposable = null; - } - - /// - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - if (this._maps is null) - { - return; - } - - builder.OpenComponent(0); - builder.AddAttribute(1, nameof(Breadcrumb.Caption), Resources.MapEditor); - builder.CloseComponent(); - - builder.OpenComponent>(10); - builder.AddAttribute(11, nameof(CascadingValue.Value), this._context); - builder.AddAttribute(12, nameof(CascadingValue.IsFixed), false); - builder.AddAttribute(13, nameof(CascadingValue.ChildContent), (RenderFragment)this.BuildMapEditorFragment); - builder.CloseComponent(); - } - - /// - protected override async Task OnParametersSetAsync() - { - await (this._disposeCts?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false); - this._disposeCts?.Dispose(); - this._disposeCts = new CancellationTokenSource(); - - this._context = await this.GameConfigurationSource - .GetContextAsync(this._disposeCts.Token) - .ConfigureAwait(false); - - await base.OnParametersSetAsync().ConfigureAwait(false); - } - - /// - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender).ConfigureAwait(false); - if (this._maps is null) - { - this._disposeCts ??= new CancellationTokenSource(); - var cts = this._disposeCts.Token; - _ = Task.Run(() => this.LoadDataAsync(cts), cts); - } - } - /// - protected override Task OnInitializedAsync() - { - this._navigationLockDisposable = this.NavigationManager.RegisterLocationChangingHandler(this.OnBeforeInternalNavigationAsync); - return base.OnInitializedAsync(); - } - - private async ValueTask OnBeforeInternalNavigationAsync(LocationChangingContext context) - { - if (!await this.AllowChangeAsync().ConfigureAwait(false)) - { - context.PreventNavigation(); - } - } - - private async Task OnSelectedMapChangingAsync(MapChangingArgs eventArgs) - { - eventArgs.Cancel = !await this.AllowChangeAsync().ConfigureAwait(true); - if (!eventArgs.Cancel) - { - this.SelectedMapId = eventArgs.NextMap; - } - } - - private async ValueTask AllowChangeAsync() - { - var cancellationToken = this._disposeCts?.Token ?? default; - var persistenceContext = await this.GameConfigurationSource - .GetContextAsync(cancellationToken) - .ConfigureAwait(true); - - if (persistenceContext?.HasChanges is not true) - { - return true; - } - - var isConfirmed = await this.JavaScript - .InvokeAsync("window.confirm", cancellationToken, Resources.UnsavedChangesQuestion) - .ConfigureAwait(true); - - if (!isConfirmed) - { - return false; - } - - await this.GameConfigurationSource.DiscardChangesAsync().ConfigureAwait(true); - - IDisposable? loadingOverlay = null; - var showOverlayTask = this.InvokeAsync(() => loadingOverlay = this.LoadingOverlay.ShowLoadingIndicator()); - - try - { - this._context = await this.GameConfigurationSource - .GetContextAsync(cancellationToken) - .ConfigureAwait(true); - - var gameConfig = await this.GameConfigurationSource - .GetOwnerAsync(Guid.Empty, cancellationToken) - .ConfigureAwait(false); - - this._maps = gameConfig.Maps.OrderBy(c => c.Number).ToList(); - } - finally - { - await showOverlayTask.ConfigureAwait(false); - loadingOverlay?.Dispose(); - } - - await this.InvokeAsync(this.StateHasChanged).ConfigureAwait(false); - - return true; - } - - private async Task LoadDataAsync(CancellationToken cancellationToken) - { - IDisposable? modal = null; - var showModalTask = this.InvokeAsync(() => modal = this.LoadingOverlay.ShowLoadingIndicator()); - - try - { - if (!cancellationToken.IsCancellationRequested) - { - var gameConfig = await this.GameConfigurationSource - .GetOwnerAsync(Guid.Empty, cancellationToken) - .ConfigureAwait(false); - - try - { - this._maps = gameConfig.Maps.OrderBy(c => c.Number).ToList(); - } - catch (Exception ex) - { - this.Logger.LogError( - ex, - "Could not load game maps: {Message}{NewLine}{StackTrace}", - ex.Message, - Environment.NewLine, - ex.StackTrace); - - await this.ModalService - .ShowMessageAsync(Resources.Error, Resources.CouldNotLoadMapDataCheckTheLogs) - .ConfigureAwait(false); - } - - await showModalTask.ConfigureAwait(false); - modal?.Dispose(); - await this.InvokeAsync(this.StateHasChanged).ConfigureAwait(false); - } - } - catch (TargetInvocationException ex) when (ex.InnerException is ObjectDisposedException) - { - // See ObjectDisposedException. - } - catch (ObjectDisposedException) - { - // Happens when the user navigated away. The persistence layer does not - // yet have a cancellation token based async API so we swallow this. - } - } - - private async Task SaveChangesAsync() - { - try - { - var context = await this.GameConfigurationSource.GetContextAsync().ConfigureAwait(true); - var success = await context.SaveChangesAsync().ConfigureAwait(true); - var text = success ? Resources.SavedChanges : Resources.NoChangesToSave; - this.ToastService.ShowSuccess(text); - } - catch (Exception ex) - { - this.Logger.LogError(ex, "Error during saving"); - this.ToastService.ShowError(string.Format(Resources.UnexpectedErrorCheckLogs, ex.Message)); - } - } - - private void BuildMapEditorFragment(RenderTreeBuilder builder) - { - builder.OpenComponent(15); - builder.AddAttribute(16, nameof(MapEditor.Maps), this._maps); - builder.AddAttribute(17, nameof(MapEditor.SelectedMapId), this.SelectedMapId); - builder.AddAttribute(18, nameof(MapEditor.OnValidSubmit), EventCallback.Factory.Create(this, this.SaveChangesAsync)); - builder.AddAttribute( - 19, - nameof(MapEditor.SelectedMapChanging), - EventCallback.Factory.Create(this, this.OnSelectedMapChangingAsync)); - builder.CloseComponent(); - } -} \ No newline at end of file + protected override string Caption => Resources.MapEditor; +} diff --git a/src/Web/AdminPanel/Pages/EditTerrain.cs b/src/Web/AdminPanel/Pages/EditTerrain.cs new file mode 100644 index 000000000..1482d0ed5 --- /dev/null +++ b/src/Web/AdminPanel/Pages/EditTerrain.cs @@ -0,0 +1,21 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.AdminPanel.Pages; + +using Microsoft.AspNetCore.Components; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.Web.AdminPanel.Properties; +using MUnique.OpenMU.Web.Shared.Components.TerrainEditor; + +/// +/// A page, which shows a for the terrains of all . +/// +[Route("/terrain-editor")] +[Route("/terrain-editor/{SelectedMapId:guid}")] +public sealed class EditTerrain : MapEditorPageBase +{ + /// + protected override string Caption => Resources.TerrainEditor; +} diff --git a/src/Web/AdminPanel/Pages/MapEditorPageBase.cs b/src/Web/AdminPanel/Pages/MapEditorPageBase.cs new file mode 100644 index 000000000..9617ad24f --- /dev/null +++ b/src/Web/AdminPanel/Pages/MapEditorPageBase.cs @@ -0,0 +1,295 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.AdminPanel.Pages; + +using System.Reflection; +using System.Threading; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.Persistence; +using MUnique.OpenMU.Web.AdminPanel.Properties; +using MUnique.OpenMU.Web.Shared; +using MUnique.OpenMU.Web.Shared.Components; +using MUnique.OpenMU.Web.Shared.Components.MapEditor; +using MUnique.OpenMU.Web.Shared.Components.Modal; +using MUnique.OpenMU.Web.Shared.Components.Toast; +using MUnique.OpenMU.Web.Shared.Services; + +/// +/// The base class of the pages, which show an editor for all . +/// It loads the maps, saves the changes and asks before discarding unsaved changes. +/// +/// The type of the editor component, which has the same parameters as the . +public abstract class MapEditorPageBase : ComponentBase, IDisposable + where TEditor : IComponent +{ + private List? _maps; + private CancellationTokenSource? _disposeCts; + private IContext? _context; + private IDisposable? _navigationLockDisposable; + + /// + /// Gets or sets the selected map identifier. + /// + [Parameter] + public Guid SelectedMapId { get; set; } + + /// + /// Gets the caption of the page. + /// + protected abstract string Caption { get; } + + /// + /// Gets or sets the modal service. + /// + [Inject] + private IModalService ModalService { get; set; } = null!; + + /// + /// Gets or sets the loading overlay service. + /// + [Inject] + private LoadingOverlayService LoadingOverlay { get; set; } = null!; + + /// + /// Gets or sets the toast service. + /// + [Inject] + private IToastService ToastService { get; set; } = null!; + + /// + /// Gets or sets the game configuration source. + /// + [Inject] + private IDataSource GameConfigurationSource { get; set; } = null!; + + /// + /// Gets or sets the logger. + /// + [Inject] + private ILogger> Logger { get; set; } = null!; + + /// + /// Gets or sets the navigation manager. + /// + [Inject] + private NavigationManager NavigationManager { get; set; } = null!; + + /// + /// Gets or sets the JavaScript runtime. + /// + [Inject] + private IJSRuntime JavaScript { get; set; } = null!; + + /// + public void Dispose() + { + this._disposeCts?.Cancel(); + this._disposeCts?.Dispose(); + this._disposeCts = null; + + this._navigationLockDisposable?.Dispose(); + this._navigationLockDisposable = null; + GC.SuppressFinalize(this); + } + + /// + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + if (this._maps is null) + { + return; + } + + builder.OpenComponent(0); + builder.AddAttribute(1, nameof(Breadcrumb.Caption), this.Caption); + builder.CloseComponent(); + + builder.OpenComponent>(10); + builder.AddAttribute(11, nameof(CascadingValue.Value), this._context); + builder.AddAttribute(12, nameof(CascadingValue.IsFixed), false); + builder.AddAttribute(13, nameof(CascadingValue.ChildContent), (RenderFragment)this.BuildEditorFragment); + builder.CloseComponent(); + } + + /// + protected override async Task OnParametersSetAsync() + { + await (this._disposeCts?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false); + this._disposeCts?.Dispose(); + this._disposeCts = new CancellationTokenSource(); + + this._context = await this.GameConfigurationSource + .GetContextAsync(this._disposeCts.Token) + .ConfigureAwait(false); + + await base.OnParametersSetAsync().ConfigureAwait(false); + } + + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender).ConfigureAwait(false); + if (this._maps is null) + { + this._disposeCts ??= new CancellationTokenSource(); + var cts = this._disposeCts.Token; + _ = Task.Run(() => this.LoadDataAsync(cts), cts); + } + } + + /// + protected override Task OnInitializedAsync() + { + this._navigationLockDisposable = this.NavigationManager.RegisterLocationChangingHandler(this.OnBeforeInternalNavigationAsync); + return base.OnInitializedAsync(); + } + + private async ValueTask OnBeforeInternalNavigationAsync(LocationChangingContext context) + { + if (!await this.AllowChangeAsync().ConfigureAwait(false)) + { + context.PreventNavigation(); + } + } + + private async Task OnSelectedMapChangingAsync(MapChangingArgs eventArgs) + { + eventArgs.Cancel = !await this.AllowChangeAsync().ConfigureAwait(true); + if (!eventArgs.Cancel) + { + this.SelectedMapId = eventArgs.NextMap; + } + } + + private async ValueTask AllowChangeAsync() + { + var cancellationToken = this._disposeCts?.Token ?? default; + var persistenceContext = await this.GameConfigurationSource + .GetContextAsync(cancellationToken) + .ConfigureAwait(true); + + if (persistenceContext?.HasChanges is not true) + { + return true; + } + + var isConfirmed = await this.JavaScript + .InvokeAsync("window.confirm", cancellationToken, Resources.UnsavedChangesQuestion) + .ConfigureAwait(true); + + if (!isConfirmed) + { + return false; + } + + await this.GameConfigurationSource.DiscardChangesAsync().ConfigureAwait(true); + + IDisposable? loadingOverlay = null; + var showOverlayTask = this.InvokeAsync(() => loadingOverlay = this.LoadingOverlay.ShowLoadingIndicator()); + + try + { + this._context = await this.GameConfigurationSource + .GetContextAsync(cancellationToken) + .ConfigureAwait(true); + + var gameConfig = await this.GameConfigurationSource + .GetOwnerAsync(Guid.Empty, cancellationToken) + .ConfigureAwait(false); + + this._maps = gameConfig.Maps.OrderBy(c => c.Number).ToList(); + } + finally + { + await showOverlayTask.ConfigureAwait(false); + loadingOverlay?.Dispose(); + } + + await this.InvokeAsync(this.StateHasChanged).ConfigureAwait(false); + + return true; + } + + private async Task LoadDataAsync(CancellationToken cancellationToken) + { + IDisposable? modal = null; + var showModalTask = this.InvokeAsync(() => modal = this.LoadingOverlay.ShowLoadingIndicator()); + + try + { + if (!cancellationToken.IsCancellationRequested) + { + var gameConfig = await this.GameConfigurationSource + .GetOwnerAsync(Guid.Empty, cancellationToken) + .ConfigureAwait(false); + + try + { + this._maps = gameConfig.Maps.OrderBy(c => c.Number).ToList(); + } + catch (Exception ex) + { + this.Logger.LogError( + ex, + "Could not load game maps: {Message}{NewLine}{StackTrace}", + ex.Message, + Environment.NewLine, + ex.StackTrace); + + await this.ModalService + .ShowMessageAsync(Resources.Error, Resources.CouldNotLoadMapDataCheckTheLogs) + .ConfigureAwait(false); + } + + await showModalTask.ConfigureAwait(false); + modal?.Dispose(); + await this.InvokeAsync(this.StateHasChanged).ConfigureAwait(false); + } + } + catch (TargetInvocationException ex) when (ex.InnerException is ObjectDisposedException) + { + // See ObjectDisposedException. + } + catch (ObjectDisposedException) + { + // Happens when the user navigated away. The persistence layer does not + // yet have a cancellation token based async API so we swallow this. + } + } + + private async Task SaveChangesAsync() + { + try + { + var context = await this.GameConfigurationSource.GetContextAsync().ConfigureAwait(true); + var success = await context.SaveChangesAsync().ConfigureAwait(true); + var text = success ? Resources.SavedChanges : Resources.NoChangesToSave; + this.ToastService.ShowSuccess(text); + } + catch (Exception ex) + { + this.Logger.LogError(ex, "Error during saving"); + this.ToastService.ShowError(string.Format(Resources.UnexpectedErrorCheckLogs, ex.Message)); + } + } + + private void BuildEditorFragment(RenderTreeBuilder builder) + { + builder.OpenComponent(15); + builder.AddAttribute(16, nameof(MapEditor.Maps), this._maps); + builder.AddAttribute(17, nameof(MapEditor.SelectedMapId), this.SelectedMapId); + builder.AddAttribute(18, nameof(MapEditor.OnValidSubmit), EventCallback.Factory.Create(this, this.SaveChangesAsync)); + builder.AddAttribute( + 19, + nameof(MapEditor.SelectedMapChanging), + EventCallback.Factory.Create(this, this.OnSelectedMapChangingAsync)); + builder.CloseComponent(); + } +} \ No newline at end of file diff --git a/src/Web/AdminPanel/Properties/Resources.Designer.cs b/src/Web/AdminPanel/Properties/Resources.Designer.cs index 760744d79..5822902bd 100644 --- a/src/Web/AdminPanel/Properties/Resources.Designer.cs +++ b/src/Web/AdminPanel/Properties/Resources.Designer.cs @@ -5386,5 +5386,14 @@ public static string CaptionsCheckingLinkable { return ResourceManager.GetString("CaptionsCheckingLinkable", resourceCulture); } } + + /// + /// Looks up a localized string similar to Terrain Editor. + /// + public static string TerrainEditor { + get { + return ResourceManager.GetString("TerrainEditor", resourceCulture); + } + } } } diff --git a/src/Web/AdminPanel/Properties/Resources.de.resx b/src/Web/AdminPanel/Properties/Resources.de.resx index ffab9d652..07487704a 100644 --- a/src/Web/AdminPanel/Properties/Resources.de.resx +++ b/src/Web/AdminPanel/Properties/Resources.de.resx @@ -1878,4 +1878,7 @@ Produkt {0} hat eine Menge kleiner als 1. + + Terrain-Editor + diff --git a/src/Web/AdminPanel/Properties/Resources.resx b/src/Web/AdminPanel/Properties/Resources.resx index d290a086c..c8da23d58 100644 --- a/src/Web/AdminPanel/Properties/Resources.resx +++ b/src/Web/AdminPanel/Properties/Resources.resx @@ -1957,4 +1957,7 @@ Product {0} has a quantity below 1. + + Terrain Editor + diff --git a/src/Web/AdminPanel/Properties/Resources.zh-CN.resx b/src/Web/AdminPanel/Properties/Resources.zh-CN.resx index d02828a29..3f6074559 100644 --- a/src/Web/AdminPanel/Properties/Resources.zh-CN.resx +++ b/src/Web/AdminPanel/Properties/Resources.zh-CN.resx @@ -1786,4 +1786,7 @@ 地图 + + 地形编辑器 + diff --git a/src/Web/Shared/Components/TerrainEditor/TerrainEditMode.cs b/src/Web/Shared/Components/TerrainEditor/TerrainEditMode.cs new file mode 100644 index 000000000..b41d5aab9 --- /dev/null +++ b/src/Web/Shared/Components/TerrainEditor/TerrainEditMode.cs @@ -0,0 +1,26 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.Shared.Components.TerrainEditor; + +/// +/// Defines how the selected attributes are applied to the edited tiles of the . +/// +public enum TerrainEditMode +{ + /// + /// The selected attributes are added to the existing attributes of a tile. + /// + Add, + + /// + /// The selected attributes are removed from the existing attributes of a tile. + /// + Remove, + + /// + /// The attributes of a tile are replaced by the selected attributes. + /// + Replace, +} diff --git a/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor b/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor new file mode 100644 index 000000000..f7a618933 --- /dev/null +++ b/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor @@ -0,0 +1,153 @@ +@using MUnique.OpenMU.DataModel +@using MUnique.OpenMU.DataModel.Configuration +@using MUnique.OpenMU.Persistence +@using MUnique.OpenMU.Web.Shared.Properties + +@implements IAsyncDisposable + +@if (this._selectedMap is null) +{ + return; +} + +

@this._selectedMap.Name.ToString()

+ +
+
+ + +
+
+ + +
+
+ + + +
+
+
+
+
+ +
+
+
+
+
+
+ + +
+ +
+
+ @Resources.TerrainTool +
+ @foreach (var tool in Enum.GetValues()) + { + + } +
+ @if (this._tool == TerrainTool.Brush) + { + + + } +
+ +
+ @Resources.TerrainEditMode + @foreach (var mode in Enum.GetValues()) + { +
+ + +
+ } +
+ +
+ @Resources.TerrainAttributes + @foreach (var attributeType in EditableAttributes) + { +
+ + +
+ } +
+ +
+ @Resources.TerrainLegend +
    +
  • @Resources.TerrainWalkable
  • + @foreach (var entry in Palette) + { +
  • @GetLegendCaption(entry.Mask)
  • + } +
+
+ +
@Resources.TerrainEditorRestartHint
+
+
diff --git a/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.cs b/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.cs new file mode 100644 index 000000000..d060c5727 --- /dev/null +++ b/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.cs @@ -0,0 +1,549 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.Shared.Components.TerrainEditor; + +using System.IO; +using BlazorInputFile; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; +using MUnique.OpenMU.DataModel; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.Persistence; +using MUnique.OpenMU.Web.Shared.Components.MapEditor; +using MUnique.OpenMU.Web.Shared.Components.Toast; +using MUnique.OpenMU.Web.Shared.Properties; + +/// +/// A graphical editor for the terrains of a , which are its +/// and the . +/// +/// +/// The tiles are edited in the browser. After each edit, the whole terrain is transferred back +/// and assigned to the edited terrain, so that the persistence context tracks the change. +/// +public partial class TerrainEditor : IAsyncDisposable +{ + private const string ImportInputId = "terrain-import-file-input"; + + private const int MaximumBrushSize = 25; + + /// + /// The default zoom, which shows the whole terrain with 3 pixels per tile. + /// + private const int DefaultZoom = 3; + + /// + /// The minimum zoom, which shows one pixel per tile. + /// + private const int MinimumZoom = 1; + + /// + /// The maximum zoom, which shows 16 pixels per tile. + /// + private const int MaximumZoom = 16; + + /// + /// The maximum size of an imported file, which is the size of a client terrain file with two bytes per tile. + /// + private const int MaximumImportFileSize = 4 + (TerrainFileConverter.TileCount * sizeof(ushort)); + + private const string WalkableColor = "#50c850"; + + /// + /// The attributes which can be edited. is + /// just set at runtime by the game server, so it doesn't make sense to edit it. + /// + private static readonly TerrainAttributeType[] EditableAttributes = + [ + TerrainAttributeType.Safezone, + TerrainAttributeType.Blocked, + TerrainAttributeType.NoGround, + TerrainAttributeType.Water, + ]; + + /// + /// The colors of the tiles, by priority. The first entry whose mask matches the attributes of a tile defines its color. + /// Tiles without attributes are drawn with the . + /// + private static readonly (int Mask, string Color)[] Palette = + [ + ((int)TerrainAttributeType.Blocked, "#7a3b3b"), + ((int)TerrainAttributeType.NoGround, "#000000"), + ((int)TerrainAttributeType.Water, "#2f6fd6"), + (OtherAttributesMask, "#b08a3c"), + ((int)TerrainAttributeType.Safezone, "#9a9a9a"), + ]; + + private static readonly string JsModulePath = + $"./_content/{typeof(TerrainEditor).Assembly.GetName().Name}/Components/TerrainEditor/{nameof(TerrainEditor)}.razor.js"; + + private ElementReference _mapSelectRef; + private ElementReference _hostRef; + private ElementReference _canvasRef; + private ElementReference _selectionRef; + private ElementReference _statusRef; + + private IJSObjectReference? _jsModule; + private IJSObjectReference? _editor; + private DotNetObjectReference? _dotNetRef; + + private GameMapDefinition? _selectedMap; + private GameMapTerrainVariant? _selectedVariant; + + /// + /// The map or variant of which the terrain is currently loaded in the browser. + /// + private object? _loadedTerrainOwner; + + private TerrainTool _tool = TerrainTool.Brush; + private TerrainEditMode _mode = TerrainEditMode.Add; + private TerrainAttributeType _attributes = TerrainAttributeType.Blocked; + private int _brushSize = 1; + private int _zoom = DefaultZoom; + private bool _showGrid; + private bool _canUndo; + private bool _canRedo; + + /// + /// Gets or sets the callback invoked before the selected map changes, + /// allowing the caller to cancel the change. + /// + [Parameter] + public EventCallback SelectedMapChanging { get; set; } + + /// + /// Gets or sets the maps which can be selected. + /// + [Parameter] + [EditorRequired] + public required List Maps { get; set; } + + /// + /// Gets or sets the ID of the initially selected map. + /// + [Parameter] + public Guid SelectedMapId { get; set; } + + /// + /// Gets or sets the callback which saves the changes. + /// + [Parameter] + public EventCallback OnValidSubmit { get; set; } + + private static int OtherAttributesMask => byte.MaxValue & ~EditableAttributes.Aggregate(0, (mask, attribute) => mask | (int)attribute); + + [Inject] + private IJSRuntime JsRuntime { get; set; } = null!; + + [Inject] + private IToastService ToastService { get; set; } = null!; + + [Inject] + private ILogger Logger { get; set; } = null!; + + /// + /// Gets the map or variant which owns the selected terrain. + /// + private object? SelectedTerrainOwner => (object?)this._selectedVariant ?? this._selectedMap; + + /// + public async ValueTask DisposeAsync() + { + try + { + if (this._editor is not null) + { + await this._editor.InvokeVoidAsync("dispose").ConfigureAwait(false); + await this._editor.DisposeAsync().ConfigureAwait(false); + } + + if (this._jsModule is not null) + { + await this._jsModule.DisposeAsync().ConfigureAwait(false); + } + } + catch (JSDisconnectedException) + { + // The circuit is already gone, so there is nothing to clean up in the browser. + } + + this._dotNetRef?.Dispose(); + GC.SuppressFinalize(this); + } + + /// + /// Called by the browser after the terrain was edited. + /// Transfers the edited terrain and assigns it to the edited map or variant. + /// + /// If set to true, an edit can be undone. + /// If set to true, an undone edit can be redone. + [JSInvokable] + public async Task OnTerrainChangedAsync(bool canUndo, bool canRedo) + { + this._canUndo = canUndo; + this._canRedo = canRedo; + if (this._editor is not { } editor || this._loadedTerrainOwner is not { } owner) + { + return; + } + + try + { + var streamReference = await editor.InvokeAsync("getData").ConfigureAwait(true); + await using (streamReference.ConfigureAwait(true)) + { + var tileAttributes = new byte[TerrainFileConverter.TileCount]; + var stream = await streamReference.OpenReadStreamAsync(TerrainFileConverter.TileCount).ConfigureAwait(true); + await using (stream.ConfigureAwait(true)) + { + await stream.ReadExactlyAsync(tileAttributes).ConfigureAwait(true); + } + + SetTerrainData(owner, TerrainFileConverter.ToServerFile(tileAttributes)); + } + } + catch (Exception ex) when (ex is not JSDisconnectedException) + { + this.Logger.LogError(ex, "Could not transfer the edited terrain."); + this.ToastService.ShowError(string.Format(Resources.TerrainTransferFailed, ex.Message)); + } + + this.StateHasChanged(); + } + + /// + /// Called by the browser when the zoom was changed by the mouse wheel. + /// + /// The zoom. + [JSInvokable] + public void OnZoomChanged(int zoom) + { + this._zoom = zoom; + this.StateHasChanged(); + } + + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + if (this.Maps is not { Count: > 0 }) + { + throw new InvalidOperationException("The Maps parameter is required and must not be empty."); + } + + // The maps are loaded again when changes were discarded, so we select the new instances. + var selectedMapId = this._selectedMap?.GetId() ?? this.SelectedMapId; + var selectedVariantId = this._selectedVariant?.GetId(); + this._selectedMap = this.Maps.FirstOrDefault(m => m.GetId() == selectedMapId) ?? this.Maps.First(); + this._selectedVariant = this._selectedMap.TerrainVariants.FirstOrDefault(v => v.GetId() == selectedVariantId); + } + + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender).ConfigureAwait(true); + if (firstRender) + { + this._jsModule = await this.JsRuntime.InvokeAsync("import", JsModulePath).ConfigureAwait(true); + this._dotNetRef = DotNetObjectReference.Create(this); + var options = new + { + Palette = Palette.Select(p => new { p.Mask, p.Color }).ToArray(), + WalkableColor, + FlagNames = Enum.GetValues().Select(a => new { Mask = (int)a, Name = a.GetEnumCaption() }).ToArray(), + Zoom = this._zoom, + MinimumZoom, + MaximumZoom, + }; + + this._editor = await this._jsModule.InvokeAsync( + "createEditor", + this._hostRef, + this._canvasRef, + this._selectionRef, + this._statusRef, + this._dotNetRef, + options).ConfigureAwait(true); + await this.UpdateToolAsync().ConfigureAwait(true); + } + + if (this._editor is not null && !ReferenceEquals(this._loadedTerrainOwner, this.SelectedTerrainOwner)) + { + await this.LoadSelectedTerrainAsync().ConfigureAwait(true); + } + } + + private static byte[]? GetTerrainData(object owner) + { + return owner switch + { + GameMapTerrainVariant variant => variant.TerrainData, + GameMapDefinition map => map.TerrainData, + _ => null, + }; + } + + private static void SetTerrainData(object owner, byte[] terrainData) + { + switch (owner) + { + case GameMapTerrainVariant variant: + variant.TerrainData = terrainData; + break; + case GameMapDefinition map: + map.TerrainData = terrainData; + break; + default: + throw new ArgumentException($"Unknown terrain owner {owner}.", nameof(owner)); + } + } + + private static string GetToolCaption(TerrainTool tool) => tool switch + { + TerrainTool.Rectangle => Resources.TerrainToolRectangle, + TerrainTool.Fill => Resources.TerrainToolFill, + _ => Resources.TerrainToolBrush, + }; + + private static string GetToolIcon(TerrainTool tool) => tool switch + { + TerrainTool.Rectangle => "oi-crop", + TerrainTool.Fill => "oi-droplet", + _ => "oi-brush", + }; + + private static string GetModeCaption(TerrainEditMode mode) => mode switch + { + TerrainEditMode.Remove => Resources.TerrainEditModeRemove, + TerrainEditMode.Replace => Resources.TerrainEditModeReplace, + _ => Resources.TerrainEditModeAdd, + }; + + private static string GetColor(TerrainAttributeType attribute) + { + return Palette.First(p => p.Mask == (int)attribute).Color; + } + + private static string GetLegendCaption(int mask) + { + return mask == OtherAttributesMask + ? Resources.TerrainOtherAttributes + : ((TerrainAttributeType)mask).GetEnumCaption(); + } + + private async Task LoadSelectedTerrainAsync() + { + if (this._editor is not { } editor || this.SelectedTerrainOwner is not { } owner) + { + return; + } + + this._loadedTerrainOwner = owner; + this._canUndo = false; + this._canRedo = false; + var tileAttributes = TerrainFileConverter.GetTileAttributes(GetTerrainData(owner)); + await editor.InvokeVoidAsync("setData", Convert.ToBase64String(tileAttributes), false).ConfigureAwait(true); + this.StateHasChanged(); + } + + private async Task OnMapSelectedAsync(ChangeEventArgs args) + { + if (args.Value is not string idString || !Guid.TryParse(idString, out var mapId) || this._selectedMap is null) + { + return; + } + + var changingArgs = new MapChangingArgs(mapId); + if (this.SelectedMapChanging.HasDelegate) + { + await this.SelectedMapChanging.InvokeAsync(changingArgs).ConfigureAwait(true); + } + + if (changingArgs.Cancel) + { + if (this._jsModule is not null) + { + await this._jsModule.InvokeVoidAsync("setSelectValue", this._mapSelectRef, this._selectedMap.GetId().ToString()).ConfigureAwait(true); + } + + return; + } + + // The maps may have been reloaded by the callback, so we look it up afterward. + this._selectedMap = this.Maps.First(m => m.GetId() == mapId); + this._selectedVariant = null; + } + + private void OnTerrainSelected(ChangeEventArgs args) + { + if (args.Value is not string idString || !Guid.TryParse(idString, out var variantId)) + { + return; + } + + this._selectedVariant = this._selectedMap?.TerrainVariants.FirstOrDefault(v => v.GetId() == variantId); + } + + private async Task SetToolAsync(TerrainTool tool) + { + this._tool = tool; + await this.UpdateToolAsync().ConfigureAwait(true); + } + + private async Task SetModeAsync(TerrainEditMode mode) + { + this._mode = mode; + await this.UpdateToolAsync().ConfigureAwait(true); + } + + private async Task ToggleAttributeAsync(TerrainAttributeType attribute) + { + this._attributes ^= attribute; + await this.UpdateToolAsync().ConfigureAwait(true); + } + + private async Task OnBrushSizeChangedAsync(ChangeEventArgs args) + { + if (int.TryParse(args.Value?.ToString(), out var size)) + { + this._brushSize = Math.Clamp(size, 1, MaximumBrushSize); + await this.UpdateToolAsync().ConfigureAwait(true); + } + } + + private async Task UpdateToolAsync() + { + if (this._editor is { } editor) + { + await editor.InvokeVoidAsync( + "setTool", + this._tool.ToString().ToLowerInvariant(), + this._brushSize, + this._mode.ToString().ToLowerInvariant(), + (int)this._attributes).ConfigureAwait(true); + } + } + + private async Task ZoomByAsync(int steps) + { + if (this._editor is { } editor) + { + this._zoom = await editor.InvokeAsync("zoomBy", steps).ConfigureAwait(true); + } + } + + private async Task ResetZoomAsync() + { + if (this._editor is { } editor) + { + this._zoom = await editor.InvokeAsync("zoomTo", DefaultZoom).ConfigureAwait(true); + } + } + + private async Task ToggleGridAsync() + { + this._showGrid = !this._showGrid; + if (this._editor is { } editor) + { + await editor.InvokeVoidAsync("setGrid", this._showGrid).ConfigureAwait(true); + } + } + + private async Task UndoAsync() + { + if (this._editor is { } editor) + { + await editor.InvokeVoidAsync("undo").ConfigureAwait(true); + } + } + + private async Task RedoAsync() + { + if (this._editor is { } editor) + { + await editor.InvokeVoidAsync("redo").ConfigureAwait(true); + } + } + + private async Task SaveAsync() + { + await this.OnValidSubmit.InvokeAsync().ConfigureAwait(true); + } + + private async Task TriggerFileImportAsync() + { + if (this._jsModule is { } jsModule) + { + await jsModule.InvokeVoidAsync("triggerFileInput", ImportInputId).ConfigureAwait(true); + } + } + + private async Task ImportFileSelectedAsync(IFileListEntry[] files) + { + if (files.FirstOrDefault() is not { } file || this._editor is not { } editor) + { + return; + } + + if (file.Size > MaximumImportFileSize) + { + this.ToastService.ShowError(Resources.InvalidTerrainFile); + return; + } + + using var memoryStream = new MemoryStream(); + await file.Data.CopyToAsync(memoryStream).ConfigureAwait(true); + if (!TerrainFileConverter.TryReadFile(memoryStream.ToArray(), out var tileAttributes)) + { + this.ToastService.ShowError(Resources.InvalidTerrainFile); + return; + } + + // The editor takes it as an edit which can be undone, and transfers it back to us. + await editor.InvokeVoidAsync("setData", Convert.ToBase64String(tileAttributes), true).ConfigureAwait(true); + this.ToastService.ShowSuccess(Resources.TerrainImported); + } + + private async Task DownloadServerFileAsync() + { + if (this._selectedMap is not { } map || this.SelectedTerrainOwner is not { } owner) + { + return; + } + + var tileAttributes = TerrainFileConverter.GetTileAttributes(GetTerrainData(owner)); + await this.DownloadAsync($"Terrain{this.GetFileNameSuffix(map)}.att", TerrainFileConverter.ToServerFile(tileAttributes)).ConfigureAwait(true); + } + + private async Task DownloadClientFileAsync() + { + if (this._selectedMap is not { } map || this.SelectedTerrainOwner is not { } owner) + { + return; + } + + var tileAttributes = TerrainFileConverter.GetTileAttributes(GetTerrainData(owner)); + await this.DownloadAsync($"EncTerrain{this.GetFileNameSuffix(map)}.att", TerrainFileConverter.ToClientFile(tileAttributes, map.Number)).ConfigureAwait(true); + } + + /// + /// Gets the suffix of the file names, which is the number of the map as it's used by the file names of the client and the original server. + /// The number of a variant is appended, because there is no common naming scheme for them. + /// + private string GetFileNameSuffix(GameMapDefinition map) + { + var suffix = (map.Number + 1).ToString(System.Globalization.CultureInfo.InvariantCulture); + return this._selectedVariant is { } variant ? $"{suffix}_{variant.Number}" : suffix; + } + + private async Task DownloadAsync(string fileName, byte[] content) + { + if (this._jsModule is { } jsModule) + { + await jsModule.InvokeVoidAsync("downloadFile", fileName, Convert.ToBase64String(content)).ConfigureAwait(true); + } + } +} diff --git a/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.js b/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.js new file mode 100644 index 000000000..d123f0b83 --- /dev/null +++ b/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.js @@ -0,0 +1,524 @@ +/** + * The terrain editor keeps the attributes of the 256 x 256 tiles of a terrain in the browser, + * so that painting is immediate. After each finished edit, the Blazor component is notified + * and fetches the whole terrain through a stream. + * + * Like in the map editor, the game X coordinate is drawn vertically (rows) and + * the game Y coordinate horizontally (columns). + */ + +const SIZE = 256; +const MAX_HISTORY = 100; + +class TerrainEditor { + /** + * @param {HTMLElement} host - The scrollable host element. + * @param {HTMLCanvasElement} canvas - The canvas which displays the terrain. + * @param {HTMLElement} selection - The element which previews a rectangle selection. + * @param {HTMLElement} status - The element which shows the hovered tile. + * @param {object} dotNetRef - The reference to the Blazor component. + * @param {object} options - The palette, flag names and the zoom limits. + */ + constructor(host, canvas, selection, status, dotNetRef, options) { + this.host = host; + this.canvas = canvas; + this.selection = selection; + this.status = status; + this.dotNetRef = dotNetRef; + this.palette = options.palette.map(p => ({ mask: p.mask, rgb: TerrainEditor.parseColor(p.color) })); + this.walkableRgb = TerrainEditor.parseColor(options.walkableColor); + this.flagNames = options.flagNames; + this.zoom = options.zoom; + this.minimumZoom = options.minimumZoom; + this.maximumZoom = options.maximumZoom; + + this.data = new Uint8Array(SIZE * SIZE); + this.undoStack = []; + this.redoStack = []; + + this.tool = "brush"; + this.brushSize = 1; + this.mode = "add"; + this.flags = 0; + + this.strokeSnapshot = null; + this.lastTile = null; + this.startTile = null; + this.pan = null; + + canvas.width = SIZE; + canvas.height = SIZE; + this.context = canvas.getContext("2d"); + this.image = this.context.createImageData(SIZE, SIZE); + + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseMove = this.onMouseMove.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + this.onMouseLeave = this.onMouseLeave.bind(this); + this.onWheel = this.onWheel.bind(this); + this.onContextMenu = e => e.preventDefault(); + + host.addEventListener("mousedown", this.onMouseDown); + host.addEventListener("mouseleave", this.onMouseLeave); + host.addEventListener("wheel", this.onWheel, { passive: false }); + host.addEventListener("contextmenu", this.onContextMenu); + document.addEventListener("mousemove", this.onMouseMove); + document.addEventListener("mouseup", this.onMouseUp); + + this.applyZoom(); + } + + static parseColor(hex) { + const value = parseInt(hex.substring(1), 16); + return [(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]; + } + + /** + * Replaces the displayed terrain. + * @param {string} base64 - The attributes of all tiles, base64 encoded. + * @param {boolean} asEdit - True, if the replacement is an undoable edit (e.g. an import); + * false, if another terrain was loaded. + */ + setData(base64, asEdit) { + const bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0)); + if (bytes.length !== SIZE * SIZE) { + return; + } + + if (asEdit) { + this.pushUndo(this.data.slice()); + this.data = bytes; + this.render(); + this.notifyChanged(); + } else { + this.data = bytes; + this.undoStack = []; + this.redoStack = []; + this.render(); + } + } + + /** + * Gets the attributes of all tiles. They are requested as stream by the Blazor component, + * because they exceed the maximum message size of the circuit. + */ + getData() { + return this.data; + } + + setTool(tool, brushSize, mode, flags) { + this.tool = tool; + this.brushSize = Math.max(1, brushSize | 0); + this.mode = mode; + this.flags = flags & 0xFF; + } + + setGrid(showGrid) { + this.host.classList.toggle("show-grid", showGrid); + } + + undo() { + if (this.undoStack.length === 0) { + return; + } + + this.redoStack.push(this.data); + this.data = this.undoStack.pop(); + this.render(); + this.notifyChanged(); + } + + redo() { + if (this.redoStack.length === 0) { + return; + } + + this.undoStack.push(this.data); + this.data = this.redoStack.pop(); + this.render(); + this.notifyChanged(); + } + + /** + * Changes the zoom by the given number of steps, keeping the center of the view. + * @returns {number} The new zoom. + */ + zoomBy(steps) { + return this.zoomTo(this.zoom + steps); + } + + /** + * Changes the zoom to the given value, keeping the center of the view. + * @returns {number} The new zoom. + */ + zoomTo(zoom) { + const rect = this.host.getBoundingClientRect(); + this.zoomAround(zoom, rect.left + this.host.clientWidth / 2, rect.top + this.host.clientHeight / 2); + return this.zoom; + } + + dispose() { + this.host.removeEventListener("mousedown", this.onMouseDown); + this.host.removeEventListener("mouseleave", this.onMouseLeave); + this.host.removeEventListener("wheel", this.onWheel); + this.host.removeEventListener("contextmenu", this.onContextMenu); + document.removeEventListener("mousemove", this.onMouseMove); + document.removeEventListener("mouseup", this.onMouseUp); + this.dotNetRef = null; + } + + zoomAround(newZoom, clientX, clientY) { + newZoom = Math.max(this.minimumZoom, Math.min(this.maximumZoom, newZoom)); + if (newZoom === this.zoom) { + return; + } + + const rect = this.host.getBoundingClientRect(); + const offsetX = clientX - rect.left; + const offsetY = clientY - rect.top; + const contentX = (this.host.scrollLeft + offsetX) / this.zoom; + const contentY = (this.host.scrollTop + offsetY) / this.zoom; + this.zoom = newZoom; + this.applyZoom(); + this.host.scrollLeft = contentX * newZoom - offsetX; + this.host.scrollTop = contentY * newZoom - offsetY; + this.dotNetRef?.invokeMethodAsync("OnZoomChanged", this.zoom); + } + + applyZoom() { + const size = `${SIZE * this.zoom}px`; + this.canvas.style.width = size; + this.canvas.style.height = size; + this.host.style.setProperty("--tile-size", `${this.zoom}px`); + this.updateSelectionPreview(null); + } + + render() { + const pixels = this.image.data; + for (let row = 0; row < SIZE; row++) { + for (let column = 0; column < SIZE; column++) { + // row = game X, column = game Y; the data is stored by y, then x. + const rgb = this.getColor(this.data[(column * SIZE) + row]); + const offset = ((row * SIZE) + column) * 4; + pixels[offset] = rgb[0]; + pixels[offset + 1] = rgb[1]; + pixels[offset + 2] = rgb[2]; + pixels[offset + 3] = 255; + } + } + + this.context.putImageData(this.image, 0, 0); + } + + getColor(value) { + for (const entry of this.palette) { + if ((value & entry.mask) !== 0) { + return entry.rgb; + } + } + + return this.walkableRgb; + } + + getTile(e) { + const rect = this.canvas.getBoundingClientRect(); + const x = Math.floor((e.clientY - rect.top) / this.zoom); + const y = Math.floor((e.clientX - rect.left) / this.zoom); + if (x < 0 || y < 0 || x >= SIZE || y >= SIZE) { + return null; + } + + return { x, y }; + } + + applyToValue(value) { + switch (this.mode) { + case "remove": + return value & ~this.flags; + case "replace": + return this.flags; + default: + return value | this.flags; + } + } + + applyToTile(x, y) { + if (x < 0 || y < 0 || x >= SIZE || y >= SIZE) { + return; + } + + const index = (y * SIZE) + x; + this.data[index] = this.applyToValue(this.data[index]); + } + + applyBrush(tile) { + const offset = Math.floor((this.brushSize - 1) / 2); + for (let x = tile.x - offset; x < tile.x - offset + this.brushSize; x++) { + for (let y = tile.y - offset; y < tile.y - offset + this.brushSize; y++) { + this.applyToTile(x, y); + } + } + } + + /** Applies the brush along the line between two tiles, so that fast mouse movements leave no gaps. */ + applyBrushLine(from, to) { + let x = from.x; + let y = from.y; + const dx = Math.abs(to.x - x); + const dy = -Math.abs(to.y - y); + const stepX = x < to.x ? 1 : -1; + const stepY = y < to.y ? 1 : -1; + let error = dx + dy; + while (true) { + this.applyBrush({ x, y }); + if (x === to.x && y === to.y) { + break; + } + + const doubledError = 2 * error; + if (doubledError >= dy) { + error += dy; + x += stepX; + } + + if (doubledError <= dx) { + error += dx; + y += stepY; + } + } + } + + applyRectangle(from, to) { + for (let x = Math.min(from.x, to.x); x <= Math.max(from.x, to.x); x++) { + for (let y = Math.min(from.y, to.y); y <= Math.max(from.y, to.y); y++) { + this.applyToTile(x, y); + } + } + } + + /** Applies the edit to all connected tiles which have the same value as the clicked one. */ + applyFill(tile) { + const target = this.data[(tile.y * SIZE) + tile.x]; + if (this.applyToValue(target) === target) { + return; + } + + const visited = new Uint8Array(SIZE * SIZE); + const stack = [(tile.y * SIZE) + tile.x]; + while (stack.length > 0) { + const index = stack.pop(); + if (visited[index] || this.data[index] !== target) { + continue; + } + + visited[index] = 1; + this.data[index] = this.applyToValue(target); + const x = index % SIZE; + const y = Math.floor(index / SIZE); + if (x > 0) stack.push(index - 1); + if (x < SIZE - 1) stack.push(index + 1); + if (y > 0) stack.push(index - SIZE); + if (y < SIZE - 1) stack.push(index + SIZE); + } + } + + onMouseDown(e) { + if (e.button === 2 || e.button === 1) { + this.pan = { x: e.clientX, y: e.clientY, left: this.host.scrollLeft, top: this.host.scrollTop }; + this.host.classList.add("panning"); + e.preventDefault(); + return; + } + + const tile = e.button === 0 ? this.getTile(e) : null; + if (!tile || (this.flags === 0 && this.mode !== "replace")) { + return; + } + + e.preventDefault(); + this.strokeSnapshot = this.data.slice(); + switch (this.tool) { + case "fill": + this.applyFill(tile); + this.finishStroke(); + break; + case "rectangle": + this.startTile = tile; + this.updateSelectionPreview(tile); + break; + default: + this.lastTile = tile; + this.applyBrush(tile); + this.render(); + break; + } + } + + onMouseMove(e) { + if (this.pan) { + this.host.scrollLeft = this.pan.left - (e.clientX - this.pan.x); + this.host.scrollTop = this.pan.top - (e.clientY - this.pan.y); + return; + } + + const tile = this.getTile(e); + this.updateStatus(tile); + if (!tile || !this.strokeSnapshot) { + return; + } + + if (this.startTile) { + this.updateSelectionPreview(tile); + } else if (this.lastTile && (tile.x !== this.lastTile.x || tile.y !== this.lastTile.y)) { + this.applyBrushLine(this.lastTile, tile); + this.lastTile = tile; + this.render(); + } + } + + onMouseUp(e) { + if (this.pan) { + this.pan = null; + this.host.classList.remove("panning"); + return; + } + + if (!this.strokeSnapshot) { + return; + } + + if (this.startTile) { + const tile = this.getTile(e) ?? this.clampTile(e); + this.applyRectangle(this.startTile, tile); + } + + this.finishStroke(); + } + + onMouseLeave() { + this.updateStatus(null); + } + + onWheel(e) { + e.preventDefault(); + this.zoomAround(this.zoom + (e.deltaY < 0 ? 1 : -1), e.clientX, e.clientY); + } + + clampTile(e) { + const rect = this.canvas.getBoundingClientRect(); + const clamp = v => Math.max(0, Math.min(SIZE - 1, v)); + return { + x: clamp(Math.floor((e.clientY - rect.top) / this.zoom)), + y: clamp(Math.floor((e.clientX - rect.left) / this.zoom)), + }; + } + + finishStroke() { + const snapshot = this.strokeSnapshot; + this.strokeSnapshot = null; + this.lastTile = null; + this.startTile = null; + this.updateSelectionPreview(null); + this.render(); + + if (snapshot.every((value, index) => value === this.data[index])) { + return; + } + + this.pushUndo(snapshot); + this.notifyChanged(); + } + + pushUndo(snapshot) { + this.undoStack.push(snapshot); + if (this.undoStack.length > MAX_HISTORY) { + this.undoStack.shift(); + } + + this.redoStack = []; + } + + notifyChanged() { + this.dotNetRef?.invokeMethodAsync("OnTerrainChangedAsync", this.undoStack.length > 0, this.redoStack.length > 0); + } + + updateSelectionPreview(tile) { + if (!this.startTile || !tile) { + this.selection.style.display = "none"; + return; + } + + const top = Math.min(this.startTile.x, tile.x); + const left = Math.min(this.startTile.y, tile.y); + this.selection.style.display = "block"; + this.selection.style.top = `${top * this.zoom}px`; + this.selection.style.left = `${left * this.zoom}px`; + this.selection.style.height = `${(Math.abs(this.startTile.x - tile.x) + 1) * this.zoom}px`; + this.selection.style.width = `${(Math.abs(this.startTile.y - tile.y) + 1) * this.zoom}px`; + } + + updateStatus(tile) { + if (!tile) { + this.status.textContent = ""; + return; + } + + const value = this.data[(tile.y * SIZE) + tile.x]; + const names = this.flagNames.filter(f => (value & f.mask) !== 0).map(f => f.name); + let text = `X: ${tile.x}, Y: ${tile.y} | ${value}`; + if (names.length > 0) { + text += ` (${names.join(", ")})`; + } + + if (this.startTile) { + text += ` | ${Math.abs(this.startTile.x - tile.x) + 1} x ${Math.abs(this.startTile.y - tile.y) + 1}`; + } + + this.status.textContent = text; + } +} + +/** + * Creates a terrain editor. + * @returns {TerrainEditor} The editor, which is used by the Blazor component through an object reference. + */ +export function createEditor(host, canvas, selection, status, dotNetRef, options) { + return new TerrainEditor(host, canvas, selection, status, dotNetRef, options); +} + +/** + * Sets the value of a select element without triggering a change event. + * @param {HTMLSelectElement} selectElement - The select element to update. + * @param {string} value - The value to select. + */ +export function setSelectValue(selectElement, value) { + if (selectElement) { + selectElement.value = value; + } +} + +/** + * Triggers a click on the file input used for importing a terrain file. + * @param {string} id - The id of the input element. + */ +export function triggerFileInput(id) { + document.getElementById(id)?.click(); +} + +/** + * Downloads a base64-encoded byte array as a file via a temporary anchor element. + * @param {string} fileName - The suggested download file name. + * @param {string} base64 - The base64-encoded file content. + */ +export function downloadFile(fileName, base64) { + const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); + const blob = new Blob([bytes], { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +} diff --git a/src/Web/Shared/Components/TerrainEditor/TerrainFileConverter.cs b/src/Web/Shared/Components/TerrainEditor/TerrainFileConverter.cs new file mode 100644 index 000000000..a141b49a4 --- /dev/null +++ b/src/Web/Shared/Components/TerrainEditor/TerrainFileConverter.cs @@ -0,0 +1,178 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.Shared.Components.TerrainEditor; + +using System.Diagnostics.CodeAnalysis; +using MUnique.OpenMU.DataModel.Configuration; + +/// +/// Converts terrain attribute data between the format which is stored in +/// (the *.att file of the original server) +/// and the encrypted EncTerrain*.att file of the game client. +/// +/// +/// Both formats hold one attribute byte per tile of the 256 x 256 tiles of a map, row by row (y major). +/// The server file starts with a header of 3 bytes (version, width - 1, height - 1). +/// The client file starts with a header of 4 bytes (version, map number, width - 1, height - 1), +/// is XOR-ed with a 3 byte key and then encrypted with the map file encryption of the client. +/// Newer clients may also use two bytes per tile, of which only the low byte is relevant. +/// +public static class TerrainFileConverter +{ + /// + /// The number of tiles of a terrain. + /// + public const int TileCount = 256 * 256; + + private const int ServerHeaderSize = 3; + + private const int ClientHeaderSize = 4; + + private const byte MaximumCoordinate = 0xFF; + + private const byte InitialMapKey = 0x5E; + + private const byte MapKeyIncrement = 0x3D; + + private static readonly byte[] MapXorKey = [0xD1, 0x73, 0x52, 0xF6, 0xD2, 0x9A, 0xCB, 0x27, 0x3E, 0xAF, 0x59, 0x31, 0x37, 0xB3, 0xE7, 0xA2]; + + private static readonly byte[] BuxKey = [0xFC, 0xCF, 0xAB]; + + /// + /// Gets the attributes of all tiles from the stored terrain data. + /// + /// The terrain data, as stored in . + /// The attributes of all tiles. Missing data results in walkable tiles. + public static byte[] GetTileAttributes(byte[]? terrainData) + { + var result = new byte[TileCount]; + if (terrainData is { Length: > ServerHeaderSize }) + { + var source = terrainData.AsSpan(ServerHeaderSize); + source[..Math.Min(source.Length, TileCount)].CopyTo(result); + } + + return result; + } + + /// + /// Creates the terrain data of the server format, as stored in . + /// + /// The attributes of all tiles. + /// The terrain data. + public static byte[] ToServerFile(ReadOnlySpan tileAttributes) + { + ValidateTileAttributes(tileAttributes); + var result = new byte[ServerHeaderSize + TileCount]; + result[1] = MaximumCoordinate; + result[2] = MaximumCoordinate; + tileAttributes.CopyTo(result.AsSpan(ServerHeaderSize)); + return result; + } + + /// + /// Creates the encrypted terrain file which is loaded by the game client (EncTerrain*.att). + /// + /// The attributes of all tiles. + /// The number of the map. + /// The content of the client terrain file. + public static byte[] ToClientFile(ReadOnlySpan tileAttributes, short mapNumber) + { + ValidateTileAttributes(tileAttributes); + var result = new byte[ClientHeaderSize + TileCount]; + result[1] = unchecked((byte)mapNumber); + result[2] = MaximumCoordinate; + result[3] = MaximumCoordinate; + tileAttributes.CopyTo(result.AsSpan(ClientHeaderSize)); + ApplyBuxKey(result); + EncryptMapFile(result); + return result; + } + + /// + /// Reads the tile attributes from a terrain file, which can be either of the server or of the client format. + /// + /// The content of the file. + /// The attributes of all tiles, if the file could be read. + /// true, if the file has a known format; otherwise, false. + public static bool TryReadFile(ReadOnlySpan fileContent, [NotNullWhen(true)] out byte[]? tileAttributes) + { + tileAttributes = null; + if (fileContent.Length == ServerHeaderSize + TileCount) + { + tileAttributes = fileContent[ServerHeaderSize..].ToArray(); + return true; + } + + var isClientFile = fileContent.Length == ClientHeaderSize + TileCount; + var isExtendedClientFile = fileContent.Length == ClientHeaderSize + (TileCount * sizeof(ushort)); + if (!isClientFile && !isExtendedClientFile) + { + return false; + } + + var decrypted = fileContent.ToArray(); + DecryptMapFile(decrypted); + ApplyBuxKey(decrypted); + if (decrypted[0] != 0 || decrypted[2] != MaximumCoordinate || decrypted[3] != MaximumCoordinate) + { + return false; + } + + var data = decrypted.AsSpan(ClientHeaderSize); + if (isClientFile) + { + tileAttributes = data.ToArray(); + } + else + { + // Two bytes per tile (little endian), of which the client just uses the low byte. + tileAttributes = new byte[TileCount]; + for (int i = 0; i < TileCount; i++) + { + tileAttributes[i] = data[i * sizeof(ushort)]; + } + } + + return true; + } + + private static void ValidateTileAttributes(ReadOnlySpan tileAttributes) + { + if (tileAttributes.Length != TileCount) + { + throw new ArgumentException($"The terrain must have exactly {TileCount} tiles, but has {tileAttributes.Length}.", nameof(tileAttributes)); + } + } + + private static void ApplyBuxKey(Span data) + { + for (int i = 0; i < data.Length; i++) + { + data[i] ^= BuxKey[i % BuxKey.Length]; + } + } + + private static void EncryptMapFile(Span data) + { + byte mapKey = InitialMapKey; + for (int i = 0; i < data.Length; i++) + { + data[i] = (byte)((data[i] + mapKey) ^ MapXorKey[i % MapXorKey.Length]); + mapKey = (byte)(data[i] + MapKeyIncrement); + } + } + + private static void DecryptMapFile(Span data) + { + byte mapKey = InitialMapKey; + for (int i = 0; i < data.Length; i++) + { + var encrypted = data[i]; + data[i] = (byte)((encrypted ^ MapXorKey[i % MapXorKey.Length]) - mapKey); + mapKey = (byte)(encrypted + MapKeyIncrement); + } + } +} diff --git a/src/Web/Shared/Components/TerrainEditor/TerrainTool.cs b/src/Web/Shared/Components/TerrainEditor/TerrainTool.cs new file mode 100644 index 000000000..8307ba4eb --- /dev/null +++ b/src/Web/Shared/Components/TerrainEditor/TerrainTool.cs @@ -0,0 +1,26 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.Shared.Components.TerrainEditor; + +/// +/// The tool of the , which defines which tiles are edited by a click. +/// +public enum TerrainTool +{ + /// + /// Edits the square of tiles around the mouse cursor while the mouse button is pressed. + /// + Brush, + + /// + /// Edits all tiles of a dragged rectangle. + /// + Rectangle, + + /// + /// Edits all connected tiles which have the same attributes as the clicked tile. + /// + Fill, +} diff --git a/src/Web/Shared/Exports.cs b/src/Web/Shared/Exports.cs index 037bb39c7..e067c57f1 100644 --- a/src/Web/Shared/Exports.cs +++ b/src/Web/Shared/Exports.cs @@ -53,6 +53,7 @@ private static IEnumerable SharedStylesheets yield return $"{prefix}/css/common.css"; yield return $"{prefix}/css/log.css"; yield return $"{prefix}/css/map-editor.css"; + yield return $"{prefix}/css/terrain-editor.css"; } } diff --git a/src/Web/Shared/Properties/Resources.Designer.cs b/src/Web/Shared/Properties/Resources.Designer.cs index 2b7a58d73..74134d818 100644 --- a/src/Web/Shared/Properties/Resources.Designer.cs +++ b/src/Web/Shared/Properties/Resources.Designer.cs @@ -1052,5 +1052,98 @@ public static string FileLoaded { /// Gets the localized GameServerSelection_UnknownServer text. public static string GameServerSelection_UnknownServer => ResourceManager.GetString("GameServerSelection_UnknownServer", resourceCulture)!; + /// Gets the localized TerrainEditorTerrain text. + public static string TerrainEditorTerrain => ResourceManager.GetString("TerrainEditorTerrain", resourceCulture)!; + + /// Gets the localized DefaultTerrain text. + public static string DefaultTerrain => ResourceManager.GetString("DefaultTerrain", resourceCulture)!; + + /// Gets the localized TerrainTool text. + public static string TerrainTool => ResourceManager.GetString("TerrainTool", resourceCulture)!; + + /// Gets the localized TerrainToolBrush text. + public static string TerrainToolBrush => ResourceManager.GetString("TerrainToolBrush", resourceCulture)!; + + /// Gets the localized TerrainToolRectangle text. + public static string TerrainToolRectangle => ResourceManager.GetString("TerrainToolRectangle", resourceCulture)!; + + /// Gets the localized TerrainToolFill text. + public static string TerrainToolFill => ResourceManager.GetString("TerrainToolFill", resourceCulture)!; + + /// Gets the localized TerrainBrushSize text. + public static string TerrainBrushSize => ResourceManager.GetString("TerrainBrushSize", resourceCulture)!; + + /// Gets the localized TerrainEditMode text. + public static string TerrainEditMode => ResourceManager.GetString("TerrainEditMode", resourceCulture)!; + + /// Gets the localized TerrainEditModeAdd text. + public static string TerrainEditModeAdd => ResourceManager.GetString("TerrainEditModeAdd", resourceCulture)!; + + /// Gets the localized TerrainEditModeRemove text. + public static string TerrainEditModeRemove => ResourceManager.GetString("TerrainEditModeRemove", resourceCulture)!; + + /// Gets the localized TerrainEditModeReplace text. + public static string TerrainEditModeReplace => ResourceManager.GetString("TerrainEditModeReplace", resourceCulture)!; + + /// Gets the localized TerrainAttributes text. + public static string TerrainAttributes => ResourceManager.GetString("TerrainAttributes", resourceCulture)!; + + /// Gets the localized TerrainWalkable text. + public static string TerrainWalkable => ResourceManager.GetString("TerrainWalkable", resourceCulture)!; + + /// Gets the localized TerrainOtherAttributes text. + public static string TerrainOtherAttributes => ResourceManager.GetString("TerrainOtherAttributes", resourceCulture)!; + + /// Gets the localized TerrainLegend text. + public static string TerrainLegend => ResourceManager.GetString("TerrainLegend", resourceCulture)!; + + /// Gets the localized Redo text. + public static string Redo => ResourceManager.GetString("Redo", resourceCulture)!; + + /// Gets the localized ImportTerrain text. + public static string ImportTerrain => ResourceManager.GetString("ImportTerrain", resourceCulture)!; + + /// Gets the localized ImportTerrainHint text. + public static string ImportTerrainHint => ResourceManager.GetString("ImportTerrainHint", resourceCulture)!; + + /// Gets the localized DownloadServerTerrain text. + public static string DownloadServerTerrain => ResourceManager.GetString("DownloadServerTerrain", resourceCulture)!; + + /// Gets the localized DownloadServerTerrainHint text. + public static string DownloadServerTerrainHint => ResourceManager.GetString("DownloadServerTerrainHint", resourceCulture)!; + + /// Gets the localized DownloadClientTerrain text. + public static string DownloadClientTerrain => ResourceManager.GetString("DownloadClientTerrain", resourceCulture)!; + + /// Gets the localized DownloadClientTerrainHint text. + public static string DownloadClientTerrainHint => ResourceManager.GetString("DownloadClientTerrainHint", resourceCulture)!; + + /// Gets the localized InvalidTerrainFile text. + public static string InvalidTerrainFile => ResourceManager.GetString("InvalidTerrainFile", resourceCulture)!; + + /// Gets the localized TerrainImported text. + public static string TerrainImported => ResourceManager.GetString("TerrainImported", resourceCulture)!; + + /// Gets the localized TerrainEditorShortcutPaint text. + public static string TerrainEditorShortcutPaint => ResourceManager.GetString("TerrainEditorShortcutPaint", resourceCulture)!; + + /// Gets the localized TerrainEditorShortcutPan text. + public static string TerrainEditorShortcutPan => ResourceManager.GetString("TerrainEditorShortcutPan", resourceCulture)!; + + /// Gets the localized TerrainEditorShortcutZoom text. + public static string TerrainEditorShortcutZoom => ResourceManager.GetString("TerrainEditorShortcutZoom", resourceCulture)!; + + /// Gets the localized TerrainEditorRestartHint text. + public static string TerrainEditorRestartHint => ResourceManager.GetString("TerrainEditorRestartHint", resourceCulture)!; + + /// Gets the localized TerrainTransferFailed text. + public static string TerrainTransferFailed => ResourceManager.GetString("TerrainTransferFailed", resourceCulture)!; + + /// Gets the localized ZoomIn text. + public static string ZoomIn => ResourceManager.GetString("ZoomIn", resourceCulture)!; + + /// Gets the localized ZoomOut text. + public static string ZoomOut => ResourceManager.GetString("ZoomOut", resourceCulture)!; + } } diff --git a/src/Web/Shared/Properties/Resources.de.resx b/src/Web/Shared/Properties/Resources.de.resx index ea9198536..aa0e2b7a6 100644 --- a/src/Web/Shared/Properties/Resources.de.resx +++ b/src/Web/Shared/Properties/Resources.de.resx @@ -523,4 +523,97 @@ Stufe: {2} - {3} Der Charakter wurde erstellt, aber die "Charakter erstellt"-Plugins sind fehlgeschlagen, daher fehlen ihm eventuell Startgegenstände oder -fertigkeiten: {0} + + Terrain + + + Standard-Terrain + + + Werkzeug + + + Pinsel + + + Rechteck + + + Füllen + + + Pinselgröße + + + Modus + + + Attribute hinzufügen + + + Attribute entfernen + + + Alle Attribute ersetzen + + + Attribute + + + Begehbar + + + Andere Attribute + + + Legende + + + Wiederholen + + + Terrain-Datei importieren + + + Akzeptiert die *.att-Datei des Servers und die EncTerrain*.att-Datei des Clients. + + + Server-Terrain-Datei + + + Lädt das Terrain im *.att-Format des Original-Servers herunter. + + + Client-Terrain-Datei + + + Lädt die verschlüsselte Terrain-Datei des Spiel-Clients herunter. Lege sie im Client als Data\World{0}\EncTerrain{0}.att ab. + + + Die Datei ist weder eine Terrain-Datei des Servers noch des Clients. + + + Die Terrain-Datei wurde importiert. Speichere, um sie zu übernehmen. + + + Linksklick, um die Felder mit dem gewählten Werkzeug zu bearbeiten + + + Rechtsklick und ziehen, um das Terrain zu verschieben + + + Mausrad zum Zoomen + + + Geänderte Terrains werden nach einem Neustart der Spielserver übernommen. Spieler benötigen zusätzlich die Client-Terrain-Datei, um die Änderung zu sehen. + + + Das bearbeitete Terrain konnte nicht übertragen werden: {0} + + + Vergrößern + + + Verkleinern + diff --git a/src/Web/Shared/Properties/Resources.resx b/src/Web/Shared/Properties/Resources.resx index a9b4e6ef2..efb91f6a3 100644 --- a/src/Web/Shared/Properties/Resources.resx +++ b/src/Web/Shared/Properties/Resources.resx @@ -569,4 +569,97 @@ Level: {2} - {3} Could not load the game servers. Close the dialog and try again. + + Terrain + + + Default terrain + + + Tool + + + Brush + + + Rectangle + + + Fill + + + Brush size + + + Mode + + + Add attributes + + + Remove attributes + + + Replace all attributes + + + Attributes + + + Walkable + + + Other attributes + + + Legend + + + Redo + + + Import terrain file + + + Accepts the *.att file of the server and the EncTerrain*.att file of the client. + + + Server terrain file + + + Downloads the terrain in the *.att format of the original server. + + + Client terrain file + + + Downloads the encrypted terrain file of the game client. Place it as Data\World{0}\EncTerrain{0}.att in the client. + + + The file is neither a terrain file of the server nor of the client. + + + The terrain file was imported. Save to apply it. + + + Left click to edit the tiles with the selected tool + + + Right click and drag to pan the terrain + + + Mouse wheel to zoom + + + Changed terrains are applied to the game after restarting the game servers. Players need the client terrain file, too, to see the change. + + + The edited terrain could not be transferred: {0} + + + Zoom in + + + Zoom out + \ No newline at end of file diff --git a/src/Web/Shared/Properties/Resources.zh-CN.resx b/src/Web/Shared/Properties/Resources.zh-CN.resx index e6ed56196..bedef40f9 100644 --- a/src/Web/Shared/Properties/Resources.zh-CN.resx +++ b/src/Web/Shared/Properties/Resources.zh-CN.resx @@ -545,4 +545,97 @@ 角色已创建,但“角色已创建”插件执行失败,因此可能缺少初始物品或技能:{0} + + 地形 + + + 默认地形 + + + 工具 + + + 画笔 + + + 矩形 + + + 填充 + + + 画笔大小 + + + 模式 + + + 添加属性 + + + 移除属性 + + + 替换所有属性 + + + 属性 + + + 可行走 + + + 其他属性 + + + 图例 + + + 重做 + + + 导入地形文件 + + + 支持服务器的 *.att 文件和客户端的 EncTerrain*.att 文件。 + + + 服务器地形文件 + + + 以原始服务器的 *.att 格式下载地形。 + + + 客户端地形文件 + + + 下载游戏客户端的加密地形文件。请将其放置到客户端的 Data\World{0}\EncTerrain{0}.att。 + + + 该文件既不是服务器的地形文件,也不是客户端的地形文件。 + + + 地形文件已导入。保存后生效。 + + + 左键单击以使用所选工具编辑图块 + + + 右键单击并拖动以平移地形 + + + 使用鼠标滚轮缩放 + + + 修改后的地形在重启游戏服务器后生效。玩家也需要客户端地形文件才能看到变化。 + + + 无法传输已编辑的地形:{0} + + + 放大 + + + 缩小 + diff --git a/src/Web/Shared/wwwroot/css/terrain-editor.css b/src/Web/Shared/wwwroot/css/terrain-editor.css new file mode 100644 index 000000000..8a4f336f2 --- /dev/null +++ b/src/Web/Shared/wwwroot/css/terrain-editor.css @@ -0,0 +1,87 @@ +/* Terrain editor: scrollable pixelated canvas with optional tile grid, rectangle preview and tool panel. */ +.terrain-editor-row { + display: flex; + flex-wrap: wrap; + gap: 15px; +} + +.terrain-column { + flex-shrink: 0; +} + +.terrain-tool-column { + flex: 1; + min-width: 280px; + max-width: 440px; +} + +.terrain-host-container { + position: relative; + display: inline-block; +} + +.terrain-host { + position: relative; + width: 768px; + max-width: calc(100vw - 32px); + height: 768px; + max-height: 80vh; + overflow: auto; + user-select: none; + cursor: crosshair; + background-color: #1a1a2e; + border: 2px solid #333; + border-radius: 4px; +} + +.terrain-host.panning { + cursor: grabbing; +} + +.terrain-content { + position: relative; + display: inline-block; +} + +.terrain-content canvas { + display: block; + image-rendering: pixelated; + image-rendering: crisp-edges; +} + +.terrain-grid { + display: none; + position: absolute; + inset: 0; + pointer-events: none; + background-image: + linear-gradient(to right, rgba(255, 255, 255, 0.15) 1px, transparent 1px), + linear-gradient(to bottom, rgba(255, 255, 255, 0.15) 1px, transparent 1px); + background-size: var(--tile-size) var(--tile-size); +} + +.terrain-host.show-grid .terrain-grid { + display: block; +} + +.terrain-selection { + display: none; + position: absolute; + pointer-events: none; + border: 1px dashed #fff; + background-color: rgba(255, 255, 255, 0.2); +} + +.terrain-status { + min-height: 1.5em; + font-family: monospace; + font-size: 0.85em; +} + +.terrain-swatch { + display: inline-block; + width: 0.9em; + height: 0.9em; + border: 1px solid #888; + vertical-align: middle; +} diff --git a/tests/MUnique.OpenMU.Web.Tests/TerrainEditor/TerrainEditorTests.cs b/tests/MUnique.OpenMU.Web.Tests/TerrainEditor/TerrainEditorTests.cs new file mode 100644 index 000000000..2d922ffc8 --- /dev/null +++ b/tests/MUnique.OpenMU.Web.Tests/TerrainEditor/TerrainEditorTests.cs @@ -0,0 +1,115 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.Tests.TerrainEditor; + +using Bunit; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.Web.Shared.Components.Toast; +using MUnique.OpenMU.Web.Shared.Components.TerrainEditor; +using BasicModel = MUnique.OpenMU.Persistence.BasicModel; +using TerrainEditorComponent = MUnique.OpenMU.Web.Shared.Components.TerrainEditor.TerrainEditor; + +/// +/// Tests for the . +/// +[TestFixture] +public class TerrainEditorTests +{ + private const string ModulePath = "./_content/MUnique.OpenMU.Web.Shared/Components/TerrainEditor/TerrainEditor.razor.js"; + + private BunitContext _context = null!; + + private BunitJSModuleInterop _module = null!; + + /// + /// Setups the test objects. + /// + [SetUp] + public void Setup() + { + this._context = new BunitContext(); + this._context.JSInterop.Mode = JSRuntimeMode.Loose; + this._module = this._context.JSInterop.SetupModule(ModulePath); + this._module.Mode = JSRuntimeMode.Loose; + this._context.Services.AddSingleton(Mock.Of()); + this._context.Services.AddSingleton(Mock.Of>()); + } + + /// + /// Tears down the test objects. + /// + [TearDown] + public void Teardown() + { + this._context.Dispose(); + } + + /// + /// Tests that the default terrain and the variants of the selected map can be selected. + /// + [Test] + public void TerrainsOfSelectedMapAreSelectable() + { + var map = CreateMap(32); + var variant = new BasicModel.GameMapTerrainVariant { Number = 2, Description = "War" }; + map.TerrainVariants.Add(variant); + + var component = this.Render(map); + + var options = component.FindAll("#terrainSelect option"); + Assert.That(options.Select(o => o.TextContent), Is.EqualTo(new[] { Shared.Properties.Resources.DefaultTerrain, variant.ToString() })); + } + + /// + /// Tests that the editor is created with the terrain of the selected map. + /// + [Test] + public void TerrainOfSelectedMapIsLoaded() + { + var map = CreateMap(0); + var tiles = new byte[TerrainFileConverter.TileCount]; + tiles[42] = (byte)TerrainAttributeType.Blocked; + map.TerrainData = TerrainFileConverter.ToServerFile(tiles); + + this.Render(map); + + this._module.VerifyInvoke("createEditor"); + var setData = this._context.JSInterop.Invocations.Single(i => i.Identifier == "setData"); + Assert.That(setData.Arguments[0], Is.EqualTo(Convert.ToBase64String(tiles))); + Assert.That(setData.Arguments[1], Is.False); + } + + /// + /// Tests that the client terrain file is downloaded with the file name which the client expects. + /// + [Test] + public void ClientFileIsDownloaded() + { + var map = CreateMap(32); + var component = this.Render(map); + + component.FindAll("button").Single(b => b.TextContent.Contains(Shared.Properties.Resources.DownloadClientTerrain, StringComparison.Ordinal)).Click(); + + var download = this._module.VerifyInvoke("downloadFile"); + Assert.That(download.Arguments[0], Is.EqualTo("EncTerrain33.att")); + var content = Convert.FromBase64String((string)download.Arguments[1]!); + Assert.That(TerrainFileConverter.TryReadFile(content, out var tiles), Is.True); + Assert.That(tiles, Is.All.EqualTo(0)); + } + + private static BasicModel.GameMapDefinition CreateMap(short number) + { + return new BasicModel.GameMapDefinition { Number = number, Name = $"Map {number}" }; + } + + private IRenderedComponent Render(GameMapDefinition map) + { + return this._context.Render(parameters => parameters + .Add(p => p.Maps, [map])); + } +} diff --git a/tests/MUnique.OpenMU.Web.Tests/TerrainEditor/TerrainFileConverterTests.cs b/tests/MUnique.OpenMU.Web.Tests/TerrainEditor/TerrainFileConverterTests.cs new file mode 100644 index 000000000..a50c05729 --- /dev/null +++ b/tests/MUnique.OpenMU.Web.Tests/TerrainEditor/TerrainFileConverterTests.cs @@ -0,0 +1,118 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.Tests.TerrainEditor; + +using MUnique.OpenMU.Web.Shared.Components.TerrainEditor; + +/// +/// Tests for the . +/// +[TestFixture] +public class TerrainFileConverterTests +{ + /// + /// Tests that the server file has the header of the original server, followed by the tiles. + /// + [Test] + public void ServerFileHasHeaderAndTiles() + { + var tiles = CreateTiles(); + + var file = TerrainFileConverter.ToServerFile(tiles); + + Assert.That(file, Has.Length.EqualTo(3 + TerrainFileConverter.TileCount)); + Assert.That(file[..3], Is.EqualTo(new byte[] { 0, 0xFF, 0xFF })); + Assert.That(file[3..], Is.EqualTo(tiles)); + } + + /// + /// Tests that the tiles of a server file can be read again. + /// + [Test] + public void ServerFileCanBeRead() + { + var tiles = CreateTiles(); + + var success = TerrainFileConverter.TryReadFile(TerrainFileConverter.ToServerFile(tiles), out var result); + + Assert.That(success, Is.True); + Assert.That(result, Is.EqualTo(tiles)); + } + + /// + /// Tests that the tiles of a client file can be read again. + /// + [Test] + public void ClientFileCanBeRead() + { + var tiles = CreateTiles(); + + var file = TerrainFileConverter.ToClientFile(tiles, 33); + var success = TerrainFileConverter.TryReadFile(file, out var result); + + Assert.That(file, Has.Length.EqualTo(4 + TerrainFileConverter.TileCount)); + Assert.That(file[4..], Is.Not.EqualTo(tiles), "The client file should be encrypted."); + Assert.That(success, Is.True); + Assert.That(result, Is.EqualTo(tiles)); + } + + /// + /// Tests that the start of an encrypted client file matches the known result of the client's encryption + /// for the header of the first map (version 0, map 0, width and height 255) and a walkable first tile. + /// + [Test] + public void ClientFileIsEncryptedLikeTheClient() + { + var file = TerrainFileConverter.ToClientFile(new byte[TerrainFileConverter.TileCount], 0); + + Assert.That(file[..5], Is.EqualTo(new byte[] { 0x8B, 0xE4, 0x27, 0x91, 0x4F })); + } + + /// + /// Tests that files of an unknown size are rejected. + /// + [Test] + public void FileWithUnknownSizeIsRejected() + { + var success = TerrainFileConverter.TryReadFile(new byte[1000], out var result); + + Assert.That(success, Is.False); + Assert.That(result, Is.Null); + } + + /// + /// Tests that a file with the size of a client file, but which doesn't decrypt to a valid header, is rejected. + /// + [Test] + public void ClientFileWithInvalidHeaderIsRejected() + { + var success = TerrainFileConverter.TryReadFile(new byte[4 + TerrainFileConverter.TileCount], out _); + + Assert.That(success, Is.False); + } + + /// + /// Tests that missing terrain data results in walkable tiles. + /// + [Test] + public void MissingTerrainDataResultsInWalkableTiles() + { + var tiles = TerrainFileConverter.GetTileAttributes(null); + + Assert.That(tiles, Has.Length.EqualTo(TerrainFileConverter.TileCount)); + Assert.That(tiles, Is.All.EqualTo(0)); + } + + private static byte[] CreateTiles() + { + var tiles = new byte[TerrainFileConverter.TileCount]; + for (int i = 0; i < tiles.Length; i++) + { + tiles[i] = (byte)(i * 7); + } + + return tiles; + } +} From 88c7efcf6a4220d5e454a79c461d2606a3d67f65 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 09:26:24 +0000 Subject: [PATCH 2/3] docs(admin-panel): add screenshots of the terrain editor Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KESqfok2EbBMX41efgZUvw --- docs-website/docs/admin-panel/map-editor.md | 4 ++++ .../img/admin-panel/terrain-editor-zoomed.png | Bin 0 -> 82760 bytes .../static/img/admin-panel/terrain-editor.png | Bin 0 -> 82408 bytes 3 files changed, 4 insertions(+) create mode 100644 docs-website/static/img/admin-panel/terrain-editor-zoomed.png create mode 100644 docs-website/static/img/admin-panel/terrain-editor.png diff --git a/docs-website/docs/admin-panel/map-editor.md b/docs-website/docs/admin-panel/map-editor.md index b9295ec3d..f881ca10c 100644 --- a/docs-website/docs/admin-panel/map-editor.md +++ b/docs-website/docs/admin-panel/map-editor.md @@ -80,6 +80,8 @@ Select the map, and in the second drop-down the terrain to edit: the **default terrain**, or one of the **terrain variants** which replace it in certain states of the map, e.g. the peace, occupied and war terrains of the Crywolf fortress. +![The terrain editor showing the war terrain of the Crywolf fortress](/img/admin-panel/terrain-editor.png) + The tools: | Tool | Edits | @@ -92,6 +94,8 @@ The **mode** decides what happens to the selected **attributes**: *add* them to tiles, *remove* them, or *replace* all attributes of the tiles with them. Replace without any selected attribute makes the tiles plain walkable ground. +![A zoomed terrain with the tile grid, while drawing a rectangle](/img/admin-panel/terrain-editor-zoomed.png) + Use the mouse wheel or the zoom buttons to zoom, and drag with the right mouse button to pan. Below the terrain you see the coordinates and the attributes of the tile under the cursor. **Undo** and **Redo** work per terrain until you switch diff --git a/docs-website/static/img/admin-panel/terrain-editor-zoomed.png b/docs-website/static/img/admin-panel/terrain-editor-zoomed.png new file mode 100644 index 0000000000000000000000000000000000000000..91569a72d95eadc88dccf39003e27cffe76111b4 GIT binary patch literal 82760 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

JNuwh;%W~ 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 HOFsVtkOFM_ literal 0 HcmV?d00001 diff --git a/docs-website/static/img/admin-panel/terrain-editor.png b/docs-website/static/img/admin-panel/terrain-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..3e1bf0b2412a7b4a38753252be77862ca099d772 GIT binary patch literal 82408 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 Date: Sun, 18 Oct 2026 10:01:15 +0000 Subject: [PATCH 3/3] style(admin-panel): address the lint findings of the terrain editor Adds braces to single-line ifs and parentheses to arrow function parameters, replaces the constant loop condition of the brush line, and wraps over-long lines in the terrain editor documentation. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KESqfok2EbBMX41efgZUvw --- docs-website/docs/admin-panel/map-editor.md | 19 ++++----- .../TerrainEditor/TerrainEditor.razor.js | 39 ++++++++++++------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/docs-website/docs/admin-panel/map-editor.md b/docs-website/docs/admin-panel/map-editor.md index f881ca10c..409282148 100644 --- a/docs-website/docs/admin-panel/map-editor.md +++ b/docs-website/docs/admin-panel/map-editor.md @@ -90,16 +90,17 @@ The tools: | **Rectangle** | All tiles of the dragged rectangle | | **Fill** | All connected tiles which have the same attributes as the clicked one | -The **mode** decides what happens to the selected **attributes**: *add* them to the -tiles, *remove* them, or *replace* all attributes of the tiles with them. Replace -without any selected attribute makes the tiles plain walkable ground. +The **mode** decides what happens to the selected **attributes**: *add* them to +the tiles, *remove* them, or *replace* all attributes of the tiles with them. +Replace without any selected attribute makes the tiles plain walkable ground. ![A zoomed terrain with the tile grid, while drawing a rectangle](/img/admin-panel/terrain-editor-zoomed.png) Use the mouse wheel or the zoom buttons to zoom, and drag with the right mouse button to pan. Below the terrain you see the coordinates and the attributes of -the tile under the cursor. **Undo** and **Redo** work per terrain until you switch -to another one; **Save** writes the changes of all terrains to the database. +the tile under the cursor. **Undo** and **Redo** work per terrain until you +switch to another one; **Save** writes the changes of all terrains to the +database. ### Terrain files @@ -109,12 +110,12 @@ to another one; **Save** writes the changes of all terrains to the database. * **Server terrain file** downloads the terrain in the `*.att` format of the original server. * **Client terrain file** downloads the encrypted terrain file of the game - client. The client loads it from `Data\World\EncTerrain.att`, where `` - is the map number plus one. + client. The client loads it from `Data\World\EncTerrain.att`, where + `` is the map number plus one. :::warning[The client needs the terrain, too] The game servers use a changed terrain after they reloaded their configuration. -The game client, however, has its own copy of the terrain: if walls, safezones or -water changed, distribute the client terrain file to your players as well. +The game client, however, has its own copy of the terrain: if walls, safezones +or water changed, distribute the client terrain file to your players as well. Otherwise characters get stuck or walk through walls on their screen. ::: diff --git a/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.js b/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.js index d123f0b83..8d18e1659 100644 --- a/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.js +++ b/src/Web/Shared/Components/TerrainEditor/TerrainEditor.razor.js @@ -25,7 +25,7 @@ class TerrainEditor { this.selection = selection; this.status = status; this.dotNetRef = dotNetRef; - this.palette = options.palette.map(p => ({ mask: p.mask, rgb: TerrainEditor.parseColor(p.color) })); + this.palette = options.palette.map((p) => ({ mask: p.mask, rgb: TerrainEditor.parseColor(p.color) })); this.walkableRgb = TerrainEditor.parseColor(options.walkableColor); this.flagNames = options.flagNames; this.zoom = options.zoom; @@ -56,7 +56,7 @@ class TerrainEditor { this.onMouseUp = this.onMouseUp.bind(this); this.onMouseLeave = this.onMouseLeave.bind(this); this.onWheel = this.onWheel.bind(this); - this.onContextMenu = e => e.preventDefault(); + this.onContextMenu = (e) => e.preventDefault(); host.addEventListener("mousedown", this.onMouseDown); host.addEventListener("mouseleave", this.onMouseLeave); @@ -80,7 +80,7 @@ class TerrainEditor { * false, if another terrain was loaded. */ setData(base64, asEdit) { - const bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0)); + const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); if (bytes.length !== SIZE * SIZE) { return; } @@ -269,12 +269,8 @@ class TerrainEditor { const stepX = x < to.x ? 1 : -1; const stepY = y < to.y ? 1 : -1; let error = dx + dy; - while (true) { - this.applyBrush({ x, y }); - if (x === to.x && y === to.y) { - break; - } - + this.applyBrush({ x, y }); + while (x !== to.x || y !== to.y) { const doubledError = 2 * error; if (doubledError >= dy) { error += dy; @@ -285,6 +281,8 @@ class TerrainEditor { error += dx; y += stepY; } + + this.applyBrush({ x, y }); } } @@ -315,10 +313,21 @@ class TerrainEditor { this.data[index] = this.applyToValue(target); const x = index % SIZE; const y = Math.floor(index / SIZE); - if (x > 0) stack.push(index - 1); - if (x < SIZE - 1) stack.push(index + 1); - if (y > 0) stack.push(index - SIZE); - if (y < SIZE - 1) stack.push(index + SIZE); + if (x > 0) { + stack.push(index - 1); + } + + if (x < SIZE - 1) { + stack.push(index + 1); + } + + if (y > 0) { + stack.push(index - SIZE); + } + + if (y < SIZE - 1) { + stack.push(index + SIZE); + } } } @@ -406,7 +415,7 @@ class TerrainEditor { clampTile(e) { const rect = this.canvas.getBoundingClientRect(); - const clamp = v => Math.max(0, Math.min(SIZE - 1, v)); + const clamp = (v) => Math.max(0, Math.min(SIZE - 1, v)); return { x: clamp(Math.floor((e.clientY - rect.top) / this.zoom)), y: clamp(Math.floor((e.clientX - rect.left) / this.zoom)), @@ -464,7 +473,7 @@ class TerrainEditor { } const value = this.data[(tile.y * SIZE) + tile.x]; - const names = this.flagNames.filter(f => (value & f.mask) !== 0).map(f => f.name); + const names = this.flagNames.filter((f) => (value & f.mask) !== 0).map((f) => f.name); let text = `X: ${tile.x}, Y: ${tile.y} | ${value}`; if (names.length > 0) { text += ` (${names.join(", ")})`;