Skip to content
Open
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
4 changes: 2 additions & 2 deletions src/DataModel/Configuration/ConfigurationUpdate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ public class ConfigurationUpdate
/// <summary>
/// Gets or sets the name of the update.
/// </summary>
public LocalizedString? Name { get; set; }
public LocalizedString Name { get; set; }

/// <summary>
/// Gets or sets the description of the update with further information.
/// </summary>
public LocalizedString? Description { get; set; }
public LocalizedString Description { get; set; }

/// <summary>
/// Gets or sets the release date.
Expand Down
4 changes: 2 additions & 2 deletions src/DataModel/Configuration/MiniGameChangeEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ public partial class MiniGameChangeEvent
/// <summary>
/// Gets or sets the description about the event.
/// </summary>
public LocalizedString? Description { get; set; }
public LocalizedString Description { get; set; }

/// <summary>
/// Gets or sets the (golden) message which should be shown to the player.
/// One placeholder can be used to show the triggering player name.
/// </summary>
public LocalizedString? Message { get; set; }
public LocalizedString Message { get; set; }

/// <summary>
/// Gets or sets the targets which need to be killed to reach the required <see cref="NumberOfKills"/>.
Expand Down
4 changes: 2 additions & 2 deletions src/DataModel/Configuration/MiniGameSpawnWave.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ public partial class MiniGameSpawnWave
/// <summary>
/// Gets or sets the description about this wave.
/// </summary>
public LocalizedString? Description { get; set; }
public LocalizedString Description { get; set; }

/// <summary>
/// Gets or sets a message which is shown to the player when the wave starts.
/// </summary>
public LocalizedString? Message { get; set; }
public LocalizedString Message { get; set; }

