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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/Dapr/GuildServer.Host/GuildServerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,28 @@ public ValueTask<bool> GuildExistsAsync([FromBody] string guildName)
return this._guildServer.GetGuildAsync(guildId);
}

/// <summary>
/// Gets the persistent identifier of a guild by its runtime identifier.
/// </summary>
/// <param name="guildId">The runtime guild identifier.</param>
/// <returns>The persistent guild identifier, or <see langword="null"/> if the guild was not found.</returns>
[HttpPost(nameof(IGuildServer.GetPersistentGuildIdAsync))]
public ValueTask<Guid?> GetPersistentGuildIdAsync([FromBody] uint guildId)
{
return this._guildServer.GetPersistentGuildIdAsync(guildId);
}

/// <summary>
/// Gets the persistent alliance master identifier of a guild by its runtime identifier.
/// </summary>
/// <param name="guildId">The runtime guild identifier.</param>
/// <returns>The persistent alliance master guild identifier, or <see langword="null"/> if the guild was not found.</returns>
[HttpPost(nameof(IGuildServer.GetPersistentAllianceMasterGuildIdAsync))]
public ValueTask<Guid?> GetPersistentAllianceMasterGuildIdAsync([FromBody] uint guildId)
{
return this._guildServer.GetPersistentAllianceMasterGuildIdAsync(guildId);
}

/// <summary>
/// Gets the guild id by the guild name.
/// </summary>
Expand Down Expand Up @@ -160,4 +182,4 @@ public ValueTask IncreaseGuildScoreAsync([FromBody] uint guildId)
{
return this._guildServer.IncreaseGuildScoreAsync(guildId);
}
}
}
30 changes: 29 additions & 1 deletion src/Dapr/ServerClients/GuildServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,34 @@ public async ValueTask<bool> GuildExistsAsync(string guildName)
}
}

/// <inheritdoc />
public async ValueTask<Guid?> GetPersistentGuildIdAsync(uint guildId)
{
try
{
return await this._daprClient.InvokeMethodAsync<uint, Guid?>(this._targetAppId, nameof(this.GetPersistentGuildIdAsync), guildId).ConfigureAwait(false);
}
catch (Exception ex)
{
this._logger.LogError(ex, "Unexpected error when retrieving a persistent guild identifier.");
return null;
}
}

/// <inheritdoc />
public async ValueTask<Guid?> GetPersistentAllianceMasterGuildIdAsync(uint guildId)
{
try
{
return await this._daprClient.InvokeMethodAsync<uint, Guid?>(this._targetAppId, nameof(this.GetPersistentAllianceMasterGuildIdAsync), guildId).ConfigureAwait(false);
}
catch (Exception ex)
{
this._logger.LogError(ex, "Unexpected error when retrieving a persistent alliance master guild identifier.");
return null;
}
}

/// <inheritdoc />
public async ValueTask<uint> GetGuildIdByNameAsync(string guildName)
{
Expand Down Expand Up @@ -263,4 +291,4 @@ public async ValueTask<GuildRelationship> GetGuildRelationshipAsync(uint guild1,
return GuildRelationship.None;
}
}
}
}
10 changes: 10 additions & 0 deletions src/DataModel/Configuration/CastleSiegeConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ public partial class CastleSiegeConfiguration
/// </summary>
public int RegisterMinMembers { get; set; } = 20;

/// <summary>
/// Gets or sets the item definition used for Sign of Lord registration.
/// </summary>
public virtual ItemDefinition? SignOfLordItemDefinition { get; set; }

/// <summary>
/// Gets or sets the required level of a Sign of Lord item.
/// </summary>
public byte SignOfLordItemLevel { get; set; } = 3;

/// <summary>
/// Gets or sets the minimum number of seconds a participant must be present in the battle to be eligible for a reward.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions src/GameLogic/CastleSiege/Actions/CastleSiegeGuildReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// <copyright file="CastleSiegeGuildReference.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.CastleSiege.Actions;

/// <summary>
/// A Castle Siege guild identity spanning runtime and persistent identifiers.
/// </summary>
/// <param name="RuntimeId">The runtime guild identifier.</param>
/// <param name="PersistentId">The persistent guild identifier.</param>
/// <param name="Name">The guild name.</param>
internal sealed record CastleSiegeGuildReference(uint RuntimeId, Guid PersistentId, string Name);
70 changes: 70 additions & 0 deletions src/GameLogic/CastleSiege/Actions/CastleSiegeGuildResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// <copyright file="CastleSiegeGuildResolver.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.CastleSiege.Actions;

