Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/Web/AdminPanel/Pages/EditItemDrops.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
@page "/edit-item-drops/"

<PageTitle>OpenMU: Item Drop Chances</PageTitle>
<Breadcrumb IsFirstFromRoot="true" Caption="Item Drop Chances" />

<h1>Item Drop Chances</h1>

@if (!this._loadingFinished)
{
<span class="spinner-border" role="status" aria-hidden="true"></span>
<span class="sr-only">Loading...</span>
return;
}

<h2>Drop Item Groups</h2>
<table>
<thead>
<tr>
<th>Type</th>
<th>Drop Rate (0.0 to 1.0)</th>
<th></th>
</tr>
</thead>
<tbody>
@if (this._moneyDropGroup is { } moneyDropGroup)
{
<tr>
<td>Money Drop:</td>
<td><InputNumber @bind-Value="moneyDropGroup.Chance" step="0.01" min="0" max="1"/></td>
<td></td>
</tr>
}
@if (this._randomDropGroup is { } randomDropGroup)
{
<tr>
<td>Common Items:</td>
<td><InputNumber @bind-Value="randomDropGroup.Chance" step="0.01" min="0" max="1"/></td>
<td></td>
</tr>
}
@if (this._excellentDropGroup is { } excellentDropGroup)
{
<tr>
<td>Excellent Item Drop:</td>
<td><InputNumber @bind-Value="excellentDropGroup.Chance" step="0.0001" min="0" max="1" /></td>
<td></td>
</tr>
}
<tr>
<td>No Drop:</td>
<td>@(this.NoDropPercentage.ToString("P"))</td>
<td></td>
</tr>
</tbody>
</table>

<h2>Options</h2>
<table>
<thead>
<tr>
<th>Type</th>
<th>Add Rate (0.0 to 1.0)</th>
<th>Maximum Options per Item</th>
</tr>
</thead>
<tbody>

@if (this._luckOption is { } luckOption)
{
<tr>
<td>@this._luckOption!.Name:</td>
<td><InputNumber @bind-Value="@luckOption.AddChance" step="0.01" min="0" max="1"/></td>
<td></td>
</tr>
}
@foreach (var option in this._excellentOptions)
{
<tr>
<td>@option.Name:</td>
<td><InputNumber @bind-Value="@option.AddChance" step="0.01" min="0" max="1" /></td>
<td><InputNumber @bind-Value="@option.MaximumOptionsPerItem" min="1" max="@option.PossibleOptions.Count"></InputNumber></td>
</tr>
}
@foreach (var option in this._normalOptions)
{
<tr>
<td>@option.Name:</td>
<td><InputNumber @bind-Value="@option.AddChance" step="0.01" min="0" max="1" /></td>
<td></td>
</tr>
}

</tbody>
</table>

<button type="submit" class="primary-button" onclick="@this.OnSaveButtonClickAsync">Save</button>
<button type="button" onclick="@this.OnCancelButtonClickAsync">Cancel</button>
<p/>
199 changes: 199 additions & 0 deletions src/Web/AdminPanel/Pages/EditItemDrops.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// <copyright file="EditItemDrops.razor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

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;

/// <summary>
/// Implements a simplified page for editing item drops.
/// </summary>
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<ItemOptionDefinition> _normalOptions = [];

private List<ItemOptionDefinition> _excellentOptions = [];

private ItemOptionDefinition? _luckOption;

private bool _loadingFinished;

private double NoDropPercentage
{
get => Math.Max(0, 1 - (this._randomDropGroup!.Chance + this._moneyDropGroup!.Chance + this._excellentDropGroup!.Chance));
}

/// <summary>
/// Gets or sets the data source.
/// </summary>
[Inject]
public IDataSource<GameConfiguration> DataSource { get; set; } = null!;

/// <summary>
/// Gets or sets the context provider.
/// </summary>
[Inject]
public IPersistenceContextProvider ContextProvider { get; set; } = null!;

/// <summary>
/// Gets or sets the toast service.
/// </summary>
[Inject]
public IToastService ToastService { get; set; } = null!;

/// <summary>
/// Gets or sets the navigation manager.
/// </summary>
[Inject]
public NavigationManager NavigationManager { get; set; } = null!;

/// <summary>
/// Gets or sets the java script runtime.
/// </summary>
[Inject]
public IJSRuntime JavaScript { get; set; } = null!;

/// <summary>
/// Gets or sets the logger.
/// </summary>
[Inject]
public ILogger<Merchants> Logger { get; set; } = null!;

/// <inheritdoc />
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 ...
}
}

/// <inheritdoc />
protected override Task OnInitializedAsync()
{
this._navigationLockDisposable = this.NavigationManager.RegisterLocationChangingHandler(this.OnBeforeInternalNavigationAsync);
return base.OnInitializedAsync();
}

/// <inheritdoc />
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);
}

private async ValueTask OnBeforeInternalNavigationAsync(LocationChangingContext context)
{
if (this._persistenceContext?.HasChanges is not true)
{
return;
}

var isConfirmed = await this.JavaScript.InvokeAsync<bool>(
"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<DropItemGroup>()
.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<ItemOptionDefinition>();
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));

this._loadingFinished = true;

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);
}
}
}
1 change: 1 addition & 0 deletions src/Web/AdminPanel/Shared/ConfigNavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<NavLink href="@($"edit-config-grid/{typeof(Skill).FullName}/")">Skills</NavLink>
<NavLink href="@($"edit-config-grid/{typeof(ItemDefinition).FullName}/")">Items</NavLink>
<NavLink href="@($"edit-config-grid/{typeof(DropItemGroup).FullName}/")">Drop Item Groups</NavLink>
<NavLink href="@("/edit-item-drops/")">Item Drops (simplified)</NavLink>
<NavLink href="@($"edit-config-grid/{typeof(GameMapDefinition).FullName}/")">Maps</NavLink>
<NavLink href="@($"edit-config-grid/{typeof(MiniGameDefinition).FullName}/")">Mini Games</NavLink>
<NavLink href="@($"edit-config-grid/{typeof(WarpInfo).FullName}/")">Warp List</NavLink>
Expand Down
Loading