From 31cbfc195eaf568a986bd7cb56e483b91002c125 Mon Sep 17 00:00:00 2001 From: sven-n Date: Sun, 23 Feb 2025 21:56:06 +0100 Subject: [PATCH 1/2] Added item drop configuration page --- src/Web/AdminPanel/Pages/EditItemDrops.razor | 98 ++++++++ .../AdminPanel/Pages/EditItemDrops.razor.cs | 212 ++++++++++++++++++ src/Web/AdminPanel/Shared/ConfigNavMenu.razor | 1 + 3 files changed, 311 insertions(+) create mode 100644 src/Web/AdminPanel/Pages/EditItemDrops.razor create mode 100644 src/Web/AdminPanel/Pages/EditItemDrops.razor.cs diff --git a/src/Web/AdminPanel/Pages/EditItemDrops.razor b/src/Web/AdminPanel/Pages/EditItemDrops.razor new file mode 100644 index 000000000..f63490e5d --- /dev/null +++ b/src/Web/AdminPanel/Pages/EditItemDrops.razor @@ -0,0 +1,98 @@ +@page "/edit-item-drops/" + +OpenMU: Item Drop Chances + + +

Item Drop Chances

+ +@if (this._randomDropGroup is null && this._excellentDropGroup is null) +{ + + Loading... + return; +} + +

Drop Item Groups

+ + + + + + + + + + @if (this._moneyDropGroup is { } moneyDropGroup) + { + + + + + + } + @if (this._randomDropGroup is { } randomDropGroup) + { + + + + + + } + @if (this._excellentDropGroup is { } excellentDropGroup) + { + + + + + + } + + + + + + +
TypeDrop Rate (0.0 to 1.0)
Money Drop:
Common Items:
Excellent Item Drop:
No Drop:@(this.NoDropPercentage.ToString("P"))
+ +

Options

+ + + + + + + + + + + @if (this._luckOption is { } luckOption) + { + + + + + + } + @foreach (var option in this._excellentOptions) + { + + + + + + } + @foreach (var option in this._normalOptions) + { + + + + + + } + + +
TypeAdd Rate (0.0 to 1.0)Maximum Options per Item
@this._luckOption!.Name:
@option.Name:
@option.Name:
+ + + +

\ No newline at end of file diff --git a/src/Web/AdminPanel/Pages/EditItemDrops.razor.cs b/src/Web/AdminPanel/Pages/EditItemDrops.razor.cs new file mode 100644 index 000000000..8dceb8c31 --- /dev/null +++ b/src/Web/AdminPanel/Pages/EditItemDrops.razor.cs @@ -0,0 +1,212 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.AdminPanel.Pages; + +using System.Threading; +using Blazored.Toast.Services; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.DataModel.Configuration.Items; +using MUnique.OpenMU.Persistence; + +///

