text:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.Players;

namespace Subterfuge.Remake.Test.Core.Components
{
    [TestClass]
    public class DrillerCarrierTest
    {
        private Mock<IEntity> _mockEntity;
        private Player playerOne = new Player(new SimpleUser() { Id = "1" });
        private Player playerTwo = new Player(new SimpleUser() { Id = "2" });

        private void MockDrillerCarrierEntity(
            int initialDrillers,
            Player owner
            )
        {
            _mockEntity = new Mock<IEntity>();
            _mockEntity.Setup(it => it.GetComponent<DrillerCarrier>())
                .Returns(new DrillerCarrier(_mockEntity.Object, initialDrillers, owner));
        }

        [TestMethod]
        public void CanInitializeDrillerCarrier()
        {
            MockDrillerCarrierEntity(0, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
        }

        [TestMethod]
        public void DrillerCarrierSetsInitialDrillers()
        {
            var initialDrillers = 10;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.AreEqual(initialDrillers, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());

            initialDrillers = 20;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.AreEqual(initialDrillers, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());
        }

        [TestMethod]
        public void DrillerCarrierSetsInitialOwner()
        {
            MockDrillerCarrierEntity(0, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.AreEqual(playerOne, _mockEntity.Object.GetComponent<DrillerCarrier>().GetOwner());

            MockDrillerCarrierEntity(0, playerTwo);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.AreEqual(playerTwo, _mockEntity.Object.GetComponent<DrillerCarrier>().GetOwner());
        }

        [TestMethod]
        public void CanAddDrillersToDrillerCarrier()
        {
            var initialDrillers = 0;
            var drillersToAdd = 100;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            _mockEntity.Object.GetComponent<DrillerCarrier>().AlterDrillers(drillersToAdd);
            Assert.AreEqual(initialDrillers + drillersToAdd,
                _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());
        }

        [TestMethod]
        public void CanRemoveDrillersFromDrillerCarrier()
        {
            var initialDrillers = 100;
            var drillersToRemove = 40;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            _mockEntity.Object.GetComponent<DrillerCarrier>().AlterDrillers(drillersToRemove * -1);
            Assert.AreEqual(initialDrillers - drillersToRemove, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());
            Assert.AreEqual(false, _mockEntity.Object.GetComponent<DrillerCarrier>().IsCaptured());
        }

        [TestMethod]
        public void RemovingMoreThanTheTotalDrillerCountCapturesTheCarrier()
        {
            var initialDrillers = 50;
            var drillersToRemove = 100;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            _mockEntity.Object.GetComponent<DrillerCarrier>().AlterDrillers(drillersToRemove * -1);
            Assert.AreEqual(0, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());
        }

        [TestMethod]
        public void CanSetDrillerCountOfDrillerCarrier()
        {
            var initialDrillers = 100;
            var newDrillers = 40;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            _mockEntity.Object.GetComponent<DrillerCarrier>().SetDrillerCount(newDrillers);
            Assert.AreEqual(newDrillers, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());
        }

        [TestMethod]
        public void DrillerCarrierHasDrillers()
        {
            var initialDrillers = 100;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.IsTrue(_mockEntity.Object.GetComponent<DrillerCarrier>().HasDrillers(75));
            Assert.IsTrue(_mockEntity.Object.GetComponent<DrillerCarrier>().HasDrillers(100));
            Assert.IsFalse(_mockEntity.Object.GetComponent<DrillerCarrier>().HasDrillers(101));
            Assert.IsFalse(_mockEntity.Object.GetComponent<DrillerCarrier>().HasDrillers(600));
        }

        [TestMethod]
        public void CanSetNewOwnerAndDrillerCountOfCarrier()
        {
            var initialDrillers = 100;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.AreEqual(playerOne, _mockEntity.Object.GetComponent<DrillerCarrier>().GetOwner());
            Assert.AreEqual(initialDrillers, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());

            var newDrillerCount = 20;
            _mockEntity.Object.GetComponent<DrillerCarrier>().SetNewOwner(playerTwo, newDrillerCount);
            Assert.AreEqual(playerTwo, _mockEntity.Object.GetComponent<DrillerCarrier>().GetOwner());
            Assert.AreEqual(newDrillerCount, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());
        }

        [TestMethod]
        public void CanSetOwner()
        {
            var initialDrillers = 100;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.AreEqual(playerOne, _mockEntity.Object.GetComponent<DrillerCarrier>().GetOwner());

            _mockEntity.Object.GetComponent<DrillerCarrier>().SetOwner(playerTwo);
            Assert.AreEqual(playerTwo, _mockEntity.Object.GetComponent<DrillerCarrier>().GetOwner());
        }

        [TestMethod]
        public void CanSetCaptured()
        {
            var initialDrillers = 100;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.AreEqual(false, _mockEntity.Object.GetComponent<DrillerCarrier>().IsCaptured());

            _mockEntity.Object.GetComponent<DrillerCarrier>().SetCaptured(true);
            Assert.AreEqual(true, _mockEntity.Object.GetComponent<DrillerCarrier>().IsCaptured());

            _mockEntity.Object.GetComponent<DrillerCarrier>().SetCaptured(false);
            Assert.AreEqual(false, _mockEntity.Object.GetComponent<DrillerCarrier>().IsCaptured());
        }

        [TestMethod]
        public void CanDestroy()
        {
            var initialDrillers = 100;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.AreEqual(false, _mockEntity.Object.GetComponent<DrillerCarrier>().IsDestroyed());
            Assert.AreEqual(initialDrillers, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());

            _mockEntity.Object.GetComponent<DrillerCarrier>().Destroy();
            Assert.AreEqual(true, _mockEntity.Object.GetComponent<DrillerCarrier>().IsDestroyed());
            Assert.AreEqual(0, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());
        }

        [TestMethod]
        public void CanAddDrillersAfterBeingDestroyed()
        {
            var initialDrillers = 100;
            MockDrillerCarrierEntity(initialDrillers, playerOne);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<DrillerCarrier>());
            Assert.AreEqual(false, _mockEntity.Object.GetComponent<DrillerCarrier>().IsDestroyed());
            Assert.AreEqual(initialDrillers, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());

            _mockEntity.Object.GetComponent<DrillerCarrier>().Destroy();
            Assert.AreEqual(true, _mockEntity.Object.GetComponent<DrillerCarrier>().IsDestroyed());
            Assert.AreEqual(0, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());

            _mockEntity.Object.GetComponent<DrillerCarrier>().AlterDrillers(50);
            Assert.AreEqual(true, _mockEntity.Object.GetComponent<DrillerCarrier>().IsDestroyed());
            Assert.AreEqual(50, _mockEntity.Object.GetComponent<DrillerCarrier>().GetDrillerCount());
        }

    }
}


text:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Subterfuge.Remake.Core.Entities;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;

namespace Subterfuge.Remake.Test.Core.Components
{
    [TestClass]
    public class SpeedManagerTest
    {
        private Mock<IEntity> _mockEntity;

        public void MockSpeedManagerEntity(
            float initialSpeed = 1.0f
        )
        {
            _mockEntity = new Mock<IEntity>();
            _mockEntity.Setup(it => it.GetComponent<SpeedManager>())
                .Returns(new SpeedManager(_mockEntity.Object, initialSpeed));
        }

        [TestMethod]
        public void CanInitializeSpeedManager()
        {
            MockSpeedManagerEntity();
            Assert.IsNotNull(_mockEntity.Object.GetComponent<SpeedManager>());
        }

        [TestMethod]
        public void CanSetTheInitialSpeed()
        {
            float initialSpeed = 10.0f;
            MockSpeedManagerEntity(initialSpeed);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<SpeedManager>());
            Assert.AreEqual(initialSpeed, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());
        }

        [TestMethod]
        public void CanIncreaseSpeed()
        {
            float initialSpeed = 10.0f;
            MockSpeedManagerEntity(initialSpeed);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<SpeedManager>());
            Assert.AreEqual(initialSpeed, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());

            var increaseBy = 0.5f;
            _mockEntity.Object.GetComponent<SpeedManager>().IncreaseSpeed(increaseBy);
            Assert.AreEqual(initialSpeed + increaseBy, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());
        }

        [TestMethod]
        public void CanDecreaseSpeed()
        {
            float initialSpeed = 10.0f;
            MockSpeedManagerEntity(initialSpeed);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<SpeedManager>());
            Assert.AreEqual(initialSpeed, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());

