// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.GameLogic.MiniGames; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using MUnique.OpenMU.DataModel.Configuration; /// /// The context of the Heykel Savasi (statue war) event. /// /// /// 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. /// public class HeykelSavasiContext : MiniGameContext { /// /// The maximum allowed difference between the red and blue team sizes after a join. /// private const int MaxTeamDifference = 2; /// /// The X1/Y1 anchor of the red team's base spawn gate. /// Must match Gates.cs targetGates 600 (RedBase). /// private const byte RedBaseAnchorX1 = 20; /// /// The X1/Y1 anchor of the red team's base spawn gate. /// Must match Gates.cs targetGates 600 (RedBase). /// private const byte RedBaseAnchorY1 = 20; /// /// The X1/Y1 anchor of the blue team's base spawn gate. /// Must match Gates.cs targetGates 601 (BlueBase). /// private const byte BlueBaseAnchorX1 = 220; /// /// The X1/Y1 anchor of the blue team's base spawn gate. /// Must match Gates.cs targetGates 601 (BlueBase). /// private const byte BlueBaseAnchorY1 = 220; private readonly ConcurrentDictionary _teams = new(); /// /// Initializes a new instance of the class. /// /// The key of this context. /// The definition of the mini game. /// The game context, to which this game belongs. /// The map initializer, which is used when the event starts. public HeykelSavasiContext(MiniGameMapKey key, MiniGameDefinition definition, IGameContext gameContext, IMapInitializer mapInitializer) : base(key, definition, gameContext, mapInitializer) { } /// /// Gets the current number of players assigned to the given team. /// /// The team. /// The number of players currently assigned to . public int TeamCount(HeykelSavasiTeam team) => this._teams.Values.Count(t => t == team); /// /// Determines whether a player may currently join the given team, based on the team-balance rule. /// /// The team which a player wants to join. /// true if the join is allowed; otherwise, false. public bool CanJoin(HeykelSavasiTeam team) => IsJoinAllowed(this.TeamCount(HeykelSavasiTeam.Red), this.TeamCount(HeykelSavasiTeam.Blue), team); /// /// Assigns the given player to the given team. /// /// The player. /// The team. public void AssignTeam(Player player, HeykelSavasiTeam team) => this._teams[player] = team; /// /// Gets the team of the given player. /// /// The player. /// The team of the player, or if the player is not assigned to a team. public HeykelSavasiTeam GetTeam(Player player) => this._teams.TryGetValue(player, out var team) ? team : HeykelSavasiTeam.None; /// /// Gets the base spawn gate for the given team. /// /// The team. /// The which is the base spawn point of . /// /// The gate is resolved by matching the X1/Y1 anchor of the gates created for this event's map /// (see Gates.cs targetGates 600/601, added in Task 0.3). Red anchors at (20, 20), Blue at (220, 220). /// public ExitGate GetTeamSpawnGate(HeykelSavasiTeam team) { var (anchorX1, anchorY1) = team == HeykelSavasiTeam.Blue ? (BlueBaseAnchorX1, BlueBaseAnchorY1) : (RedBaseAnchorX1, RedBaseAnchorY1); return this.Definition.Entrance!.Map!.ExitGates.First(g => g.X1 == anchorX1 && g.Y1 == anchorY1); } /// /// Gets all players which are currently assigned to the given team. /// /// The team. /// All players assigned to . public IEnumerable PlayersOf(HeykelSavasiTeam team) => this._teams.Where(kv => kv.Value == team).Select(kv => kv.Key); /// /// Determines, in a pure way, whether joining 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 after the prospective join. /// /// The current number of players in the red team. /// The current number of players in the blue team. /// The team which a player wants to join. /// true if the join is allowed; otherwise, false. 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; } }