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,37 @@
// <copyright file="AddAgilityStatChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the command to add agility stat points.
/// </summary>
[Guid("43156A52-03EE-42C0-88BF-CA9665DC8E1E")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AddAgilityStatChatCommandPlugIn_Name), Description = nameof(PlugInResources.AddAgilityStatChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, null, MinimumStatus)]
public class AddAgilityStatChatCommandPlugIn : AddStatChatCommandPlugIn, IDisabledByDefault
{
private const string Command = "/addagi";
private const CharacterStatus MinimumStatus = CharacterStatus.Normal;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
public override async ValueTask HandleCommandAsync(Player player, string command)
{
command = command.Insert(4, " ");
await base.HandleCommandAsync(player, command).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,37 @@
// <copyright file="AddCommandStatChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the command to add command stat points.
/// </summary>
[Guid("EFE421FB-BE79-4656-AF39-D22A105D1455")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AddCommandStatChatCommandPlugIn_Name), Description = nameof(PlugInResources.AddCommandStatChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, null, MinimumStatus)]
public class AddCommandStatChatCommandPlugIn : AddStatChatCommandPlugIn, IDisabledByDefault
{
private const string Command = "/addcmd";
private const CharacterStatus MinimumStatus = CharacterStatus.Normal;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
public override async ValueTask HandleCommandAsync(Player player, string command)
{
command = command.Insert(4, " ");
await base.HandleCommandAsync(player, command).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,37 @@
// <copyright file="AddEnergyStatChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the command to add energy stat points.
/// </summary>
[Guid("A597B6E7-9395-4CF4-8439-A1D60134B63E")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AddEnergyStatChatCommandPlugIn_Name), Description = nameof(PlugInResources.AddEnergyStatChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, null, MinimumStatus)]
public class AddEnergyStatChatCommandPlugIn : AddStatChatCommandPlugIn, IDisabledByDefault
{
private const string Command = "/addene";
private const CharacterStatus MinimumStatus = CharacterStatus.Normal;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
public override async ValueTask HandleCommandAsync(Player player, string command)
{
command = command.Insert(4, " ");
await base.HandleCommandAsync(player, command).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,78 @@
// <copyright file="AddStatChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlayerActions.Character;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the command to add stat points.
/// </summary>
[Guid("042EC5C6-27C8-4E00-A48B-C5458EDEA0BC")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AddStatChatCommandPlugIn_Name), Description = nameof(PlugInResources.AddStatChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(Arguments), MinimumStatus)]
public class AddStatChatCommandPlugIn : ChatCommandPlugInBase<AddStatChatCommandPlugIn.Arguments>
{
private const string Command = "/add";
private const CharacterStatus MinimumStatus = CharacterStatus.Normal;
private readonly IncreaseStatsAction _action = new();
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
if (player.SelectedCharacter is null)
{
return;
}
var attribute = await this.TryGetAttributeAsync(player, arguments.StatType).ConfigureAwait(false);
if (attribute is null)
{
return;
}
var selectedCharacter = player.SelectedCharacter;
if (!selectedCharacter.CanIncreaseStats(arguments.Amount))
{
return;
}
if (player.CurrentMiniGame is not null)
{
await player.ShowLocalizedBlueMessageAsync(PlayerMessage.AddingMultiplePointsWhileMiniGameNotAllowed).ConfigureAwait(false);
return;
}
await this._action.IncreaseStatsAsync(player, attribute, arguments.Amount).ConfigureAwait(false);
}
/// <summary>
/// Arguments for this command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the type of the stat.
/// </summary>
[ValidValues("str", "agi", "vit", "ene", "cmd")]
public string? StatType { get; set; }
/// <summary>
/// Gets or sets the amount.
/// </summary>
public ushort Amount { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
// <copyright file="AddStrengthStatChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the command to add strength stat points.
/// </summary>
[Guid("21B15D95-BA2F-40A3-AB7D-8BD886FAEAE5")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AddStrengthStatChatCommandPlugIn_Name), Description = nameof(PlugInResources.AddStrengthStatChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, null, MinimumStatus)]
public class AddStrengthStatChatCommandPlugIn : AddStatChatCommandPlugIn, IDisabledByDefault
{
private const string Command = "/addstr";
private const CharacterStatus MinimumStatus = CharacterStatus.Normal;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
public override async ValueTask HandleCommandAsync(Player player, string command)
{
command = command.Insert(4, " ");
await base.HandleCommandAsync(player, command).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,37 @@
// <copyright file="AddVitalityStatChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the command to add vitality stat points.
/// </summary>
[Guid("370CE86C-E382-4E0F-93F4-AD75FA079129")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AddVitalityStatChatCommandPlugIn_Name), Description = nameof(PlugInResources.AddVitalityStatChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, null, MinimumStatus)]
public class AddVitalityStatChatCommandPlugIn : AddStatChatCommandPlugIn, IDisabledByDefault
{
private const string Command = "/addvit";
private const CharacterStatus MinimumStatus = CharacterStatus.Normal;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
public override async ValueTask HandleCommandAsync(Player player, string command)
{
command = command.Insert(4, " ");
await base.HandleCommandAsync(player, command).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,43 @@
// <copyright file="ArgumentAttribute.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
/// <summary>
/// Attribute used in the arguments of the commands.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ArgumentAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentAttribute"/> class
/// which is a required argument.
/// </summary>
/// <param name="shortName">The short name.</param>
public ArgumentAttribute(string shortName)
: this(shortName, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentAttribute" /> class.
/// </summary>
/// <param name="shortName">The short name.</param>
/// <param name="isRequired">If set to <c>true</c>, this argument is required to execute the command.</param>
public ArgumentAttribute(string shortName, bool isRequired)
{
this.ShortName = shortName;
this.IsRequired = isRequired;
}
/// <summary>
/// Gets or sets the short name of the argument to be used in chat.
/// </summary>
public string ShortName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the argument is required to execute the command.
/// </summary>
public bool IsRequired { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="BanAccChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by BanAccChatCommandPlugIn.
/// </summary>
public class BanAccChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the account name.
/// </summary>
[Argument("acc")]
public string? AccountName { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="BanCharChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by BanCharChatCommandPlugIn.
/// </summary>
public class BanCharChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("char")]
public string? CharacterName { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="ChangeLanguageChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by <see cref="ChangeLanguageChatCommandArgs"/>>.
/// </summary>
public class ChangeLanguageChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the iso 2/3 language code of the requested language.
/// </summary>
[Argument("isoCode")]
public string? IsoLanguageCode { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="CharInfoChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by CharInfoChatCommandPlugIn.
/// </summary>
public class CharInfoChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("char")]
public string? CharacterName { get; set; }
}

View File

@@ -0,0 +1,23 @@
// <copyright file="ChatBanCharChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by the <see cref="ChatBanCharChatCommandPlugIn"/>.
/// </summary>
public class ChatBanCharChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("characterName")]
public string? CharacterName { get; set; }
/// <summary>
/// Gets or sets the duration.
/// </summary>
[Argument("durationMinutes")]
public int DurationMinutes { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="ChatUnbanCharChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by the <see cref="ChatUnbanCharChatCommandPlugIn"/>.
/// </summary>
public class ChatUnbanCharChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("characterName")]
public string? CharacterName { get; set; }
}

View File

@@ -0,0 +1,30 @@
// <copyright file="CoordinatesCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.Pathfinding;
/// <summary>
/// Arguments used by <see cref="TeleportChatCommandPlugIn"/> and others which just require an X and Y coordinate of a game map.
/// </summary>
public class CoordinatesCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the coordinate X.
/// </summary>
[Argument("x", true)]
public byte X { get; set; }
/// <summary>
/// Gets or sets the coordinate Y.
/// </summary>
[Argument("y", true)]
public byte Y { get; set; }
/// <summary>
/// Gets the coordinates X and Y.
/// </summary>
public Point Coordinates => new(this.X, this.Y);
}

View File

@@ -0,0 +1,23 @@
// <copyright file="CreateMonsterChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by <see cref="CreateMonsterChatCommand"/>.
/// </summary>
public class CreateMonsterChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("number")]
public short MonsterNumber { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the created monster should be intelligent (walking, attacking), or should do nothing at all.
/// </summary>
[Argument("intelligence", false)]
public bool IsIntelligent { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="DisconnectChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by DisconnectChatCommandPlugIn.
/// </summary>
public class DisconnectChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("char")]
public string? CharacterName { get; set; }
}

View File

@@ -0,0 +1,12 @@
// <copyright file="EmptyChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by any ChatCommandPlugIn which don't need any arguments.
/// </summary>
public class EmptyChatCommandArgs : ArgumentsBase
{
}

View File

@@ -0,0 +1,17 @@
// <copyright file="GuildDisconnectChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by GuildDisconnectChatCommandPlugIn.
/// </summary>
public class GuildDisconnectChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the guild name.
/// </summary>
[Argument("guild")]
public string? GuildName { get; set; }
}

View File

@@ -0,0 +1,42 @@
// <copyright file="GuildMoveChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.Pathfinding;
/// <summary>
/// Arguments used by GuildMoveChatCommandPlugIn.
/// </summary>
public class GuildMoveChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the guild name.
/// </summary>
[Argument("guild")]
public string? GuildName { get; set; }
/// <summary>
/// Gets or sets the name or id of the map.
/// </summary>
[Argument("mapIdOrName")]
public string? MapIdOrName { get; set; }
/// <summary>
/// Gets or sets the coordinate X.
/// </summary>
[Argument("x", false)]
public byte X { get; set; }
/// <summary>
/// Gets or sets the coordinate Y.
/// </summary>
[Argument("y", false)]
public byte Y { get; set; }
/// <summary>
/// Gets the coordinates X and Y.
/// </summary>
public Point Coordinates => new(this.X, this.Y);
}

View File

@@ -0,0 +1,17 @@
// <copyright file="GuildWarChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by <see cref="GuildWarChatCommandPlugIn"/> and <see cref="GuildBattleSoccerChatCommandPlugIn"/>.
/// </summary>
public class GuildWarChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the guild name.
/// </summary>
[Argument("guildname")]
public string GuildName { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,17 @@
// <copyright file="IdCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by <see cref="RemoveNpcChatCommand"/> and others which just require an id of an object.
/// </summary>
public class IdCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the npc id.
/// </summary>
[Argument("id", true)]
public short Id { get; set; }
}

View File

@@ -0,0 +1,70 @@
// <copyright file="ItemChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by ItemChatCommandPlugIn.
/// </summary>
public class ItemChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the group.
/// </summary>
[Argument("group")]
public byte Group { get; set; }
/// <summary>
/// Gets or sets the number.
/// </summary>
[Argument("number")]
public short Number { get; set; }
/// <summary>
/// Gets or sets the level.
/// </summary>
[Argument("lvl", false)]
public byte Level { get; set; }
/// <summary>
/// Gets or sets the excellent number.
/// </summary>
[Argument("ex", false)]
public byte ExcellentNumber { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the item contains skill.
/// </summary>
[Argument("sk", false)]
public bool Skill { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the item contains luck.
/// </summary>
[Argument("lu", false)]
public bool Luck { get; set; }
/// <summary>
/// Gets or sets the option.
/// </summary>
[Argument("opt", false)]
public byte Opt { get; set; }
/// <summary>
/// Gets or sets the ancient set discriminator.
/// When 0, it's not an ancient.
/// When 1, the first ancient type of an item is applied; When 2, the second, if available.
/// Example for a Dragon Set item: 1 will be Hyon, 2 will be Vicious..
/// </summary>
[Argument("anc", false)]
[ValidValues("0", "1", "2")]
public byte Ancient { get; set; }
/// <summary>
/// Gets or sets the ancient bonus option; Should be 1 or 2. Only applies, when <see cref="Ancient"/> is bigger than 0.
/// </summary>
[Argument("ancBonuslvl", false)]
[ValidValues("1", "2")]
public byte AncientBonusLevel { get; set; } = 1;
}

View File

@@ -0,0 +1,43 @@
// <copyright file="MoveChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.Pathfinding;
/// <summary>
/// Arguments used by MoveChatCommandPlugIn.
/// </summary>
public class MoveChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the target.
/// The target can be the map name, map id or character name.
/// </summary>
[Argument("target")]
public string? Target { get; set; }
/// <summary>
/// Gets or sets the name or id of the map.
/// </summary>
[Argument("mapIdOrName", false)]
public string? MapIdOrName { get; set; }
/// <summary>
/// Gets or sets the coordinate X.
/// </summary>
[Argument("x", false)]
public byte X { get; set; }
/// <summary>
/// Gets or sets the coordinate Y.
/// </summary>
[Argument("y", false)]
public byte Y { get; set; }
/// <summary>
/// Gets the coordinates X and Y.
/// </summary>
public Point Coordinates => new(this.X, this.Y);
}

View File

@@ -0,0 +1,17 @@
// <copyright file="MoveMonsterCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by <see cref="MoveMonsterChatCommand"/> and others which just require an X and Y coordinate of a game map.
/// </summary>
public class MoveMonsterCommandArgs : CoordinatesCommandArgs
{
/// <summary>
/// Gets or sets the monster id.
/// </summary>
[Argument("id", true)]
public short Id { get; set; }
}

View File

@@ -0,0 +1,30 @@
// <copyright file="PKChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by PKChatCommandPlugIn.
/// </summary>
public class PkChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("char")]
public string? CharacterName { get; set; }
/// <summary>
/// Gets or sets the pk level.
/// </summary>
[Argument("pk_lvl")]
[ValidValues("1", "2", "3")]
public int Level { get; set; }
/// <summary>
/// Gets or sets the pk count.
/// </summary>
[Argument("pk_count")]
public int Count { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="PKClearChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by PKClearChatCommandPlugIn.
/// </summary>
public class PkClearChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("char", false)]
public string? CharacterName { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="SkinChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by the <see cref="SkinChatCommandPlugIn"/>.
/// </summary>
public class SkinChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the skin number, which is mostly equivalent to the <see cref="MonsterDefinition.Number"/>.
/// </summary>
[Argument("skin", true)]
public short SkinNumber { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="TraceChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by TraceChatCommandPlugIn.
/// </summary>
public class TraceChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("char")]
public string? CharacterName { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="UnBanAccChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by UnBanAccChatCommandPlugIn.
/// </summary>
public class UnBanAccChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the account name.
/// </summary>
[Argument("acc")]
public string? AccountName { get; set; }
}

View File

@@ -0,0 +1,17 @@
// <copyright file="UnBanCharChatCommandArgs.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
/// <summary>
/// Arguments used by UnBanCharChatCommandPlugIn.
/// </summary>
public class UnBanCharChatCommandArgs : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name.
/// </summary>
[Argument("char")]
public string? CharacterName { get; set; }
}

View File

@@ -0,0 +1,34 @@
// <copyright file="ArgumentsBase.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
/// <summary>
/// The base of every arguments class used in the commands.
/// </summary>
public class ArgumentsBase
{
/// <summary>
/// This makes it easier to print the arguments and its values for debugging.
/// </summary>
/// <returns>String.</returns>
public override string ToString()
{
var properties = this.GetType().GetProperties();
var stringBuilder = new StringBuilder();
bool isFirst = true;
foreach (var property in properties)
{
if (!isFirst)
{
stringBuilder.Append(" ");
}
stringBuilder.Append($"{property.Name}:{property.GetValue(this)}");
isFirst = false;
}
return stringBuilder.ToString();
}
}

View File

@@ -0,0 +1,35 @@
// <copyright file="BanAccChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles banacc commands.
/// </summary>
[Guid("EF869270-847E-48D5-9012-F5D111D9C8EB")]
[PlugIn]
[Display(Name = nameof(PlugInResources.BanAccChatCommandPlugIn_Name), Description = nameof(PlugInResources.BanAccChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(BanAccChatCommandArgs), CharacterStatus.GameMaster)]
public class BanAccChatCommandPlugIn : ChatCommandPlugInBase<BanAccChatCommandArgs>
{
private const string Command = "/banacc";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, BanAccChatCommandArgs arguments)
{
await this.ChangeAccountStateByLoginNameAsync(gameMaster, arguments.AccountName ?? string.Empty, AccountState.Banned).ConfigureAwait(false);
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.AccountHasBeenBanned), this.Key, arguments.AccountName).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,38 @@
// <copyright file="BanCharChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles banchar commands.
/// </summary>
[Guid("7AD1E5F4-4B07-4165-B9A4-188614F00F7C")]
[PlugIn]
[Display(Name = nameof(PlugInResources.BanCharChatCommandPlugIn_Name), Description = nameof(PlugInResources.BanCharChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(BanCharChatCommandArgs), CharacterStatus.GameMaster)]
public class BanCharChatCommandPlugIn : ChatCommandPlugInBase<BanCharChatCommandArgs>
{
private const string Command = "/banchar";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, BanCharChatCommandArgs arguments)
{
if (!await this.TryChangeAccountStateByCharacterNameAsync(gameMaster, arguments.CharacterName ?? string.Empty, AccountState.Banned).ConfigureAwait(false))
{
return;
}
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.AccountOfHasBeenBanned), this.Key, arguments.CharacterName).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,62 @@
// <copyright file="ChangeLanguageChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Globalization;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the language change command.
/// </summary>
[Guid("06870B25-3240-49CF-ADD4-F3060EA1FA7D")]
[PlugIn]
[Display(Name = nameof(PlugInResources.ChangeLanguageChatCommandPlugIn_Name), Description = nameof(PlugInResources.ChangeLanguageChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(ChangeLanguageChatCommandArgs), CharacterStatus.Normal)]
public class ChangeLanguageChatCommandPlugIn : ChatCommandPlugInBase<ChangeLanguageChatCommandArgs>
{
private const string Command = "/language";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, ChangeLanguageChatCommandArgs arguments)
{
var languages = CultureHelper.GetAvailableCultures<PlayerMessage>();
if (string.IsNullOrWhiteSpace(arguments.IsoLanguageCode))
{
await ShowAvailableLanguagesAsync(player, languages).ConfigureAwait(false);
return;
}
var requestedCulture = languages.FirstOrDefault(cu => cu.TwoLetterISOLanguageName == arguments.IsoLanguageCode
|| cu.ThreeLetterISOLanguageName == arguments.IsoLanguageCode);
if (requestedCulture is null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.RequestedLanguageNotFound), arguments.IsoLanguageCode).ConfigureAwait(false);
await ShowAvailableLanguagesAsync(player, languages).ConfigureAwait(false);
}
else
{
player.Culture = requestedCulture;
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.LanguageChanged), requestedCulture.NativeName, requestedCulture.TwoLetterISOLanguageName).ConfigureAwait(false);
}
}
private static async ValueTask ShowAvailableLanguagesAsync(Player player, IEnumerable<CultureInfo> languages)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.AvailableLanguagesCaption)).ConfigureAwait(false);
foreach (var lang in languages.OrderBy(l => l.TwoLetterISOLanguageName))
{
await player.ShowBlueMessageAsync($" {lang.TwoLetterISOLanguageName} - {lang.NativeName} / {lang.EnglishName}").ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,92 @@
// <copyright file="CharInfoChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.IO;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles charinfo commands.
/// </summary>
[Guid("0C7162BC-C74E-4A65-82E3-12811E4BE170")]
[PlugIn]
[Display(Name = nameof(PlugInResources.CharInfoChatCommandPlugIn_Name), Description = nameof(PlugInResources.CharInfoChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(CharInfoChatCommandArgs), CharacterStatus.GameMaster)]
public class CharInfoChatCommandPlugIn : ChatCommandPlugInBase<CharInfoChatCommandArgs>
{
private const string Command = "/charinfo";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, CharInfoChatCommandArgs arguments)
{
var player = await this.GetPlayerByCharacterNameAsync(gameMaster, arguments.CharacterName ?? string.Empty).ConfigureAwait(false);
if (player?.Account is not { } account
|| player.SelectedCharacter is not { } character)
{
return;
}
await gameMaster.ShowBlueMessageAsync($"Account Name: {account.LoginName}").ConfigureAwait(false);
await this.ShowAllLinesMessageToAsync(gameMaster, GetCharacterInfo(gameMaster, character)).ConfigureAwait(false);
await this.ShowAllLinesMessageToAsync(gameMaster, player.Attributes?.ToString()).ConfigureAwait(false);
}
private static string GetCharacterInfo(Player gameMaster, Character character)
{
var stringBuilder = new StringBuilder()
.AppendLine($"Id: {character.Id}")
.AppendLine($"Name: {character.Name}")
.AppendLine($"Class: {character.CharacterClass?.Name.GetTranslation(gameMaster.Culture)}")
.AppendLine($"Slot: {character.CharacterSlot}")
.AppendLine($"Create Date: {character.CreateDate}")
.AppendLine($"Exp: {character.Experience}")
.AppendLine($"Level Up Points: {character.LevelUpPoints}")
.AppendLine($"Master Exp: {character.MasterExperience}")
.AppendLine($"Master Lv Up Points: {character.MasterLevelUpPoints}")
.AppendLine($"Location: {character.CurrentMap?.Name}({character.PositionX}, {character.PositionY})")
.AppendLine($"Kill Count: {character.PlayerKillCount}")
.AppendLine($"State Remaining Seconds: {character.StateRemainingSeconds}")
.AppendLine($"State: {Enum.GetName(character.State)}")
.AppendLine($"Status: {Enum.GetName(character.CharacterStatus)}")
.AppendLine($"Used Fruit Points: {character.UsedFruitPoints}")
.AppendLine($"Used Neg Fruit Points: {character.UsedNegFruitPoints}")
.AppendLine($"Inventory Extensions: {character.InventoryExtensions}");
return stringBuilder.ToString();
}
private async ValueTask ShowAllLinesMessageToAsync(Player gameMaster, string? message)
{
if (string.IsNullOrEmpty(message))
{
return;
}
using var reader = new StringReader(message);
while (true)
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
if (line == null)
{
break;
}
await gameMaster.ShowBlueMessageAsync(line).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,61 @@
// <copyright file="ChatBanCharChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles chatban command.
/// </summary>
[Guid("287AE9A6-E434-4E52-A791-8AAD267A8E05")]
[PlugIn]
[Display(Name = nameof(PlugInResources.ChatBanCharChatCommandPlugIn_Name), Description = nameof(PlugInResources.ChatBanCharChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(ChatBanCharChatCommandArgs), CharacterStatus.GameMaster)]
public class ChatBanCharChatCommandPlugIn : ChatCommandPlugInBase<ChatBanCharChatCommandArgs>
{
private const string Command = "/chatban";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, ChatBanCharChatCommandArgs arguments)
{
if (string.IsNullOrEmpty(arguments.CharacterName))
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNameIsRequired)).ConfigureAwait(false);
return;
}
if (arguments.DurationMinutes == 0)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.DurationMustBeLongerThan0)).ConfigureAwait(false);
return;
}
var player = gameMaster.GameContext.GetPlayerByCharacterName(arguments.CharacterName);
if (player == null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), arguments.CharacterName).ConfigureAwait(false);
return;
}
if (!await this.ChangeAccountChatBanUntilAsync(player, DateTime.UtcNow.AddMinutes(arguments.DurationMinutes)).ConfigureAwait(false))
{
return;
}
// Send ban notice to Game Master
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.AccountChatBannedResult), this.Key, arguments.CharacterName, arguments.DurationMinutes).ConfigureAwait(false);
// Send ban notice to character
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.YouAreChatBanned), arguments.DurationMinutes).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,76 @@
// <copyright file="ChatCommandHelpAttribute.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
/// <summary>
/// An attribute which decorates an <see cref="IChatCommandPlugIn"/> with help information.
/// This information is then used in the <see cref="HelpCommand"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ChatCommandHelpAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="ChatCommandHelpAttribute" /> class.
/// </summary>
/// <param name="command">The command.</param>
public ChatCommandHelpAttribute(string command)
: this(command, CharacterStatus.Normal)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChatCommandHelpAttribute" /> class.
/// </summary>
/// <param name="command">The command.</param>
/// <param name="minimumCharacterStatus">The minimum character status.</param>
public ChatCommandHelpAttribute(string command, CharacterStatus minimumCharacterStatus)
: this(command, (Type?)null, minimumCharacterStatus)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChatCommandHelpAttribute" /> class.
/// </summary>
/// <param name="command">The command.</param>
/// <param name="description">The description of the command.</param>
/// <param name="argumentsType">Type of the arguments.</param>
public ChatCommandHelpAttribute(string command, string description, Type? argumentsType)
: this(command, argumentsType, CharacterStatus.Normal)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChatCommandHelpAttribute" /> class.
/// </summary>
/// <param name="command">The command.</param>
/// <param name="argumentsType">Type of the arguments.</param>
/// <param name="minimumCharacterStatus">The minimum character status.</param>
public ChatCommandHelpAttribute(string command, Type? argumentsType, CharacterStatus minimumCharacterStatus)
{
this.Command = command;
this.ArgumentsType = argumentsType;
this.MinimumCharacterStatus = minimumCharacterStatus;
}
/// <summary>
/// Gets the command.
/// </summary>
public string Command { get; }
/// <summary>
/// Gets the minimum character status.
/// </summary>
public CharacterStatus MinimumCharacterStatus { get; }
/// <summary>
/// Gets the type of the arguments of the chat command.
/// </summary>
public Type? ArgumentsType { get; }
/// <summary>
/// Gets the usage text for the chat command.
/// </summary>
public string Usage => this.ArgumentsType is null ? this.Command : CommandExtensions.CreateUsage(this.ArgumentsType, this.Command);
}