            var decreaseBy = 0.5f;
            _mockEntity.Object.GetComponent<SpeedManager>().DecreaseSpeed(decreaseBy);
            Assert.AreEqual(initialSpeed - decreaseBy, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());
        }

        [TestMethod]
        public void CanSetSpeed()
        {
            float initialSpeed = 10.0f;
            MockSpeedManagerEntity(initialSpeed);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<SpeedManager>());
            Assert.AreEqual(initialSpeed, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());

            var newSpeed = 5.0f;
            _mockEntity.Object.GetComponent<SpeedManager>().SetSpeed(newSpeed);
            Assert.AreEqual(newSpeed, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());
        }

        // Event Handler tests
        [TestMethod]
        public void IncreasingSpeedTriggersSpeedChangedEvent()
        {
            float initialSpeed = 10.0f;
            MockSpeedManagerEntity(initialSpeed);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<SpeedManager>());
            Assert.AreEqual(initialSpeed, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());

            OnSpeedChangedEventArgs speedChangeArgs = null;
            _mockEntity.Object.GetComponent<SpeedManager>().OnSpeedChanged += (sender, args) =>
            {
                speedChangeArgs = args;
            };

            var increaseBy = 0.5f;
            _mockEntity.Object.GetComponent<SpeedManager>().IncreaseSpeed(increaseBy);
            Assert.AreEqual(initialSpeed + increaseBy, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());

            Assert.IsNotNull(speedChangeArgs);
            Assert.AreEqual(initialSpeed, speedChangeArgs.PreviousSpeed);
            Assert.AreEqual(initialSpeed + increaseBy, speedChangeArgs.NewSpeed);
            Assert.AreEqual(_mockEntity.Object.GetComponent<SpeedManager>(), speedChangeArgs.SpeedManager);
        }

        [TestMethod]
        public void DecreasingSpeedTriggersSpeedChangedEvent()
        {
            float initialSpeed = 10.0f;
            MockSpeedManagerEntity(initialSpeed);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<SpeedManager>());
            Assert.AreEqual(initialSpeed, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());

            OnSpeedChangedEventArgs speedChangeArgs = null;
            _mockEntity.Object.GetComponent<SpeedManager>().OnSpeedChanged += (sender, args) =>
            {
                speedChangeArgs = args;
            };

            var decreaseBy = 0.5f;
            _mockEntity.Object.GetComponent<SpeedManager>().DecreaseSpeed(decreaseBy);
            Assert.AreEqual(initialSpeed - decreaseBy, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());

            Assert.IsNotNull(speedChangeArgs);
            Assert.AreEqual(initialSpeed, speedChangeArgs.PreviousSpeed);
            Assert.AreEqual(initialSpeed - decreaseBy, speedChangeArgs.NewSpeed);
            Assert.AreEqual(_mockEntity.Object.GetComponent<SpeedManager>(), speedChangeArgs.SpeedManager);
        }

        [TestMethod]
        public void SettingSpeedTriggersSpeedChangedEvent()
        {
            float initialSpeed = 10.0f;
            MockSpeedManagerEntity(initialSpeed);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<SpeedManager>());
            Assert.AreEqual(initialSpeed, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());

            OnSpeedChangedEventArgs speedChangeArgs = null;
            _mockEntity.Object.GetComponent<SpeedManager>().OnSpeedChanged += (sender, args) =>
            {
                speedChangeArgs = args;
            };

            var newSpeed = 5.0f;
            _mockEntity.Object.GetComponent<SpeedManager>().DecreaseSpeed(newSpeed);
            Assert.AreEqual(newSpeed, _mockEntity.Object.GetComponent<SpeedManager>().GetSpeed());

            Assert.IsNotNull(speedChangeArgs);
            Assert.AreEqual(initialSpeed, speedChangeArgs.PreviousSpeed);
            Assert.AreEqual(newSpeed, speedChangeArgs.NewSpeed);
            Assert.AreEqual(_mockEntity.Object.GetComponent<SpeedManager>(), speedChangeArgs.SpeedManager);
        }
    }
}

repo_name: Subterfuge-Revived/Remake-Core

repo_language: C#

mime_type: text/plain


text:
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core;
using Subterfuge.Remake.Core.Entities;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.Entities.Positions;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;
using Subterfuge.Remake.Core.Topologies;

namespace Subterfuge.Remake.Test.Core.Components
{
    [TestClass]
    public class VisionManagerTest
    {
        private Mock<IEntity> _mockEntity;
        private Mock<IEntity> _mockSecondEntity;

        private List<Player> players;
        private TimeMachine _timeMachine;

        public void MockVisionManagerEntity(
            RftVector initialLocation,
            IEntity destination,
            GameTick startTime,
            float visionRange = 1.0f
        )
        {

            _mockEntity = new Mock<IEntity>();
            _mockSecondEntity = new Mock<IEntity>();
            _mockEntity.Setup(it => it.GetComponent<SpeedManager>())
                .Returns(new SpeedManager(_mockEntity.Object, 1.0f));
            _mockEntity.Setup(it => it.GetComponent<PositionManager>())
                .Returns(new PositionManager(_mockEntity.Object, EntityAtPosition(initialLocation), destination, startTime, _timeMachine));
            _mockEntity.Setup(it => it.GetComponent<VisionManager>())
                .Returns(new VisionManager(_mockEntity.Object, visionRange));
        }

        [TestInitialize]
        public void Setup()
        {
            players = new List<Player>()
            {
                new Player(new SimpleUser() { Id = "1" })
            };
            _timeMachine = new TimeMachine(new GameState(players));
        }

        [TestMethod]
        public void CanInitializeVisionManager()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);

            MockVisionManagerEntity(initialLocation, destination, new GameTick());
            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
        }

        [TestMethod]
        public void CanSetInitialVisionRange()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);
            var initialVisionRange = 10.0f;

            MockVisionManagerEntity(initialLocation, destination, new GameTick(), initialVisionRange);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
            Assert.AreEqual(initialVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());
        }

        [TestMethod]
        public void CanChangeTheVisionRange()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);
            var initialVisionRange = 10.0f;

            MockVisionManagerEntity(initialLocation, destination, new GameTick(), initialVisionRange);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
            Assert.AreEqual(initialVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());

            float newVisionRange = 1.0f;
            var mockGameState = new Mock<IGameState>();
            mockGameState.Setup(it => it.EntitesInRange(It.IsAny<float>(), It.IsAny<RftVector>())).Returns(new List<IEntity>());
            _mockEntity.Object.GetComponent<VisionManager>().SetVisionRange(newVisionRange, mockGameState.Object);

            Assert.AreEqual(newVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());
        }

        [TestMethod]
        public void PositionInsideOfVisionRangeIsDetectedInVisionRange()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);
            var initialVisionRange = 1.0f;

            MockVisionManagerEntity(initialLocation, destination, new GameTick(), initialVisionRange);
            var positionInsideVisionRange = new RftVector(0.5f, 0.0f);
            _mockSecondEntity.Setup(it => it.GetComponent<SpeedManager>())
                .Returns(new SpeedManager(_mockSecondEntity.Object,0.0f));
            _mockSecondEntity.Setup(it => it.GetComponent<PositionManager>())
                .Returns(new PositionManager(_mockSecondEntity.Object, EntityAtPosition(positionInsideVisionRange), destination, new GameTick(), _timeMachine));
            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
            Assert.AreEqual(initialVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());
            Assert.IsTrue(_mockEntity.Object.GetComponent<VisionManager>().IsInVisionRange(_mockSecondEntity.Object.GetComponent<PositionManager>()));
        }

        [TestMethod]
        public void GetEntitesInVisionRangeReturnsEntitesInVisionRange()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);
            var initialVisionRange = 1.0f;

            MockVisionManagerEntity(initialLocation, destination, new GameTick(), initialVisionRange);

            var positionInsideVisionRange = new RftVector(0.5f, 0.0f);
            _mockSecondEntity.Setup(it => it.GetComponent<SpeedManager>())
                .Returns(new SpeedManager(_mockSecondEntity.Object,0.0f));
            _mockSecondEntity.Setup(it => it.GetComponent<PositionManager>())
                .Returns(new PositionManager(_mockSecondEntity.Object, EntityAtPosition(positionInsideVisionRange), destination, new GameTick(), _timeMachine));
            List<IEntity> secondEntity = new List<IEntity> { _mockSecondEntity.Object };
            var mockGameState = new Mock<IGameState>();
            mockGameState.Setup(it => it.EntitesInRange(It.IsAny<float>(), It.IsAny<RftVector>())).Returns(secondEntity);
            mockGameState.Setup(it => it.GetAllGameObjects()).Returns(secondEntity);

            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
            Assert.AreEqual(initialVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());
            Assert.IsTrue(_mockEntity.Object.GetComponent<VisionManager>().GetEntitiesInVisionRange(mockGameState.Object).Contains(_mockSecondEntity.Object));
        }

        [TestMethod]
        public void PositionOutsideOfVisionRangeIsDetectedOutOfVisionRange()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);
            var initialVisionRange = 1.0f;

            MockVisionManagerEntity(initialLocation, destination, new GameTick(), initialVisionRange);

            var positionOutsideVisionRange = new RftVector(1.1f, 0.0f);
            _mockSecondEntity.Setup(it => it.GetComponent<SpeedManager>())
                .Returns(new SpeedManager(_mockSecondEntity.Object,0.0f));
            _mockSecondEntity.Setup(it => it.GetComponent<PositionManager>())
                .Returns(new PositionManager(_mockSecondEntity.Object, EntityAtPosition(positionOutsideVisionRange), destination, new GameTick(), _timeMachine));
            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
            Assert.AreEqual(initialVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());
            Assert.IsFalse(_mockEntity.Object.GetComponent<VisionManager>().IsInVisionRange(_mockSecondEntity.Object.GetComponent<PositionManager>()));
        }

        [TestMethod]
        public void GetEntitesInVisionRangeDoesNotReturnsEntitesOutsideVisionRange()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);
            var initialVisionRange = 1.0f;

            MockVisionManagerEntity(initialLocation, destination, new GameTick(), initialVisionRange);

            var positionInsideVisionRange = new RftVector(0.5f, 0.0f);
            _mockSecondEntity.Setup(it => it.GetComponent<SpeedManager>())
                .Returns(new SpeedManager(_mockSecondEntity.Object,0.0f));
            _mockSecondEntity.Setup(it => it.GetComponent<PositionManager>())
                .Returns(new PositionManager(_mockSecondEntity.Object, EntityAtPosition(positionInsideVisionRange), destination, new GameTick(), _timeMachine));
            List<IEntity> emptyList = new List<IEntity>();
            List<IEntity> secondEntity = new List<IEntity> { _mockSecondEntity.Object };
            var mockGameState = new Mock<IGameState>();
            mockGameState.Setup(it => it.EntitesInRange(It.IsAny<float>(), It.IsAny<RftVector>())).Returns(emptyList);
            mockGameState.Setup(it => it.GetAllGameObjects()).Returns(secondEntity);

            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
            Assert.AreEqual(initialVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());
            Assert.IsFalse(_mockEntity.Object.GetComponent<VisionManager>().GetEntitiesInVisionRange(mockGameState.Object).Contains(_mockSecondEntity.Object));
        }

        // Test Vision event handlers
        [TestMethod]
        public void WhenVisionRangeIsChangedVisionRangeChangeEventIsRaised()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);
            var initialVisionRange = 10.0f;

            MockVisionManagerEntity(initialLocation, destination, new GameTick(), initialVisionRange);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
            Assert.AreEqual(initialVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());

            OnVisionRangeChangeEventArgs visionRangeChangeArgs = null;
            _mockEntity.Object.GetComponent<VisionManager>().OnVisionRangeChange += (sender, args) =>
            {
                visionRangeChangeArgs = args;
            };

            float newVisionRange = 1.0f;
            var mockGameState = new Mock<IGameState>();
            mockGameState.Setup(it => it.EntitesInRange(It.IsAny<float>(), It.IsAny<RftVector>())).Returns(new List<IEntity>());
            _mockEntity.Object.GetComponent<VisionManager>().SetVisionRange(newVisionRange, mockGameState.Object);

            Assert.AreEqual(newVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());
            Assert.IsNotNull(visionRangeChangeArgs);
            Assert.AreEqual(initialVisionRange, visionRangeChangeArgs.PreviousVisionRange);
            Assert.AreEqual(newVisionRange, visionRangeChangeArgs.NewVisionRange);
            Assert.AreEqual(_mockEntity.Object.GetComponent<VisionManager>(), visionRangeChangeArgs.VisionManager);
        }

        [TestMethod]
        public void WhenANewEntityEntersTheVisionRangeAnOnEntityEnterVisionRangeEventIsTriggered()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);
            var initialVisionRange = 1.0f;

            MockVisionManagerEntity(initialLocation, destination, new GameTick(), initialVisionRange);

            var positionInsideVisionRange = new RftVector(0.5f, 0.0f);
            _mockSecondEntity.Setup(it => it.GetComponent<SpeedManager>())
                .Returns(new SpeedManager(_mockSecondEntity.Object,0.0f));
            _mockSecondEntity.Setup(it => it.GetComponent<PositionManager>())
                .Returns(new PositionManager(_mockSecondEntity.Object, EntityAtPosition(positionInsideVisionRange), destination, new GameTick(), _timeMachine));
            List<IEntity> emptyList = new List<IEntity>();
            List<IEntity> secondEntity = new List<IEntity> { _mockSecondEntity.Object };
            var mockGameState = new Mock<IGameState>();
            mockGameState.Setup(it => it.EntitesInRange(It.IsAny<float>(), It.IsAny<RftVector>())).Returns(emptyList);
            mockGameState.Setup(it => it.GetAllGameObjects()).Returns(secondEntity);

            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
            Assert.AreEqual(initialVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());
            Assert.IsFalse(_mockEntity.Object.GetComponent<VisionManager>().GetEntitiesInVisionRange(mockGameState.Object).Contains(_mockSecondEntity.Object));

            // Update to have the entity in rage.
            mockGameState.Setup(it => it.EntitesInRange(It.IsAny<float>(), It.IsAny<RftVector>())).Returns(secondEntity);

            OnEntityEnterVisionRangeEventArgs enterVisionArgs = null;
            _mockEntity.Object.GetComponent<VisionManager>().OnEntityEnterVisionRange += (sender, args) =>
            {
                enterVisionArgs = args;
            };

            Assert.IsTrue(_mockEntity.Object.GetComponent<VisionManager>().GetEntitiesInVisionRange(mockGameState.Object).Contains(_mockSecondEntity.Object));
            Assert.IsNotNull(enterVisionArgs);
            Assert.AreEqual(_mockSecondEntity.Object, enterVisionArgs.EntityInVision);
            Assert.AreEqual(_mockEntity.Object.GetComponent<VisionManager>(), enterVisionArgs.VisionManager);
        }

        [TestMethod]
        public void WhenAnEntityLeavesTheVisionRangeAnOnEntityLeaveVisionRangeEventIsTriggered()
        {
            RftVector.Map = new Rft(100, 100);
            var destination = new Generator("A", new RftVector(0, 0), _timeMachine);
            var initialLocation = new RftVector(0, 0);
            var initialVisionRange = 1.0f;

            MockVisionManagerEntity(initialLocation, destination, new GameTick(), initialVisionRange);

            var positionInsideVisionRange = new RftVector(0.5f, 0.0f);
            _mockSecondEntity.Setup(it => it.GetComponent<SpeedManager>())
                .Returns(new SpeedManager(_mockSecondEntity.Object,0.0f));
            _mockSecondEntity.Setup(it => it.GetComponent<PositionManager>())
                .Returns(new PositionManager(_mockSecondEntity.Object, EntityAtPosition(positionInsideVisionRange), destination, new GameTick(), _timeMachine));
            List<IEntity> secondEntity = new List<IEntity> { _mockSecondEntity.Object };
            var mockGameState = new Mock<IGameState>();
            mockGameState.Setup(it => it.EntitesInRange(It.IsAny<float>(), It.IsAny<RftVector>())).Returns(secondEntity);
            mockGameState.Setup(it => it.GetAllGameObjects()).Returns(secondEntity);

            Assert.IsNotNull(_mockEntity.Object.GetComponent<VisionManager>());
            Assert.AreEqual(initialVisionRange, _mockEntity.Object.GetComponent<VisionManager>().GetVisionRange());
            Assert.IsTrue(_mockEntity.Object.GetComponent<VisionManager>().GetEntitiesInVisionRange(mockGameState.Object).Contains(_mockSecondEntity.Object));

            // Make entity leave vision range
            mockGameState.Setup(it => it.EntitesInRange(It.IsAny<float>(), It.IsAny<RftVector>())).Returns(new List<IEntity>());

            OnEntityLeaveVisionRangeEventArgs leaveVisionArgs = null;
            _mockEntity.Object.GetComponent<VisionManager>().OnEntityLeaveVisionRange += (sender, args) =>
            {
                leaveVisionArgs = args;
            };

            Assert.IsFalse(_mockEntity.Object.GetComponent<VisionManager>().GetEntitiesInVisionRange(mockGameState.Object).Contains(_mockSecondEntity.Object));
            Assert.IsNotNull(leaveVisionArgs);
            Assert.AreEqual(_mockSecondEntity.Object, leaveVisionArgs.EntityLeavingVision);
            Assert.AreEqual(_mockEntity.Object.GetComponent<VisionManager>(), leaveVisionArgs.VisionManager);
        }

        private IEntity EntityAtPosition(RftVector position)
        {
            return new Generator(Guid.NewGuid().ToString(), position, _timeMachine);
        }
    }
}


text:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Subterfuge.Remake.Core.Entities;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;

namespace Subterfuge.Remake.Test.Core.Components
{
    [TestClass]
    public class ShieldManagerTest
    {
        private Mock<IEntity> _mockEntity;

        private void MockShieldManagerEntity(
            int shieldCapacity,
            int initialShields = 0
        )
        {
            _mockEntity = new Mock<IEntity>();
            _mockEntity.Setup(it => it.GetComponent<ShieldManager>())
                .Returns(new ShieldManager(_mockEntity.Object, shieldCapacity, initialShields));
        }

        [TestMethod]
        public void CanInitializeShieldManager()
        {
            MockShieldManagerEntity(10);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
        }

        [TestMethod]
        public void CanSetInitialShieldValue()
        {
            var initialShields = 10;
            var initialShieldCapacity = 20;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShields, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());
        }

        [TestMethod]
        public void CanSetInitialShieldCapacity()
        {
            var initialShields = 10;
            MockShieldManagerEntity(initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(0, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShields, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());
        }

        [TestMethod]
        public void CannotSetInitialShieldValueAboveCapacity()
        {
            var initialShields = 100;
            var initialShieldCapacity = 20;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());
        }

        [TestMethod]
        public void CanSetShieldValue()
        {
            var initialShieldCapacity = 10;
            MockShieldManagerEntity(initialShieldCapacity);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());
            Assert.AreEqual(0, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());

            var newShieldCount = 5;
            _mockEntity.Object.GetComponent<ShieldManager>().SetShields(newShieldCount);
            Assert.AreEqual(newShieldCount, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
        }

        [TestMethod]
        public void CannotSetShieldValueAboveCapacity()
        {
            var initialShieldCapacity = 10;
            MockShieldManagerEntity(initialShieldCapacity);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());
            Assert.AreEqual(0, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());

