baseline: OpenMU upstream b5a0961 (fresh source)

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

View File

@@ -0,0 +1,52 @@
// <copyright file="GuildCreateAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.Views.Guild;
/// <summary>
/// Action to create a guild.
/// </summary>
public class GuildCreateAction
{
/// <summary>
/// Creates the guild.
/// </summary>
/// <param name="creator">The creator.</param>
/// <param name="guildName">Name of the guild.</param>
/// <param name="guildEmblem">The guild emblem.</param>
public async ValueTask CreateGuildAsync(Player creator, string guildName, byte[] guildEmblem)
{
using var loggerScope = creator.Logger.BeginScope(this.GetType());
if (creator.PlayerState.CurrentState != PlayerState.EnteredWorld)
{
creator.Logger.LogError($"Account {creator.Account?.LoginName} not in the right state, but {creator.PlayerState.CurrentState}.");
return;
}
var guildServer = (creator.GameContext as IGameServerContext)?.GuildServer;
if (guildServer is null)
{
creator.Logger.LogError($"No guild server available");
return;
}
if (await guildServer.GuildExistsAsync(guildName).ConfigureAwait(false))
{
await creator.InvokeViewPlugInAsync<IShowGuildCreateResultPlugIn>(p => p.ShowGuildCreateResultAsync(GuildCreateErrorDetail.GuildAlreadyExist)).ConfigureAwait(false);
return;
}
if (await guildServer.CreateGuildAsync(guildName, creator.SelectedCharacter!.Name, creator.SelectedCharacter.Id, guildEmblem, ((IGameServerContext)creator.GameContext).Id).ConfigureAwait(false))
{
await creator.InvokeViewPlugInAsync<IShowGuildCreateResultPlugIn>(p => p.ShowGuildCreateResultAsync(GuildCreateErrorDetail.None)).ConfigureAwait(false);
creator.Logger.LogInformation("Guild created: [{0}], Master: [{1}]", guildName, creator.SelectedCharacter.Name);
}
else
{
await creator.InvokeViewPlugInAsync<IShowGuildCreateResultPlugIn>(p => p.ShowGuildCreateResultAsync(GuildCreateErrorDetail.GuildAlreadyExist)).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,23 @@
// <copyright file="GuildInfoRequestAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.Views.Guild;
/// <summary>
/// Action to request the information (name, symbol) of a guild.
/// </summary>
public class GuildInfoRequestAction
{
/// <summary>
/// Requests the guild information.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="guildId">The guild identifier.</param>
public async ValueTask RequestGuildInfoAsync(Player player, uint guildId)
{
await player.InvokeViewPlugInAsync<IShowGuildInfoPlugIn>(p => p.ShowGuildInfoAsync(guildId)).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,71 @@
// <copyright file="GuildKickPlayerAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
/// <summary>
/// Action to kick a player out of a guild.
/// </summary>
public class GuildKickPlayerAction
{
/// <summary>
/// Kicks the player out of the guild.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="nickname">The nickname.</param>
/// <param name="securityCode">The security code.</param>
public async ValueTask KickPlayerAsync(Player player, string nickname, string securityCode)
{
using var loggerScope = player.Logger.BeginScope(this.GetType());
if (player.PlayerState.CurrentState != PlayerState.EnteredWorld)
{
player.Logger.LogError($"Account {player.Account?.LoginName} not in the right state, but {player.PlayerState.CurrentState}.");
return;
}
if (player.GuildStatus is null)
{
player.Logger.LogError($"Player {player} not in a guild.");
return;
}
var guildServer = (player.GameContext as IGameServerContext)?.GuildServer;
if (guildServer is null)
{
player.Logger.LogWarning("No guild server available");
return;
}
if (player.Account!.SecurityCode != null && player.Account.SecurityCode != securityCode)
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.WrongSecurityCode)).ConfigureAwait(false);
player.Logger.LogDebug("Wrong Security Code: [{0}] <> [{1}], Player: {2}", securityCode, player.Account.SecurityCode, player.SelectedCharacter?.Name);
await player.InvokeViewPlugInAsync<IGuildKickResultPlugIn>(p => p.GuildKickResultAsync(GuildKickSuccess.Failed)).ConfigureAwait(false);
return;
}
var isKickingHimself = player.SelectedCharacter!.Name == nickname;
if (!isKickingHimself && player.GuildStatus?.Position != GuildPosition.GuildMaster)
{
player.Logger.LogWarning("Suspicious kick request for player with name: {0} (player is not a guild master) to kick {1}, could be hack attempt.", player.Name, nickname);
await player.InvokeViewPlugInAsync<IGuildKickResultPlugIn>(p => p.GuildKickResultAsync(GuildKickSuccess.FailedBecausePlayerIsNotGuildMaster)).ConfigureAwait(false);
return;
}
if (isKickingHimself && player.GuildStatus?.Position == GuildPosition.GuildMaster)
{
var guildId = player.GuildStatus.GuildId;
await player.InvokeViewPlugInAsync<IGuildKickResultPlugIn>(p => p.GuildKickResultAsync(GuildKickSuccess.GuildDisband)).ConfigureAwait(false);
await guildServer.KickMemberAsync(guildId, nickname).ConfigureAwait(false);
return;
}
await guildServer.KickMemberAsync(player.GuildStatus!.GuildId, nickname).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,33 @@
// <copyright file="GuildListRequestAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.Views.Guild;
/// <summary>
/// Action to request the guild list.
/// </summary>
public class GuildListRequestAction
{
/// <summary>
/// Requests the guild list of the guild the player is currently part of.
/// </summary>
/// <param name="player">The player.</param>
public async ValueTask RequestGuildListAsync(Player player)
{
if (player.GuildStatus is null)
{
return;
}
// TODO: We may want to retrieve guild and guild members in one call, to avoid multiple calls. But for now, we can live with that.
if ((player.GameContext as IGameServerContext)?.GuildServer is { } guildServer
&& await guildServer.GetGuildAsync(player.GuildStatus.GuildId).ConfigureAwait(false) is { } guild)
{
var players = await guildServer.GetGuildListAsync(player.GuildStatus.GuildId).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<IShowGuildListPlugIn>(p => p.ShowGuildListAsync(players, guild)).ConfigureAwait(false);
}
}
}

View File

@@ -0,0 +1,50 @@
// <copyright file="GuildMasterAnswerAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.Views.Guild;
/// <summary>
/// Action to answer the dialog of the guild master npc.
/// </summary>
public class GuildMasterAnswerAction
{
/// <summary>
/// Type of the answer.
/// </summary>
public enum Answer
{
/// <summary>
/// Cancels the guild master npc dialog.
/// </summary>
Cancel = 0,
/// <summary>
/// The guild master npc dialog should be shown.
/// </summary>
ShowDialog = 1,
}
/// <summary>
/// Processes the answer.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="answer">The answer.</param>
public async ValueTask ProcessAnswerAsync(Player player, Answer answer)
{
if (player.PlayerState.CurrentState == PlayerState.EnteredWorld && answer == Answer.ShowDialog)
{
await player.InvokeViewPlugInAsync<IShowGuildCreationDialogPlugIn>(p => p.ShowGuildCreationDialogAsync()).ConfigureAwait(false);
}
else if (player.OpenedNpc?.Definition.NpcWindow == NpcWindow.GuildMaster && await player.PlayerState.TryAdvanceToAsync(PlayerState.EnteredWorld).ConfigureAwait(false))
{
player.OpenedNpc = null;
}
else
{
// nothing to do.
}
}
}

View File

@@ -0,0 +1,228 @@
// <copyright file="GuildRelationshipChangeAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
/// <summary>
/// Action which handles guild relationship changes (alliance creation/removal and hostility).
/// </summary>
public class GuildRelationshipChangeAction
{
/// <summary>
/// Handles an incoming relationship change request from a guild master.
/// Validates and forwards the request to the target guild master.
/// </summary>
/// <param name="player">The player requesting the relationship change.</param>
/// <param name="targetPlayerId">The player id of the target guild master.</param>
/// <param name="relationshipType">The type of relationship change (Alliance or Hostility).</param>
/// <param name="requestType">The type of request (Join or Leave).</param>
public async ValueTask RequestAsync(Player player, ushort targetPlayerId, GuildRelationshipType relationshipType, GuildRelationshipRequestType requestType)
{
var (success, (sourceGuildId, serverContext, sourceGuild)) = await this.CommonChecksAsync(player, targetPlayerId, relationshipType, requestType).ConfigureAwait(false);
if (!success)
{
return;
}
// Find the target player
var targetPlayer = await player.GetObservingPlayerWithIdAsync(targetPlayerId).ConfigureAwait(false);
if (targetPlayer?.GuildStatus is not { } targetGuildStatus
|| await serverContext.GuildServer.GetGuildAsync(targetGuildStatus.GuildId).ConfigureAwait(false) is not { Name: not null } targetGuild)
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.Failed, targetPlayerId)).ConfigureAwait(false);
return;
}
if (targetGuildStatus.Position != GuildPosition.GuildMaster)
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.Failed, targetPlayerId)).ConfigureAwait(false);
return;
}
if (relationshipType == GuildRelationshipType.Hostility)
{
if (requestType == GuildRelationshipRequestType.Join
&& (targetGuild.Hostility is not null || sourceGuild.Hostility is not null))
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.AlreadyInHostility, targetPlayerId)).ConfigureAwait(false);
return;
}
if (requestType == GuildRelationshipRequestType.Leave
&& targetGuild.Hostility?.Name != sourceGuild.Name
&& sourceGuild.Hostility?.Name != targetGuild.Name)
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.HostileGuildDoesNotExist, targetPlayerId)).ConfigureAwait(false);
return;
}
}
if (relationshipType == GuildRelationshipType.Alliance
&& requestType == GuildRelationshipRequestType.Join)
{
if (targetGuild.AllianceGuild is not null)
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.AlreadyInAlliance, targetPlayerId)).ConfigureAwait(false);
return;
}
if (sourceGuild.AllianceGuild is not null && sourceGuild.AllianceGuild != sourceGuild)
{
// Request is not done by the master of the alliance, but by the master of a sub-guild
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.NoAuthorization, targetPlayerId)).ConfigureAwait(false);
return;
}
// Check the maximum alliance size configured for this game version.
// A value of 0 means no limit is configured (e.g. for game versions that pre-date alliances).
var maxAllianceSize = (int)(player.Attributes?[Stats.MaximumAllianceSize] ?? 0);
if (maxAllianceSize > 0)
{
var allianceGuilds = await serverContext.GuildServer.GetAllianceGuildsAsync(sourceGuildId).ConfigureAwait(false);
// Compute the size of the alliance after the target guild would be added.
// When there is no alliance yet (count == 0), the source guild itself is the first
// member, so the resulting alliance would have 2 guilds (source + target).
var sizeAfterAdding = allianceGuilds.Count == 0 ? 2 : allianceGuilds.Count + 1;
if (sizeAfterAdding > maxAllianceSize)
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.MaximumNumberOfGuildsInAllianceReached, targetPlayerId)).ConfigureAwait(false);
return;
}
}
}
// Store the pending request on the target player and ask for consent
targetPlayer.PendingAllianceRequest = (player, relationshipType, requestType);
await targetPlayer.InvokeViewPlugInAsync<IShowGuildRelationshipRequestPlugIn>(p => p.ShowRequestAsync(
player,
relationshipType,
requestType)).ConfigureAwait(false);
}
/// <summary>
/// Handles an incoming relationship leave request from a guild master.
/// Validates the request and processes the leave action.
/// </summary>
/// <param name="player">The player requesting the relationship change.</param>
/// <param name="targetGuildName">The name of the guild which should be removed. If <see langword="null"/>, then the own guild should be removed.</param>
public async ValueTask RequestLeaveAllianceAsync(Player player, string? targetGuildName = null)
{
var (success, (sourceGuildId, serverContext, sourceGuild)) = await this.CommonChecksAsync(player, 0, GuildRelationshipType.Alliance, GuildRelationshipRequestType.Leave).ConfigureAwait(false);
if (!success)
{
return;
}
var targetGuildId = sourceGuildId;
var leaveWithOwnGuild = string.IsNullOrEmpty(targetGuildName) || sourceGuild.Name == targetGuildName;
if (!leaveWithOwnGuild)
{
if (!await serverContext.GuildServer.IsAllianceMasterAsync(sourceGuildId).ConfigureAwait(false))
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(GuildRelationshipType.Alliance, GuildRelationshipRequestType.Leave, GuildRelationshipChangeResultType.NoAuthorization, null)).ConfigureAwait(false);
return;
}
targetGuildId = await serverContext.GuildServer.GetGuildIdByNameAsync(targetGuildName!).ConfigureAwait(false);
}
var removeSuccess = await serverContext.GuildServer.RemoveAllianceAsync(targetGuildId).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowRemoveResultAsync(removeSuccess)).ConfigureAwait(false);
}
/// <summary>
/// Processes the response from the target guild master to an alliance request.
/// </summary>
/// <param name="player">The target guild master who is responding.</param>
/// <param name="relationshipType">The type of relationship change (Alliance or Hostility).</param>
/// <param name="requestType">The type of request (Join or Leave).</param>
/// <param name="accepted">Whether the relationship change was accepted.</param>
public async ValueTask ProcessResponseAsync(Player player, GuildRelationshipType relationshipType, GuildRelationshipRequestType requestType, bool accepted)
{
var pending = player.PendingAllianceRequest;
var (requester, pendingRelationshipType, pendingRequestType) = pending;
player.PendingAllianceRequest = default;
if (pendingRelationshipType != relationshipType || pendingRequestType != requestType)
{
// No pending request or mismatch in the request details, ignore the response, leave with default state
return;
}
if (requester is null
|| requester.GuildStatus is not { } requesterGuildStatus
|| player.GuildStatus is not { } responderGuildStatus
|| player.GameContext is not IGameServerContext serverContext)
{
return;
}
var guildMasterId = player.GetId(requester);
if (!accepted)
{
await requester.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.RequestCancelled, guildMasterId)).ConfigureAwait(false);
return;
}
var res = pending switch
{
(_, GuildRelationshipType.Alliance, GuildRelationshipRequestType.Join) =>
await serverContext.GuildServer.CreateAllianceAsync(requesterGuildStatus.GuildId, responderGuildStatus.GuildId).ConfigureAwait(false)
switch
{
AllianceCreationResult.Success => GuildRelationshipChangeResultType.Success,
AllianceCreationResult.MasterGuildNotFound or AllianceCreationResult.TargetGuildNotFound => GuildRelationshipChangeResultType.GuildNotFound,
AllianceCreationResult.TargetGuildAlreadyInAlliance => GuildRelationshipChangeResultType.AlreadyInAlliance,
AllianceCreationResult.MaximumAllianceSizeReached => GuildRelationshipChangeResultType.MaximumNumberOfGuildsInAllianceReached,
_ => GuildRelationshipChangeResultType.Failed,
},
(_, GuildRelationshipType.Hostility, _) => await serverContext.GuildServer.SetHostilityAsync(requesterGuildStatus.GuildId, responderGuildStatus.GuildId, pendingRequestType == GuildRelationshipRequestType.Join).ConfigureAwait(false)
? GuildRelationshipChangeResultType.Success
: GuildRelationshipChangeResultType.Failed,
_ => GuildRelationshipChangeResultType.Failed,
};
await requester.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, res, guildMasterId)).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, res, guildMasterId)).ConfigureAwait(false);
}
private async ValueTask<(bool Success, GuildData GuildData)> CommonChecksAsync(Player player, ushort? targetPlayerId, GuildRelationshipType relationshipType, GuildRelationshipRequestType requestType)
{
if (player.PendingAllianceRequest != default)
{
// There is already a pending request, so we cannot process another one at the moment. This can happen with multiple requests from different players.
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.RequestCancelled, targetPlayerId)).ConfigureAwait(false);
return (false, null!);
}
if (player.GuildStatus is not { } guildStatus
|| player.GameContext is not IGameServerContext serverContext)
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.Failed, targetPlayerId)).ConfigureAwait(false);
return (false, null!);
}
if (guildStatus.Position != GuildPosition.GuildMaster)
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.NoAuthorization, targetPlayerId)).ConfigureAwait(false);
return (false, null!);
}
var sourceGuild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false);
if (sourceGuild is null)
{
await player.InvokeViewPlugInAsync<IGuildRelationshipChangeResultPlugIn>(p => p.ShowResultAsync(relationshipType, requestType, GuildRelationshipChangeResultType.GuildNotFound, targetPlayerId)).ConfigureAwait(false);
return (false, null!);
}
return (true, new(guildStatus.GuildId, serverContext, sourceGuild));
}
private record GuildData(uint GuildId, IGameServerContext Context, Guild Guild);
}