View File

@@ -0,0 +1,263 @@
// <copyright file="ChatCommandPlugInBase.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Pathfinding;
/// <summary>
/// The base of every chat command plug in.
/// </summary>
/// <typeparam name="T">The type of arguments base.</typeparam>
public abstract class ChatCommandPlugInBase<T> : IChatCommandPlugIn
where T : ArgumentsBase, new()
{
/// <inheritdoc/>
public abstract string Key { get; }
/// <inheritdoc/>
public abstract CharacterStatus MinCharacterStatusRequirement { get; }
/// <inheritdoc/>
public virtual async ValueTask HandleCommandAsync(Player player, string command)
{
try
{
var arguments = await command.TryParseArgumentsAsync<T>(player).ConfigureAwait(false);
if (arguments is not null)
{
await this.DoHandleCommandAsync(player, arguments).ConfigureAwait(false);
}
}
catch (Exception ex)
{
player.Logger.LogError(ex, $"Unexpected error handling the chat command '{this.Key}'.", command);
}
}
/// <summary>
/// Handles the chat command safely.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="arguments">The arguments.</param>
protected abstract ValueTask DoHandleCommandAsync(Player player, T arguments);
/// <summary>
/// Gets a player by his character name.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="characterName">The character name.</param>
/// <returns>The target player.</returns>
protected async ValueTask<Player?> GetPlayerByCharacterNameAsync(Player player, string characterName)
{
if (string.IsNullOrWhiteSpace(characterName))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNameIsRequired)).ConfigureAwait(false);
return null;
}
var result = player.GameContext.GetPlayerByCharacterName(characterName);
if (result is null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
}
return result;
}
/// <summary>
/// Gets a guild id by name.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="guildName">The guild name.</param>
/// <returns>The guild id.</returns>
protected async ValueTask<uint?> GetGuildIdByNameAsync(Player player, string guildName)
{
var guildServer = (player.GameContext as IGameServerContext)!.GuildServer;
var guildId = await guildServer.GetGuildIdByNameAsync(guildName).ConfigureAwait(false);
if (guildId == 0)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.GuildNotFound), guildName).ConfigureAwait(false);
return null;
}
return guildId;
}
/// <summary>
/// Gets a exit gate.
/// </summary>
/// <param name="gameMaster">The game master.</param>
/// <param name="map">The name or id of the map.</param>
/// <param name="coordinates">The coordinates X and Y.</param>
/// <returns>The ExitGate.</returns>
protected async ValueTask<ExitGate?> GetExitGateAsync(Player gameMaster, string map, Point coordinates)
{
if (coordinates == default)
{
var result = this.GetWarpInfo(gameMaster, map)?.Gate;
if (result is null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MapNotFound), map).ConfigureAwait(false);
}
return result;
}
var mapDefinition = ushort.TryParse(map, out var mapId)
? (await gameMaster.GameContext.GetMapAsync(mapId).ConfigureAwait(false))?.Definition
: gameMaster.GameContext.Configuration.Maps.FirstOrDefault(x =>
x.Name.GetTranslationAsSpan(gameMaster.Culture).Equals(map, StringComparison.OrdinalIgnoreCase)
|| x.Name.ValueInNeutralLanguageAsSpan.Equals(map, StringComparison.OrdinalIgnoreCase));
if (mapDefinition is null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MapNotFound), map).ConfigureAwait(false);
return null;
}
return new ExitGate
{
Map = mapDefinition,
X1 = coordinates.X,
X2 = coordinates.X,
Y1 = coordinates.Y,
Y2 = coordinates.Y,
};
}
/// <summary>
/// Gets a warp info.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="map">The name or id of the map.</param>
/// <returns>The WarpInfo.</returns>
protected WarpInfo? GetWarpInfo(Player player, string map)
{
var warpList = player.GameContext.Configuration.WarpList;
return ushort.TryParse(map, out var mapId)
? warpList.FirstOrDefault(info => info.Gate?.Map?.Number == mapId)
: warpList.FirstOrDefault(info => map.Equals(info.Name.GetTranslationAsSpan(player.Culture), StringComparison.CurrentCultureIgnoreCase));
}
/// <summary>
/// Change <see cref="AccountState"/> from Account by character name.
/// </summary>
/// <param name="gameMaster">GameMaster Player.</param>
/// <param name="name">Name of character to be changed.</param>
/// <param name="accountState">New <see cref="AccountState"/>.</param>
/// <returns>Flag, if successful.</returns>
protected async ValueTask<bool> TryChangeAccountStateByCharacterNameAsync(Player gameMaster, string? name, AccountState accountState)
{
if (string.IsNullOrEmpty(name))
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNameIsRequired)).ConfigureAwait(false);
return false;
}
return await this.ChangeAccountStateAsync(gameMaster, context => context.GetAccountByCharacterNameAsync(name), accountState).ConfigureAwait(false);
}
/// <summary>
/// Change <see cref="AccountState"/> from Account by login name.
/// </summary>
/// <param name="gameMaster">GameMaster Player.</param>
/// <param name="loginName">Login from account to be changed.</param>
/// <param name="accountState">New <see cref="AccountState"/>.</param>
protected async ValueTask<bool> ChangeAccountStateByLoginNameAsync(Player gameMaster, string? loginName, AccountState accountState)
{
if (string.IsNullOrEmpty(loginName))
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.LoginNameRequired)).ConfigureAwait(false);
return false;
}
return await this.ChangeAccountStateAsync(gameMaster, context => context.GetAccountByLoginNameAsync(loginName), accountState).ConfigureAwait(false);
}
/// <summary>
/// Changes ChatBanUntil value from Account.
/// </summary>
/// <param name="player">Player to be banned/unbanned.</param>
/// <param name="chatBanUntil">Date and time until which the chat ban is in effect.</param>
protected async ValueTask<bool> ChangeAccountChatBanUntilAsync(Player player, DateTime? chatBanUntil)
{
if (player.Account == null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.AccountNotFound)).ConfigureAwait(false);
return false;
}
player.Account.ChatBanUntil = chatBanUntil;
return true;
}
/// <summary>
/// Tries to get the stat attribute of the player.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="statType">Type of the stat.</param>
/// <returns>The found stat attribute, or <see langword="null"/>.</returns>
protected async ValueTask<AttributeDefinition?> TryGetAttributeAsync(Player player, string? statType)
{
if (player.SelectedCharacter is not { } selectedCharacter)
{
return null;
}
var attribute = statType switch
{
"str" => Stats.BaseStrength,
"agi" => Stats.BaseAgility,
"vit" => Stats.BaseVitality,
"ene" => Stats.BaseEnergy,
"cmd" => Stats.BaseLeadership,
_ => null,
};
if (attribute is null)
{
await player.ShowLocalizedBlueMessageAsync(PlayerMessage.UnknownAttribute, statType).ConfigureAwait(false);
return null;
}
if (selectedCharacter.Attributes.All(sa => sa.Definition != attribute))
{
await player.ShowLocalizedBlueMessageAsync(PlayerMessage.CharacterHasNoStatAttribute, statType).ConfigureAwait(false);
return null;
}
return attribute;
}
private async ValueTask<bool> ChangeAccountStateAsync(Player gameMaster, Func<MUnique.OpenMU.Persistence.IPlayerContext, ValueTask<Account?>> accountSelector, AccountState accountState)
{
using var context = gameMaster.GameContext.PersistenceContextProvider.CreateNewPlayerContext(gameMaster.GameContext.Configuration);
var account = await accountSelector(context).ConfigureAwait(false);
if (account == null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.AccountNotFound)).ConfigureAwait(false);
return false;
}
foreach (var character in account.Characters)
{
var player = gameMaster.GameContext.GetPlayerByCharacterName(character.Name ?? string.Empty);
// disconnect to change account
if (player != null)
{
await player.DisconnectAsync().ConfigureAwait(false);
break;
}
}
account.State = accountState;
await context.SaveChangesAsync().ConfigureAwait(false);
return true;
}
}