            var newShieldCount = 25;
            _mockEntity.Object.GetComponent<ShieldManager>().SetShields(newShieldCount);
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
        }

        [TestMethod]
        public void CanRemoveShields()
        {
            var initialShields = 100;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShields, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());

            var shieldsToRemove = 40;
            _mockEntity.Object.GetComponent<ShieldManager>().AlterShields(-1 * shieldsToRemove);
            Assert.AreEqual(initialShields - shieldsToRemove, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
        }

        [TestMethod]
        public void CannotRemoveMoreShieldsThanPresent()
        {
            var initialShields = 100;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShields, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());

            var shieldsToRemove = initialShields + 40;
            _mockEntity.Object.GetComponent<ShieldManager>().AlterShields(-1 * shieldsToRemove);
            Assert.AreEqual(0, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
        }

        [TestMethod]
        public void ShieldIsActiveByDefault()
        {
            var initialShields = 100;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.IsTrue(_mockEntity.Object.GetComponent<ShieldManager>().IsShieldActive());
        }

        [TestMethod]
        public void CanDeactivateShield()
        {
            var initialShields = 100;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.IsTrue(_mockEntity.Object.GetComponent<ShieldManager>().IsShieldActive());

            _mockEntity.Object.GetComponent<ShieldManager>().ToggleShield();
            Assert.IsFalse(_mockEntity.Object.GetComponent<ShieldManager>().IsShieldActive());
        }

        [TestMethod]
        public void CanReActivateShield()
        {
            var initialShields = 100;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.IsTrue(_mockEntity.Object.GetComponent<ShieldManager>().IsShieldActive());

            _mockEntity.Object.GetComponent<ShieldManager>().ToggleShield();
            Assert.IsFalse(_mockEntity.Object.GetComponent<ShieldManager>().IsShieldActive());

            _mockEntity.Object.GetComponent<ShieldManager>().ToggleShield();
            Assert.IsTrue(_mockEntity.Object.GetComponent<ShieldManager>().IsShieldActive());
        }

        [TestMethod]
        public void CanAddShields()
        {
            var initialShields = 10;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShields, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());

            var shieldsToAdd = 10;
            _mockEntity.Object.GetComponent<ShieldManager>().AlterShields(shieldsToAdd);
            Assert.AreEqual(initialShields + shieldsToAdd, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
        }

        [TestMethod]
        public void CannotExceedShieldCapacityWhenAddingShields()
        {
            var initialShields = 10;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShields, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());

            var shieldsToAdd = initialShieldCapacity * 2;
            _mockEntity.Object.GetComponent<ShieldManager>().AlterShields(shieldsToAdd);
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
        }

        [TestMethod]
        public void CanChangeTheShieldCapacity()
        {
            var initialShields = 10;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShields, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());

            var newShieldCapacity = initialShieldCapacity * 2;
            _mockEntity.Object.GetComponent<ShieldManager>().SetShieldCapacity(newShieldCapacity);
            Assert.AreEqual(newShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());
        }

        [TestMethod]
        public void DecreasingShieldCapacityWithMaxShieldsAlsoReducesTheShieldValue()
        {
            var initialShieldCapacity = 100;
            var initialShields = initialShieldCapacity;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShields, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());

            var newShieldCapacity = initialShieldCapacity - 50;
            _mockEntity.Object.GetComponent<ShieldManager>().SetShieldCapacity(newShieldCapacity);
            Assert.AreEqual(newShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());
            Assert.AreEqual(newShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
        }

        [TestMethod]
        public void CanAddShieldsPastOriginalCapacityAfterCapacityIsIncreased()
        {
            var initialShields = 10;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.AreEqual(initialShields, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());

            var shieldsToAdd = initialShieldCapacity * 2;
            _mockEntity.Object.GetComponent<ShieldManager>().AlterShields(shieldsToAdd);
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());

            var newShieldCapacity = initialShieldCapacity * 2;
            _mockEntity.Object.GetComponent<ShieldManager>().SetShieldCapacity(newShieldCapacity);
            Assert.AreEqual(newShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShieldCapacity());
            Assert.AreEqual(initialShieldCapacity, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());

            shieldsToAdd = 10;
            _mockEntity.Object.GetComponent<ShieldManager>().AlterShields(shieldsToAdd);
            Assert.AreEqual(initialShieldCapacity + shieldsToAdd, _mockEntity.Object.GetComponent<ShieldManager>().GetShields());
        }

        // Event Handler Tests
        [TestMethod]
        public void DeactivatingShieldsTriggersShieldDisabledEvent()
        {
            var initialShields = 100;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.IsTrue(_mockEntity.Object.GetComponent<ShieldManager>().IsShieldActive());

            var eventFired = false;
            OnToggleShieldEventArgs toggleArgs = null;
            _mockEntity.Object.GetComponent<ShieldManager>().OnToggleShield += (sender, args) => {
                eventFired = true;
                toggleArgs = args;
            };

            _mockEntity.Object.GetComponent<ShieldManager>().ToggleShield();
            Assert.IsFalse(_mockEntity.Object.GetComponent<ShieldManager>().IsShieldActive());
            Assert.IsTrue(eventFired);
            Assert.IsNotNull(toggleArgs);
            Assert.AreEqual(toggleArgs.ShieldManager, _mockEntity.Object.GetComponent<ShieldManager>());
        }

        [TestMethod]
        public void ReActivatingShieldsTriggersShieldActivationEvent()
        {
            var initialShields = 100;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());
            Assert.IsTrue(_mockEntity.Object.GetComponent<ShieldManager>().IsShieldActive());

            var disableEventFired = false;
            OnToggleShieldEventArgs disableArgs = null;
            _mockEntity.Object.GetComponent<ShieldManager>().OnToggleShield += (sender, args) =>
            {
                disableEventFired = true;
                disableArgs = args;
            };

            var enableEventFired = false;
            OnToggleShieldEventArgs enableArgs = null;
            _mockEntity.Object.GetComponent<ShieldManager>().OnToggleShield += (sender, args) =>
            {
                enableEventFired = true;
                enableArgs = args;
            };

            _mockEntity.Object.GetComponent<ShieldManager>().ToggleShield();
            _mockEntity.Object.GetComponent<ShieldManager>().ToggleShield();
            Assert.IsTrue(disableEventFired);
            Assert.IsNotNull(disableArgs);
            Assert.AreEqual(disableArgs.ShieldManager, _mockEntity.Object.GetComponent<ShieldManager>());
            Assert.IsTrue(enableEventFired);
            Assert.IsNotNull(enableArgs);
            Assert.AreEqual(enableArgs.ShieldManager, _mockEntity.Object.GetComponent<ShieldManager>());
        }

        [TestMethod]
        public void SettingShieldValueTriggersShieldValueChangeEvent()
        {
            var initialShields = 100;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());

            var eventFired = false;
            OnShieldValueChangeEventArgs valueChangeArgs = null;
            _mockEntity.Object.GetComponent<ShieldManager>().OnShieldValueChange += (sender, args) =>
            {
                eventFired = true;
                valueChangeArgs = args;
            };
            _mockEntity.Object.GetComponent<ShieldManager>().SetShields(1);

            Assert.IsTrue(eventFired);
            Assert.IsNotNull(valueChangeArgs);
            Assert.AreEqual(1 - initialShields, valueChangeArgs.ShieldDelta);
            Assert.AreEqual(_mockEntity.Object.GetComponent<ShieldManager>(), valueChangeArgs.ShieldManager);
        }

        [TestMethod]
        public void AddingShieldValueTriggersShieldValueChangeEvent()
        {
            var initialShields = 50;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());

            var eventFired = false;
            OnShieldValueChangeEventArgs valueChangeArgs = null;
            _mockEntity.Object.GetComponent<ShieldManager>().OnShieldValueChange += (sender, args) =>
            {
                eventFired = true;
                valueChangeArgs = args;
            };
            _mockEntity.Object.GetComponent<ShieldManager>().AlterShields(10);

            Assert.IsTrue(eventFired);
            Assert.IsNotNull(valueChangeArgs);
            Assert.AreEqual(10, valueChangeArgs.ShieldDelta);
            Assert.AreEqual(_mockEntity.Object.GetComponent<ShieldManager>(), valueChangeArgs.ShieldManager);
        }

        [TestMethod]
        public void RemovingShieldValueTriggersShieldValueChangeEvent()
        {
            var initialShields = 50;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());

            var eventFired = false;
            OnShieldValueChangeEventArgs valueChangeArgs = null;
            _mockEntity.Object.GetComponent<ShieldManager>().OnShieldValueChange += (sender, args) =>
            {
                eventFired = true;
                valueChangeArgs = args;
            };
            _mockEntity.Object.GetComponent<ShieldManager>().AlterShields(-1 * 10);

            Assert.IsTrue(eventFired);
            Assert.IsNotNull(valueChangeArgs);
            Assert.AreEqual(-10, valueChangeArgs.ShieldDelta);
            Assert.AreEqual(_mockEntity.Object.GetComponent<ShieldManager>(), valueChangeArgs.ShieldManager);
        }

        [TestMethod]
        public void SettingShieldCapacityTriggersShieldCapacityChangeEvent()
        {
            var initialShields = 50;
            var initialShieldCapacity = 100;
            MockShieldManagerEntity(initialShieldCapacity, initialShields);
            Assert.IsNotNull(_mockEntity.Object.GetComponent<ShieldManager>());

            var eventFired = false;
            OnShieldCapacityChangeEventArgs capacityChangeArgs = null;
            _mockEntity.Object.GetComponent<ShieldManager>().OnShieldCapacityChange += (sender, args) =>
            {
                eventFired = true;
                capacityChangeArgs = args;
            };
            _mockEntity.Object.GetComponent<ShieldManager>().SetShieldCapacity(10);

            Assert.IsTrue(eventFired);
            Assert.IsNotNull(capacityChangeArgs);
            Assert.AreEqual(10 - initialShieldCapacity, capacityChangeArgs.CapacityDelta);
            Assert.AreEqual(_mockEntity.Object.GetComponent<ShieldManager>(), capacityChangeArgs.ShieldManager);
        }
    }
}


text:
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFrameworks>net7.0;netstandard2.0</TargetFrameworks>
		<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
        <LangVersion>8</LangVersion>
        <Nullable>enable</Nullable>
        <RootNamespace>Subterfuge.Remake.Api</RootNamespace>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
    </ItemGroup>

</Project>


text:
using System;

namespace Subterfuge.Remake.Api.Network
{

    public class SubterfugeResponse<T>
    {
        public ResponseStatus ResponseDetail;
        public T data;

        public void Get(Action<T> success, Action<ResponseStatus> failure)
        {
            if (ResponseDetail.IsSuccess)
            {
                success(data);
            }
            else
            {
                failure(ResponseDetail);
            }
        }

        public TResponse Get<TResponse>(Func<T, TResponse> success, Func<ResponseStatus, TResponse> failure)
        {
            if (ResponseDetail.IsSuccess)
            {
                return success(data);
            }
            else
            {
                return failure(ResponseDetail);
            }
        }

        public bool IsSuccess()
        {
            return ResponseDetail.IsSuccess;
        }

        public T GetOrThrow()
        {
            if (ResponseDetail.IsSuccess)
            {
                return data;
            }

            throw new NullReferenceException($"Attempted to collect data from HTTP response, but the request was an error: {ResponseDetail.Detail}");
        }

        public static SubterfugeResponse<T> OfFailure(ResponseType type, string detail)
        {
            return new SubterfugeResponse<T>()
            {
                ResponseDetail = new ResponseStatus()
                {
                    IsSuccess = false,
                    Detail = detail,
                    ResponseType = type,
                },
            };
        }

        public static SubterfugeResponse<T> OfSuccess(T data)
        {
            return new SubterfugeResponse<T>()
            {
                ResponseDetail = new ResponseStatus() { IsSuccess = true, ResponseType = ResponseType.SUCCESS},
                data = data,
            };
        }
    }
}


text:
using System.Collections.Generic;

namespace Subterfuge.Remake.Api.Network
{
    public class BlockPlayerRequest { }

    public class BlockPlayerResponse { }

    public class UnblockPlayerRequest { }

    public class UnblockPlayerResponse { }

