// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // using Nito.AsyncEx; namespace MUnique.OpenMU.Tests; using Moq; using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.GameLogic; using MUnique.OpenMU.Pathfinding; /// /// Tests for the game map. /// [TestFixture] public class GameMapTest { private const byte ChunkSize = 8; /// /// An interface which combines several other interfaces which are needed in combination for this test. /// public interface ITestPlayer : ILocateable, IBucketMapObserver, IObservable, ISupportIdUpdate { } /// /// Tests if the discovery of players works when a new player is entering the map in the view range. /// [Test] public async ValueTask TestPlayerEntersMapAsync() { var map = new GameMap(new GameMapDefinition(), TimeSpan.FromSeconds(60), ChunkSize); var player1 = this.GetPlayer(); player1.Object.Position = new Point(100, 100); await map.AddAsync(player1.Object).ConfigureAwait(false); var player2 = this.GetPlayer(); player2.Object.Position = new Point(101, 100); await map.AddAsync(player2.Object).ConfigureAwait(false); player1.Verify(p => p.NewLocateablesInScopeAsync(It.Is>(n => n.Contains(player2.Object))), Times.Once); player2.Verify(p => p.NewLocateablesInScopeAsync(It.Is>(n => n.Contains(player1.Object))), Times.Once); player1.Verify(p => p.LocateableAddedAsync(It.IsAny()), Times.Once); } /// /// Tests if movements of a player into the view range of another player causes /// that the players get notified about each other as soon as they are in view range. /// [Test] public async ValueTask TestPlayerMovesInMapAsync() { var map = new GameMap(new GameMapDefinition(), TimeSpan.FromSeconds(60), ChunkSize); var player1 = this.GetPlayer(); await map.AddAsync(player1.Object).ConfigureAwait(false); var player2 = this.GetPlayer(); player2.Object.Position = new Point(101, 100); await map.AddAsync(player2.Object).ConfigureAwait(false); await map.MoveAsync(player1.Object, new Point(100, 100), new AsyncLock(), 0).ConfigureAwait(false); player1.Verify(p => p.NewLocateablesInScopeAsync(It.Is>(n => n.Contains(player2.Object))), Times.Once); player1.Verify(p => p.NewLocateablesInScopeAsync(It.Is>(n => n.Contains(player1.Object))), Times.Once); player2.Verify(p => p.NewLocateablesInScopeAsync(It.Is>(n => n.Contains(player1.Object))), Times.Once); player2.Verify(p => p.LocateableAddedAsync(It.IsAny()), Times.Once); } /// /// Tests if movements of a player out of the view range of another player causes /// that the players get notified about it. /// [Test] public async ValueTask PlayerMovesOutOfRangeAsync() { var map = new GameMap(new GameMapDefinition(), TimeSpan.FromSeconds(60), ChunkSize); var player1 = this.GetPlayer(); player1.Object.Position = new Point(101, 100); await map.AddAsync(player1.Object).ConfigureAwait(false); var player2 = this.GetPlayer(); player2.Object.Position = new Point(101, 100); await map.AddAsync(player2.Object).ConfigureAwait(false); await map.MoveAsync(player1.Object, new Point(100, 130), new AsyncLock(), 0).ConfigureAwait(false); player1.Verify(p => p.LocateablesOutOfScopeAsync(It.Is>(n => n.Contains(player2.Object))), Times.Once); player2.Verify(p => p.LocateableRemovedAsync(It.IsAny()), Times.Once); } /// /// Tests if movements of a player into and out of the view range of another player causes /// that the players get notified about it. /// [Test] public async ValueTask PlayerMovesOutAndIntoTheRangeAsync() { var map = new GameMap(new GameMapDefinition(), TimeSpan.FromSeconds(60), ChunkSize); var player1 = this.GetPlayer(); player1.Object.Position = new Point(101, 100); await map.AddAsync(player1.Object).ConfigureAwait(false); var player2 = this.GetPlayer(); player2.Object.Position = new Point(101, 100); await map.AddAsync(player2.Object).ConfigureAwait(false); player1.Verify(p => p.NewLocateablesInScopeAsync(It.Is>(n => n.Contains(player2.Object))), Times.Once); player2.Verify(p => p.NewLocateablesInScopeAsync(It.Is>(n => n.Contains(player1.Object))), Times.Once); player1.Verify(p => p.LocateableAddedAsync(It.IsAny()), Times.Once); player1.Invocations.Clear(); player2.Invocations.Clear(); await map.MoveAsync(player1.Object, new Point(100, 130), new AsyncLock(), 0).ConfigureAwait(false); player1.Verify(p => p.LocateablesOutOfScopeAsync(It.Is>(n => n.Contains(player2.Object))), Times.Once); player2.Verify(p => p.LocateableRemovedAsync(It.IsAny()), Times.Once); player1.Invocations.Clear(); player2.Invocations.Clear(); await map.MoveAsync(player2.Object, new Point(101, 130), new AsyncLock(), 0).ConfigureAwait(false); player2.Verify(p => p.NewLocateablesInScopeAsync(It.Is>(n => n.Contains(player1.Object))), Times.Once); player1.Verify(p => p.LocateableAddedAsync(It.IsAny()), Times.Once); } /// /// Tests the performance of the movements. Not a standard test. /// /// [Test] public async ValueTask TestPerformanceMoveAsync() { var map = new GameMap(new GameMapDefinition(), TimeSpan.FromSeconds(60), ChunkSize); var player1 = this.GetPlayer(); await map.AddAsync(player1.Object).ConfigureAwait(false); var player2 = this.GetPlayer(); player2.Object.Position = new Point(101, 100); await map.AddAsync(player2.Object).ConfigureAwait(false); var sw = new System.Diagnostics.Stopwatch(); sw.Start(); var moveLock = new AsyncLock(); for (int i = 0; i < 1000; i++) { await map.MoveAsync(player1.Object, new Point((byte)(100 + (i % 30)), (byte)(100 + (i % 30))), moveLock, 0).ConfigureAwait(false); } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); } /// /// Tests if the players in view range get notified when another player leaves the map. /// [Test] public async ValueTask TestPlayerLeavesMapAsync() { var map = new GameMap(new GameMapDefinition(), TimeSpan.FromSeconds(60), ChunkSize); var player1 = this.GetPlayer(); player1.Object.Position = new Point(100, 100); await map.AddAsync(player1.Object).ConfigureAwait(false); var player2 = this.GetPlayer(); player2.Object.Position = new Point(101, 100); await map.AddAsync(player2.Object).ConfigureAwait(false); await map.RemoveAsync(player2.Object).ConfigureAwait(false); Assert.AreEqual(player2.Object.ObservingBuckets.Count, 0); player1.Verify(p => p.LocateableRemovedAsync(It.IsAny()), Times.Once); player2.Verify(p => p.LocateableRemovedAsync(It.IsAny()), Times.Once); Assert.That(player1.Object.Observers.Count, Is.EqualTo(0)); Assert.That(player2.Object.Observers.Count, Is.EqualTo(0)); } /// /// ADAMU-CUSTOM: remote-NPC — GetNpcByNumber, definition number ile spawn olmuş NPC'yi bulur; /// bilinmeyen number için null döner. (upstream merge bu özelliği bozarsa bu test kırılır.) /// [Test] public async ValueTask GetNpcByNumberFindsSpawnedNpcAsync() { var map = new GameMap(new GameMapDefinition(), TimeSpan.FromSeconds(60), ChunkSize); var definition = new MUnique.OpenMU.DataModel.Configuration.MonsterDefinition { Number = 540 }; var spawnArea = new MUnique.OpenMU.DataModel.Configuration.MonsterSpawnArea { MonsterDefinition = definition }; var npc = new MUnique.OpenMU.GameLogic.NPC.NonPlayerCharacter(spawnArea, definition, map) { Position = new Point(100, 100), }; await map.AddAsync(npc).ConfigureAwait(false); Assert.That(map.GetNpcByNumber(540), Is.SameAs(npc)); Assert.That(map.GetNpcByNumber(999), Is.Null); } private Mock GetPlayer() { var player = new Mock(); player.SetupAllProperties(); player.As().SetupGet(p => p.Id).Returns(() => (player.Object as ISupportIdUpdate).Id); player.Setup(p => p.ObservingBuckets).Returns(new List>()); player.Setup(p => p.Observers).Returns(new HashSet()); player.Setup(p => p.ObserverLock).Returns(new AsyncReaderWriterLock()); player.Setup(p => p.InfoRange).Returns(20); player.Setup(p => p.AddObserverAsync(It.IsAny())).Callback(o => player.Object.Observers.Add(o)); player.Setup(p => p.RemoveObserverAsync(It.IsAny())).Callback(o => player.Object.Observers.Remove(o)); return player; } }