View File

@@ -0,0 +1,28 @@
// <copyright file="ChatCommandTypeExtensions.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Reflection;
/// <summary>
/// Extension methods regarding chat command types.
/// </summary>
public static class ChatCommandTypeExtensions
{
/// <summary>
/// Gets the available chat commands of the player.
/// </summary>
/// <param name="player">The player.</param>
/// <returns>The available chat commands of the player.</returns>
public static IEnumerable<ChatCommandHelpAttribute> GetAvailableChatCommands(this Player player)
{
return player.GameContext?.PlugInManager
.GetKnownPlugInsOf<IChatCommandPlugIn>()
.Select(CustomAttributeExtensions.GetCustomAttribute<ChatCommandHelpAttribute>)
.Where(attribute => attribute is { })
.Where(attribute => player.SelectedCharacter?.CharacterStatus >= attribute!.MinimumCharacterStatus)
.Select(attribute => attribute!) ?? Enumerable.Empty<ChatCommandHelpAttribute>();
}
}

View File

@@ -0,0 +1,55 @@
// <copyright file="ChatUnbanCharChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles chatunban command.
/// </summary>
[Guid("82E74664-7700-433B-9428-90C17CC71350")]
[PlugIn]
[Display(Name = nameof(PlugInResources.ChatUnbanCharChatCommandPlugIn_Name), Description = nameof(PlugInResources.ChatUnbanCharChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(ChatUnbanCharChatCommandArgs), CharacterStatus.GameMaster)]
public class ChatUnbanCharChatCommandPlugIn : ChatCommandPlugInBase<ChatUnbanCharChatCommandArgs>
{
private const string Command = "/chatunban";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, ChatUnbanCharChatCommandArgs arguments)
{
if (string.IsNullOrEmpty(arguments.CharacterName))
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNameIsRequired)).ConfigureAwait(false);
return;
}
var player = gameMaster.GameContext.GetPlayerByCharacterName(arguments.CharacterName);
if (player == null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), arguments.CharacterName).ConfigureAwait(false);
return;
}
if (!await this.ChangeAccountChatBanUntilAsync(player, null).ConfigureAwait(false))
{
return;
}
// Send unban notice to Game Master
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ChatBanRemoved), this.Key, arguments.CharacterName).ConfigureAwait(false);
// Send unban notice to character
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.YourChatBanRemovedByGameMaster)).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,166 @@
// <copyright file="ClearInventoryChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.ComponentModel.DataAnnotations;
using System.Runtime.InteropServices;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which clears a character's inventory.
/// </summary>
[Guid("1E895A6F-3056-4A78-BA64-96E24363B8BC")]
[PlugIn]
[Display(Name = nameof(PlugInResources.ClearInventoryChatCommandPlugIn_Name), Description = nameof(PlugInResources.ClearInventoryChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, null, MinimumStatus)]
public class ClearInventoryChatCommandPlugIn : ChatCommandPlugInBase<ClearInventoryChatCommandPlugIn.Arguments>, ISupportCustomConfiguration<ClearInventoryChatCommandPlugIn.ClearInventoryConfiguration>, ISupportDefaultCustomConfiguration, IDisabledByDefault
{
private const string Command = "/clearinv";
private const CharacterStatus MinimumStatus = CharacterStatus.Normal;
private const int ConfirmationTimeoutSeconds = 10;
private readonly Dictionary<Guid, DateTime> pendingConfirmations = new();
/// <summary>
/// Gets or sets the configuration.
/// </summary>
public ClearInventoryConfiguration? Configuration { get; set; }
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
public object CreateDefaultConfig() => new ClearInventoryConfiguration();
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
if (player.SelectedCharacter is not { } selectedCharacter)
{
return;
}
var configuration = this.Configuration ??= (ClearInventoryConfiguration)this.CreateDefaultConfig();
bool removeMoney = configuration.MoneyCost > 0;
var targetPlayer = player;
bool isGameMaster = selectedCharacter?.CharacterStatus >= CharacterStatus.GameMaster;
if (isGameMaster)
{
removeMoney = false;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
}
if (targetPlayer.Inventory is null)
{
return;
}
if (!isGameMaster && configuration.RequireConfirmation)
{
var playerId = selectedCharacter!.Id;
if (!this.pendingConfirmations.TryGetValue(playerId, out var confirmationTime) || (DateTime.UtcNow - confirmationTime).TotalSeconds > ConfirmationTimeoutSeconds)
{
this.pendingConfirmations[playerId] = DateTime.UtcNow;
if (configuration.ConfirmationMessage.GetTranslation(player.Culture) is { Length: > 0 } message)
{
await player.ShowBlueMessageAsync(message).ConfigureAwait(false);
}
return;
}
this.pendingConfirmations.Remove(playerId);
}
var itemsToRemove = targetPlayer.Inventory.Items
.Where(item => item is not null &&
(item.ItemSlot < InventoryConstants.FirstEquippableItemSlotIndex ||
item.ItemSlot > InventoryConstants.LastEquippableItemSlotIndex))
.ToList();
if (itemsToRemove.Count == 0)
{
return;
}
if (removeMoney && !player.TryRemoveMoney(configuration.MoneyCost))
{
if (configuration.NotEnoughMoneyMessage.GetTranslation(player.Culture) is { Length: > 0 } notEnoughMoneyMessage)
{
await player.ShowBlueMessageAsync(notEnoughMoneyMessage).ConfigureAwait(false);
}
return;
}
foreach (var item in itemsToRemove)
{
await targetPlayer.DestroyInventoryItemAsync(item).ConfigureAwait(false);
}
if (configuration.InventoryClearedMessage.GetTranslation(player.Culture) is { Length: > 0 } clearedMessage)
{
await player.ShowBlueMessageAsync(clearedMessage).ConfigureAwait(false);
}
}
/// <summary>
/// Arguments for the Clear Inventory chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name to clear inventory for (GM only).
/// </summary>
public string? CharacterName { get; set; }
}
/// <summary>
/// The configuration of a <see cref="ClearInventoryChatCommandPlugIn"/>.
/// </summary>
public class ClearInventoryConfiguration
{
/// <summary>
/// Gets or sets the character name to clear inventory for (GM only).
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ClearInventoryConfiguration_MoneyCost_Name), Description = nameof(PlugInResources.ClearInventoryConfiguration_MoneyCost_Description))]
public int MoneyCost { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the player needs to run the command again within 10 seconds to confirm the inventory clearing (excluding GM).
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ClearInventoryConfiguration_RequireConfirmation_Name), Description = nameof(PlugInResources.ClearInventoryConfiguration_RequireConfirmation_Description))]
public bool RequireConfirmation { get; set; } = true;
/// <summary>
/// Gets or sets the message to show the confirmation message.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ClearInventoryConfiguration_ConfirmationMessage_Name), Description = nameof(PlugInResources.ClearInventoryConfiguration_ConfirmationMessage_Description))]
public LocalizedString ConfirmationMessage { get; set; } = "Confirmation: run again within 10 seconds to confirm inventory clearing";
/// <summary>
/// Gets or sets the message to show when the player does not have enough money to run the command.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ClearInventoryConfiguration_NotEnoughMoneyMessage_Name), Description = nameof(PlugInResources.ClearInventoryConfiguration_NotEnoughMoneyMessage_Description))]
public LocalizedString NotEnoughMoneyMessage { get; set; } = "Not enough money to run command";
/// <summary>
/// Gets or sets the message to show when the inventory is cleared.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ClearInventoryConfiguration_InventoryClearedMessage_Name), Description = nameof(PlugInResources.ClearInventoryConfiguration_InventoryClearedMessage_Description))]
public LocalizedString InventoryClearedMessage { get; set; } = "Inventory cleared";
}
}

View File

@@ -0,0 +1,208 @@
// <copyright file="CommandExtensions.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Globalization;
using System.Reflection;
/// <summary>
/// Extensions to make the process of creating more commands easier.
/// </summary>
public static class CommandExtensions
{
/// <summary>
/// Parse the arguments of a command string.
/// </summary>
/// <param name="command">The command.</param>
/// <param name="player">The player which issued the command.</param>
/// <typeparam name="T">The type.</typeparam>
/// <returns>Returns the parsed object, if successful; Otherwise <see langword="null"/>.</returns>
public static async ValueTask<T?> TryParseArgumentsAsync<T>(this string command, Player? player)
where T : class, new()
{
var instance = new T();
var properties = typeof(T).GetProperties()
.Where(property => property.SetMethod is { })
.ToList();
var arguments = command.Split(' ').Where(x => !x.Contains("/")).ToList();
if (command.Contains('='))
{
// [Short argument parsing]
// If the command string contains = it means it is using the short version
if (await ReadNamedArgumentsAsync(instance, properties, arguments, player).ConfigureAwait(false))
{
return instance;
}
return null;
}
var attributedArguments = properties
.Select(p => p.GetCustomAttribute<ArgumentAttribute>(inherit: true))
.Where(a => a is { })
.Select(a => a!)
.ToList();
var requiredArgumentCount = attributedArguments.Any()
? attributedArguments.Count(a => a.IsRequired)
: arguments.Count;
if (arguments.Count < requiredArgumentCount)
{
if (player is not null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CommandExtensionsInvalidArgumentCount), requiredArgumentCount, arguments.Count).ConfigureAwait(false);
}
return null;
}
var success = true;
for (var i = 0; i < Math.Min(arguments.Count, properties.Count); i++)
{
var property = properties[i];
var argument = arguments[i];
success = await TrySetPropertyValueAsync(instance, property, argument, player).ConfigureAwait(false) && success;
}
return instance;
}
/// <summary>
/// Create the usage string for the command using the argument class.
/// </summary>
/// <param name="argumentsType">Type of the arguments.</param>
/// <param name="commandName">The command name.</param>
/// <returns>
/// The usage string.
/// </returns>
public static string CreateUsage(Type argumentsType, string commandName)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append($"{commandName} ");
foreach (var parameter in GetParameters(argumentsType).ToList())
{
if (!string.IsNullOrWhiteSpace(parameter.ValidValues))
{
stringBuilder.Append($"{{{parameter.Name}:{parameter.ValidValues}}}");
}
else if (parameter.Type == nameof(String))
{
stringBuilder.Append($"{{{parameter.Name}}}");
}
else
{
stringBuilder.Append($"{{{parameter.Name}:{parameter.Type}}}");
}
stringBuilder.Append(" ");
}
stringBuilder.ToString().TrimEnd(' ');
return stringBuilder.ToString();
}
/// <summary>
/// Gets the parameters for an argument class.
/// </summary>
/// <param name="argumentsType">Type of the arguments.</param>
/// <returns>A list of parameters with name, type, and valid values.</returns>
public static IEnumerable<(string Name, string Type, string ValidValues)> GetParameters(Type argumentsType)
{
var properties = argumentsType.GetProperties().Where(p => p.CanWrite);
foreach (var property in properties)
{
string validValues = string.Empty;
if (property.GetCustomAttribute<ValidValuesAttribute>() is { } validValuesAttribute)
{
validValues = string.Join('|', validValuesAttribute.ValidValues);
}
else if (property.PropertyType == typeof(bool))
{
validValues = "0|1";
}
else if (property.PropertyType == typeof(byte) || property.PropertyType == typeof(ushort) || property.PropertyType == typeof(uint))
{
// todo: ranges in ParameterAttribute
// validValues = "";
}
yield return (property.Name, property.PropertyType.Name, validValues);
}
}
private static async ValueTask<bool> ReadNamedArgumentsAsync(object instance, IList<PropertyInfo> properties, IList<string> arguments, Player? player)
{
var argumentProperties = properties.Where(property => property.GetCustomAttribute<ArgumentAttribute>() is { }).ToList();
var requiredProperties = argumentProperties.Where(prop => prop.GetCustomAttribute<ArgumentAttribute>() is { IsRequired: true }).ToList();
foreach (var property in argumentProperties)
{
var attribute = property.GetCustomAttributes<ArgumentAttribute>().First();
var argument = arguments.FirstOrDefault(x => x.Split('=').First().Trim() == attribute.ShortName);
if (argument is null)
{
continue;
}
// Cleans the argument from the short name
var argumentValue = argument.Replace($"{attribute.ShortName}=", string.Empty);
if (!await TrySetPropertyValueAsync(instance, property, argumentValue, player).ConfigureAwait(false))
{
return false;
}
requiredProperties.Remove(property);
}
if (!requiredProperties.Any())
{
return true;
}
if (player is null)
{
return false;
}
// One or many required properties were not used
foreach (var requiredProperty in requiredProperties)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CommandExtensions_RequiredArgumentMissing), requiredProperty.Name).ConfigureAwait(false);
}
return false;
}
private static async ValueTask<bool> TrySetPropertyValueAsync(object instance, PropertyInfo propertyInfo, string stringValue, Player? player)
{
try
{
// Special handling of booleans; we want to allow 0 and 1 as valid values.
if (propertyInfo.PropertyType == typeof(bool) && int.TryParse(stringValue, out var intBool))
{
stringValue = intBool == 1 ? bool.TrueString : bool.FalseString;
}
propertyInfo.SetValue(instance, Convert.ChangeType(stringValue, propertyInfo.PropertyType, CultureInfo.InvariantCulture));
return true;
}
catch
{
if (player is not null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CommandExtensionsArgumentInvalidType), propertyInfo.Name, propertyInfo.PropertyType.Name).ConfigureAwait(false);
}
return false;
}
}
}

