//
// 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;
}
}