View File

@@ -0,0 +1,51 @@
// <copyright file="GuildRequestAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
/// <summary>
/// Action to request guild membership from a guild master player.
/// </summary>
public class GuildRequestAction
{
/// <summary>
/// Requests the guild from the guild master player with the specified id.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="guildMasterId">The guild master identifier.</param>
public async ValueTask RequestGuildAsync(Player player, ushort guildMasterId)
{
if (player.Level < 6)
{
await player.InvokeViewPlugInAsync<IGuildJoinResponsePlugIn>(p => p.ShowGuildJoinResponseAsync(GuildRequestAnswerResult.MinimumLevel6)).ConfigureAwait(false);
return;
}
if (player.GuildStatus is not null)
{
await player.InvokeViewPlugInAsync<IGuildJoinResponsePlugIn>(p => p.ShowGuildJoinResponseAsync(GuildRequestAnswerResult.AlreadyHaveGuild)).ConfigureAwait(false);
return;
}
var guildMaster = player.CurrentMap?.GetObject(guildMasterId) as Player;
if (guildMaster?.GuildStatus?.Position != GuildPosition.GuildMaster)
{
await player.InvokeViewPlugInAsync<IGuildJoinResponsePlugIn>(p => p.ShowGuildJoinResponseAsync(GuildRequestAnswerResult.NotTheGuildMaster)).ConfigureAwait(false);
return; // targeted player not in a guild or not the guild master
}
if (guildMaster.LastGuildRequester != null || player.PlayerState.CurrentState != PlayerState.EnteredWorld)
{
await player.InvokeViewPlugInAsync<IGuildJoinResponsePlugIn>(p => p.ShowGuildJoinResponseAsync(GuildRequestAnswerResult.GuildMasterOrRequesterIsBusy)).ConfigureAwait(false);
return;
}
guildMaster.LastGuildRequester = player;
await guildMaster.InvokeViewPlugInAsync<IShowGuildJoinRequestPlugIn>(p => p.ShowGuildJoinRequestAsync(player)).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,62 @@
// <copyright file="GuildRequestAnswerAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
/// <summary>
/// Action for a guild master player to answer the guild membership request.
/// </summary>
public class GuildRequestAnswerAction
{
/// <summary>
/// Answers the request.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="accept">If set to <c>true</c>, the membership has been accepted. Otherwise, not.</param>
public async ValueTask AnswerRequestAsync(Player player, bool accept)
{
using var loggerScope = player.Logger.BeginScope(this.GetType());
var guildServer = (player.GameContext as IGameServerContext)?.GuildServer;
if (guildServer is null)
{
return;
}
var lastGuildRequester = player.LastGuildRequester;
if (lastGuildRequester?.SelectedCharacter is null)
{
return;
}
if (lastGuildRequester.GuildStatus is not null)
{
await lastGuildRequester.InvokeViewPlugInAsync<IGuildJoinResponsePlugIn>(p => p.ShowGuildJoinResponseAsync(GuildRequestAnswerResult.AlreadyHaveGuild)).ConfigureAwait(false);
return;
}
if (player.GuildStatus?.Position != GuildPosition.GuildMaster)
{
player.Logger.LogWarning("Suspicious request for player with name: {0} (player is not a guild master), could be hack attempt.", player.Name);
await lastGuildRequester.InvokeViewPlugInAsync<IGuildJoinResponsePlugIn>(p => p.ShowGuildJoinResponseAsync(GuildRequestAnswerResult.NotTheGuildMaster)).ConfigureAwait(false);
return;
}
if (player.PlayerState.CurrentState != PlayerState.EnteredWorld
|| lastGuildRequester.PlayerState.CurrentState != PlayerState.EnteredWorld)
{
await lastGuildRequester.InvokeViewPlugInAsync<IGuildJoinResponsePlugIn>(p => p.ShowGuildJoinResponseAsync(GuildRequestAnswerResult.GuildMasterOrRequesterIsBusy)).ConfigureAwait(false);
}
if (accept)
{
await guildServer.CreateGuildMemberAsync(player.GuildStatus.GuildId, lastGuildRequester.SelectedCharacter.Id, lastGuildRequester.SelectedCharacter.Name, GuildPosition.NormalMember, ((IGameServerContext)player.GameContext).Id).ConfigureAwait(false);
}
await lastGuildRequester.InvokeViewPlugInAsync<IGuildJoinResponsePlugIn>(p => p.ShowGuildJoinResponseAsync(accept ? GuildRequestAnswerResult.Accepted : GuildRequestAnswerResult.Refused)).ConfigureAwait(false);
player.LastGuildRequester = null;
}
}

View File

@@ -0,0 +1,208 @@
// <copyright file="GuildWarAnswerAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using System.ComponentModel;
using MUnique.OpenMU.GameLogic.GuildWar;
using MUnique.OpenMU.GameLogic.Views.Guild;
/// <summary>
/// Action to handle the response of the requested guild master about the guild war.
/// </summary>
public class GuildWarAnswerAction
{
/// <summary>
/// Processes the answer.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="isWarAccepted">The answer.</param>
public async ValueTask ProcessAnswerAsync(Player player, bool isWarAccepted)
{
if (player.GuildWarContext is not { } guildWarContext
|| guildWarContext.Requester is not { } requester)
{
await player.InvokeViewPlugInAsync<IShowShowGuildWarRequestResultPlugIn>(p => p.ShowResultAsync(GuildWarRequestResult.GuildNotFound)).ConfigureAwait(false);
return;
}
SoccerGameMap? soccerMap = null;
if (guildWarContext.WarType == GuildWarType.Soccer
&& player.GameContext.Configuration.Maps.FirstOrDefault(m => m.BattleZone?.Type == BattleType.Soccer) is { } definition)
{
soccerMap = (SoccerGameMap?)await player.GameContext.GetMapAsync(definition.Number.ToUnsigned()).ConfigureAwait(false);
}
var soccerInitFailed = false;
if (guildWarContext.WarType == GuildWarType.Soccer && (soccerMap is null || soccerMap.IsBattleOngoing))
{
soccerInitFailed = true;
await player.InvokeViewPlugInAsync<IShowShowGuildWarRequestResultPlugIn>(p => p.ShowResultAsync(GuildWarRequestResult.Failed)).ConfigureAwait(false);
}
if (!isWarAccepted
|| requester.GuildWarContext is not { } requesterGuildWarContext
|| soccerInitFailed)
{
player.GuildWarContext = null;
requester.GuildWarContext = null;
return;
}
soccerMap?.InitializeBattle();
guildWarContext.State = GuildWarState.Started;
requesterGuildWarContext.State = GuildWarState.Started;
var playerTeam = this.GetTeamPlayers(player);
var requesterTeam = this.GetTeamPlayers(requester);
var score = guildWarContext.Score;
#pragma warning disable VSTHRD101 // Avoid unsupported async delegates
score.PropertyChanged += async (_, args) =>
{
try
{
if (args.PropertyName != nameof(score.HasEnded))
{
return;
}
guildWarContext.State = GuildWarState.Ended;
requesterGuildWarContext.State = GuildWarState.Ended;
if (player.GameContext is IGameServerContext gameContext && score.Winners.HasValue)
{
var winner = score.Winners == player.GuildWarContext.Team ? player.GuildStatus!.GuildId : requester.GuildStatus!.GuildId;
await gameContext.GuildServer.IncreaseGuildScoreAsync(winner).ConfigureAwait(false);
}
}
catch
{
// must be catched because it's async void.
}
};
#pragma warning restore VSTHRD101 // Avoid unsupported async delegates
foreach (var guildPlayer in playerTeam)
{
guildPlayer.GuildWarContext = guildWarContext;
await guildPlayer.InvokeViewPlugInAsync<IShowGuildWarDeclaredPlugIn>(p => p.ShowDeclaredAsync()).ConfigureAwait(false);
await guildPlayer.InvokeViewPlugInAsync<IGuildWarScoreUpdatePlugIn>(p => p.UpdateScoreAsync()).ConfigureAwait(false);
RegisterScoreChangedEventWeakly(guildPlayer, score, soccerMap);
}
foreach (var guildPlayer in requesterTeam)
{
guildPlayer.GuildWarContext = requesterGuildWarContext;
await guildPlayer.InvokeViewPlugInAsync<IShowGuildWarDeclaredPlugIn>(p => p.ShowDeclaredAsync()).ConfigureAwait(false);
await guildPlayer.InvokeViewPlugInAsync<IGuildWarScoreUpdatePlugIn>(p => p.UpdateScoreAsync()).ConfigureAwait(false);
RegisterScoreChangedEventWeakly(guildPlayer, score, soccerMap);
}
if (guildWarContext.WarType == GuildWarType.Soccer && soccerMap is { })
{
await this.MovePartyToArenaAsync(player.GuildWarContext.Team, playerTeam, soccerMap).ConfigureAwait(false);
await this.MovePartyToArenaAsync(requester.GuildWarContext.Team, requesterTeam, soccerMap).ConfigureAwait(false);
await soccerMap.StartBattleAsync(score).ConfigureAwait(false);
}
}
private static void RegisterScoreChangedEventWeakly(Player guildPlayer, GuildWarScore score, SoccerGameMap? soccerMap)
{
var playerReference = new WeakReference<Player>(guildPlayer);
#pragma warning disable VSTHRD100 // Avoid async void methods
async void OnScorePropertyChanged(object? sender, PropertyChangedEventArgs args)
#pragma warning restore VSTHRD100 // Avoid async void methods
{
try
{
if (playerReference.TryGetTarget(out var p))
{
await OnScoreChangedAsync(score, p, soccerMap, args).ConfigureAwait(false);
}
else
{
score.PropertyChanged -= OnScorePropertyChanged;
}
}
catch (Exception ex)
{
guildPlayer.Logger.LogError(ex, "Error handling a changed guild war score.");
}
}
score.PropertyChanged += OnScorePropertyChanged;
}
private static async ValueTask OnScoreChangedAsync(GuildWarScore score, Player player, SoccerGameMap? soccerMap, PropertyChangedEventArgs args)
{
try
{
if (args.PropertyName == nameof(score.HasEnded))
{
if (player.GuildWarContext is { } context)
{
var isWinner = context.Team == score.Winners;
await player.InvokeViewPlugInAsync<IShowGuildWarResultPlugIn>(p => p.ShowResultAsync(context.EnemyTeamName, isWinner ? GuildWarResult.Won : GuildWarResult.Lost)).ConfigureAwait(false);
if (soccerMap is not null)
{
var spawnGates = soccerMap.Definition.ExitGates.Where(g => g.IsSpawnGate);
if (spawnGates.Any())
{
await player.WarpToAsync(spawnGates.SelectRandom()!).ConfigureAwait(false);
}
}
player.GuildWarContext = null;
}
}
else
{
await player.InvokeViewPlugInAsync<IGuildWarScoreUpdatePlugIn>(p => p.UpdateScoreAsync()).ConfigureAwait(false);
}
}
catch (Exception ex)
{
player.Logger.LogError(ex, "Unexpected error when notifying the player about a guild war score update");
}
}
private ICollection<Player> GetTeamPlayers(Player guildMaster)
{
if (guildMaster.Party is { } party)
{
return party.PartyList.OfType<Player>().Where(p => p.GuildStatus?.GuildId == guildMaster.GuildStatus?.GuildId).ToList();
}
return new List<Player>(1) { guildMaster };
}
private async ValueTask MovePartyToArenaAsync(GuildWarTeam team, ICollection<Player> members, SoccerGameMap soccerMap)
{
var ground = soccerMap.Definition.BattleZone?.Ground;
if (ground is null)
{
return;
}
var increaseX = soccerMap.Definition.BattleZone?.LeftTeamSpawnPointX is not null;
var exitGate = new ExitGate
{
X1 = (team == GuildWarTeam.First ? soccerMap.Definition.BattleZone?.LeftTeamSpawnPointX : soccerMap.Definition.BattleZone?.RightTeamSpawnPointX) ?? ground.X1,
X2 = (team == GuildWarTeam.First ? soccerMap.Definition.BattleZone?.LeftTeamSpawnPointX : soccerMap.Definition.BattleZone?.RightTeamSpawnPointX) ?? ground.X2,
Y1 = (team == GuildWarTeam.First ? soccerMap.Definition.BattleZone?.LeftTeamSpawnPointY : soccerMap.Definition.BattleZone?.RightTeamSpawnPointY) ?? ground.Y1,
Y2 = (team == GuildWarTeam.First ? soccerMap.Definition.BattleZone?.LeftTeamSpawnPointY : soccerMap.Definition.BattleZone?.RightTeamSpawnPointY) ?? ground.Y2,
Map = soccerMap.Definition,
};
foreach (var member in members)
{
await member.WarpToAsync(exitGate).ConfigureAwait(false);
if (increaseX)
{
exitGate.X1++;
exitGate.X2++;
}
}
}
}

View File

@@ -0,0 +1,91 @@
// <copyright file="GuildWarRequestAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.PlayerActions.Guild;
using MUnique.OpenMU.GameLogic.GuildWar;
using MUnique.OpenMU.GameLogic.Views.Guild;
using MUnique.OpenMU.Interfaces;
/// <summary>
/// Action to request a guild war.
/// </summary>
public class GuildWarRequestAction
{
/// <summary>
/// Requests the a guild war at the guild master of the target guild.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="targetGuildName">Name of the target guild.</param>
public ValueTask RequestWarAsync(Player player, string targetGuildName)
{
return this.TryRequestWarAsync(player, targetGuildName, GuildWarType.Normal);
}
/// <summary>
/// Requests the a battle soccer at the guild master of the target guild.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="targetGuildName">Name of the target guild.</param>
public ValueTask RequestBattleSoccerAsync(Player player, string targetGuildName)
{
return this.TryRequestWarAsync(player, targetGuildName, GuildWarType.Soccer);
}
private async ValueTask TryRequestWarAsync(Player player, string targetGuildName, GuildWarType guildWarType)
{
if (player.GuildStatus is not { } guildStatus
|| player.GameContext is not IGameServerContext serverContext
|| await serverContext.GuildServer.GetGuildAsync(player.GuildStatus.GuildId).ConfigureAwait(false) is not { Name: not null } guild)
{
await player.InvokeViewPlugInAsync<IShowShowGuildWarRequestResultPlugIn>(p => p.ShowResultAsync(GuildWarRequestResult.NotInGuild)).ConfigureAwait(false);
return;
}
if (guildStatus.Position != GuildPosition.GuildMaster)
{
await player.InvokeViewPlugInAsync<IShowShowGuildWarRequestResultPlugIn>(p => p.ShowResultAsync(GuildWarRequestResult.NotTheGuildMaster)).ConfigureAwait(false);
return;
}
if (!await serverContext.GuildServer.GuildExistsAsync(targetGuildName).ConfigureAwait(false))
{
await player.InvokeViewPlugInAsync<IShowShowGuildWarRequestResultPlugIn>(p => p.ShowResultAsync(GuildWarRequestResult.GuildNotFound)).ConfigureAwait(false);
return;
}
var targetGuildId = await serverContext.GuildServer.GetGuildIdByNameAsync(targetGuildName).ConfigureAwait(false);
Player? targetGuildMaster = null;
await serverContext.ForEachGuildPlayerAsync(targetGuildId, p =>
{
targetGuildMaster = p.GuildStatus?.Position == GuildPosition.GuildMaster ? p : targetGuildMaster;
return Task.CompletedTask;
}).ConfigureAwait(false);
if (targetGuildMaster is null)
{
await player.InvokeViewPlugInAsync<IShowShowGuildWarRequestResultPlugIn>(p => p.ShowResultAsync(GuildWarRequestResult.GuildMasterOffline)).ConfigureAwait(false);
return;
}
if (targetGuildMaster.GuildWarContext is not null || player.GuildWarContext is not null)
{
await player.InvokeViewPlugInAsync<IShowShowGuildWarRequestResultPlugIn>(p => p.ShowResultAsync(GuildWarRequestResult.AlreadyInWar)).ConfigureAwait(false);
return;
}
var score = new GuildWarScore
{
FirstGuildName = targetGuildName,
SecondGuildName = guild.Name!,
MaximumScore = (byte)(guildWarType == GuildWarType.Soccer ? 100 : 20),
};
targetGuildMaster.GuildWarContext = new GuildWarContext(guildWarType, score, GuildWarTeam.First, player);
player.GuildWarContext = new GuildWarContext(guildWarType, score, GuildWarTeam.Second, null);
await targetGuildMaster.InvokeViewPlugInAsync<IShowGuildWarRequestPlugIn>(p => p.ShowRequestAsync(guild.Name!, guildWarType)).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<IShowShowGuildWarRequestResultPlugIn>(p => p.ShowResultAsync(GuildWarRequestResult.RequestSentToGuildMaster)).ConfigureAwait(false);
}
}