View File

@@ -0,0 +1,62 @@
// <copyright file="CreateMonsterChatCommand.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command to create a new monster which can be remote controlled.
/// </summary>
[Guid("BF4DA282-8CFE-4110-B1C5-A01D3F224FAB")]
[PlugIn]
[Display(Name = nameof(PlugInResources.CreateMonsterChatCommand_Name), Description = nameof(PlugInResources.CreateMonsterChatCommand_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(CreateMonsterChatCommandArgs), CharacterStatus.GameMaster)]
internal class CreateMonsterChatCommand : ChatCommandPlugInBase<CreateMonsterChatCommandArgs>
{
private const string Command = "/createmonster";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, CreateMonsterChatCommandArgs arguments)
{
var monsterDef = gameMaster.GameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == arguments.MonsterNumber);
if (monsterDef is null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MonsterNotFoundByNumber), arguments.MonsterNumber).ConfigureAwait(false);
return;
}
var gameMap = gameMaster.CurrentMap;
var area = new MonsterSpawnArea
{
GameMap = gameMap!.Definition,
MonsterDefinition = monsterDef,
SpawnTrigger = SpawnTrigger.OnceAtEventStart,
Quantity = 1,
X1 = (byte)Math.Max(gameMaster.Position.X - 3, byte.MinValue),
X2 = (byte)Math.Min(gameMaster.Position.X + 3, byte.MaxValue),
Y1 = (byte)Math.Max(gameMaster.Position.Y - 3, byte.MinValue),
Y2 = (byte)Math.Min(gameMaster.Position.Y + 3, byte.MaxValue),
};
INpcIntelligence intelligence = arguments.IsIntelligent ? new BasicMonsterIntelligence() : new NullMonsterIntelligence();
var monster = new Monster(area, monsterDef, gameMap, gameMaster.GameContext.DropGenerator, intelligence, gameMaster.GameContext.PlugInManager, gameMaster.GameContext.PathFinderPool);
intelligence.Npc = monster;
monster.Initialize();
await gameMap.AddAsync(monster).ConfigureAwait(false);
monster.OnSpawn();
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MonsterCreatedByGameMaster), arguments.MonsterNumber, monster.Id).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,44 @@
// <copyright file="DisconnectChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles disconnect commands.
/// </summary>
[Guid("B5E0F108-9E55-48F6-A7A8-220BFAEF2F3E")]
[PlugIn]
[Display(Name = nameof(PlugInResources.DisconnectChatCommandPlugIn_Name), Description = nameof(PlugInResources.DisconnectChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(DisconnectChatCommandArgs), CharacterStatus.GameMaster)]
public class DisconnectChatCommandPlugIn : ChatCommandPlugInBase<DisconnectChatCommandArgs>
{
private const string Command = "/disconnect";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, DisconnectChatCommandArgs arguments)
{
var player = await this.GetPlayerByCharacterNameAsync(gameMaster, arguments.CharacterName ?? string.Empty).ConfigureAwait(false);
if (player is null)
{
return;
}
await player.DisconnectAsync().ConfigureAwait(false);
if (!player.Name.Equals(gameMaster.Name))
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CommandResultPlayerDisconnected), this.Key, player.Name).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,62 @@
// <copyright file="GetLevelChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin to get a character's level.
/// </summary>
[Guid("9D5C8FFE-EC32-48AC-8B6F-BB361AD184E5")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GetLevelChatCommandPlugIn_Name), Description = nameof(PlugInResources.GetLevelChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Gets level of a player. Usage: /getlevel (optional:character)", null)]
public class GetLevelChatCommandPlugIn : ChatCommandPlugInBase<GetLevelChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/getlevel";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter is null)
{
return;
}
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.LevelInformation), targetPlayer.SelectedCharacter.Name, targetPlayer.Attributes![Stats.Level]).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Get Level chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name to get level for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,61 @@
// <copyright file="GetLevelUpPointsChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin to get a character's level-up points.
/// </summary>
[Guid("E4D65354-CCD2-4960-BDCA-D4582A57BBCB")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GetLevelUpPointsChatCommandPlugIn_Name), Description = nameof(PlugInResources.GetLevelUpPointsChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Gets level up points of a player. Usage: /getleveluppoints (optional:character)", null)]
public class GetLevelUpPointsChatCommandPlugIn : ChatCommandPlugInBase<GetLevelUpPointsChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/getleveluppoints";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter is null)
{
return;
}
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.LevelUpPointsInfo), targetPlayer.SelectedCharacter.Name, targetPlayer.SelectedCharacter.LevelUpPoints).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Get Level-Up Points chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name to get level-up points for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,65 @@
// <copyright file="GetMasterLevelChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Globalization;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin to get a character's master level.
/// </summary>
[Guid("4CED4BF8-9D91-47F9-82DE-51E2646F77C8")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GetMasterLevelChatCommandPlugIn_Name), Description = nameof(PlugInResources.GetMasterLevelChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Gets master level of a player. Usage: /getmasterlevel (optional:character)", null)]
public class GetMasterLevelChatCommandPlugIn : ChatCommandPlugInBase<GetMasterLevelChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/getmasterlevel";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter is null)
{
return;
}
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MasterLevelInfo), targetPlayer.SelectedCharacter.Name, targetPlayer.Attributes![Stats.MasterLevel]).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Get Master Level chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name to get master level for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,61 @@
// <copyright file="GetMasterLevelUpPointsChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin to get a character's master level-up points.
/// </summary>
[Guid("8ACCF267-F5F3-4003-B4C3-536ACCB5181D")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GetMasterLevelUpPointsChatCommandPlugIn_Name), Description = nameof(PlugInResources.GetMasterLevelUpPointsChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Gets master level up points of a player. Usage: /getmasterleveluppoints (optional:character)", null)]
public class GetMasterLevelUpPointsChatCommandPlugIn : ChatCommandPlugInBase<GetMasterLevelUpPointsChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/getmasterleveluppoints";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter is null)
{
return;
}
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MasterLevelUpPointsInfo), targetPlayer.SelectedCharacter.Name, targetPlayer.SelectedCharacter.MasterLevelUpPoints).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Get Master Level-Up Points chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name to get master level-up points for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,61 @@
// <copyright file="GetMoneyChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin to get a character's money.
/// </summary>
[Guid("207F5872-33AB-4764-B67F-95AB7C6313E3")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GetMoneyChatCommandPlugIn_Name), Description = nameof(PlugInResources.GetMoneyChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Gets money of a player. Usage: /getmoney (optional:character)", null)]
public class GetMoneyChatCommandPlugIn : ChatCommandPlugInBase<GetMoneyChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/getmoney";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter?.Inventory is null)
{
return;
}
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MoneyInfo), targetPlayer.SelectedCharacter.Name, targetPlayer.Money).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Get Money chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name to get money for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,70 @@
// <copyright file="GetResetsChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Resets;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which sets a character's resets.
/// </summary>
[Guid("26ACF6A9-346A-49DF-8583-EA610F6E3AEA")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GetResetsChatCommandPlugIn_Name), Description = nameof(PlugInResources.GetResetsChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Gets resets of a player. Usage: /getresets (optional:character)", null)]
public class GetResetsChatCommandPlugIn : ChatCommandPlugInBase<GetResetsChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/getresets";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var configuration = player.GameContext.FeaturePlugIns.GetPlugIn<ResetFeaturePlugIn>()?.Configuration;
if (configuration is null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ResetSystemInactive)).ConfigureAwait(false);
return;
}
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter is null)
{
return;
}
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ResetsInfo), targetPlayer.SelectedCharacter.Name, targetPlayer.Attributes![Stats.Resets]).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Get Resets chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the character name to get resets for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,67 @@
// <copyright file="GetStatChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the command to get stat points.
/// </summary>
[Guid("F8CACA47-D486-45AE-814F-C6218AD87652")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GetStatChatCommandPlugIn_Name), Description = nameof(PlugInResources.GetStatChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(Arguments), MinimumStatus)]
public class GetStatChatCommandPlugIn : ChatCommandPlugInBase<GetStatChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/get";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (await this.TryGetAttributeAsync(targetPlayer, arguments.StatType).ConfigureAwait(false) is not { } attribute)
{
return;
}
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.StatPointInfo), targetPlayer.SelectedCharacter?.Name, targetPlayer.Attributes![attribute]).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Get Stat chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the stat type to get.
/// </summary>
[ValidValues("str", "agi", "vit", "ene", "cmd")]
public string? StatType { get; set; }
/// <summary>
/// Gets or sets the character name to get stat for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
// <copyright file="GuildBattleSoccerChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles guild battle soccer requests.
/// </summary>
[Guid("A456F032-CE7D-4EA5-8EB2-96C2B04C70D1")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GuildBattleSoccerChatCommandPlugIn_Name), Description = nameof(PlugInResources.GuildBattleSoccerChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(GuildWarChatCommandArgs), CharacterStatus.Normal)]
public class GuildBattleSoccerChatCommandPlugIn : ChatCommandPlugInBase<GuildWarChatCommandArgs>
{
private const string Command = "/battlesoccer";
private readonly GuildWarRequestAction _action = new();
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player guildMaster, GuildWarChatCommandArgs arguments)
{
await this._action.RequestBattleSoccerAsync(guildMaster, arguments.GuildName).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,47 @@
// <copyright file="GuildDisconnectChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles gm disconnect commands.
/// </summary>
[Guid("F23262E6-0D7C-4B9C-8CD5-7E44AF4EE469")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GuildDisconnectChatCommandPlugIn_Name), Description = nameof(PlugInResources.GuildDisconnectChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(GuildDisconnectChatCommandArgs), CharacterStatus.GameMaster)]
public class GuildDisconnectChatCommandPlugIn : ChatCommandPlugInBase<GuildDisconnectChatCommandArgs>
{
private const string Command = "/guilddisconnect";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, GuildDisconnectChatCommandArgs arguments)
{
var guildId = await this.GetGuildIdByNameAsync(gameMaster, arguments.GuildName!).ConfigureAwait(false);
if (guildId is null || gameMaster.GameContext is not IGameServerContext gameServerContext)
{
return;
}
await gameServerContext.ForEachGuildPlayerAsync(guildId.Value, async guildPlayer =>
{
await guildPlayer.DisconnectAsync().ConfigureAwait(false);
if (!guildPlayer.Name.Equals(gameMaster.Name))
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.GuildDisconnectResult), this.Key, guildPlayer.Name).ConfigureAwait(false);
}
}).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,55 @@
// <copyright file="GuildMoveChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles gm move commands.
/// </summary>
[Guid("9163C3EA-6722-4E55-A109-20C163C05266")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GuildMoveChatCommandPlugIn_Name), Description = nameof(PlugInResources.GuildMoveChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(GuildMoveChatCommandArgs), CharacterStatus.GameMaster)]
public class GuildMoveChatCommandPlugIn : ChatCommandPlugInBase<GuildMoveChatCommandArgs>
{
private const string Command = "/guildmove";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, GuildMoveChatCommandArgs arguments)
{
var guildId = await this.GetGuildIdByNameAsync(gameMaster, arguments.GuildName!).ConfigureAwait(false);
if (guildId is null || gameMaster.GameContext is not IGameServerContext gameServerContext)
{
return;
}
var exitGate = await this.GetExitGateAsync(gameMaster, arguments.MapIdOrName!, arguments.Coordinates).ConfigureAwait(false);
if (exitGate is null)
{
return;
}
await gameServerContext.ForEachGuildPlayerAsync(guildId.Value, async guildPlayer =>
{
await guildPlayer.WarpToAsync(exitGate).ConfigureAwait(false);
if (!guildPlayer.Name.Equals(gameMaster.Name))
{
await guildPlayer.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MovedByGameMaster)).ConfigureAwait(false);
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MovedPlayerResult), this.Key, guildPlayer.Name, exitGate!.Map!.Name.GetTranslation(gameMaster.Culture), guildPlayer.Position.X, guildPlayer.Position.Y).ConfigureAwait(false);
}
}).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,36 @@
// <copyright file="GuildWarChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles guild war requests.
/// </summary>
[Guid("12A6E159-0D5E-44DE-8CF8-012A7278D42C")]
[PlugIn]
[Display(Name = nameof(PlugInResources.GuildWarChatCommandPlugIn_Name), Description = nameof(PlugInResources.GuildWarChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(GuildWarChatCommandArgs), CharacterStatus.Normal)]
public class GuildWarChatCommandPlugIn : ChatCommandPlugInBase<GuildWarChatCommandArgs>
{
private const string Command = "/war";
private readonly GuildWarRequestAction _action = new();
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player guildMaster, GuildWarChatCommandArgs arguments)
{
await this._action.RequestWarAsync(guildMaster, arguments.GuildName).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,59 @@
// <copyright file="HelpCommand.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The help command which shows the usage of a command.
/// </summary>
[Guid("EFE9399A-9A14-4B94-BBC1-20718584C4C2")]
[PlugIn]
[Display(Name = nameof(PlugInResources.HelpCommand_Name), Description = nameof(PlugInResources.HelpCommand_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Shows information about the requested command.", typeof(Arguments))]
public class HelpCommand : IChatCommandPlugIn
{
private const string Command = "/help";
/// <inheritdoc />
public string Key => Command;
/// <inheritdoc />
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
try
{
if (await command.TryParseArgumentsAsync<Arguments>(player).ConfigureAwait(false) is not { } arguments)
{
return;
}
var commandName = arguments.CommandName;
var commandPluginAttribute = player.GetAvailableChatCommands()
.FirstOrDefault(x => x.Command.Equals("/" + commandName, StringComparison.InvariantCultureIgnoreCase));
if (commandPluginAttribute is null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CommandDoesNotExist), commandName ?? string.Empty).ConfigureAwait(false);
return;
}
await player.ShowBlueMessageAsync(commandPluginAttribute.Usage).ConfigureAwait(false);
}
catch (ArgumentException e)
{
// Should not happen, as we don't throw them anymore. But just in case...
await player.ShowBlueMessageAsync(e.Message).ConfigureAwait(false);
}
}
private class Arguments
{
public string? CommandName { get; set; }
}
}

View File

@@ -0,0 +1,32 @@
// <copyright file="HideChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles hide commands.
/// </summary>
[Guid("7CE1CA66-C6B1-4840-9997-EF15C49FAB49")]
[PlugIn]
[Display(Name = nameof(PlugInResources.HideChatCommandPlugIn_Name), Description = nameof(PlugInResources.HideChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, CharacterStatus.GameMaster)]
public class HideChatCommandPlugIn : IChatCommandPlugIn
{
private const string Command = "/hide";
/// <inheritdoc />
public string Key => Command;
/// <inheritdoc/>
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
await player.AddInvisibleEffectAsync().ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,28 @@
// <copyright file="IChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A plugin interface for chat commands.
/// </summary>
[Guid("6CABB847-AE91-4F2B-9FD2-296990950EA3")]
[PlugInPoint("Chat commands", "Plugins which will be executed when a chat message arrives with a slash prefix.")]
public interface IChatCommandPlugIn : IStrategyPlugIn<string>
{
/// <summary>
/// Gets min Status Requirement to run a command.
/// </summary>
public CharacterStatus MinCharacterStatusRequirement { get; }
/// <summary>
/// Handles the chat command.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="command">The command.</param>
ValueTask HandleCommandAsync(Player player, string command);
}

View File

@@ -0,0 +1,178 @@
// <copyright file="ItemChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles gm item command.
/// </summary>
/// <seealso cref="MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.IChatCommandPlugIn" />
[Guid("ABFE2440-E765-4F17-A588-BD9AE3799887")]
[PlugIn]
[Display(Name = nameof(PlugInResources.ItemChatCommandPlugIn_Name), Description = nameof(PlugInResources.ItemChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(ItemChatCommandArgs), CharacterStatus.GameMaster)]
public class ItemChatCommandPlugIn : ChatCommandPlugInBase<ItemChatCommandArgs>
{
private const string Command = "/item";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, ItemChatCommandArgs arguments)
{
if (gameMaster.CurrentMap != null)
{
var (isValid, itemDefinition) = await TryParseArgumentsAsync(gameMaster, arguments).ConfigureAwait(false);
if (!isValid)
{
return;
}
var item = CreateItem(itemDefinition!, arguments);
var dropCoordinates = gameMaster.CurrentMap.Terrain.GetRandomCoordinate(gameMaster.Position, 1);
var droppedItem = new DroppedItem(item, dropCoordinates, gameMaster.CurrentMap, gameMaster);
await gameMaster.CurrentMap.AddAsync(droppedItem).ConfigureAwait(false);
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ItemCreatedResult), this.Key, item).ConfigureAwait(false);
}
}
private static async ValueTask<(bool Success, ItemDefinition? Definition)> TryParseArgumentsAsync(Player gameMaster, ItemChatCommandArgs arguments)
{
var itemDefinition = gameMaster.GameContext.Configuration.Items
.FirstOrDefault(def => def.Group == arguments.Group && def.Number == arguments.Number);
if (itemDefinition is null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ItemGroupNumberNotExists), arguments.Group, arguments.Number).ConfigureAwait(false);
return (false, null);
}
if (arguments.Level > itemDefinition.MaximumItemLevel)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ItemLevelExceeded), itemDefinition.MaximumItemLevel).ConfigureAwait(false);
return (false, null);
}
return (true, itemDefinition);
}
private static Item CreateItem(DataModel.Configuration.Items.ItemDefinition itemDefinition, ItemChatCommandArgs arguments)
{
var item = new TemporaryItem();
item.Definition = itemDefinition;
item.Durability = item.IsStackable() ? 1 : item.Definition.Durability;
item.HasSkill = item.Definition.Skill != null && arguments.Skill;
item.Level = arguments.Level;
item.SocketCount = item.Definition.MaximumSockets;
AddOption(item, arguments);
AddLuckOption(item, arguments);
AddExcellentOptions(item, arguments);
AddAncientBonusOption(item, arguments);
return item;
}
private static void AddOption(TemporaryItem item, ItemChatCommandArgs arguments)
{
if (item.Definition != null && arguments.Opt > 0)
{
var allOptions = item.Definition.PossibleItemOptions
.SelectMany(o => o.PossibleOptions)
.Where(o => o.OptionType == ItemOptionTypes.Option);
IncreasableItemOption itemOption;
// Dinorant.
if (item.Definition.Skill?.Number == 49)
{
if ((arguments.Opt & 1) > 0)
{
itemOption = allOptions.First(o => o.PowerUpDefinition!.TargetAttribute == Stats.DamageReceiveDecrement);
var dinoOptionLink = new ItemOptionLink { ItemOption = itemOption, Level = 1 };
item.ItemOptions.Add(dinoOptionLink);
}
if ((arguments.Opt & 2) > 0)
{
itemOption = allOptions.First(o => o.PowerUpDefinition!.TargetAttribute == Stats.MaximumAbility);
var dinoOptionLink = new ItemOptionLink { ItemOption = itemOption, Level = 2 };
item.ItemOptions.Add(dinoOptionLink);
}
if ((arguments.Opt & 4) > 0)
{
itemOption = allOptions.First(o => o.PowerUpDefinition!.TargetAttribute == Stats.AttackSpeedAny);
var dinoOptionLink = new ItemOptionLink { ItemOption = itemOption, Level = 4 };
item.ItemOptions.Add(dinoOptionLink);
}
}
else
{
itemOption = allOptions.First();
var level = arguments.Opt;
var optionLink = new ItemOptionLink { ItemOption = itemOption, Level = level };
item.ItemOptions.Add(optionLink);
}
}
}
private static void AddLuckOption(TemporaryItem item, ItemChatCommandArgs arguments)
{
if (item.Definition != null && arguments.Luck)
{
var optionLink = new ItemOptionLink
{
ItemOption = item.Definition.PossibleItemOptions
.SelectMany(o => o.PossibleOptions)
.First(o => o.OptionType == ItemOptionTypes.Luck),
};
item.ItemOptions.Add(optionLink);
}
}
private static void AddExcellentOptions(TemporaryItem item, ItemChatCommandArgs arguments)
{
if (item.Definition != null && arguments.ExcellentNumber > 0)
{
var excellentOptions = item.Definition.PossibleItemOptions
.SelectMany(o => o.PossibleOptions)
.Where(o => o.OptionType == ItemOptionTypes.Excellent)
.Where(o => ((1 << (o.Number - 1)) & arguments.ExcellentNumber) > 0)
.ToList();
ushort appliedOptions = 0;
foreach (var excellentOption in excellentOptions)
{
var optionLink = new ItemOptionLink { ItemOption = excellentOption };
item.ItemOptions.Add(optionLink);
appliedOptions++;
}
// every excellent item has skill (if is in item definition)
item.HasSkill = appliedOptions > 0 && item.Definition.Skill != null;
}
}
private static void AddAncientBonusOption(TemporaryItem item, ItemChatCommandArgs arguments)
{
if (item.Definition != null && arguments.Ancient > 0
&& item.Definition.PossibleItemSetGroups.FirstOrDefault(g => g.Items.Any(i => i.ItemDefinition == item.Definition && i.AncientSetDiscriminator == arguments.Ancient)) is { } ancientSet
&& ancientSet.Items.FirstOrDefault(i => i.ItemDefinition == item.Definition) is { } itemOfItemSet)
{
var optionLink = new ItemOptionLink { ItemOption = itemOfItemSet.BonusOption, Level = arguments.AncientBonusLevel };
item.ItemOptions.Add(optionLink);
item.ItemSetGroups.Add(itemOfItemSet);
}
}
}