using MUnique.OpenMU.GameLogic.Views.CastleSiege;
using MUnique.OpenMU.Interfaces;

/// <summary>
/// Resolves runtime guild information to its persistent Castle Siege identity.
/// </summary>
internal static class CastleSiegeGuildResolver
{
/// <summary>
/// Resolves a guild which is allowed to mutate a Castle Siege registration.
/// </summary>
/// <param name="player">The requesting player.</param>
/// <returns>The resolved guild and validation result.</returns>
public static async ValueTask<(CastleSiegeGuildReference? Guild, CastleSiegeRegistrationResult Result)> ResolveAuthorizedGuildAsync(
Player player)
{
if (player.GuildStatus is not { } guildStatus)
{
return (null, CastleSiegeRegistrationResult.NoGuild);
}

if (guildStatus.Position != GuildPosition.GuildMaster
|| player.GameContext is not IGameServerContext gameServerContext)
{
return (null, CastleSiegeRegistrationResult.InvalidGuild);
}

if (await gameServerContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false) is not { Name: not null } guild)
{
return (null, CastleSiegeRegistrationResult.InvalidGuild);
}

if (guild.AllianceGuild is not null
&& !await gameServerContext.GuildServer.IsAllianceMasterAsync(guildStatus.GuildId).ConfigureAwait(false))
{
return (null, CastleSiegeRegistrationResult.InvalidGuild);
}

if (await gameServerContext.GuildServer.GetPersistentGuildIdAsync(guildStatus.GuildId).ConfigureAwait(false) is not { } persistentGuildId)
{
return (null, CastleSiegeRegistrationResult.InvalidGuild);
}

return (new(guildStatus.GuildId, persistentGuildId, guild.Name), CastleSiegeRegistrationResult.Success);
}