    public class ViewBlockedPlayersRequest { }

    public class ViewBlockedPlayersResponse
    {
        public List<User> BlockedUsers { get; set; }
    }

    public class SendFriendRequestRequest
    {
        public string FriendId { get; set; }
    }

    public class AddAcceptFriendResponse { }

    public class DenyFriendRequestRequest
    {
        public string FriendId { get; set; }
    }

    public class DenyFriendRequestResponse { }

    public class ViewFriendRequestsRequest { }

    public class ViewFriendRequestsResponse
    {
        public List<User> FriendRequests { get; set; }
    }

    public class RemoveFriendRequest
    {
        public string FriendId { get; set; }
    }

    public class RemoveFriendResponse { }

    public class ViewFriendsRequest { }

    public class ViewFriendsResponse
    {
        public List<User> Friends { get; set; }
    }
}


text:
using System;
using System.Collections.Generic;

namespace Subterfuge.Remake.Api.Network
{
    // IMPORTANT! If new event types are added, ensure that SubterfugeGameEventController implements a deserializer for the event data to verify it
    // Also ensure that GameEventFactory has a deserializer to convert the event into a game event.
    public enum EventDataType
    {
        Unknown = 0,
        LaunchEventData = 1,
        ToggleShieldEventData = 2,
        DrillMineEventData = 3,
        PlayerLeaveGameEventData = 4,
        PauseGameEventData = 5, // Admin Only
        UnpauseGameEventData = 6, // Admin Only
        GameEndEventData = 7, // Admin Only
        HireSpecialistEventData = 8,
        PromoteSpecialistEventData = 9,
    }

    // Make all of these classes have a common base
    public class EventData { }

    public class LaunchEventData : EventData
    {
        public string SourceId { get; set; }
        public string DestinationId { get; set; }
        public int DrillerCount { get; set; }
        public List<string> SpecialistIds { get; set; } = new List<string>();
    }

    public class ToggleShieldEventData : EventData
    {
        public string SourceId { get; set; }
    }

    public class DrillMineEventData : EventData
    {
        public String SourceId { get; set; }
    }

    public class PlayerLeaveGameEventData : EventData
    {
        public SimpleUser Player { get; set; }
    }

    // Empty event. Will prevent the game from moving/ticking forwards until a following "Unpause game event data" is present in the
    public class PauseGameEventData : EventData
    {
        public DateTime TimePaused { get; set; } = DateTime.Now;
    }

    public class UnpauseGameEventData : EventData
    {
        public DateTime TimeUnpaused { get; set; } = DateTime.Now;
    }

    public class GameEndEventData : EventData
    {
        public DateTime GameEndAt { get; set; } = DateTime.Now;
        public bool ClosedByAdmin { get; set; } = false;
        public SimpleUser? WinningPlayer { get; set; }
    }

    public class HireSpecialistEventData : EventData
    {
        public string HireLocation { get; set; }
        public SpecialistTypeId SpecialistTypeIdHired { get; set; }
    }

    public class PromoteSpecialistEventData : EventData
    {
        public string PromotionLocation { get; set; }
        public string SpecialistIdPromotedFrom { get; set; }
        public string SpecialistIdPromotedTo { get; set; }
    }
}


text:
using System;
using System.Collections.Generic;

namespace Subterfuge.Remake.Api.Network
{
    public enum RoomStatus
    {
        Unknown = 0,
        Open = 1,
        Ongoing = 2,
        Closed = 3,
        Completed = 4,
        Expired = 5,
    }

    public class GameConfiguration {
        public string Id { get; set; }
        public RoomStatus RoomStatus { get; set; }
        public User Creator { get; set; }
        public GameSettings GameSettings { get; set; }
        public MapConfiguration MapConfiguration { get; set; }
        public string RoomName { get; set; }
        public GameVersion GameVersion { get; set; } = GameVersion.ALPHA01;
        public DateTime TimeCreated { get; set; }
        public DateTime TimeStarted { get; set; }
        public DateTime ExpiresAt { get; set; } = DateTime.MaxValue;
        public List<User> PlayersInLobby { get; set; }
        public Dictionary<string, List<SpecialistTypeId>> PlayerSpecialistDecks { get; set; }
    }

    public enum GameVersion
    {
        UNKNOWN = 0,
        ALPHA01 = 1,
    }

    public class GameSettings
    {
        public double MinutesPerTick { get; set; }
        public Goal Goal { get; set; }
        public Boolean IsRanked { get; set; }
        public Boolean IsAnonymous { get; set; }
        public Boolean IsPrivate { get; set; }
        public int MaxPlayers { get; set; }
    }

    public class MapConfiguration
    {
        public int Seed { get; set; }
        public int OutpostsPerPlayer { get; set; }
        public int MinimumOutpostDistance { get; set; }
        public int MaximumOutpostDistance { get; set; }
        public int DormantsPerPlayer { get; set; }
        public OutpostDistribution OutpostDistribution { get; set; }
    }

    public class OutpostDistribution
    {
        public float GeneratorWeight { get; set; }
        public float FactoryWeight { get; set; }
        public float WatchtowerWeight { get; set; }
    }

    public enum Goal
    {
        Unknown = 0,
        Mining = 1,
        Domination = 2,
    }

    public class GetLobbyRequest
    {
        public int Pagination { get; set; } = 1;
        public RoomStatus? RoomStatus { get; set; } = null;
        public string? CreatedByUserId { get; set; } = null;
        public string? UserIdInRoom { get; set; } = null;
        public string? RoomId { get; set; } = null;
        public Goal? Goal { get; set; } = null;
        public int MinPlayers { get; set; } = 0;
        public int MaxPlayers { get; set; } = 999;
        public bool? IsAnonymous { get; set; } = null;
        public bool? IsRanked { get; set; } = null;
    }

    public class OpenLobbiesResponse
    {
        public GameConfiguration[] Rooms { get; set; } = new GameConfiguration[] { };
    }

    public class PlayerCurrentGamesRequest {}

    public class CreateRoomRequest
    {
        public GameSettings GameSettings { get; set; }
        public MapConfiguration MapConfiguration { get; set; }
        public string RoomName { get; set; }
        public Boolean IsPrivate { get; set; }
        public List<SpecialistTypeId> CreatorSpecialistDeck { get; set; }
    }

    public class CreateRoomResponse
    {
        public GameConfiguration GameConfiguration { get; set; }
    }

    public class JoinRoomResponse { }

    public class JoinRoomRequest
    {
        public List<SpecialistTypeId> SpecialistDeck { get; set; }
    }

    public class LeaveRoomResponse { }

    public class StartGameEarlyResponse { }

    public class GetLobbyResponse
    {
        public GameConfiguration[] Lobbies { get; set; }
    }

    public enum SpecialistTypeId
    {
        Unknown = 0,
        Infiltrator = 1,
        Helmsman = 2,
        Veteran = 3,
        Dispatcher = 4,
        Assasin = 5,
        Pirate = 6,
        Inspector = 7,
        Smuggler = 8,
        Sentry = 9,
        Revered_Elder = 10,
        Saboteur = 11,
        Princess = 12,
        Intelligence_Officer = 13,
        Foreman = 14,
        Tinkerer = 15,
        Hypnotist = 16,
        Warden = 17,
        Technician = 18,
        Automation = 19,
        Advisor = 20,
        Amnesiac = 21,
        Martyr = 22,
        Sniper = 23,
        Iron_Maiden = 24,
        Scrutineer = 25,
        Theif = 26,
        Escort = 27,
        Sapper = 28,
        Icicle = 29,
        Engineer = 30,
        Enforcer = 31,
        Merchant = 32,
        Economist = 33,
        Double_Agent = 34,
        Industrialist = 35,
        Breeder = 36,
        SignalJammer = 37,
        Bolster = 38,

        // Heroes in the 1000 range
        Queen = 1001,
    }
}


text:
namespace Subterfuge.Remake.Api.Network
{
    public class Friend
    {
        public string Id { get; set; }
        public string PlayerId { get; set; }
        public string FriendId { get; set; }
        public RelationshipStatus RelationshipStatus { get; set; }
        public long UnixTimeCreated { get; set; }

        public string GetFriendId(string friendToUser)
        {
            return PlayerId == friendToUser ? FriendId : PlayerId;
        }
    }

    public enum RelationshipStatus
    {
        NoRelation = 0,
        Pending = 1,
        Friends = 2,
        Blocked = 3,
    }

}


text:
#nullable enable
using System.Collections.Generic;

namespace Subterfuge.Remake.Api.Network
{

    public class AuthorizationRequest
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }

    public class AuthorizationResponse
    {
        public User User { get; set; }
        public string Token { get; set; }
    }

    public class AccountRegistrationRequest
    {
        public string Username { get; set; } = "username";
        public string Password { get; set; } = "password";
        public string DeviceIdentifier { get; set; } = "someDeviceIdentifier";
        public string PhoneNumber { get; set; } = "+13069999999";
    }

    public class AccountRegistrationResponse
    {
        public User User { get; set; } = new User();
        public string Token { get; set; } = "LoginToken";
        public bool RequirePhoneValidation = true;
    }

    public class AccountValidationRequest
    {
        public string VerificationCode { get; set; }
    }

    public class AccountVadliationResponse
    {
        public bool wasValidationSuccessful { get; set; } = false;
        public User User { get; set; } = new User();
    }

    public class GetRolesResponse
    {
        public UserClaim[] Claims { get; set; } = new UserClaim[] {};
    }

    public class UpdateRolesRequest
    {
        public UserClaim[] Claims { get; set; } = new UserClaim[] {};
    }

    public class GetUserRequest
    {
        public string? UsernameSearch { get; set; } = "";
        public string? DeviceIdentifierSearch { get; set; } = "";
        public string? UserIdSearch { get; set; } = "";
        public UserClaim? RequireUserClaim { get; set; } = null;
        public bool? isBanned { get; set; } = null;
        public int pagination { get; set; } = 1;
    }

    public class GetUserResponse
    {
        public User User { get; set; }
    }

    public class GetDetailedUsersResponse
    {
        public List<DetailedUser> users { get; set; }
    }

    public enum UserClaim
    {
        Unknown = 0,
        User = 1,
        Moderator = 2,
        Administrator = 3,
        PhoneVerified = 4,
        DiscordVerified = 5,
    }
}