View File

@@ -0,0 +1,37 @@
// <copyright file="ListCommand.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A command which lists all available chat commands with their usage.
/// </summary>
[Guid("a5b0a3e5-bb2a-4287-821a-cd97714fe209")]
[PlugIn]
[Display(Name = nameof(PlugInResources.ListCommand_Name), Description = nameof(PlugInResources.ListCommand_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Lists all the commands.", null)]
public class ListCommand : IChatCommandPlugIn
{
private const string Command = "/list";
/// <inheritdoc />
public string Key => Command;
/// <inheritdoc />
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
var commands = player.GetAvailableChatCommands();
foreach (var commandUsage in commands.Select(x => x.Usage))
{
await player.ShowBlueMessageAsync(commandUsage).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,61 @@
// <copyright file="MoveChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlayerActions;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles move commands.
/// </summary>
[Guid("4564AE2B-4819-4155-B5B2-FE2ED0CF7A7F")]
[PlugIn]
[Display(Name = nameof(PlugInResources.MoveChatCommandPlugIn_Name), Description = nameof(PlugInResources.MoveChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(MoveChatCommandArgs), CharacterStatus.Normal)]
public class MoveChatCommandPlugIn : ChatCommandPlugInBase<MoveChatCommandArgs>
{
private const string Command = "/move";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player sender, MoveChatCommandArgs arguments)
{
var senderIsGameMaster = sender.SelectedCharacter?.CharacterStatus == CharacterStatus.GameMaster;
var isGameMasterWarpingCharacter = senderIsGameMaster && !string.IsNullOrWhiteSpace(arguments.MapIdOrName);
if (isGameMasterWarpingCharacter)
{
var targetPlayer = await this.GetPlayerByCharacterNameAsync(sender, arguments.Target!).ConfigureAwait(false);
var exitGate = await this.GetExitGateAsync(sender, arguments.MapIdOrName!, arguments.Coordinates).ConfigureAwait(false);
if (targetPlayer is null || exitGate is null)
{
return;
}
await targetPlayer.WarpToAsync(exitGate).ConfigureAwait(false);
if (!targetPlayer.Name.Equals(sender.Name))
{
await targetPlayer.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MovedByGameMaster)).ConfigureAwait(false);
await sender.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MovedPlayerResult), this.Key, targetPlayer.Name, exitGate!.Map!.Name.GetTranslation(sender.Culture), targetPlayer.Position.X, targetPlayer.Position.Y).ConfigureAwait(false);
}
}
else
{
var warpInfo = this.GetWarpInfo(sender, arguments.Target!);
if (warpInfo != null)
{
await new WarpAction().WarpToAsync(sender, warpInfo).ConfigureAwait(false);
}
}
}
}

View File

@@ -0,0 +1,41 @@
// <copyright file="MoveMonsterChatCommand.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Chat command to instantly move a monster to specific coordinates.
/// </summary>
[Guid("B3DE58F3-B604-4F59-9122-E686AD90BE7B")]
[PlugIn]
[Display(Name = nameof(PlugInResources.MoveMonsterChatCommand_Name), Description = nameof(PlugInResources.MoveMonsterChatCommand_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(MoveMonsterCommandArgs), CharacterStatus.GameMaster)]
internal class MoveMonsterChatCommand : ChatCommandPlugInBase<MoveMonsterCommandArgs>
{
private const string Command = "/movemonster";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, MoveMonsterCommandArgs arguments)
{
var monster = gameMaster.ObservingBuckets.SelectMany(b => b).OfType<Monster>().FirstOrDefault(m => m.Id == arguments.Id);
if (monster is null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MonsterNotFoundById), arguments.Id).ConfigureAwait(false);
return;
}
await monster.MoveAsync(arguments.Coordinates).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,40 @@
// <copyright file="NoticeChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles post commands by sending a golden notice message to all players.
/// </summary>
[Guid("2BFC9464-4B76-4D76-8CE1-69B712B65E6C")]
[PlugIn]
[Display(Name = nameof(PlugInResources.NoticeChatCommandPlugIn_Name), Description = nameof(PlugInResources.NoticeChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class NoticeChatCommandPlugIn : IChatCommandPlugIn
{
private const string CommandKey = "/goldnotice";
/// <inheritdoc />
public string Key => CommandKey;
/// <inheritdoc />
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
var regex = new Regex(Regex.Escape(CommandKey));
var message = regex.Replace(command, string.Empty, 1)?.Trim();
if (string.IsNullOrWhiteSpace(message))
{
return;
}
await player.GameContext.SendGlobalMessageAsync(message, Interfaces.MessageType.GoldenCenter).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,132 @@
// <copyright file="NpcChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.ComponentModel.DataAnnotations;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.GameLogic.PlayerActions;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which opens NPC windows.
/// </summary>
[Guid("D8AC2F15-AB30-4432-A042-A41ACA1B274D")]
[PlugIn]
[Display(Name = nameof(PlugInResources.NpcChatCommandPlugIn_Name), Description = nameof(PlugInResources.NpcChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Opens the NPC store.", null)]
public class NpcChatCommandPlugIn : ChatCommandPlugInBase<NpcChatCommandPlugIn.Arguments>, ISupportCustomConfiguration<NpcChatCommandPlugIn.NpcChatCommandConfiguration>, ISupportDefaultCustomConfiguration, IDisabledByDefault
{
private const string Command = "/npc";
private const CharacterStatus MinimumStatus = CharacterStatus.Normal;
private readonly TalkNpcAction _talkNpcAction = new();
/// <summary>
/// Gets or sets the configuration.
/// </summary>
public NpcChatCommandConfiguration? Configuration { get; set; }
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
public object CreateDefaultConfig() => new NpcChatCommandConfiguration();
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
if (player.CurrentMap is not { } currentMap)
{
return;
}
var configuration = this.Configuration ??= (NpcChatCommandConfiguration)this.CreateDefaultConfig();
var npcDefinition = configuration.OpenMerchantNpc;
if (player.SelectedCharacter?.CharacterStatus >= CharacterStatus.GameMaster && arguments?.NpcId is { } npcIdStr)
{
if (int.TryParse(npcIdStr, out var npcId) && player.GameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == npcId) is { } definition)
{
npcDefinition = definition;
}
else
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidMerchantId), npcIdStr).ConfigureAwait(false);
return;
}
}
if (npcDefinition is null)
{
return;
}
if (npcDefinition.MerchantStore is null)
{
if (player.SelectedCharacter?.CharacterStatus >= CharacterStatus.GameMaster)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.NpcIsNotMerchant)).ConfigureAwait(false);
}
return;
}
if (configuration.MinimumVipLevel > 0 && (player.Attributes?[Stats.IsVip] ?? 0) < configuration.MinimumVipLevel)
{
if (configuration.InsufficientVipLevelMessage.GetTranslation(player.Culture) is { Length: > 0 } message)
{
await player.ShowBlueMessageAsync(message).ConfigureAwait(false);
}
return;
}
var npc = new NonPlayerCharacter(new MonsterSpawnArea { MonsterDefinition = npcDefinition }, npcDefinition, currentMap);
await this._talkNpcAction.TalkToNpcAsync(player, npc).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the NPC chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the NPC ID to open the merchant store for (GM only).
/// </summary>
public string? NpcId { get; set; }
}
/// <summary>
/// The configuration of a <see cref="NpcChatCommandPlugIn"/>.
/// </summary>
public class NpcChatCommandConfiguration
{
/// <summary>
/// Gets or sets the NPC ID of the NPC to open the merchant store.
/// </summary>
// TODO: Change to a list of possible NPCs merchants
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.NpcChatCommandConfiguration_OpenMerchantNpc_Name), Description = nameof(PlugInResources.NpcChatCommandConfiguration_OpenMerchantNpc_Description))]
public MonsterDefinition? OpenMerchantNpc { get; set; }
/// <summary>
/// Gets or sets the minimum VIP level to use the command.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.NpcChatCommandConfiguration_MinimumVipLevel_Name), Description = nameof(PlugInResources.NpcChatCommandConfiguration_MinimumVipLevel_Description))]
public int MinimumVipLevel { get; set; }
/// <summary>
/// Gets or sets the message to show when the player does not have the required VIP level for this command (excluding GM).
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.NpcChatCommandConfiguration_InsufficientVipLevelMessage_Name), Description = nameof(PlugInResources.NpcChatCommandConfiguration_InsufficientVipLevelMessage_Description))]
public LocalizedString InsufficientVipLevelMessage { get; set; } = "Insufficient VIP level to use this command";
}
}