+/// Implements a simplified page for editing item drops. +/// +public partial class EditItemDrops : IAsyncDisposable +{ + private Task? _loadTask; + private CancellationTokenSource? _disposeCts; + private IContext? _persistenceContext; + private IDisposable? _navigationLockDisposable; + + private DropItemGroup? _randomDropGroup; + + private DropItemGroup? _excellentDropGroup; + + private DropItemGroup? _moneyDropGroup; + + private List _normalOptions = []; + + private List _excellentOptions = []; + + private ItemOptionDefinition? _luckOption; + + private double CommonItemPercentage + { + get => this._randomDropGroup!.Chance * 100; + set => this._randomDropGroup!.Chance = value / 100; + } + + private double ExcellentItemPercentage + { + get => this._excellentDropGroup!.Chance * 100; + set => this._excellentDropGroup!.Chance = value / 100; + } + + private double MoneyPercentage + { + get => this._moneyDropGroup!.Chance * 100; + set => this._moneyDropGroup!.Chance = value / 100; + } + + private double NoDropPercentage + { + get => Math.Max(0, 1 - (this._randomDropGroup!.Chance + this._moneyDropGroup!.Chance + this._excellentDropGroup!.Chance)); + } + + /// + /// Gets or sets the data source. + /// + [Inject] + public IDataSource DataSource { get; set; } = null!; + + /// + /// Gets or sets the context provider. + /// + [Inject] + public IPersistenceContextProvider ContextProvider { get; set; } = null!; + + /// + /// Gets or sets the toast service. + /// + [Inject] + public IToastService ToastService { get; set; } = null!; + + /// + /// Gets or sets the navigation manager. + /// + [Inject] + public NavigationManager NavigationManager { get; set; } = null!; + + /// + /// Gets or sets the java script runtime. + /// + [Inject] + public IJSRuntime JavaScript { get; set; } = null!; + + /// + /// Gets or sets the logger. + /// + [Inject] + public ILogger Logger { get; set; } = null!; + + /// + public async ValueTask DisposeAsync() + { + this._navigationLockDisposable?.Dispose(); + this._navigationLockDisposable = null; + + await (this._disposeCts?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false); + this._disposeCts?.Dispose(); + this._disposeCts = null; + + try + { + await (this._loadTask ?? Task.CompletedTask).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // we can ignore that ... + } + catch + { + // and we should not throw exceptions in the dispose method ... + } + } + + /// + protected override Task OnInitializedAsync() + { + this._navigationLockDisposable = this.NavigationManager.RegisterLocationChangingHandler(this.OnBeforeInternalNavigationAsync); + return base.OnInitializedAsync(); + } + + /// + protected override async Task OnParametersSetAsync() + { + var cts = new CancellationTokenSource(); + this._disposeCts = cts; + this._loadTask = Task.Run(() => this.LoadDataAsync(cts.Token), cts.Token); + await base.OnParametersSetAsync().ConfigureAwait(true); + } + + private async ValueTask OnBeforeInternalNavigationAsync(LocationChangingContext context) + { + if (this._persistenceContext?.HasChanges is not true) + { + return; + } + + var isConfirmed = await this.JavaScript.InvokeAsync( + "window.confirm", + "There are unsaved changes. Are you sure you want to discard them?") + .ConfigureAwait(true); + + if (!isConfirmed) + { + context.PreventNavigation(); + } + else + { + await this.DataSource.DiscardChangesAsync().ConfigureAwait(true); + } + } + + private async Task LoadDataAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + this._persistenceContext = await this.DataSource.GetContextAsync(cancellationToken).ConfigureAwait(true); + await this.DataSource.GetOwnerAsync(default, cancellationToken).ConfigureAwait(true); + cancellationToken.ThrowIfCancellationRequested(); + var data = this.DataSource.GetAll() + .Where(m => m is { Monster: null, }) + .ToList(); + this._excellentDropGroup = data.FirstOrDefault(d => d is { ItemType: SpecialItemType.Excellent, PossibleItems.Count: 0 }); + this._randomDropGroup = data.FirstOrDefault(d => d is { ItemType: SpecialItemType.RandomItem, PossibleItems.Count: 0 }); + this._moneyDropGroup = data.FirstOrDefault(d => d.ItemType == SpecialItemType.Money); + + var options = this.DataSource.GetAll(); + this._normalOptions = options.Where(d => d.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Option)).ToList(); + this._excellentOptions = options.Where(d => d.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Excellent)).ToList(); + this._luckOption = options.FirstOrDefault(d => d.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Luck)); + + await this.InvokeAsync(this.StateHasChanged).ConfigureAwait(false); + } + + private async Task OnSaveButtonClickAsync() + { + try + { + if (this._persistenceContext is { } context) + { + var success = await context.SaveChangesAsync().ConfigureAwait(true); + var text = success ? "The changes have been saved." : "There were no changes to save."; + this.ToastService.ShowSuccess(text); + } + else + { + this.ToastService.ShowError("Failed, context not initialized."); + } + } + catch (Exception ex) + { + this.Logger.LogError(ex, $"An unexpected error occurred on save: {ex.Message}"); + this.ToastService.ShowError($"An unexpected error occurred: {ex.Message}"); + } + } + + private async Task OnCancelButtonClickAsync() + { + if (this._persistenceContext?.HasChanges is true) + { + await this.DataSource.DiscardChangesAsync().ConfigureAwait(true); + await this.LoadDataAsync(this._disposeCts?.Token ?? default).ConfigureAwait(true); + } + } +} diff --git a/src/Web/AdminPanel/Shared/ConfigNavMenu.razor b/src/Web/AdminPanel/Shared/ConfigNavMenu.razor index 070526bb5..49afb82cb 100644 --- a/src/Web/AdminPanel/Shared/ConfigNavMenu.razor +++ b/src/Web/AdminPanel/Shared/ConfigNavMenu.razor @@ -20,6 +20,7 @@ Skills Items Drop Item Groups + Item Drops (simplified) Maps Mini Games Warp List From db4ef61ecb32902477e33074478421ae841e4713 Mon Sep 17 00:00:00 2001 From: sven-n Date: Wed, 26 Feb 2025 20:49:30 +0100 Subject: [PATCH 2/2] Fixed loading issue it rendered too early... --- src/Web/AdminPanel/Pages/EditItemDrops.razor | 2 +- .../AdminPanel/Pages/EditItemDrops.razor.cs | 21 ++++--------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/Web/AdminPanel/Pages/EditItemDrops.razor b/src/Web/AdminPanel/Pages/EditItemDrops.razor index f63490e5d..581d6c9c5 100644 --- a/src/Web/AdminPanel/Pages/EditItemDrops.razor +++ b/src/Web/AdminPanel/Pages/EditItemDrops.razor @@ -5,7 +5,7 @@