text:
using System;
using System.Collections.Generic;

namespace Subterfuge.Remake.Api.Network
{

    public class MessageGroup
    {
        public string Id { get; set; }
        public string RoomId { get; set; }
        public List<User> GroupMembers { get; set; }
        public List<ChatMessage> Messages { get; set; } = new List<ChatMessage>();
    }

    public class ChatMessage
    {
        public string Id { get; set; } = Guid.NewGuid().ToString();
        public DateTime SentAt { get; set; } = DateTime.UtcNow;
        public string RoomId { get; set; }
        public string GroupId { get; set; }
        public SimpleUser SentBy { get; set; }
        public string Message { get; set; }
    }

    public class CreateMessageGroupRequest
    {
        public List<string> UserIdsInGroup { get; set; }
    }

    public class CreateMessageGroupResponse
    {
        public string GroupId { get; set; }
    }

    public class SendMessageRequest
    {
        public string Message { get; set; }
    }

    public class SendMessageResponse { }

    public class GetMessageGroupsRequest
    {
    }

    public class GetMessageGroupsResponse
    {
        public List<MessageGroup> MessageGroups { get; set; }
    }

    public class GetGroupMessagesRequest
    {
        public int Pagination { get; set; } = 1;
    }

    public class GetGroupMessagesResponse
    {
        public List<ChatMessage> Messages { get; set; }
    }

    public class GetPlayerChatMessagesResponse
    {
        public List<ChatMessage> Messages { get; set; }
    }
}


text:
using System;
using System.Collections.Generic;

namespace Subterfuge.Remake.Api.Network
{
    // This data structure is ONLY for administrators.
    // Players will NOT get this information.
    public class DetailedUser
    {
        public string Id { get; set; }
        public string Username { get; set; }
        public string DeviceIdentifier { get; set; }
        public string PhoneNumberHash { get; set; }
        public string DiscordUsername { get; set; }
        public UserClaim[] Claims { get; set; }
        public List<MultiboxAccount> MultiboxAccounts { get; set; }
        public string PushNotificationIdentifier { get; set; }
        public string DeviceType { get; set; }

        // Administrative
        public DateTime DateCreated { get; set; }
        public DateTime BannedUntil { get; set; }

        public List<AccountBan> BanHistory { get; set; } = new List<AccountBan>();
    }

    public class SimpleUser
    {
        public string Id { get; set; }
        public string Username { get; set; }

        public User ToUser()
        {
            return new User()
            {
                Id = Id,
                Username = Username,
            };
        }
    }

    public class User
    {
        public string Id { get; set; }
        public string Username { get; set; }
        public UserClaim[] Claims { get; set; }
        // A list of other accounts that this user has associated as.
        public List<SimpleUser> Pseudonyms { get; set; } = new List<SimpleUser>() { };

        // Administrative
        public DateTime DateCreated { get; set; }
        public DateTime BannedUntil { get; set; }

        public SimpleUser ToSimpleUser()
        {
            return new SimpleUser()
            {
                Id = Id,
                Username = Username
            };
        }
    }

    public enum DeviceType
    {
        Desktop,
        iOS,
        Android,
        Windows,
    }

    public class MultiboxAccount
    {
        public SimpleUser User { get; set; }
        public MultiBoxReason MultiboxReason { get; set; }
        public DateTime TimeOccured { get; set; } = DateTime.UtcNow;
    }

    public enum MultiBoxReason
    {
        None = 0,
        LoginWithCredentialsForAnotherAccount = 1,
        DuplicatePhoneNumber = 2,
        DuplicateDeviceId = 3,
        DuplicateDiscordUsername = 4,
    }

    public class AccountBan
    {
        public string Reason { get; set; }
        public string AdministratorNotes { get; set; }
        public DateTime DateExpires { get; set; }
        public DateTime DateApplied { get; set; } = DateTime.UtcNow;
    }
}


text:
using System;
using System.Collections.Generic;

namespace Subterfuge.Remake.Api.Network
{
    public class CreateAnnouncementRequest
    {
        public string Title { get; set; }
        public string Message { get; set; }

        /**
         * Who to send the message to.
         * If 'global', everyone will see the message. Otherwise, a comma-separated list of user IDs should be provided.
         */
        public string BroadcastTo { get; set; } = "global";
        public DateTime StartsAt { get; set; } = DateTime.Now;
        public DateTime ExpiresAt { get; set; } = DateTime.Now.AddDays(21);
    }

    public class CreateAnnouncementResponse
    {
        public string AnnouncementId { get; set; }
    }

    public class GetAnnouncementsResponse
    {
        public List<GameAnnouncement> Announcements { get; set; }
    }

    public class GameAnnouncement
    {
        public string Id { get; set; }
        public string Title { get; set; }
        public string Message { get; set; }
        public SimpleUser PostedBy { get; set; }
        public DateTime StartsAt { get; set; } = DateTime.UtcNow;
        public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddDays(21);
    }
}


text:
#nullable enable
using System;
using System.Collections.Generic;

namespace Subterfuge.Remake.Api.Network
{
    public class ServerAction
    {
        public string Username { get; set; }
        public string UserId { get; set; }
        public string RemoteIpAddress { get; set; }
        public string HttpMethod { get; set; }
        public string RequestUrl { get; set; }
        public int? StatusCode { get; set; }
        public DateTime TimeProcessed { get; set; } = DateTime.UtcNow;
    }

    public class ServerActionLogRequeset
    {
        public int Pagination { get; set; } = 1;
        public string? Username { get; set; } = null;
        public string? UserId { get; set; } = null;
        public string? HttpMethod { get; set; } = null;
        public string? RequestUrl { get; set; } = null;
    }

    public class ServerActionLogResponse
    {
        public List<ServerAction> Actions { get; set; }
    }

    public class ServerException
    {
        public string Id { get; set; } = Guid.NewGuid().ToString();
        public string Username { get; set; }
        public string UserId { get; set; }
        public string RemoteIpAddress { get; set; }
        public string HttpMethod { get; set; }
        public string RequestUri { get; set; }
        public string RequestBody { get; set; }
        public string QueryString { get; set; }
        public string ExceptionMessage { get; set; }
        public string ExceptionSource { get; set; }
        public string StackTrace { get; set; }
        public DateTime UnixTimeProcessed { get; set; } = DateTime.UtcNow;
    }

    public class ServerExceptionLogRequest
    {
        public int Pagination { get; set; } = 1;
        public string? Username { get; set; } = null;
        public string? UserId { get; set; } = null;
        public string? HttpMethod { get; set; } = null;
        public string? RequestUrl { get; set; } = null;
        public string? ExceptionSource { get; set; } = null;
        public string? RemoteIpAddress { get; set; } = null;
    }

    public class ServerExceptionLogResponse
    {
        public List<ServerException> Exceptions { get; set; }
    }

    public class GetIpBansResponse
    {
        public List<IpBans> BannedIps { get; set; }
    }

    public class IpBans
    {
        public string Reason { get; set; }
        public string IpOrRegex { get; set; }
        public DateTime DateApplied { get; set; }
        public DateTime BannedUntil { get; set; }
        public string AdminNotes { get; set; }
    }

    public class BanPlayerRequest
    {
        public string UserId { get; set; }
        public DateTime Until { get; set; } = DateTime.Now.AddDays(1);
        public string Reason { get; set; }
        public string AdminNotes { get; set; } = "";
    }

    public class BanPlayerResponse
    {
        public bool wasSuccess { get; set; }
    }

    public class BanIpRequest
    {
        public string DirectIpOrRegex { get; set; }

        public string Reason { get; set; }
        public DateTime Until { get; set; } = DateTime.Now.AddDays(1);
        public string AdminNotes { get; set; } = "";
    }

    public class BanIpResponse
    {
        public bool wasSuccess { get; set; }
    }

    public class GetBannedPlayerResponse
    {
        public List<DetailedUser> BannedUsers { get; set; }
    }

    public class Echo
    {
        public string EchoContent { get; set; }
    }
}


text:
using System;
using System.Collections.Generic;

namespace Subterfuge.Remake.Api.Network
{
    public class GameRoomEvent
    {
        public string Id { get; set; }
        public string RoomId { get; set; }
        public DateTime TimeIssued { get; set; }
        public SimpleUser IssuedBy { get; set; }

        public GameEventData GameEventData { get; set; }
    }

    public class GameEventData
    {
        public int OccursAtTick { get; set; }
        public EventDataType EventDataType { get; set; }
        public String SerializedEventData { get; set; }
    }

    public class GetGameRoomEventsResponse
    {
        public List<GameRoomEvent> GameEvents { get; set; }
    }

    public class SubmitGameEventRequest
    {
        public GameEventData GameEventData { get; set; }
    }

    public class SubmitGameEventResponse
    {
        public string EventId { get; set; }
        public GameRoomEvent GameRoomEvent { get; set; }
    }

    public class UpdateGameEventRequest
    {
        public GameEventData GameEventData { get; set; }
    }

    public class DeleteGameEventResponse { }

}


text:
using System;

namespace Subterfuge.Remake.Api.Network
{
    public class ResponseStatus
    {
        public Boolean IsSuccess { get; set; }
        public ResponseType ResponseType { get; set; }
        public String Detail { get; set; }
    }

    public class PingRequest { }

    public class PingResponse { }

    public class AuthorizedPingResponse
    {
        public User LoggedInUser { get; set; }
    }

    public class AuthorizedPingRequest { }

    public enum ResponseType
    {
        UNKNOWN = 0,
        SUCCESS = 1,                      // Used if a message is OK
        DUPLICATE = 2,                    // Used if something already exists.
        INVALID_REQUEST = 3,              // Used if a request argument is not valid or doesn't meet requirements
        UNAUTHORIZED = 4,                 // Used if the user is not logged in
        INTERNAL_SERVER_ERROR = 5,        // Used if an unknown exception is thrown. This probably indicates that the code is doing something wrong...
        NOT_FOUND = 6,
        BANNED = 7,
        VALIDATION_ERROR = 8,
        PERMISSION_DENIED = 9,
        ROOM_IS_FULL = 10,
        GAME_ALREADY_STARTED = 11,
    }
}