View File

@@ -0,0 +1,87 @@
// <copyright file="OfflineLevelingChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.MuHelper;
using MUnique.OpenMU.GameLogic.Offline;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Handles the <c>/offlevel</c> chat command.
/// <list type="bullet">
/// <item>Logs the login-server entry off so the real player can re-connect at any time.</item>
/// <item>Disconnects the real client connection.</item>
/// <item>Creates a silent ghost player on the current map using the character's MU Helper config.</item>
/// <item>On next login the ghost is automatically stopped before character selection.</item>
/// </list>
/// </summary>
[Guid("A1C4E7F2-3B8D-4A09-8E5C-2D6F0B3A7E14")]
[PlugIn]
[Display(
Name = nameof(PlugInResources.OfflineLevelingChatCommandPlugIn_Name),
Description = nameof(PlugInResources.OfflineLevelingChatCommandPlugIn_Description),
ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, CharacterStatus.Normal)]
public sealed class OfflineLevelingChatCommandPlugIn : IChatCommandPlugIn
{
private const string Command = "/offlevel";
/// <inheritdoc />
public string Key => Command;
/// <inheritdoc />
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
if (player.SelectedCharacter is null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.OfflineLevelingNoCharacterSelected)).ConfigureAwait(false);
return;
}
if (!player.IsAlive)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.OfflineLevelingMustBeAlive)).ConfigureAwait(false);
return;
}
if (player.CurrentMap is null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.OfflineLevelingNotOnMap)).ConfigureAwait(false);
return;
}
if (player.Attributes?[Stats.IsMuHelperActive] <= 0)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.OfflineLevelingMuHelperNotRunning)).ConfigureAwait(false);
return;
}
var loginName = player.Account?.LoginName;
if (loginName is null)
{
return;
}
var manager = player.GameContext.OfflinePlayerManager;
if (manager.IsActive(loginName))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.OfflineLevelingAlreadyActive)).ConfigureAwait(false);
return;
}
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.OfflineLevelingStarted)).ConfigureAwait(false);
if (!await manager.StartAsync(player, loginName).ConfigureAwait(false))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.OfflineLevelingFailed)).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,51 @@
// <copyright file="OnlineChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using System.Threading;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles online commands.
/// </summary>
[Guid("6693ABA3-7B35-4800-815B-096F3420E998")]
[PlugIn]
[Display(Name = nameof(PlugInResources.OnlineChatCommandPlugIn_Name), Description = nameof(PlugInResources.OnlineChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(EmptyChatCommandArgs), CharacterStatus.GameMaster)]
public class OnlineChatCommandPlugIn : ChatCommandPlugInBase<EmptyChatCommandArgs>, IChatCommandPlugIn
{
private const string Command = "/online";
/// <inheritdoc/>
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc/>
protected override async ValueTask DoHandleCommandAsync(Player gameMasterPlayer, EmptyChatCommandArgs arguments)
{
var totalCharactersCount = 0;
var totalGameMastersCount = 0;
await gameMasterPlayer.GameContext.ForEachPlayerAsync(player =>
{
switch (player.SelectedCharacter?.CharacterStatus)
{
case CharacterStatus.Normal:
Interlocked.Increment(ref totalCharactersCount);
break;
case CharacterStatus.GameMaster:
Interlocked.Increment(ref totalGameMastersCount);
break;
}
return Task.CompletedTask;
}).ConfigureAwait(false);
await gameMasterPlayer.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.OnlineCountInfo), this.Key, totalGameMastersCount, totalCharactersCount).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,102 @@
// <copyright file="OpenWarehouseChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.ComponentModel.DataAnnotations;
using System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.GameLogic.PlayerActions;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which opens the warehouse NPC window.
/// </summary>
[Guid("62027B6B-D8E7-4DDB-A16B-7070D1BC4A56")]
[PlugIn]
[Display(Name = nameof(PlugInResources.OpenWarehouseChatCommandPlugIn_Name), Description = nameof(PlugInResources.OpenWarehouseChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Opens the warehouse.", null)]
public class OpenWarehouseChatCommandPlugIn : ChatCommandPlugInBase<OpenWarehouseChatCommandPlugIn.Arguments>, ISupportCustomConfiguration<OpenWarehouseChatCommandPlugIn.OpenWarehouseChatCommandConfiguration>, ISupportDefaultCustomConfiguration, IDisabledByDefault
{
private const string Command = "/openware";
private const CharacterStatus MinimumStatus = CharacterStatus.Normal;
private readonly TalkNpcAction _talkNpcAction = new();
/// <summary>
/// Gets or sets the configuration.
/// </summary>
public OpenWarehouseChatCommandConfiguration? Configuration { get; set; }
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
public object CreateDefaultConfig() => new OpenWarehouseChatCommandConfiguration();
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
if (player.CurrentMap is not { } currentMap)
{
return;
}
var configuration = this.Configuration ??= (OpenWarehouseChatCommandConfiguration)this.CreateDefaultConfig();
if (configuration.MinimumVipLevel > 0 && (player.Attributes?[Stats.IsVip] ?? 0) < configuration.MinimumVipLevel)
{
if (configuration.InsufficientVipLevelMessage.GetTranslation(player.Culture) is { Length: > 0 } message)
{
await player.ShowBlueMessageAsync(message).ConfigureAwait(false);
}
return;
}
if (player.GameContext.Configuration.Monsters.FirstOrDefault(m => m.NpcWindow == NpcWindow.VaultStorage) is not { } definition)
{
if (player.SelectedCharacter?.CharacterStatus >= CharacterStatus.GameMaster)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.NoWarehouseNpcFound)).ConfigureAwait(false);
}
return;
}
var npc = new NonPlayerCharacter(new MonsterSpawnArea { MonsterDefinition = definition }, definition, currentMap);
await this._talkNpcAction.TalkToNpcAsync(player, npc).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Open Warehouse chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
}
/// <summary>
/// The configuration of a <see cref="OpenWarehouseChatCommandPlugIn"/>.
/// </summary>
public class OpenWarehouseChatCommandConfiguration
{
/// <summary>
/// Gets or sets the minimum VIP level to use the command (excluding GM).
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.OpenWarehouseChatCommandConfiguration_MinimumVipLevel_Name), Description = nameof(PlugInResources.OpenWarehouseChatCommandConfiguration_MinimumVipLevel_Description))]
public int MinimumVipLevel { get; set; }
/// <summary>
/// Gets or sets the message to show when the player does not have the required VIP level for this command (excluding GM).
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.OpenWarehouseChatCommandConfiguration_InsufficientVipLevelMessage_Name), Description = nameof(PlugInResources.OpenWarehouseChatCommandConfiguration_InsufficientVipLevelMessage_Description))]
public LocalizedString InsufficientVipLevelMessage { get; set; } = "Insufficient VIP level to use this command";
}
}

View File

@@ -0,0 +1,67 @@
// <copyright file="PKChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles pk commands.
/// </summary>
[Guid("30B7EFF0-33EE-4136-BEB0-BE503B748DC6")]
[PlugIn]
[Display(Name = nameof(PlugInResources.PkChatCommandPlugIn_Name), Description = nameof(PlugInResources.PkChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(PkChatCommandArgs), CharacterStatus.GameMaster)]
public class PkChatCommandPlugIn : ChatCommandPlugInBase<PkChatCommandArgs>
{
private const string Command = "/pk";
private const int MinPkLevel = HeroState.PlayerKillWarning - HeroState.Normal;
private const int MaxPkLevel = HeroState.PlayerKiller2ndStage - HeroState.Normal;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, PkChatCommandArgs arguments)
{
if (arguments.Level < MinPkLevel || arguments.Level > MaxPkLevel)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PlayerKillLevelRangeError), MinPkLevel, MaxPkLevel).ConfigureAwait(false);
return;
}
if (arguments.Count <= 0)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PlayerKillCountMustBePositive)).ConfigureAwait(false);
return;
}
var targetPlayer = await this.GetPlayerByCharacterNameAsync(gameMaster, arguments.CharacterName ?? string.Empty).ConfigureAwait(false);
var character = targetPlayer?.SelectedCharacter;
if (character is null)
{
// logged out in the mean time ...
return;
}
character.State = HeroState.Normal + arguments.Level;
character.StateRemainingSeconds = (int)TimeSpan.FromHours(arguments.Count).TotalSeconds;
character.PlayerKillCount = arguments.Count;
await targetPlayer!.ForEachWorldObserverAsync<IUpdateCharacterHeroStatePlugIn>(p => p.UpdateCharacterHeroStateAsync(targetPlayer!), true).ConfigureAwait(false);
await gameMaster.ShowLocalizedBlueMessageAsync(
nameof(PlayerMessage.PlayerKillStateChangeResult),
this.Key,
character.Name,
character.State,
character.PlayerKillCount,
Math.Round(TimeSpan.FromSeconds(character!.StateRemainingSeconds).TotalMinutes)).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,141 @@
// <copyright file="PKClearChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.ComponentModel.DataAnnotations;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles pk clear commands.
/// </summary>
[Guid("EB97A8F6-F6BD-460A-BCBE-253BF679361A")]
[PlugIn]
[Display(Name = nameof(PlugInResources.PkClearChatCommandPlugIn_Name), Description = nameof(PlugInResources.PkClearChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(PkClearChatCommandArgs), CharacterStatus.Normal)]
public class PkClearChatCommandPlugIn : ChatCommandPlugInBase<PkClearChatCommandArgs>, ISupportCustomConfiguration<PkClearChatCommandPlugIn.PKClearConfiguration>, ISupportDefaultCustomConfiguration
{
private const string Command = "/pkclear";
/// <summary>
/// Gets or sets the configuration.
/// </summary>
public PKClearConfiguration? Configuration { get; set; }
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
/// <inheritdoc />
public object CreateDefaultConfig() => new PKClearConfiguration();
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, PkClearChatCommandArgs arguments)
{
var selectedCharacter = player.SelectedCharacter;
if (selectedCharacter is null)
{
return;
}
var configuration = this.Configuration ?? (PKClearConfiguration)this.CreateDefaultConfig();
var isGameMaster = selectedCharacter.CharacterStatus >= CharacterStatus.GameMaster;
Player? targetPlayer;
if (isGameMaster)
{
targetPlayer = await this.GetPlayerByCharacterNameAsync(player, arguments.CharacterName ?? string.Empty).ConfigureAwait(false);
if (targetPlayer is null)
{
return;
}
}
else
{
if (!configuration.AllowRegularPlayers)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PkClearCommandOnlyForGameMasters)).ConfigureAwait(false);
return;
}
if (!string.IsNullOrEmpty(arguments.CharacterName) && !arguments.CharacterName.Equals(player.Name, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PkClearOnlyClearOwnPk)).ConfigureAwait(false);
return;
}
targetPlayer = player;
}
var targetCharacter = targetPlayer.SelectedCharacter;
if (targetCharacter is null)
{
return;
}
if (targetCharacter.PlayerKillCount == 0 && targetCharacter.State < HeroState.PlayerKillWarning)
{
if (isGameMaster)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PkClearTargetNotPlayerKiller), targetCharacter.Name).ConfigureAwait(false);
}
else
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PkClearYouAreNotPlayerKiller)).ConfigureAwait(false);
}
return;
}
if (!isGameMaster)
{
var cost = (long)targetCharacter.PlayerKillCount * configuration.ZenCostPerKill;
if (cost > int.MaxValue)
{
cost = int.MaxValue;
}
if (cost > 0 && !player.TryRemoveMoney((int)cost))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.NotEnoughMoney)).ConfigureAwait(false);
return;
}
}
targetCharacter.State = HeroState.Normal;
targetCharacter.StateRemainingSeconds = 0;
targetCharacter.PlayerKillCount = 0;
await targetPlayer.ForEachWorldObserverAsync<IUpdateCharacterHeroStatePlugIn>(p => p.UpdateCharacterHeroStateAsync(targetPlayer), true).ConfigureAwait(false);
if (!targetPlayer.Name.Equals(player.Name))
{
await targetPlayer.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PkStatusClearedByGameMaster)).ConfigureAwait(false);
}
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.PkClearedResult), this.Key, targetPlayer.Name).ConfigureAwait(false);
}
/// <summary>
/// Configuration for the <see cref="PkClearChatCommandPlugIn"/>.
/// </summary>
public class PKClearConfiguration
{
/// <summary>
/// Gets or sets the Zen cost per kill.
/// </summary>
[Display(Name = "Zen Cost Per Kill", Description = "The amount of Zen required to clear one PK count.")]
public int ZenCostPerKill { get; set; } = 10_000_000;
/// <summary>
/// Gets or sets a value indicating whether regular players are allowed to clear their PK status.
/// </summary>
[Display(Name = "Allow Regular Players", Description = "Allows regular players to use this command to clear their own PK status for Zen.")]
public bool AllowRegularPlayers { get; set; } = true;
}
}

View File