/// <summary>
/// Gets or sets the starting time of the wave.
Expand Down
33 changes: 17 additions & 16 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,24 @@
<PackageVersion Include="Dapr.AspNetCore" Version="1.16.1" />
<PackageVersion Include="Dapr.Client" Version="1.16.1" />
<PackageVersion Include="Mapster" Version="7.4.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.QuickGrid" Version="10.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="10.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.QuickGrid" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc" Version="2.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="10.0.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="10.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.OpenApi" Version="1.6.22" />
<PackageVersion Include="Microsoft.TypeScript.MSBuild" Version="5.9.3" />
Expand All @@ -56,7 +57,7 @@
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.0.0" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="6.0.0-preview.4.21253.7" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.1" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.2" />
<PackageVersion Include="System.IO.Pipelines" Version="10.0.1" />
<PackageVersion Include="System.Linq.Dynamic.Core" Version="1.7.1" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
Expand Down
1 change: 0 additions & 1 deletion src/GameLogic/MUnique.OpenMU.GameLogic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
<PackageReference Include="MathParser.org-mXparser" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
<PackageReference Include="Nito.AsyncEx" />
<PackageReference Include="System.Memory" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 10 additions & 1 deletion src/GameLogic/MiniGames/MiniGameContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,16 @@ protected async ValueTask SaveRankingAsync(IEnumerable<(int Rank, Character Char
/// </param>
protected async ValueTask ShowGoldenMessageAsync(LocalizedString message, params object?[] args)
{
await this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync(string.Format(message.GetTranslation(player.Culture), args), MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false);
if (string.IsNullOrEmpty(message.Value))
{
return;
}

await this.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p =>
message.GetTranslation(player.Culture) is { Length: > 0 } translation
? p.ShowMessageAsync(string.Format(translation, args), MessageType.GoldenCenter)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling string.Format with an empty args array will throw a FormatException if the translation string contains any format specifiers (e.g., {0}). Some call sites of ShowGoldenMessageAsync pass an empty args array. To make this more robust, you could check if args has any elements before calling string.Format.

                    ? p.ShowMessageAsync(args.Length > 0 ? string.Format(translation, args) : translation, MessageType.GoldenCenter)

: ValueTask.CompletedTask)
.AsTask()).ConfigureAwait(false);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ protected override async ValueTask DoHandleCommandAsync(Player player, Arguments
if (!this.pendingConfirmations.TryGetValue(playerId, out var confirmationTime) || (DateTime.UtcNow - confirmationTime).TotalSeconds > ConfirmationTimeoutSeconds)
{
this.pendingConfirmations[playerId] = DateTime.UtcNow;
await player.ShowBlueMessageAsync(configuration.ConfirmationMessage.GetTranslation(player.Culture)).ConfigureAwait(false);
if (configuration.ConfirmationMessage.GetTranslation(player.Culture) is { Length: > 0 } message)
{
await player.ShowBlueMessageAsync(message).ConfigureAwait(false);
}

return;
}

Expand All @@ -94,7 +98,11 @@ protected override async ValueTask DoHandleCommandAsync(Player player, Arguments

if (removeMoney && !player.TryRemoveMoney(configuration.MoneyCost))
{
await player.ShowBlueMessageAsync(configuration.NotEnoughMoneyMessage.GetTranslation(player.Culture)).ConfigureAwait(false);
if (configuration.NotEnoughMoneyMessage.GetTranslation(player.Culture) is { Length: > 0 } notEnoughMoneyMessage)
{
await player.ShowBlueMessageAsync(notEnoughMoneyMessage).ConfigureAwait(false);
}

return;
}

Expand All @@ -103,7 +111,10 @@ protected override async ValueTask DoHandleCommandAsync(Player player, Arguments
await targetPlayer.DestroyInventoryItemAsync(item).ConfigureAwait(false);
}

await player.ShowBlueMessageAsync(configuration.InventoryClearedMessage.GetTranslation(player.Culture)).ConfigureAwait(false);
if (configuration.InventoryClearedMessage.GetTranslation(player.Culture) is { Length: > 0 } clearedMessage)
{
await player.ShowBlueMessageAsync(clearedMessage).ConfigureAwait(false);
}
}

/// <summary>
Expand Down
6 changes: 5 additions & 1 deletion src/GameLogic/PlugIns/ChatCommands/NpcChatCommandPlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ protected override async ValueTask DoHandleCommandAsync(Player player, Arguments

if (configuration.MinimumVipLevel > 0 && (player.Attributes?[Stats.IsVip] ?? 0) < configuration.MinimumVipLevel)
{
await player.ShowBlueMessageAsync(configuration.InsufficientVipLevelMessage.GetTranslation(player.Culture)).ConfigureAwait(false);
if (configuration.InsufficientVipLevelMessage.GetTranslation(player.Culture) is { Length: > 0 } message)
{
await player.ShowBlueMessageAsync(message).ConfigureAwait(false);
}

return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ protected override async ValueTask DoHandleCommandAsync(Player player, Arguments

if (configuration.MinimumVipLevel > 0 && (player.Attributes?[Stats.IsVip] ?? 0) < configuration.MinimumVipLevel)
{
await player.ShowBlueMessageAsync(configuration.InsufficientVipLevelMessage.GetTranslation(player.Culture)).ConfigureAwait(false);
if (configuration.InsufficientVipLevelMessage.GetTranslation(player.Culture) is { Length: > 0 } message)
{
await player.ShowBlueMessageAsync(message).ConfigureAwait(false);
}

return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/GameLogic/PlugIns/InvasionEvents/BaseInvasionPlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ protected async Task TrySendStartMessageAsync(Player player, LocalizedString map
return;
}

var message = (configuration.Message?.ToString() ?? PlugInResources.BaseInvasionPlugIn_DefaultStartMessage).Replace("{mapName}", mapName.GetTranslation(player.Culture), StringComparison.InvariantCulture);
var message = (configuration.Message.GetTranslation(player.Culture) ?? PlugInResources.BaseInvasionPlugIn_DefaultStartMessage).Replace("{mapName}", mapName.GetTranslation(player.Culture), StringComparison.InvariantCulture);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The call to mapName.GetTranslation(player.Culture) can return null after the changes to LocalizedString. This would cause an ArgumentNullException in string.Replace because the second argument cannot be null. You should handle the possible null value, for example by using the null-coalescing operator.

        var message = (configuration.Message.GetTranslation(player.Culture) ?? PlugInResources.BaseInvasionPlugIn_DefaultStartMessage).Replace("{mapName}", mapName.GetTranslation(player.Culture) ?? string.Empty, StringComparison.InvariantCulture);


if (this.IsPlayerOnMap(player))
{
Expand Down
2 changes: 1 addition & 1 deletion src/GameLogic/PlugIns/PeriodicTasks/HappyHourPlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ protected async Task TrySendStartMessageAsync(Player player)

try
{
var message = configuration.Message?.GetTranslation(player.Culture) ?? player.GetLocalizedMessage(nameof(PlayerMessage.HappyHourEventHasBeenStarted));
var message = configuration.Message.GetTranslation(player.Culture) is { Length: > 0 } translation ? translation : player.GetLocalizedMessage(nameof(PlayerMessage.HappyHourEventHasBeenStarted));
await player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync(message, Interfaces.MessageType.GoldenCenter)).ConfigureAwait(false);
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class PeriodicTaskConfiguration
/// Gets or sets the text which prints as a golden message in the game.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.PeriodicTaskConfiguration_Message_Name))]
public LocalizedString? Message { get; set; }
public LocalizedString Message { get; set; }

/// <summary>
/// Generate a sequence of time points like [00:00, 00:01, ...].
Expand Down
5 changes: 3 additions & 2 deletions src/GameLogic/PlugIns/QuestMonsterKillCountPlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ public async ValueTask AttackableGotKilledAsync(IAttackable killed, IAttacker? k

requirementState!.KillCount++;

if (killRequirement.MinimumNumber >= requirementState!.KillCount)
if (killRequirement.MinimumNumber >= requirementState!.KillCount
&& configuration.Message.GetTranslation(player.Culture) is { Length: > 0 } translation)
{
var message = string.Format(
configuration.Message.GetTranslation(player.Culture),
translation,
questState.ActiveQuest.Name.GetTranslation(player.Culture),
monster.Definition.Designation.GetTranslation(player.Culture),
requirementState.KillCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public object CreateDefaultConfig()
{
return new WanderingMerchantsConfiguration
{
Message = null,
PreStartMessageDelay = TimeSpan.Zero,

// we check every minute if we have to move a merchant.
Expand Down
5 changes: 5 additions & 0 deletions src/GameServer/RemoteView/ShowMessagePlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public class ShowMessagePlugIn : IShowMessagePlugIn
/// <inheritdoc/>
public async ValueTask ShowMessageAsync(string message, OpenMU.Interfaces.MessageType messageType)
{
if (string.IsNullOrEmpty(message))
{
return;
}

const int maxMessageLength = 241;

if (Encoding.UTF8.GetByteCount(message) > maxMessageLength)
Expand Down
8 changes: 5 additions & 3 deletions src/Interfaces/LocalizedString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
/// </returns>
public override string? ToString()
{
return this.GetTranslation(CultureInfo.CurrentCulture);
return this.Value is null ? null : this.GetTranslation(CultureInfo.CurrentCulture);
}

/// <summary>
Expand All @@ -155,10 +155,12 @@
/// <paramref name="fallbackToNeutral"/> is <see langword="true"/> and no specific translation exists,
/// or an empty string if neither is available.
/// </returns>
public string GetTranslation(CultureInfo cultureInfo, bool fallbackToNeutral = true)
public string? GetTranslation(CultureInfo cultureInfo, bool fallbackToNeutral = true)
{
var span = this.GetTranslationAsSpan(cultureInfo, fallbackToNeutral);
return new(span);
return span.IsEmpty
? null
: new(span);
}

/// <summary>
Expand Down Expand Up @@ -236,7 +238,7 @@
// i. If text is null or empty: remove that section (and possible trailing/leading separators).
// ii. Else: replace its content with text.
// - If not found and text is not null/empty: append "||xx=text".
// 4. Return new LocalizedString with computed value.

Check warning on line 241 in src/Interfaces/LocalizedString.cs

View workflow job for this annotation

GitHub Actions / build

Single-line comments should not be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1512.md)

var languageCode = cultureInfo.TwoLetterISOLanguageName;

Expand Down
3 changes: 0 additions & 3 deletions src/Network/MUnique.OpenMU.Network.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Nito.AsyncEx.Tasks" />
<PackageReference Include="Pipelines.Sockets.Unofficial" />
<PackageReference Include="System.IO.Pipelines" />
<PackageReference Include="System.Net.Sockets" />
<PackageReference Include="System.Text.Encoding" />
</ItemGroup>

<ItemGroup>
Expand Down
24 changes: 24 additions & 0 deletions src/Persistence/EntityFramework/EntityDataContextFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// <copyright file="EntityDataContextFactory.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.Persistence.EntityFramework;

using Microsoft.EntityFrameworkCore.Design;

/// <summary>
/// Design-time factory for <see cref="EntityDataContext"/>.
/// </summary>
public class EntityDataContextFactory : IDesignTimeDbContextFactory<EntityDataContext>
{
/// <inheritdoc />
public EntityDataContext CreateDbContext(string[] args)
{
if (!ConnectionConfigurator.IsInitialized)
{
ConnectionConfigurator.Initialize(new ConfigFileDatabaseConnectionStringProvider());
}

return new EntityDataContext();
}
}
Loading
Loading