text:
#nullable enable
using System.Threading.Tasks;

namespace Subterfuge.Remake.Api.Network.Api
{
    /// <summary>
    /// Perform account operations
    /// </summary>
    public interface ISubterfugeAccountApi
    {
        /// <summary>
        /// Login to your account.
        /// </summary>
        /// <param name="request">The login request</param>
        /// <returns>A login response</returns>
        Task<SubterfugeResponse<AuthorizationResponse>> Login(AuthorizationRequest request);

        /// <summary>
        /// Register a new account
        /// </summary>
        /// <param name="registrationRequeset">Request params to register a new account</param>
        /// <returns>A registration response</returns>
        Task<SubterfugeResponse<AccountRegistrationResponse>> RegisterAccount(AccountRegistrationRequest registrationRequeset);

        /// <summary>
        /// Verifies the user's account with their verification code.
        /// </summary>
        /// <param name="validationRequest">The user's verification code</param>
        /// <returns>If the verification was successful or not</returns>
        Task<SubterfugeResponse<AccountVadliationResponse>> VerifyPhone(AccountValidationRequest validationRequest);

        /// <summary>
        /// Gets an individual user account
        /// </summary>
        /// <param name="userId">The userID to query</param>
        /// <returns>Userdata about the queried user</returns>
        Task<SubterfugeResponse<GetUserResponse>> GetUser(string userId);

        /// <summary>
        /// Queries all created accounts based on the set query parameters
        ///
        /// This endpoint can only be used by Administrators.
        /// </summary>
        /// <param name="request">Query and filter parameters to narrow the search</param>
        /// <returns>A list of users matching the query</returns>
        Task<SubterfugeResponse<GetDetailedUsersResponse>> GetUsers(GetUserRequest request);

        /// <summary>
        /// Gets all of a player's chat messages with no context.
        /// This is useful to let admins get a glimpse of messages being sent by the player.
        ///
        /// This endpoint can only be used by Administrators.
        /// </summary>
        /// <param name="playerId">The playerID to get chat messages for</param>
        /// <param name="pagination">Page number</param>
        /// <returns>A list of the player's chat messages ordered by most recently sent</returns>
        Task<SubterfugeResponse<GetPlayerChatMessagesResponse>> GetPlayerChatMessages(string playerId, int pagination = 1);
    }

    public interface ISubterfugeUserRoleApi
    {
        /// <summary>
        /// Gets all of the player's roles
        /// </summary>
        /// <param name="userId">The userID to get the roles of</param>
        /// <returns>A list of the user's roles</returns>
        Task<SubterfugeResponse<GetRolesResponse>> GetRoles(string userId);

        /// <summary>
        /// Updates a user's roles
        /// </summary>
        /// <param name="userId">The userID to update</param>
        /// <param name="request">An updated list of the user's roles.</param>
        /// <returns>If the update was successful or not</returns>
        Task<SubterfugeResponse<GetRolesResponse>> SetRoles(string userId, UpdateRolesRequest request);
    }

    public interface ISubterfugeAdminApi
    {
        /// <summary>
        /// Returns a list of all player actions.
        ///
        /// May only be used by Administrators.
        /// </summary>
        /// <param name="request">Filter and query params</param>
        /// <returns>A list of server actions matching the query params</returns>
        Task<SubterfugeResponse<ServerActionLogResponse>> GetActionLog(ServerActionLogRequeset request);

        /// <summary>
        /// Gets a list of all server exceptions that have been thrown.
        ///
        /// May only be used by administrators
        /// </summary>
        /// <param name="request">Query and filter params</param>
        /// <returns>A list of server exceptions</returns>
        Task<SubterfugeResponse<ServerExceptionLogResponse>> GetServerExceptions(ServerExceptionLogRequest request);

        /// <summary>
        /// Bans a player.
        ///
        /// May only be used by administrators
        /// </summary>
        /// <param name="userId">The userID to ban</param>
        /// <param name="until">The date to ban the user until</param>
        /// <param name="reason">The reason for the ban</param>
        /// <param name="adminNotes">Extra admin-only notes</param>
        /// <returns>If the ban was successful or not</returns>
        Task<SubterfugeResponse<BanPlayerResponse>> BanPlayer(BanPlayerRequest banRequest);

        /// <summary>
        /// Bans an IP or range of IP addresses
        ///
        /// May only be used by administrators
        /// </summary>
        /// <param name="directIpOrRegex">A regex string that matches a particular pattern of IP addresses.</param>
        /// <param name="until">The date to ban the IPs until.</param>
        /// <param name="adminNotes">Extra admin-only notes</param>
        /// <returns>If the ban was successful or not</returns>
        Task<SubterfugeResponse<BanIpResponse>> BanIp(BanIpRequest banIpRequest);

        /// <summary>
        /// Get a list of current IP bans
        ///
        /// May only be used by administrators
        /// </summary>
        /// <param name="pagination">Page number</param>
        /// <returns>A list of current IP bans</returns>
        Task<SubterfugeResponse<GetIpBansResponse>> GetIpBans(int pagination);

        /// <summary>
        /// Gets a list of currently banned players
        /// </summary>
        /// <param name="pagination">Page Number</param>
        /// <returns>A list of the currently banned players</returns>
        Task<SubterfugeResponse<GetBannedPlayerResponse>> GetBannedPlayers(int pagination);

        Task<SubterfugeResponse<Echo>> EchoRequest(Echo request);
    }

    public interface ISubterfugeGameLobbyApi
    {
        /// <summary>
        /// Gets a list of lobbies based on the filters provided.
        /// Regular players can see open lobbies from everyone, but can only see private lobbies that they have created or joined.
        ///
        /// Administrators can see all lobbies.
        /// </summary>
        /// <param name="lobbyRequest">Filters to narrow the search</param>
        /// <returns>A list of visible lobbies matching the supplied filters</returns>
        Task<SubterfugeResponse<GetLobbyResponse>> GetLobbies(GetLobbyRequest lobbyRequest);

        /// <summary>
        /// Creates a new game lobby. If successful, the creator of the lobby is a member of the lobby.
        /// </summary>
        /// <param name="createRoomRequest">Parameters for the game settings</param>
        /// <returns>If the lobby was created, and the ID of the lobby if it was.</returns>
        Task<SubterfugeResponse<CreateRoomResponse>> CreateNewRoom(CreateRoomRequest createRoomRequest);

        /// <summary>
        /// Join a lobby.
        /// </summary>
        /// <param name="request">TODO: Delete this class.</param>
        /// <param name="guid">The ID of the lobby to join</param>
        /// <returns>If the player successfully joined the lobby</returns>
        Task<SubterfugeResponse<JoinRoomResponse>> JoinRoom(JoinRoomRequest request, string guid);

        /// <summary>
        /// Leave a lobby.
        /// </summary>
        /// <param name="guid">The ID of the lobby to leave</param>
        /// <returns>If the player successfully left the lobby</returns>
        Task<SubterfugeResponse<LeaveRoomResponse>> LeaveRoom(string guid);

        /// <summary>
        /// Starts a game early if the maximum number of players has not been reached.
        /// Games can only be started early by the lobby's creator.
        /// </summary>
        /// <param name="guid">The ID of the lobby to start</param>
        /// <returns>If the lobby was successfully started</returns>
        Task<SubterfugeResponse<StartGameEarlyResponse>> StartGameEarly(string guid);
    }

    public interface ISubterfugeGameEventApi
    {
        /// <summary>
        /// Gets a list of game events currently visible to the player
        ///
        /// Administrators can see every event in a lobby.
        /// </summary>
        /// <param name="roomId">The ID of the room to get events for</param>
        /// <returns>A list of visible game events</returns>
        Task<SubterfugeResponse<GetGameRoomEventsResponse>> GetGameRoomEvents(string roomId);

        /// <summary>
        /// Submits a new game event to the room
        /// </summary>
        /// <param name="request">Details about the event to submit</param>
        /// <param name="roomId">The room id to submit the event to. The game must be ongoing and the player must be a member of the room.</param>
        /// <returns>If the event was successfully submitted to the room</returns>
        Task<SubterfugeResponse<SubmitGameEventResponse>> SubmitGameEvent(SubmitGameEventRequest request, string roomId);

        /// <summary>
        /// Updates an event that was previously submitted to the room.
        /// </summary>
        /// <param name="request">The updated event details</param>
        /// <param name="roomId">The ID of the room</param>
        /// <param name="eventGuid">The ID of the event to update</param>
        /// <returns>If the event was updated successfully</returns>
        Task<SubterfugeResponse<SubmitGameEventResponse>> UpdateGameEvent(UpdateGameEventRequest request, string roomId, string eventGuid);

        /// <summary>
        /// Deletes a game event you have previously submitted
        /// </summary>
        /// <param name="roomId">The room ID the event was submitted to</param>
        /// <param name="eventGuid">The ID of the event to delete. Must have been an event submitted by the player</param>
        /// <returns>If the event was deleted</returns>
        Task<SubterfugeResponse<DeleteGameEventResponse>> DeleteGameEvent(string roomId, string eventGuid);
    }

    public interface ISubterfugeHealthApi
    {
        /// <summary>
        /// Generic server health check.
        /// </summary>
        /// <returns>A server response</returns>
        Task<SubterfugeResponse<PingResponse>> Ping();

        /// <summary>
        /// A server health check that requires the user to be logged in to access
        /// </summary>
        /// <returns>A server response</returns>
        Task<SubterfugeResponse<AuthorizedPingResponse>> AuthorizedPing();
    }