@@ -0,0 +1,42 @@
// <copyright file="PostChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles post commands by sending a blue system message to all players.
/// </summary>
[Guid("ED2523C1-F66D-4B53-814E-D2FC0C1F46C0")]
[PlugIn]
[Display(Name = nameof(PlugInResources.PostChatCommandPlugIn_Name), Description = nameof(PlugInResources.PostChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class PostChatCommandPlugIn : IChatCommandPlugIn
{
private const string CommandKey = "/post";
/// <inheritdoc />
public string Key => CommandKey;
/// <inheritdoc />
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
var regex = new Regex(Regex.Escape(CommandKey));
var message = regex.Replace(command, string.Empty, 1)?.Trim();
if (string.IsNullOrWhiteSpace(message))
{
return;
}
message = $"{player.SelectedCharacter?.Name}: {message}";
await player.GameContext.SendGlobalChatMessageAsync("[POST]", message, ChatMessageType.Gens).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,42 @@
// <copyright file="RemoveNpcChatCommand.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Chat command to remove a npc with a specific id.
/// </summary>
[Guid("34FAAD0A-FCA4-42E2-8F37-CEF48783BD78")]
[PlugIn]
[Display(Name = nameof(PlugInResources.RemoveNpcChatCommand_Name), Description = nameof(PlugInResources.RemoveNpcChatCommand_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(IdCommandArgs), CharacterStatus.GameMaster)]
internal class RemoveNpcChatCommand : ChatCommandPlugInBase<IdCommandArgs>
{
private const string Command = "/removenpc";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, IdCommandArgs arguments)
{
var monster = gameMaster.ObservingBuckets.SelectMany(b => b).OfType<NonPlayerCharacter>().FirstOrDefault(m => m.Id == arguments.Id);
if (monster is null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.NpcNotFoundById), arguments.Id).ConfigureAwait(false);
return;
}
monster.Dispose();
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.NpcRemovedById), arguments.Id).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,77 @@
// <copyright file="SetLevelChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which sets a character's level.
/// </summary>
[Guid("4BE779C9-E6B6-47F2-BC23-2E71D82A6C1D")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SetLevelChatCommandPlugIn_Name), Description = nameof(PlugInResources.SetLevelChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Sets level of a player. Usage: /setlevel (level) (optional:character)", null)]
public class SetLevelChatCommandPlugIn : ChatCommandPlugInBase<SetLevelChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/setlevel";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter is null)
{
return;
}
if (arguments is null || arguments.Level < 1 || arguments.Level > targetPlayer.GameContext.Configuration.MaximumLevel)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidCharacterLevel), targetPlayer.GameContext.Configuration.MaximumLevel).ConfigureAwait(false);
return;
}
targetPlayer.Attributes![Stats.Level] = checked(arguments.Level);
await targetPlayer.InvokeViewPlugInAsync<IUpdateLevelPlugIn>(p => p.UpdateLevelAsync()).ConfigureAwait(false);
await targetPlayer.ForEachWorldObserverAsync<IShowEffectPlugIn>(p => p.ShowEffectAsync(targetPlayer, IShowEffectPlugIn.EffectType.LevelUp), true).ConfigureAwait(false);
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.SetLevelResult), arguments.Level).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Set Level chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the level to set.
/// </summary>
public int Level { get; set; }
/// <summary>
/// Gets or sets the character name to set level for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,77 @@
// <copyright file="SetLevelUpPointsChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Globalization;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which sets a character's level-up points.
/// </summary>
[Guid("50EF670A-DF7A-4FEE-8E42-7C7A18A68941")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SetLevelUpPointsChatCommandPlugIn_Name), Description = nameof(PlugInResources.SetLevelUpPointsChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Sets level up points of a player. Usage: /setleveluppoints (points) (optional:character)", null)]
public class SetLevelUpPointsChatCommandPlugIn : ChatCommandPlugInBase<SetLevelUpPointsChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/setleveluppoints";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter is null)
{
return;
}
if (arguments is null || arguments.LevelUpPoints < 0)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidLevelUpPoints)).ConfigureAwait(false);
return;
}
targetPlayer.SelectedCharacter.LevelUpPoints = checked(arguments.LevelUpPoints);
await targetPlayer.InvokeViewPlugInAsync<IUpdateLevelPlugIn>(p => p.UpdateLevelAsync()).ConfigureAwait(false);
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.SetLevelUpPointsResult), arguments.LevelUpPoints).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Set Level-Up Points chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the level-up points to set.
/// </summary>
public int LevelUpPoints { get; set; }
/// <summary>
/// Gets or sets the character name to set level-up points for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,77 @@
// <copyright file="SetMasterLevelChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which sets a character's master level.
/// </summary>
[Guid("E401CA16-7827-495B-9DD0-EABDFF39901E")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SetMasterLevelChatCommandPlugIn_Name), Description = nameof(PlugInResources.SetMasterLevelChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Sets master level of a player. Usage: /setmasterlevel (level) (optional:character)", null)]
public class SetMasterLevelChatCommandPlugIn : ChatCommandPlugInBase<SetMasterLevelChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/setmasterlevel";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter is null)
{
return;
}
if (arguments is null || arguments.MasterLevel < 1 || arguments.MasterLevel > targetPlayer.GameContext.Configuration.MaximumMasterLevel)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidCharacterLevel), targetPlayer.GameContext.Configuration.MaximumMasterLevel).ConfigureAwait(false);
return;
}
targetPlayer.Attributes![Stats.MasterLevel] = checked(arguments.MasterLevel);
await targetPlayer.InvokeViewPlugInAsync<IUpdateLevelPlugIn>(p => p.UpdateMasterLevelAsync()).ConfigureAwait(false);
await targetPlayer.ForEachWorldObserverAsync<IShowEffectPlugIn>(p => p.ShowEffectAsync(targetPlayer, IShowEffectPlugIn.EffectType.LevelUp), true).ConfigureAwait(false);
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.SetMasterLevelResult), arguments.MasterLevel).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Set Master Level chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the master level to set.
/// </summary>
public int MasterLevel { get; set; }
/// <summary>
/// Gets or sets the character name to set master level for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,81 @@
// <copyright file="SetMasterLevelUpPointsChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which sets a character's master level-up points.
/// </summary>
[Guid("69AC0B9E-1063-448E-ABD6-C5837A1E8A4B")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SetMasterLevelUpPointsChatCommandPlugIn_Name), Description = nameof(PlugInResources.SetMasterLevelUpPointsChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Sets master level up points of a player. Usage: /setmasterleveluppoints (points) (optional:character)", null)]
public class SetMasterLevelUpPointsChatCommandPlugIn : ChatCommandPlugInBase<SetMasterLevelUpPointsChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/setmasterleveluppoints";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
if (arguments is null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidMasterLevelUpPoints)).ConfigureAwait(false);
return;
}
var targetPlayer = player;
if (arguments.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer?.SelectedCharacter is null)
{
return;
}
if (arguments.MasterLevelUpPoints < 0)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidMasterLevelUpPoints)).ConfigureAwait(false);
return;
}
targetPlayer.SelectedCharacter.MasterLevelUpPoints = checked(arguments.MasterLevelUpPoints);
await targetPlayer.InvokeViewPlugInAsync<IUpdateLevelPlugIn>(p => p.UpdateMasterLevelAsync()).ConfigureAwait(false);
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.SetMasterLevelUpPointsResult), arguments.MasterLevelUpPoints).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Set Master Level-Up Points chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the master level-up points to set.
/// </summary>
public int MasterLevelUpPoints { get; set; }
/// <summary>
/// Gets or sets the character name to set master level-up points for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,78 @@
// <copyright file="SetMoneyChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which sets a character's money.
/// </summary>
[Guid("00AA4F0E-911D-49FE-8D88-114C7496D383")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SetMoneyChatCommandPlugIn_Name), Description = nameof(PlugInResources.SetMoneyChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Sets money of a player. Usage: /setmoney (amount) (optional:character)", null)]
public class SetMoneyChatCommandPlugIn : ChatCommandPlugInBase<SetMoneyChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/setmoney";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer.SelectedCharacter?.Inventory is null)
{
return;
}
if (targetPlayer.GameContext?.Configuration?.MaximumInventoryMoney is not int maxMoney)
{
return;
}
if (arguments is null || arguments.Amount < 0 || arguments.Amount > maxMoney)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidMoneyAmount), maxMoney).ConfigureAwait(false);
return;
}
targetPlayer.Money = checked(arguments.Amount);
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.SetMoneyResult), arguments.Amount).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Set Money chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the amount of money to set.
/// </summary>
public int Amount { get; set; }
/// <summary>
/// Gets or sets the character name to set money for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,93 @@
// <copyright file="SetResetsChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Resets;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which sets a character's resets.
/// </summary>
[Guid("47A8644C-B6C5-439E-BAB0-C1A7AE72691C")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SetResetsChatCommandPlugIn_Name), Description = nameof(PlugInResources.SetResetsChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, "Sets resets of a player. Usage: /setresets (resets) (optional:character)", null)]
public class SetResetsChatCommandPlugIn : ChatCommandPlugInBase<SetResetsChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/setresets";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var configuration = player.GameContext.FeaturePlugIns.GetPlugIn<ResetFeaturePlugIn>()?.Configuration;
if (configuration is null)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ResetSystemInactive)).ConfigureAwait(false);
return;
}
var targetPlayer = player;
if (arguments?.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
if (targetPlayer?.SelectedCharacter is null)
{
return;
}
if (configuration.ResetLimit is null)
{
if (arguments is null || arguments.Resets < 0)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidResetsAmount)).ConfigureAwait(false);
return;
}
}
else
{
if (arguments is null || arguments.Resets < 0 || arguments.Resets > configuration.ResetLimit)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidResetsWithLimits), configuration.ResetLimit).ConfigureAwait(false);
return;
}
}
targetPlayer.Attributes![Stats.Resets] = checked(arguments.Resets);
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.SetResetsResult), arguments.Resets).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Set Resets chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the resets to set.
/// </summary>
public int Resets { get; set; }
/// <summary>
/// Gets or sets the character name to set resets for (GM only).
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,91 @@
// <copyright file="SetStatChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the command to add stat points.
/// </summary>
[Guid("D074E8AB-9D6E-49A4-956F-1F4818188AF1")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SetStatChatCommandPlugIn_Name), Description = nameof(PlugInResources.SetStatChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(Arguments), MinimumStatus)]
public class SetStatChatCommandPlugIn : ChatCommandPlugInBase<SetStatChatCommandPlugIn.Arguments>, IDisabledByDefault
{
private const string Command = "/set";
private const CharacterStatus MinimumStatus = CharacterStatus.GameMaster;
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc />
public override CharacterStatus MinCharacterStatusRequirement => MinimumStatus;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player player, Arguments arguments)
{
var targetPlayer = player;
if (arguments.CharacterName is { } characterName)
{
targetPlayer = player.GameContext.GetPlayerByCharacterName(characterName);
if (targetPlayer?.SelectedCharacter is null ||
!targetPlayer.SelectedCharacter.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase))
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.CharacterNotFound), characterName).ConfigureAwait(false);
return;
}
}
var attribute = await this.TryGetAttributeAsync(targetPlayer, arguments.StatType).ConfigureAwait(false);
if (attribute is null)
{
return;
}
if (attribute.MaximumValue is null && arguments.Amount < 1)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidStatValue), arguments.StatType).ConfigureAwait(false);
return;
}
if (attribute.MaximumValue < 0 || arguments.Amount > attribute.MaximumValue)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.InvalidStatValueRange), arguments.StatType, attribute.MaximumValue).ConfigureAwait(false);
return;
}
targetPlayer.Attributes![attribute] = arguments.Amount;
await targetPlayer.InvokeViewPlugInAsync<IUpdateCharacterBaseStatsPlugIn>(p => p.UpdateCharacterBaseStatsAsync()).ConfigureAwait(false);
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.SetStatResult), arguments.StatType, arguments.Amount, targetPlayer.SelectedCharacter?.Name).ConfigureAwait(false);
}
/// <summary>
/// Arguments for the Get Stat chat command.
/// </summary>
public class Arguments : ArgumentsBase
{
/// <summary>
/// Gets or sets the stat type to set.
/// </summary>
[ValidValues("str", "agi", "vit", "ene", "cmd")]
public string? StatType { get; set; }
/// <summary>
/// Gets or sets the amount to set.
/// </summary>
public ushort Amount { get; set; }
/// <summary>
/// Gets or sets the character name to set stat for.
/// </summary>
public string? CharacterName { get; set; }
}
}

View File

