baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
142
src/GameLogic/PlugIns/InvasionEvents/InvasionGameServerState.cs
Normal file
142
src/GameLogic/PlugIns/InvasionEvents/InvasionGameServerState.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
// <copyright file="InvasionGameServerState.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.PlugIns.InvasionEvents;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
|
||||
|
||||
/// <summary>
|
||||
/// Invasion state that is created for every periodic invasion run.
|
||||
/// </summary>
|
||||
public class InvasionGameServerState : PeriodicTaskGameServerState
|
||||
{
|
||||
private readonly HashSet<ushort> _mapIds = [];
|
||||
private readonly Dictionary<ushort, ushort> _selectedMaps = [];
|
||||
private readonly List<ushort> _announcedMapIds = [];
|
||||
private readonly ConcurrentDictionary<Monster, byte> _monsters = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InvasionGameServerState"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">The game context.</param>
|
||||
public InvasionGameServerState(IGameContext context)
|
||||
: base(context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the map identifier used for UI display / map-event state broadcasts.
|
||||
/// <c>null</c> means no event is active or the display map has not been selected yet.
|
||||
/// </summary>
|
||||
public ushort? MapId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of map identifiers on which monsters will spawn this run.
|
||||
/// </summary>
|
||||
public IReadOnlySet<ushort> MapIds => this._mapIds;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the read-only mapping of monster ID to selected map identifier.
|
||||
/// Populated for spawns whose <see cref="SpawnMapStrategy"/> is <see cref="SpawnMapStrategy.RandomMap"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<ushort, ushort> SelectedMaps => this._selectedMaps;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of map identifiers that are named in the invasion start/end broadcast.
|
||||
/// These are the maps on which the announced (featured) monster actually spawns this run,
|
||||
/// so the message always points players at a map that really contains it.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ushort> AnnouncedMapIds => this._announcedMapIds;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maps named in the broadcast message and the single <see cref="MapId"/> used
|
||||
/// for the map-event UI state.
|
||||
/// </summary>
|
||||
/// <param name="mapIds">The maps on which the announced monster spawns this run.</param>
|
||||
internal void SetAnnouncedMaps(IReadOnlyCollection<ushort> mapIds)
|
||||
{
|
||||
this._announcedMapIds.Clear();
|
||||
this._announcedMapIds.AddRange(mapIds);
|
||||
this.MapId = this._announcedMapIds.Count > 0 ? this._announcedMapIds[0] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a map as active for this run and optionally records which map was
|
||||
/// randomly chosen for a particular monster type.
|
||||
/// </summary>
|
||||
/// <param name="mapId">The map identifier to register.</param>
|
||||
/// <param name="monsterId">
|
||||
/// When provided, records the <paramref name="mapId"/> as the chosen map for this monster.
|
||||
/// Pass <c>null</c> for "spawn-on-all-maps" entries.
|
||||
/// </param>
|
||||
internal void RegisterMap(ushort mapId, ushort? monsterId = null)
|
||||
{
|
||||
this._mapIds.Add(mapId);
|
||||
if (monsterId.HasValue)
|
||||
{
|
||||
this._selectedMaps[monsterId.Value] = mapId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all state accumulated from a previous run so the object can be reused.
|
||||
/// </summary>
|
||||
internal void Reset()
|
||||
{
|
||||
this.MapId = null;
|
||||
this._mapIds.Clear();
|
||||
this._selectedMaps.Clear();
|
||||
this._announcedMapIds.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks a monster spawned by this invasion and handles its cleanup on death.
|
||||
/// </summary>
|
||||
/// <param name="monster">The monster to track.</param>
|
||||
internal void AddMonster(Monster monster)
|
||||
{
|
||||
if (this._monsters.TryAdd(monster, 0))
|
||||
{
|
||||
monster.Died += this.OnMonsterDied;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns and disposes all active monsters tracked by this invasion state.
|
||||
/// </summary>
|
||||
internal async ValueTask CleanUpMonstersAsync()
|
||||
{
|
||||
if (this._monsters.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var monsters = this._monsters.Keys.ToArray();
|
||||
var tasks = monsters.Select(async monster =>
|
||||
{
|
||||
this._monsters.TryRemove(monster, out _);
|
||||
monster.Died -= this.OnMonsterDied;
|
||||
|
||||
if (!monster.IsDisposed)
|
||||
{
|
||||
await monster.CurrentMap.RemoveAsync(monster).ConfigureAwait(false);
|
||||
monster.Dispose();
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnMonsterDied(object? sender, DeathInformation e)
|
||||
{
|
||||
if (sender is Monster monster)
|
||||
{
|
||||
this._monsters.TryRemove(monster, out _);
|
||||
monster.Died -= this.OnMonsterDied;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user