    public interface ISubterfugeGroupChatApi
    {
        /// <summary>
        /// Creates a message group between players within a lobby
        /// </summary>
        /// <param name="request">The details about players to start a chat between</param>
        /// <param name="roomId">The ID of the room to chat in</param>
        /// <returns>If the message group was created and the ID of the group if it was</returns>
        Task<SubterfugeResponse<CreateMessageGroupResponse>> CreateMessageGroup(CreateMessageGroupRequest request, string roomId);

        /// <summary>
        /// Sends a chat message to a group chat
        /// </summary>
        /// <param name="request">Details about the message to send</param>
        /// <param name="roomId">The roomID to send the message to</param>
        /// <param name="groupId">The groupID to send the message to</param>
        /// <returns>If the message was successfully sent</returns>
        Task<SubterfugeResponse<SendMessageResponse>> SendMessage(SendMessageRequest request, string roomId, string groupId);

        /// <summary>
        /// Gets a list of message groups you are a member of within the room.
        ///
        /// Administrators can see all message groups in a lobby.
        /// </summary>
        /// <param name="roomId">The ID of the room to view groups of</param>
        /// <returns>A list of message groups the player is a member of within the room</returns>
        Task<SubterfugeResponse<GetMessageGroupsResponse>> GetMessageGroups(string roomId);

        /// <summary>
        /// Gets a list of chat messages sent within a group chat for a room.
        ///
        /// Administrators can view all messages.
        /// </summary>
        /// <param name="request">TODO: remove this to just a param</param>
        /// <param name="roomId">The ID of the room to get messages for</param>
        /// <param name="groupId">The ID of the group to get messages from</param>
        /// <returns>A list of messages in the group</returns>
        Task<SubterfugeResponse<GetGroupMessagesResponse>> GetMessages(
            GetGroupMessagesRequest request,
            string roomId,
            string groupId);
    }

    public interface ISubterfugeSocialApi
    {
        /// <summary>
        /// Blocks a player. Blocked players cannot communicate with you or see lobbies you create.
        /// If you block a friend, they will be removed as a friend. You cannot block an administrator.
        /// </summary>
        /// <param name="request">TODO: Remove this.</param>
        /// <param name="userId">The ID of the user to block</param>
        /// <returns>If the player was blocked.</returns>
        Task<SubterfugeResponse<BlockPlayerResponse>> BlockPlayer(BlockPlayerRequest request, string userId);

        /// <summary>
        /// Unblocks a previously blocked player.
        /// </summary>
        /// <param name="request">TODO: remove this.</param>
        /// <param name="userId">The ID of the user to block</param>
        /// <returns>If the player was unblocked.</returns>
        Task<SubterfugeResponse<UnblockPlayerResponse>> UnblockPlayer(UnblockPlayerRequest request, string userId);

        /// <summary>
        /// Get a list of all players you have blocked. Normal players can only view their own list
        /// Administrators can view anyone's blocked player list.
        /// </summary>
        /// <param name="userId">
        /// The userID to view the blocked players of. Normal users can only use their own userID here. Administrators can use any userID
        /// </param>
        /// <returns>A list of players that the user has blocked</returns>
        Task<SubterfugeResponse<ViewBlockedPlayersResponse>> ViewBlockedPlayers(string userId);

        /// <summary>
        /// Sends a friend request to another player.
        /// If you have an incoming friend request, accepts the friend requests.
        /// </summary>
        /// <param name="userId">The used ID to add as a friend</param>
        /// <returns>If the request was successful</returns>
        Task<SubterfugeResponse<AddAcceptFriendResponse>> AddAcceptFriendRequest(string userId);

        /// <summary>
        /// Removes a player as your friend.
        /// If you have an incoming friend request, removes the friend request.
        /// </summary>
        /// <param name="userId">The user ID to remove as a friend</param>
        /// <returns>If the user was removed as a friend</returns>
        Task<SubterfugeResponse<DenyFriendRequestResponse>> RemoveRejectFriend(string userId);

        /// <summary>
        /// Gets a list of your incoming friend requests.
        /// Administrators can view anyone's friend requests.
        /// </summary>
        /// <param name="userId">The userID to view friend requests for.</param>
        /// <returns>A list of friend requests for the player</returns>
        Task<SubterfugeResponse<ViewFriendRequestsResponse>> ViewFriendRequests(string userId);

        /// <summary>
        /// Gets a list of your friends.
        /// Administrators can view anyone's friend list.
        /// </summary>
        /// <param name="userId">The userID to get the friend list of.</param>
        /// <returns>A list of the player's friends</returns>
        Task<SubterfugeResponse<ViewFriendsResponse>> GetFriendList(string userId);
    }

    public interface ISubterfugeAnnouncementApi
    {
        /// <summary>
        /// Submites a new announcement to share to members of the game.
        /// Admin Only.
        /// </summary>
        /// <param name="announcementRequest">The new announcement to create</param>
        /// <returns>The ID of the created announcement</returns>
        Task<SubterfugeResponse<CreateAnnouncementResponse>> CreateAnnouncement(CreateAnnouncementRequest announcementRequest);

        /// <summary>
        /// Submites a new announcement to share to members of the game.
        /// Admin Only.
        /// </summary>
        /// <param name="announcementRequest">The ID of the announcement to update</param>
        /// <param name="announcementRequest">The new announcement to create</param>
        /// <returns>The ID of the created announcement</returns>
        Task<SubterfugeResponse<CreateAnnouncementResponse>> UpdateAnnouncement(string id, CreateAnnouncementRequest announcementRequest);

        /// <summary>
        /// Get a list of all announcements visible to the current player.
        /// </summary>
        /// <returns>Announcements visible to the current player</returns>
        Task<SubterfugeResponse<GetAnnouncementsResponse>> GetAnnouncements();
    }
}


text:
[![languages](https://img.shields.io/github/languages/top/Subterfuge-Revived/Remake-Core)]()
[![code-size](https://img.shields.io/github/languages/code-size/Subterfuge-Revived/Remake-Core)]()
[![commit-activity](https://img.shields.io/github/commit-activity/y/Subterfuge-Revived/Remake-Core)](https://github.com/Subterfuge-Revived/Remake-Core/pulse/yearly)
[![license](https://img.shields.io/github/license/Subterfuge-Revived/Remake-Core)](LICENSE)
[![discord](https://img.shields.io/discord/617149385196961792)](https://discord.gg/GNk7Xw4)
[![issues](https://img.shields.io/github/issues/Subterfuge-Revived/Remake-Core)](https://github.com/Subterfuge-Revived/Remake-Core/issues?q=is%3Aopen)
[![issues-closed-raw](https://img.shields.io/github/issues-closed/Subterfuge-Revived/Remake-Core)](https://github.com/Subterfuge-Revived/Remake-Core/issues?q=is%3Aclosed+)
[![Banner](banner.png)]()

# Remake-Server

The server directory is a combination of server files that are used to facilitate the backend services of the game. These services include a number of projects to allow network communications
to take place between the players and the server. Within this project you will find 4 projects.

## Setup

See the [main repository readme](https://github.com/Subterfuge-Revived/Remake-Core) to setup your IDE.

## Local Server Deployment & Testing

#### Running the Server

To run the server, there are a few different options depending on the type of setup you would like to run.
However, no matter the setup, we will be using docker compose.

All deployments will require a running instances of the database to connect to in order to ensure that data can be saved to the database.

There are currently 3 different deployment modes: `default`, `production`, `test`:

- `default` - Only deploys the database. This is useful if you are working on changes to the server
- `production` - Deploys the database and server. This is useful if you are working on a client (website, unity, discord bot, etc.) and don't want to change the server.
- `test` - Only really useful for CI/CD pipelines. This deployment will deploy the server, database, and will start up an extra container that runs the integration tests against the server. Not advised for local development because the integration tests will flush(delete) the entire database for testing.

#### `default` Deployment

The default deployment will only deploy the database. This is useful if you are trying to debug the server, or plan on running the server locally for development purposes.
This mode allows you to quickly stop the server to make quick modifications to debug or implement server functionality.

To deploy in the default mode (only the database), ensure you are in the root directory (One directory up from the file you are looking at right now):

Then, deploy the database with the following command:

```
docker-compose up -d
```

#### Debugging the Server

Now that we have started the database, it is likely we will want to run or debug the server locally.
You can then start your server locally (with debugging) if needed directly from the IDE.
Right click on the `SubterfugeRestApiServer` project in your IDE and click one of the two options:

- "Run SubterfugeRestApiServer" to just run the server locally or,
- "Debug SubterfugeRestApiServer" to run the server in debug mode. This will allow you to set breakpoints in the server for debugging potential bugs.

Once the server is running you can then run the Unity client, frontend web server, run integration tests, discord bot, etc. to send requests to the local server for debugging.

#### `production` Deployment

The production deployment will deploy the mongo database as well as a running instance of the server.
This is most useful if you are not planning to change the server and just want to develop a client, for example, the unity client, discord bot, website, etc.

To deploy in production mode, ensure that you are in the root directory (One directory up from the file you are looking at right now).
Then, deploy with the following command:

```
docker-compose --profile production up -d
```

Once this command is executed, you will have a running database and server.
Access the server at: `http://localhost:5295`, or view the swagger UI at `http://localhost:5295/swagger/index.html`

#### `test` Deployment

The `test` deployment is not advised to be used locally.
This deployment is mainly for testing the server builds within a CI/CD pipeline.
This deployment starts up the database, server, and a container that executes integration tests.

The integration tests will flush the mongodb database when they start up so it is not advised to start this mode locally unless you are okay with deleting your entire database.

To deploy in production mode, ensure that you are in the root directory (One directory up from the file you are looking at right now).
Then, deploy with the following command:

```
docker-compose --profile test up -d
```

Once this command is executed, you will have a running database and server as well as a container executing integration tests against them.
The CI/CD pipeline uses the following extra flags: `--abort-on-container-exit --exit-code-from server_test`.
These flags will get the exit code from the integration test container and return them to the CI/CD pipeline. If the tests pass, the CI/CD pipeline passes, otherwise the pipeline fails.

# MongoDB Table Design

View the database models [here](https://github.com/Subterfuge-Revived/Remake-Core/tree/master/Server/SubterfugeDatabaseProvider/Models)