@@ -0,0 +1,36 @@
// <copyright file="ShowFireworksEffectChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Pathfinding;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles fireworks effect commands.
/// </summary>
[Guid("658F7F9D-B8FF-4D52-A835-5B3D658B6B9F")]
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowFireworksEffectChatCommandPlugIn_Name), Description = nameof(PlugInResources.ShowFireworksEffectChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(CoordinatesCommandArgs), CharacterStatus.GameMaster)]
public class ShowFireworksEffectChatCommandPlugIn : ChatCommandPlugInBase<CoordinatesCommandArgs>
{
private const string Command = "/fireworks";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc/>
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, CoordinatesCommandArgs arguments)
{
var coordinates = arguments.X == 0 ? gameMaster.Position : new Point(arguments.X, arguments.Y);
await gameMaster.ForEachWorldObserverAsync<IShowItemDropEffectPlugIn>(p => p.ShowEffectAsync(ItemDropEffect.Fireworks, coordinates), true).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,38 @@
// <copyright file="ShowNpcIdsChatCommand.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.GameLogic.Views.NPC;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Chat command to request the ids of all NPCs in the range of the player.
/// </summary>
[Guid("498D0205-388F-410A-A7C7-49069A64D3A9")]
[PlugIn]
[Display(ResourceType = typeof(PlugInResources), Name = "ShowNpcIdsChatCommand_ShowNpcIdsChatCommand_Name", Description = "ShowNpcIdsChatCommand_ShowNpcIdsChatCommand_Description")]
[ChatCommandHelp(Command, CharacterStatus.GameMaster)]
internal sealed class ShowNpcIdsChatCommand : IChatCommandPlugIn
{
private const string Command = "/showids";
/// <inheritdoc />
public string Key => Command;
/// <inheritdoc/>
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc/>
public async ValueTask HandleCommandAsync(Player player, string command)
{
var monsters = player.ObservingBuckets.SelectMany(b => b).OfType<NonPlayerCharacter>().ToList();
foreach (var monster in monsters)
{
await player.InvokeViewPlugInAsync<IShowMessageOfObjectPlugIn>(p => p.ShowMessageOfObjectAsync($"{monster.Id}", monster)).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,36 @@
// <copyright file="ShowXmasFireworksEffectChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.Pathfinding;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles xmas fireworks effect commands.
/// </summary>
[Guid("0E23E4CE-6E7B-4F29-92D8-04A1335EC722")]
[PlugIn]
[Display(Name = nameof(PlugInResources.ShowXmasFireworksEffectChatCommandPlugIn_Name), Description = nameof(PlugInResources.ShowXmasFireworksEffectChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(CoordinatesCommandArgs), CharacterStatus.GameMaster)]
public class ShowXmasFireworksEffectChatCommandPlugIn : ChatCommandPlugInBase<CoordinatesCommandArgs>
{
private const string Command = "/xmasfireworks";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc/>
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, CoordinatesCommandArgs arguments)
{
var coordinates = arguments.X == 0 ? gameMaster.Position : new Point(arguments.X, arguments.Y);
await gameMaster.ForEachWorldObserverAsync<IShowItemDropEffectPlugIn>(p => p.ShowEffectAsync(ItemDropEffect.ChristmasFireworks, coordinates), true).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,43 @@
// <copyright file="SkinChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.Persistence.BasicModel;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles skin commands.
/// </summary>
[Guid("4735CC2C-9E5D-457A-92CB-9D765F74FDFB")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SkinChatCommandPlugIn_Name), Description = nameof(PlugInResources.SkinChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(SkinChatCommandArgs), CharacterStatus.GameMaster)]
public class SkinChatCommandPlugIn : ChatCommandPlugInBase<SkinChatCommandArgs>
{
private const string Command = "/skin";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc/>
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, SkinChatCommandArgs arguments)
{
if (gameMaster?.Attributes is { } attributes
&& attributes.GetComposableAttribute(Stats.TransformationSkin) is { } attribute)
{
attribute.Elements.ToList().ForEach(attribute.RemoveElement);
attribute.AddElement(attributes.CreateElement(new PowerUpDefinitionValue { AggregateType = AggregateType.AddRaw, Value = arguments.SkinNumber }, Stats.TransformationSkin));
attributes[Stats.TransformationSkin] = arguments.SkinNumber;
}
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="StartBloodCastleEventChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the startds command.
/// </summary>
[Guid("7177533A-F147-407E-97B0-C4D8E1AC1AF4")]
[PlugIn]
[Display(Name = nameof(PlugInResources.StartBloodCastleEventChatCommandPlugIn_Name), Description = nameof(PlugInResources.StartBloodCastleEventChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, CharacterStatus.GameMaster)]
public class StartBloodCastleEventChatCommandPlugIn : IChatCommandPlugIn
{
private const string Command = "/startbc";
/// <inheritdoc />
public string Key => Command;
/// <inheritdoc/>
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
var bloodCastle = player.GameContext.PlugInManager.GetStrategy<MiniGameType, IPeriodicMiniGameStartPlugIn>(MiniGameType.BloodCastle);
bloodCastle?.ForceStart();
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="StartChaosCastleEventChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the startcc command.
/// </summary>
[Guid("A990270E-B9C6-4445-BBA9-56367A90D31D")]
[PlugIn]
[Display(Name = nameof(PlugInResources.StartChaosCastleEventChatCommandPlugIn_Name), Description = nameof(PlugInResources.StartChaosCastleEventChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, CharacterStatus.GameMaster)]
public class StartChaosCastleEventChatCommandPlugIn : IChatCommandPlugIn
{
private const string Command = "/startcc";
/// <inheritdoc />
public string Key => Command;
/// <inheritdoc/>
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
var chaosCastle = player.GameContext.PlugInManager.GetStrategy<MiniGameType, IPeriodicMiniGameStartPlugIn>(MiniGameType.ChaosCastle);
chaosCastle?.ForceStart();
}
}

View File

@@ -0,0 +1,34 @@
// <copyright file="StartDevilSquareEventChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles the startds command.
/// </summary>
[Guid("3684DC79-D81E-4033-AB2C-537334CF0BB6")]
[PlugIn]
[Display(Name = nameof(PlugInResources.StartDevilSquareEventChatCommandPlugIn_Name), Description = nameof(PlugInResources.StartDevilSquareEventChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, CharacterStatus.GameMaster)]
public class StartDevilSquareEventChatCommandPlugIn : IChatCommandPlugIn
{
private const string Command = "/startds";
/// <inheritdoc />
public string Key => Command;
/// <inheritdoc/>
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
var devilSquare = player.GameContext.PlugInManager.GetStrategy<MiniGameType, IPeriodicMiniGameStartPlugIn>(MiniGameType.DevilSquare);
devilSquare?.ForceStart();
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="TeleportChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles teleport commands.
/// </summary>
[Guid("ABFE2440-E765-4F17-A588-BD9AE3799886")]
[PlugIn]
[Display(Name = nameof(PlugInResources.TeleportChatCommandPlugIn_Name), Description = nameof(PlugInResources.TeleportChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(CoordinatesCommandArgs), CharacterStatus.GameMaster)]
public class TeleportChatCommandPlugIn : ChatCommandPlugInBase<CoordinatesCommandArgs>
{
private const string Command = "/teleport";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc/>
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, CoordinatesCommandArgs arguments)
{
await gameMaster.MoveAsync(arguments.Coordinates).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,50 @@
// <copyright file="TraceChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles trace commands.
/// </summary>
[Guid("F22C989B-A2A1-4991-B6C2-658337CC19CE")]
[PlugIn]
[Display(Name = nameof(PlugInResources.TraceChatCommandPlugIn_Name), Description = nameof(PlugInResources.TraceChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(TraceChatCommandArgs), CharacterStatus.GameMaster)]
public class TraceChatCommandPlugIn : ChatCommandPlugInBase<TraceChatCommandArgs>
{
private const string Command = "/trace";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc/>
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, TraceChatCommandArgs arguments)
{
var player = await this.GetPlayerByCharacterNameAsync(gameMaster, arguments.CharacterName ?? string.Empty).ConfigureAwait(false);
var character = player?.SelectedCharacter;
if (character is null)
{
return;
}
var characterLocation = new ExitGate
{
Map = character.CurrentMap,
X1 = character.PositionX,
X2 = (byte)(character.PositionX + 2),
Y1 = character.PositionY,
Y2 = (byte)(character.PositionY + 2),
};
await gameMaster.WarpToAsync(characterLocation).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,49 @@
// <copyright file="TrackChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles track commands.
/// </summary>
[Guid("7F12326A-9B84-4A56-A013-8C485D7B2EF6")]
[PlugIn]
[Display(Name = nameof(PlugInResources.TrackChatCommandPlugIn_Name), Description = nameof(PlugInResources.TrackChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(TraceChatCommandArgs), CharacterStatus.GameMaster)]
public class TrackChatCommandPlugIn : ChatCommandPlugInBase<TraceChatCommandArgs>
{
private const string Command = "/track";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc/>
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, TraceChatCommandArgs arguments)
{
var player = await this.GetPlayerByCharacterNameAsync(gameMaster, arguments.CharacterName ?? string.Empty).ConfigureAwait(false);
if (gameMaster.SelectedCharacter is null || player is null)
{
return;
}
var gameMasterLocation = new ExitGate
{
Map = gameMaster.SelectedCharacter.CurrentMap,
X1 = gameMaster.SelectedCharacter.PositionX,
X2 = (byte)(gameMaster.SelectedCharacter.PositionX + 2),
Y1 = gameMaster.SelectedCharacter.PositionY,
Y2 = (byte)(gameMaster.SelectedCharacter.PositionY + 2),
};
await player.WarpToAsync(gameMasterLocation).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,35 @@
// <copyright file="UnBanAccChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles banacc commands.
/// </summary>
[Guid("FCBC9CC0-3C8F-45E2-96DF-9C55BE30C5D9")]
[PlugIn]
[Display(Name = nameof(PlugInResources.UnBanAccChatCommandPlugIn_Name), Description = nameof(PlugInResources.UnBanAccChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(UnBanAccChatCommandArgs), CharacterStatus.GameMaster)]
public class UnBanAccChatCommandPlugIn : ChatCommandPlugInBase<UnBanAccChatCommandArgs>
{
private const string Command = "/unbanacc";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, UnBanAccChatCommandArgs arguments)
{
await this.ChangeAccountStateByLoginNameAsync(gameMaster, arguments.AccountName ?? string.Empty, AccountState.Normal).ConfigureAwait(false);
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.UnbanAccountResult), this.Key, arguments.AccountName).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,38 @@
// <copyright file="UnBanCharChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles unbanchar commands.
/// </summary>
[Guid("2830B01B-57A4-4925-AB6B-242C242B96C9")]
[PlugIn]
[Display(Name = nameof(PlugInResources.UnBanCharChatCommandPlugIn_Name), Description = nameof(PlugInResources.UnBanCharChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(UnBanCharChatCommandArgs), CharacterStatus.GameMaster)]
public class UnBanCharChatCommandPlugIn : ChatCommandPlugInBase<BanCharChatCommandArgs>
{
private const string Command = "/unbanchar";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, BanCharChatCommandArgs arguments)
{
if (!await this.TryChangeAccountStateByCharacterNameAsync(gameMaster, arguments.CharacterName ?? string.Empty, AccountState.Normal).ConfigureAwait(false))
{
return;
}
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.UnbanAccountOfCharacterResult), this.Key, arguments.CharacterName).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,32 @@
// <copyright file="UnHideChatCommandPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A chat command plugin which handles unhide commands.
/// </summary>
[Guid("0F0ADAC6-88C7-4EC0-94A2-A289173DEDA7")]
[PlugIn]
[Display(Name = nameof(PlugInResources.UnHideChatCommandPlugIn_Name), Description = nameof(PlugInResources.UnHideChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, CharacterStatus.GameMaster)]
public class UnHideChatCommandPlugIn : IChatCommandPlugIn
{
private const string Command = "/unhide";
/// <inheritdoc />
public string Key => Command;
/// <inheritdoc/>
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
public async ValueTask HandleCommandAsync(Player player, string command)
{
await player.RemoveInvisibleEffectAsync().ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,31 @@
// <copyright file="ValidValuesAttribute.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
/// <summary>
/// Describes valid values for string argument properties.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ValidValuesAttribute : Attribute
{
/// <summary>
/// The valid values.
/// </summary>
private readonly ISet<string> _validValues;
/// <summary>
/// Initializes a new instance of the <see cref="ValidValuesAttribute"/> class.
/// </summary>
/// <param name="values">The values.</param>
public ValidValuesAttribute(params string[] values)
{
this._validValues = new SortedSet<string>(values);
}
/// <summary>
/// Gets the valid values.
/// </summary>
public IEnumerable<string> ValidValues => this._validValues;
}

View File

@@ -0,0 +1,41 @@
// <copyright file="WalkMonsterChatCommand.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.Arguments;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Chat command to let a monster walk to specific coordinates.
/// </summary>
[Guid("1852FED5-8184-431E-8C5F-5131356D348F")]
[PlugIn]
[Display(Name = nameof(PlugInResources.WalkMonsterChatCommand_Name), Description = nameof(PlugInResources.WalkMonsterChatCommand_Description), ResourceType = typeof(PlugInResources))]
[ChatCommandHelp(Command, typeof(MoveMonsterCommandArgs), CharacterStatus.GameMaster)]
internal class WalkMonsterChatCommand : ChatCommandPlugInBase<MoveMonsterCommandArgs>
{
private const string Command = "/walkmonster";
/// <inheritdoc />
public override string Key => Command;
/// <inheritdoc/>
public override CharacterStatus MinCharacterStatusRequirement => CharacterStatus.GameMaster;
/// <inheritdoc />
protected override async ValueTask DoHandleCommandAsync(Player gameMaster, MoveMonsterCommandArgs arguments)
{
var monster = gameMaster.ObservingBuckets.SelectMany(b => b).OfType<Monster>().FirstOrDefault(m => m.Id == arguments.Id);
if (monster is null)
{
await gameMaster.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.MonsterNotFoundById), arguments.Id).ConfigureAwait(false);
return;
}
await monster.WalkToAsync(arguments.Coordinates).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,42 @@
// <copyright file="GuildWarKillScorePlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.GuildWar;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// This plugin increases the score of the soccer result, if a kill occurred.
/// </summary>
[PlugIn]
[Display(Name = nameof(PlugInResources.GuildWarKillScorePlugIn_Name), Description = nameof(PlugInResources.GuildWarKillScorePlugIn_Description), ResourceType = typeof(PlugInResources))]
[Guid("2D4E16CD-B7FF-4ED3-B4B1-4AABD04BAD71")]
public class GuildWarKillScorePlugIn : IAttackableGotKilledPlugIn
{
/// <summary>
/// Is called when an <see cref="IAttackable" /> object got killed by another.
/// </summary>
/// <param name="killed">The killed <see cref="IAttackable" />.</param>
/// <param name="killer">The killer.</param>
public async ValueTask AttackableGotKilledAsync(IAttackable killed, IAttacker? killer)
{
if (killer is Player { GuildWarContext: not null } killerPlayer
&& killed is Player { GuildWarContext: not null } killedPlayer
&& killerPlayer.GuildWarContext!.Team != killedPlayer.GuildWarContext!.Team)
{
var score = killedPlayer.GuildStatus?.Position == GuildPosition.GuildMaster ? (byte)2 : (byte)1;
if (killerPlayer.GuildWarContext.Team == GuildWarTeam.First)
{
killerPlayer.GuildWarContext.Score.IncreaseFirstGuildScore(score);
}
else
{
killerPlayer.GuildWarContext.Score.IncreaseSecondGuildScore(score);
}
}
}
}

View File

@@ -0,0 +1,28 @@
// <copyright file="IAreaSkillPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns;
using System.Runtime.InteropServices;
using MUnique.OpenMU.Pathfinding;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A plugin interface which is called when a target got attacked by an area skill.
/// The key is the skill number.
/// </summary>
[Guid("BAE1E31E-08EA-4B77-BE0E-89DECD9EAA29")]
[PlugInPoint("Area skill plugins", "Is called when a target got attacked by an area skill.")]
public interface IAreaSkillPlugIn : IStrategyPlugIn<short>
{
/// <summary>
/// Is called after a target got automatically attacked by an area skill, regardless if the attack effectively hit or not.
/// </summary>
/// <param name="attacker">The attacker.</param>
/// <param name="target">The target.</param>
/// <param name="skillEntry">The skill entry.</param>
/// <param name="targetAreaCenter">The target area center.</param>
/// <param name="hitInfo">Hit info produced by the skill.</param>
ValueTask AfterTargetGotAttackedAsync(IAttacker attacker, IAttackable target, SkillEntry skillEntry, Point targetAreaCenter, HitInfo? hitInfo);
}

View File

@@ -0,0 +1,29 @@
// <copyright file="IAreaSkillTargetFilter.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns;
using System.Runtime.InteropServices;
using MUnique.OpenMU.Pathfinding;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Plugins which will be executed when an area skill is about to hit its targets.
/// It allows to filter out targets which are out of range.
/// The key is the <see cref="Skill.Number"/>.
/// </summary>
[Guid("CD813E47-A926-48E1-A63F-9A80121CDBE9")]
[PlugInPoint("Area skill target filter", "Plugins which will be executed when an area skill is about to hit its targets. It allows to filter out targets which are out of range.")]
public interface IAreaSkillTargetFilter : IStrategyPlugIn<short>
{
/// <summary>
/// Determines whether the target is within the hit bounds.
/// </summary>
/// <param name="attacker">The attacker.</param>
/// <param name="target">The target.</param>
/// <param name="targetAreaCenter">The target area center.</param>
/// <param name="rotation">The rotation.</param>
/// <returns><c>true</c> if the target is within hit bounds; otherwise, <c>false</c>.</returns>
bool IsTargetWithinBounds(ILocateable attacker, ILocateable target, Point targetAreaCenter, byte rotation);
}

View File

@@ -0,0 +1,24 @@
// <copyright file="IAttackableGotHitPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A plugin interface which is called when an <see cref="IAttackable"/> got hit.
/// </summary>
[Guid("4FD59298-0424-4F93-83E5-290C8E7EB5E5")]
[PlugInPoint("Attackable got hit", "Plugins which will be executed when an attackable object got hit by an attacker.")]
public interface IAttackableGotHitPlugIn
{
/// <summary>
/// This method is called when an <see cref="IAttackable"/> got hit.
/// </summary>
/// <param name="attackable">The attackable.</param>
/// <param name="attacker">The attacker.</param>
/// <param name="hitInfo">The hit information.</param>
void AttackableGotHit(IAttackable attackable, IAttacker attacker, HitInfo hitInfo);
}

View File

@@ -0,0 +1,23 @@
// <copyright file="IAttackableGotKilledPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A plugin interface which is called when an <see cref="IAttackable"/> object got killed.
/// </summary>
[Guid("89CC1180-8FB4-4194-B895-D1F8D88124F9")]
[PlugInPoint("Attackable got killed", "Plugins which will be executed when an attackable object got killed by an attacker.")]
public interface IAttackableGotKilledPlugIn
{
/// <summary>
/// Is called when an <see cref="IAttackable"/> object got killed by another.
/// </summary>
/// <param name="killed">The killed <see cref="IAttackable"/>.</param>
/// <param name="killer">The killer.</param>
ValueTask AttackableGotKilledAsync(IAttackable killed, IAttacker? killer);
}

View File

@@ -0,0 +1,22 @@
// <copyright file="IAttackableMovedPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A plugin interface which Is called when a <see cref="IAttackable"/> moved on the game map.
/// </summary>
[Guid("ABD191BC-AEA2-4308-8EAA-CAF0A0D6B46B")]
[PlugInPoint("Attackable moved", "Plugins which are called when an attackable object moved on the game map.")]
public interface IAttackableMovedPlugIn
{
/// <summary>
/// Is called when a <see cref="IAttackable"/> moved on the game map.
/// </summary>
/// <param name="attackable">The <see cref="IAttackable"/>.</param>
void AttackableMoved(IAttackable attackable);
}

View File

@@ -0,0 +1,23 @@
// <copyright file="ICharacterCreatedPlugIn.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlugIns;
using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A plugin interface which is called when a character has been created.
/// </summary>
[Guid("B5588572-A324-4A94-9644-4DC3C8FEA4A4")]
[PlugInPoint("Character created", "Is called when a character got created.")]
public interface ICharacterCreatedPlugIn
{
/// <summary>
/// Is called when a new character has been created.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="createdCharacter">The created character.</param>
void CharacterCreated(Player player, Character createdCharacter);
}

Some files were not shown because too many files have changed in this diff Show More