feat(hs): HeykelSavasiContext team tracking + balance rule
Adds the HeykelSavasiTeam enum (None/Red/Blue) and the HeykelSavasiContext skeleton (subclass of MiniGameContext) with team registration (AssignTeam/GetTeam/PlayersOf/TeamCount) and a join-balance rule (|Red-Blue| <= 2 after the prospective join), exposed as a pure static IsJoinAllowed for fast unit testing without constructing the full context. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
101
src/GameLogic/MiniGames/HeykelSavasiContext.cs
Normal file
101
src/GameLogic/MiniGames/HeykelSavasiContext.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
// <copyright file="HeykelSavasiContext.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.MiniGames;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MUnique.OpenMU.DataModel.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// The context of the Heykel Savasi (statue war) event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the foundation class for the event: it tracks which team every participating player
|
||||
/// belongs to, and enforces a team-balance rule when players attempt to join a team.
|
||||
/// </remarks>
|
||||
public class HeykelSavasiContext : MiniGameContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum allowed difference between the red and blue team sizes after a join.
|
||||
/// </summary>
|
||||
private const int MaxTeamDifference = 2;
|
||||
|
||||
private readonly ConcurrentDictionary<Player, HeykelSavasiTeam> _teams = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HeykelSavasiContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of this context.</param>
|
||||
/// <param name="definition">The definition of the mini game.</param>
|
||||
/// <param name="gameContext">The game context, to which this game belongs.</param>
|
||||
/// <param name="mapInitializer">The map initializer, which is used when the event starts.</param>
|
||||
public HeykelSavasiContext(MiniGameMapKey key, MiniGameDefinition definition, IGameContext gameContext, IMapInitializer mapInitializer)
|
||||
: base(key, definition, gameContext, mapInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current number of players assigned to the given team.
|
||||
/// </summary>
|
||||
/// <param name="team">The team.</param>
|
||||
/// <returns>The number of players currently assigned to <paramref name="team"/>.</returns>
|
||||
public int TeamCount(HeykelSavasiTeam team) => this._teams.Values.Count(t => t == team);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a player may currently join the given team, based on the team-balance rule.
|
||||
/// </summary>
|
||||
/// <param name="team">The team which a player wants to join.</param>
|
||||
/// <returns><c>true</c> if the join is allowed; otherwise, <c>false</c>.</returns>
|
||||
public bool CanJoin(HeykelSavasiTeam team) => IsJoinAllowed(this.TeamCount(HeykelSavasiTeam.Red), this.TeamCount(HeykelSavasiTeam.Blue), team);
|
||||
|
||||
/// <summary>
|
||||
/// Assigns the given player to the given team.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <param name="team">The team.</param>
|
||||
public void AssignTeam(Player player, HeykelSavasiTeam team) => this._teams[player] = team;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the team of the given player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player.</param>
|
||||
/// <returns>The team of the player, or <see cref="HeykelSavasiTeam.None"/> if the player is not assigned to a team.</returns>
|
||||
public HeykelSavasiTeam GetTeam(Player player) => this._teams.TryGetValue(player, out var team) ? team : HeykelSavasiTeam.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all players which are currently assigned to the given team.
|
||||
/// </summary>
|
||||
/// <param name="team">The team.</param>
|
||||
/// <returns>All players assigned to <paramref name="team"/>.</returns>
|
||||
public IEnumerable<Player> PlayersOf(HeykelSavasiTeam team) => this._teams.Where(kv => kv.Value == team).Select(kv => kv.Key);
|
||||
|
||||
/// <summary>
|
||||
/// Determines, in a pure way, whether joining <paramref name="team"/> is allowed given the current
|
||||
/// team counts, according to the balance rule: the absolute difference between the red and blue
|
||||
/// team sizes must not exceed <see cref="MaxTeamDifference"/> after the prospective join.
|
||||
/// </summary>
|
||||
/// <param name="redCount">The current number of players in the red team.</param>
|
||||
/// <param name="blueCount">The current number of players in the blue team.</param>
|
||||
/// <param name="team">The team which a player wants to join.</param>
|
||||
/// <returns><c>true</c> if the join is allowed; otherwise, <c>false</c>.</returns>
|
||||
internal static bool IsJoinAllowed(int redCount, int blueCount, HeykelSavasiTeam team)
|
||||
{
|
||||
switch (team)
|
||||
{
|
||||
case HeykelSavasiTeam.Red:
|
||||
redCount++;
|
||||
break;
|
||||
case HeykelSavasiTeam.Blue:
|
||||
blueCount++;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return Math.Abs(redCount - blueCount) <= MaxTeamDifference;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user