baseline: OpenMU upstream b5a0961 (fresh source)

This commit is contained in:
Acentech Dev
2026-07-14 19:00:35 +03:00
parent 450402e47d
commit 36fc125d5c
11968 changed files with 748705 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
// <copyright file="BannableChatMessageBaseProcessor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
/// <summary>
/// A chat message processor for normal chat.
/// </summary>
public abstract class BannableChatMessageBaseProcessor : IChatMessageProcessor
{
/// <inheritdoc />
public async ValueTask ProcessMessageAsync(Player sender, (string Message, string PlayerName) content)
{
TimeSpan remainingChatBan = this.RemainingChatBanTimeSpan(sender);
if (this.IsSenderBanned(remainingChatBan))
{
if (remainingChatBan.TotalMinutes >= 1)
{
await sender.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ChatBanMinutesRemaining), (int)Math.Ceiling(remainingChatBan.TotalMinutes)).ConfigureAwait(false);
}
else
{
await sender.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ChatBanSecondsRemaining), (int)Math.Ceiling(remainingChatBan.TotalSeconds)).ConfigureAwait(false);
}
return;
}
await this.SubclassProcessMessageAsync(sender, content).ConfigureAwait(false);
}
/// <summary>
/// A method to be overriden for processing a specific chat message.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="content">The content.</param>
/// <returns>A value task with the result.</returns>
public abstract ValueTask SubclassProcessMessageAsync(Player sender, (string Message, string PlayerName) content);
private TimeSpan RemainingChatBanTimeSpan(Player sender)
{
DateTime chatBanUntil = sender.Account?.ChatBanUntil ?? default;
DateTime currentDateTime = DateTime.UtcNow;
return chatBanUntil - currentDateTime;
}
private bool IsSenderBanned(TimeSpan remainingChatBan)
{
return remainingChatBan > TimeSpan.Zero;
}
}

View File