/// <summary>
/// Resolves the registration identity visible to any member of a guild or alliance.
/// </summary>
/// <param name="player">The requesting player.</param>
/// <returns>The persistent registration guild identifier, or <see langword="null"/>.</returns>
public static async ValueTask<Guid?> ResolveRegistrationGuildIdAsync(Player player)
{
if (player.GuildStatus is not { } guildStatus
|| player.GameContext is not IGameServerContext gameServerContext)
{
return null;
}

return await gameServerContext.GuildServer
.GetPersistentAllianceMasterGuildIdAsync(guildStatus.GuildId)
.ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// <copyright file="CastleSiegeRegisterGuildAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.CastleSiege.Actions;

using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.CastleSiege;

/// <summary>
/// Validates and processes Castle Siege guild registrations.
/// </summary>
public class CastleSiegeRegisterGuildAction
{
/// <summary>
/// Tries to register the player's guild or alliance for Castle Siege.
/// </summary>
/// <param name="player">The requesting player.</param>
/// <param name="context">The Castle Siege context, if initialized.</param>
/// <returns>A task which represents the asynchronous operation.</returns>
public async ValueTask RegisterAsync(Player player, CastleSiegeContext? context)
{
var (result, guildName) = await RegisterCoreAsync(player, context).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<ICastleSiegeRegistrationResultPlugIn>(
view => view.ShowRegistrationResultAsync(result, guildName)).ConfigureAwait(false);
}

private static async ValueTask<(CastleSiegeRegistrationResult Result, string GuildName)> RegisterCoreAsync(
Player player,
CastleSiegeContext? context)
{
if (context is not { Configuration.Enabled: true })
{
return (CastleSiegeRegistrationResult.Failed, string.Empty);
}

await context.ExecutionLock.WaitAsync().ConfigureAwait(false);
try
{
if (context.CurrentState != CastleSiegeState.RegisterGuild)
{
return (CastleSiegeRegistrationResult.NotRegistrationPeriod, string.Empty);
}

var (guild, resolutionResult) = await CastleSiegeGuildResolver.ResolveAuthorizedGuildAsync(player).ConfigureAwait(false);
if (guild is null)
{
return (resolutionResult, string.Empty);
}

var combinedLevel = player.Level + (int)(player.Attributes?[Stats.MasterLevel] ?? 0);
if (combinedLevel < context.Configuration.RegisterMinLevel)
{
return (CastleSiegeRegistrationResult.LevelInsufficient, guild.Name);
}

if (player.GameContext is not IGameServerContext gameServerContext)
{
return (CastleSiegeRegistrationResult.InvalidGuild, guild.Name);
}

var members = await gameServerContext.GuildServer.GetGuildListAsync(guild.RuntimeId).ConfigureAwait(false);
if (members.Count < context.Configuration.RegisterMinMembers)
{
return (CastleSiegeRegistrationResult.NotEnoughMembers, guild.Name);
}

if (context.RegisteredGuilds.ContainsKey(guild.PersistentId))
{
return (CastleSiegeRegistrationResult.AlreadyRegistered, guild.Name);
}

if (context.SiegeData.OwnerGuildId == guild.PersistentId)
{
return (CastleSiegeRegistrationResult.IsDefender, guild.Name);
}

await context.AddRegistrationAsync(guild.PersistentId, guild.Name).ConfigureAwait(false);
return (CastleSiegeRegistrationResult.Success, guild.Name);
}
finally
{
context.ExecutionLock.Release();
}
}
}
77 changes: 77 additions & 0 deletions src/GameLogic/CastleSiege/Actions/CastleSiegeRegisterMarkAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// <copyright file="CastleSiegeRegisterMarkAction.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.CastleSiege.Actions;

using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic.Views.CastleSiege;

/// <summary>
/// Validates and processes Sign of Lord submissions.
/// </summary>
public class CastleSiegeRegisterMarkAction
{
/// <summary>
/// Tries to submit the Sign of Lord from an inventory slot.
/// </summary>
/// <param name="player">The requesting player.</param>
/// <param name="context">The Castle Siege context, if initialized.</param>
/// <param name="inventorySlot">The inventory slot.</param>
/// <returns>A task which represents the asynchronous operation.</returns>
public async ValueTask RegisterMarkAsync(Player player, CastleSiegeContext? context, byte inventorySlot)
{
var (result, guildName, marks) = await RegisterMarkCoreAsync(player, context, inventorySlot).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<ICastleSiegeMarkRegistrationResultPlugIn>(
view => view.ShowMarkRegistrationResultAsync(result, guildName, marks)).ConfigureAwait(false);
}

private static async ValueTask<(CastleSiegeMarkRegistrationResult Result, string GuildName, int Marks)> RegisterMarkCoreAsync(
Player player,
CastleSiegeContext? context,
byte inventorySlot)
{
if (context is not { Configuration.Enabled: true })
{
return (CastleSiegeMarkRegistrationResult.Failed, string.Empty, 0);
}

await context.ExecutionLock.WaitAsync().ConfigureAwait(false);
try
{
if (context.CurrentState != CastleSiegeState.RegisterMark)
{
return (CastleSiegeMarkRegistrationResult.Failed, string.Empty, 0);
}

var (guild, _) = await CastleSiegeGuildResolver.ResolveAuthorizedGuildAsync(player).ConfigureAwait(false);
if (guild is null
|| !context.RegisteredGuilds.TryGetValue(guild.PersistentId, out var registration))
{
return (CastleSiegeMarkRegistrationResult.GuildNotRegistered, guild?.Name ?? string.Empty, 0);
}

var signOfLord = player.Inventory?.GetItem(inventorySlot);
if (signOfLord is null
|| signOfLord.Definition != context.Configuration.SignOfLordItemDefinition
|| signOfLord.Level != context.Configuration.SignOfLordItemLevel)
{
return (CastleSiegeMarkRegistrationResult.IncorrectItem, guild.Name, registration.Marks);
}

// Persist first so a failed registration never consumes the player's item. If item removal fails afterward,
// the durable mark is preferred over losing an item without receiving credit.
if (await context.IncrementMarksAsync(registration).ConfigureAwait(false) is not { } marks)
{
return (CastleSiegeMarkRegistrationResult.GuildNotRegistered, guild.Name, registration.Marks);
}

await player.DestroyInventoryItemAsync(signOfLord).ConfigureAwait(false);
return (CastleSiegeMarkRegistrationResult.Success, guild.Name, marks);
}
finally
{
context.ExecutionLock.Release();
}
}
}
Loading