Item Drop Chances

-@if (this._randomDropGroup is null && this._excellentDropGroup is null) +@if (!this._loadingFinished) { Loading... diff --git a/src/Web/AdminPanel/Pages/EditItemDrops.razor.cs b/src/Web/AdminPanel/Pages/EditItemDrops.razor.cs index 8dceb8c31..b5bab7c71 100644 --- a/src/Web/AdminPanel/Pages/EditItemDrops.razor.cs +++ b/src/Web/AdminPanel/Pages/EditItemDrops.razor.cs @@ -36,23 +36,7 @@ public partial class EditItemDrops : IAsyncDisposable private ItemOptionDefinition? _luckOption; - private double CommonItemPercentage - { - get => this._randomDropGroup!.Chance * 100; - set => this._randomDropGroup!.Chance = value / 100; - } - - private double ExcellentItemPercentage - { - get => this._excellentDropGroup!.Chance * 100; - set => this._excellentDropGroup!.Chance = value / 100; - } - - private double MoneyPercentage - { - get => this._moneyDropGroup!.Chance * 100; - set => this._moneyDropGroup!.Chance = value / 100; - } + private bool _loadingFinished; private double NoDropPercentage { @@ -131,6 +115,7 @@ protected override async Task OnParametersSetAsync() { var cts = new CancellationTokenSource(); this._disposeCts = cts; + this._loadingFinished = false; this._loadTask = Task.Run(() => this.LoadDataAsync(cts.Token), cts.Token); await base.OnParametersSetAsync().ConfigureAwait(true); } @@ -176,6 +161,8 @@ private async Task LoadDataAsync(CancellationToken cancellationToken) this._excellentOptions = options.Where(d => d.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Excellent)).ToList(); this._luckOption = options.FirstOrDefault(d => d.PossibleOptions.Any(o => o.OptionType == ItemOptionTypes.Luck)); + this._loadingFinished = true; + await this.InvokeAsync(this.StateHasChanged).ConfigureAwait(false); }