@@ -0,0 +1,105 @@
// <copyright file="ChatMessageAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
using MUnique.OpenMU.GameLogic.Views;
/// <summary>
/// Action to send chat messages.
/// </summary>
public class ChatMessageAction
{
private readonly IDictionary<string, ChatMessageType> _messagePrefixes;
private readonly IDictionary<ChatMessageType, IChatMessageProcessor> _chatProcessMessages;
/// <summary>
/// Initializes a new instance of the <see cref="ChatMessageAction"/> class.
/// </summary>
public ChatMessageAction()
{
this._messagePrefixes = new SortedDictionary<string, ChatMessageType>(new ReverseComparer())
{
{ "~", ChatMessageType.Party },
{ "@", ChatMessageType.Guild },
{ "@@", ChatMessageType.Alliance },
{ "$", ChatMessageType.Gens },
{ "!", ChatMessageType.GlobalNotification },
{ "/", ChatMessageType.Command },
};
this._chatProcessMessages = new Dictionary<ChatMessageType, IChatMessageProcessor>
{
{ ChatMessageType.Command, new ChatMessageCommandProcessor() },
{ ChatMessageType.Whisper, new ChatMessageWhisperProcessor() },
{ ChatMessageType.Party, new ChatMessagePartyProcessor() },
{ ChatMessageType.Alliance, new ChatMessageAllianceProcessor() },
{ ChatMessageType.Guild, new ChatMessageGuildProcessor() },
{ ChatMessageType.GlobalNotification, new ChatMessageGlobalNotificationProcessor() },
{ ChatMessageType.Normal, new ChatMessageNormalProcessor() },
};
}
/// <summary>
/// Sends a chat message from the player to other players.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="playerName">Name of the <paramref name="sender"/>, except for <see cref="ChatMessageType.Whisper"/>, then its the receiver name.</param>
/// <param name="message">The message which should be sent.</param>
/// <param name="whisper">If set to <c>true</c> the message is whispered to the player with the <paramref name="playerName"/>; Otherwise, it's not a whisper.</param>
public async ValueTask ChatMessageAsync(Player sender, string playerName, string message, bool whisper)
{
using var loggerScope = sender.Logger.BeginScope(this.GetType());
ChatMessageType messageType = this.GetMessageType(message, whisper);
if (sender.SelectedCharacter is null)
{
// Is possible to receive null?
return;
}
if (messageType != ChatMessageType.Whisper && playerName != sender.SelectedCharacter?.Name)
{
sender.Logger.LogWarning("Maybe Hacker, Charname in chat packet != charname\t [{0}] <> [{1}]", sender.SelectedCharacter?.Name, playerName);
}
if (!this._chatProcessMessages.ContainsKey(messageType))
{
sender.Logger.LogDebug("Not implemented chat message type: {0}", messageType);
return;
}
await this._chatProcessMessages[messageType].ProcessMessageAsync(sender, (message, playerName)).ConfigureAwait(true);
}
private ChatMessageType GetMessageType(string message, bool whisper)
{
if (whisper)
{
return ChatMessageType.Whisper;
}
// byte 13: begin message
foreach (var keyValuePair in this._messagePrefixes)
{
if (message.StartsWith(keyValuePair.Key, StringComparison.InvariantCulture))
{
return keyValuePair.Value;
}
}
return ChatMessageType.Normal;
}
/// <summary>
/// We have to implement a reverse comparer, so that the strings which are longer, come first.
/// </summary>
private class ReverseComparer : IComparer<string>
{
public int Compare(string? x, string? y)
{
return string.Compare(y, x, StringComparison.InvariantCultureIgnoreCase);
}
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ChatMessageAllianceProcessor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
using System.ComponentModel;
using MUnique.OpenMU.GameLogic.PlugIns;
/// <summary>
/// A chat message processor for alliance chat.
/// </summary>
public class ChatMessageAllianceProcessor : BannableChatMessageBaseProcessor
{
/// <inheritdoc />
public override async ValueTask SubclassProcessMessageAsync(Player sender, (string Message, string PlayerName) content)
{
var eventArgs = new CancelEventArgs();
sender.GameContext.PlugInManager.GetPlugInPoint<IChatMessageReceivedPlugIn>()?.ChatMessageReceived(sender, content.Message, eventArgs);
if (eventArgs.Cancel)
{
return;
}
if (!(sender.GuildStatus != null && (sender.GameContext as IGameServerContext)?.EventPublisher is { } publisher))
{
return;
}
// TODO: Use DI to get the IEventPublisher
await publisher.AllianceMessageAsync(sender.GuildStatus.GuildId, sender.SelectedCharacter!.Name, content.Message).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,32 @@
// <copyright file="ChatMessageCommandProcessor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
/// <summary>
/// A chat message processor which handles chat commands.
/// </summary>
public class ChatMessageCommandProcessor : IChatMessageProcessor
{
/// <inheritdoc />
public async ValueTask ProcessMessageAsync(Player sender, (string Message, string PlayerName) content)
{
var commandKey = content.Message.Split(' ').First();
var commandHandler = sender.GameContext.PlugInManager.GetStrategy<IChatCommandPlugIn>(commandKey);
if (commandHandler is null)
{
return;
}
if (sender.SelectedCharacter!.CharacterStatus < commandHandler.MinCharacterStatusRequirement)
{
sender.Logger.LogWarning($"{sender.Name} is trying to execute {commandKey} command without meeting the requirements");
return;
}
await commandHandler.HandleCommandAsync(sender, content.Message).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="ChatMessageGlobalNotificationProcessor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
using System.ComponentModel;
using MUnique.OpenMU.GameLogic.PlugIns;
/// <summary>
/// A chat message processor which sends a global notification.
/// </summary>
public class ChatMessageGlobalNotificationProcessor : IChatMessageProcessor
{
/// <inheritdoc />
public async ValueTask ProcessMessageAsync(Player sender, (string Message, string PlayerName) content)
{
var eventArgs = new CancelEventArgs();
sender.GameContext.PlugInManager.GetPlugInPoint<IChatMessageReceivedPlugIn>()
?.ChatMessageReceived(sender, content.Message, eventArgs);
if (eventArgs.Cancel)
{
return;
}
if (sender.SelectedCharacter!.CharacterStatus < CharacterStatus.GameMaster)
{
return;
}
await sender.GameContext.SendGlobalNotificationAsync(content.Message).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,32 @@
// <copyright file="ChatMessageGuildProcessor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
using System.ComponentModel;
using MUnique.OpenMU.GameLogic.PlugIns;
/// <summary>
/// A chat message processor which sends the message to the guild.
/// </summary>
public class ChatMessageGuildProcessor : BannableChatMessageBaseProcessor
{
/// <inheritdoc />
public override async ValueTask SubclassProcessMessageAsync(Player sender, (string Message, string PlayerName) content)
{
var eventArgs = new CancelEventArgs();
sender.GameContext.PlugInManager.GetPlugInPoint<IChatMessageReceivedPlugIn>()?.ChatMessageReceived(sender, content.Message, eventArgs);
if (eventArgs.Cancel)
{
return;
}
if (!(sender.GuildStatus != null && (sender.GameContext as IGameServerContext)?.EventPublisher is { } publisher))
{
return;
}
await publisher.GuildMessageAsync(sender.GuildStatus.GuildId, sender.SelectedCharacter!.Name, content.Message).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,20 @@
// <copyright file="ChatMessageNormalProcessor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
using MUnique.OpenMU.GameLogic.Views;
/// <summary>
/// A chat message processor for normal chat.
/// </summary>
public class ChatMessageNormalProcessor : BannableChatMessageBaseProcessor
{
/// <inheritdoc/>
public override async ValueTask SubclassProcessMessageAsync(Player sender, (string Message, string PlayerName) content)
{
sender.Logger.LogDebug("Sending Chat Message to Observers, Count: {0}", sender.Observers.Count);
await sender.ForEachWorldObserverAsync<IChatViewPlugIn>(p => p.ChatMessageAsync(content.Message, sender.SelectedCharacter!.Name, ChatMessageType.Normal), true).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,27 @@
// <copyright file="ChatMessagePartyProcessor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
using System.ComponentModel;
using MUnique.OpenMU.GameLogic.PlugIns;
/// <summary>
/// A chat message processor which sends the message to the party.
/// </summary>
public class ChatMessagePartyProcessor : BannableChatMessageBaseProcessor
{
/// <inheritdoc />
public override async ValueTask SubclassProcessMessageAsync(Player sender, (string Message, string PlayerName) content)
{
var eventArgs = new CancelEventArgs();
sender.GameContext.PlugInManager.GetPlugInPoint<IChatMessageReceivedPlugIn>()?.ChatMessageReceived(sender, content.Message, eventArgs);
if (eventArgs.Cancel)
{
return;
}
sender.Party?.SendChatMessageAsync(content.Message, sender.SelectedCharacter!.Name);
}
}

View File

@@ -0,0 +1,30 @@
// <copyright file="ChatMessageWhisperProcessor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
using System.ComponentModel;
using MUnique.OpenMU.GameLogic.PlugIns;
using MUnique.OpenMU.GameLogic.Views;
/// <summary>
/// A chat message processor which sends the message to the whisper receiver.
/// </summary>
public class ChatMessageWhisperProcessor : BannableChatMessageBaseProcessor
{
/// <inheritdoc />
public override async ValueTask SubclassProcessMessageAsync(Player sender, (string Message, string PlayerName) content)
{
var whisperReceiver = sender.GameContext.GetPlayerByCharacterName(content.PlayerName);
if (whisperReceiver != null)
{
var eventArgs = new CancelEventArgs();
sender.GameContext.PlugInManager.GetPlugInPoint<IWhisperMessageReceivedPlugIn>()?.WhisperMessageReceived(sender, whisperReceiver, content.Message, eventArgs);
if (!eventArgs.Cancel)
{
await whisperReceiver.InvokeViewPlugInAsync<IChatViewPlugIn>(p => p.ChatMessageAsync(content.Message, sender.SelectedCharacter!.Name, ChatMessageType.Whisper)).ConfigureAwait(false);
}
}
}
}

View File

@@ -0,0 +1,19 @@
// <copyright file="IChatMessageProcessor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Chat;
/// <summary>
/// Interface for a chat message processor.
/// </summary>
public interface IChatMessageProcessor
{
/// <summary>
/// Sends a chat message from the player to other players.
/// </summary>
/// <param name="sender" cref="Player">The sending Player.</param>
/// <param name="content">The chat message's content.</param>
/// <returns>The value task with the result.</returns>
ValueTask ProcessMessageAsync(Player sender, (string Message, string PlayerName) content);
}