text:
using System;
using System.Collections.Generic;
using System.Linq;
using Subterfuge.Remake.Core.GameEvents.Combat;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Timing;
using Subterfuge.Remake.Core.Topologies;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class PositionManager : EntityComponent, ICombatEventPublisher
    {
        private IEntity _destination;
        private readonly IEntity _source;
        private readonly RftVector _initialLocation;
        public RftVector CurrentLocation { get; private set; }
        private readonly GameTick _startTime;
        private readonly SpeedManager _speedManager;

        private List<CombatEvent> localCombatEvents = new List<CombatEvent>();

        public PositionManager(
            IEntity parent,
            IEntity initialLocation,
            IEntity destination,
            GameTick startTime,
            TimeMachine timeMachine
        ) : base(parent) {
            this._destination = destination;
            this._source = initialLocation;
            this._initialLocation = _source.GetComponent<PositionManager>().CurrentLocation;
            _speedManager = parent.GetComponent<SpeedManager>();
            this._startTime = startTime;
            CurrentLocation = this._initialLocation;

            // Register to move this object on every tick.
            timeMachine.OnTick += OnTickCheck;
        }

        public PositionManager(
            IEntity parent,
            RftVector location,
            GameTick startTime,
            TimeMachine timeMachine
        ) : base(parent) {
            this._destination = null;
            this._source = null;
            this._initialLocation = location;
            this._startTime = startTime;
            _speedManager = parent.GetComponent<SpeedManager>();
            CurrentLocation = this._initialLocation;

            // Register to move this object on every tick.
            timeMachine.OnTick += OnTickCheck;
        }

        /// <summary>
        /// Moves the object 1 unit every tick scaled by speed.
        /// </summary>
        /// <param name="sender">The TimeMachine instance that published the event</param>
        /// <param name="onTick">On Tick event args</param>
        private void OnTickCheck(object sender, OnTickEventArgs onTick)
        {
            RftVector direction = GetDirection().Normalize();

            if (_destination != null)
            {
                if (onTick.Direction == TimeMachineDirection.FORWARD)
                {
                    this.CurrentLocation += (direction * _speedManager.GetSpeed());
                    // Check for combat events.
                    if (Parent is Sub && onTick.CurrentState.EntityExists(_destination) && onTick.CurrentState.EntityExists(Parent))
                    {
                        CheckCombat(sender as TimeMachine, onTick);
                    }
                }
                else
                {
                    this.CurrentLocation -= (direction * _speedManager.GetSpeed());
                }
            }
        }

        private void CheckCombat(TimeMachine timeMachine, OnTickEventArgs onTick)
        {
            timeMachine
                .GetState()
                .GetAllGameObjects()
                .Where(entity => entity != Parent)
                .Where(entity =>
                {
                    // If the sub has just launched, ignore it's source.
                    // Otherwise, it could be returning to the source.
                    if (onTick.CurrentTick.GetTick() - this.GetSpawnTick().GetTick() < 2)
                    {
                        return entity != _source;
                    }
                    return true;
                })
                .Where(entity =>
                    (entity.GetComponent<PositionManager>().CurrentLocation - this.CurrentLocation).Length() < Parent.GetComponent<SpeedManager>().GetSpeed())
                .ToList()
                .ForEach(entityToFight =>
                {
                    if (onTick.Direction == TimeMachineDirection.FORWARD)
                    {
                        var combat = new CombatEvent(this.Parent, entityToFight,
                            timeMachine.GetCurrentTick());

                        // Allow any listening specialists to modify the combat object.
                        OnRegisterCombatEffects?.Invoke(this, new OnRegisterCombatEventArgs()
                        {
                            Direction = onTick.Direction,
                            CurrentState = timeMachine.GetState(),
                            CombatEvent = combat,
                        });

                        timeMachine.AddEvent(combat);
                        localCombatEvents.Add(combat);
                    }
                });
        }

        /// <summary>
        /// Gets the rotation of the sub
        /// </summary>
        /// <returns>Gets the angle of the sub's path</returns>
        public double GetRotationRadians()
        {
            RftVector direction = GetDirection();
            return Math.Atan2(direction.Y, direction.X);
        }

        public GameTick GetSpawnTick()
        {
            return _startTime;
        }

        public RftVector GetExpectedDestination()
        {
            // If there is no destination, return to the source.
            if (_destination == null)
            {
                return _initialLocation;
            }

            // If the target is not moving, interpolate the current position from the destination.
            // Linear interpolation
            if (Math.Abs(_destination.GetComponent<SpeedManager>().GetSpeed()) < 0.001)
            {
                return _destination.GetComponent<PositionManager>().CurrentLocation;
            }
            else
            {
                // If this is not moving, but the target is, show the destination as here...
                if (Math.Abs(_speedManager.GetSpeed()) < 0.001)
                {
                    return this.CurrentLocation;
                }

                // Get the target's interception point using the initial position & the speed.
                var interceptionPoint = _destination.GetComponent<PositionManager>()
                    .GetInterceptionPosition(_initialLocation, _speedManager.GetSpeed());
                return interceptionPoint;
            }
        }

        /// <summary>
        /// Get the combat location if this object is targeted by another
        /// </summary>
        /// <param name="targetFrom">The location that this sub is being targeted from</param>
        /// <param name="chaserSpeed">The speed this is being targeted by</param>
        /// <returns>The location of combat</returns>
		public RftVector GetInterceptionPosition(RftVector targetFrom, float chaserSpeed)
        {
            // https://stackoverflow.com/questions/37250215/intersection-of-two-moving-objects-with-latitude-longitude-coordinates

            var distanceBetweenTargets = (CurrentLocation - targetFrom).Length();
            var directionBetweenTargets = Math.Atan2(CurrentLocation.Y - targetFrom.Y, CurrentLocation.X - targetFrom.X);
            var alpha = Math.PI + directionBetweenTargets - Math.Atan2(GetDirection().Y, GetDirection().X);

            if (Math.Abs(chaserSpeed - _speedManager.GetSpeed()) < 0.001)
            {
                if (Math.Cos(alpha) < 0)
                {
                    return null;
                }
                // This determines what direction the chaser needs to go.
                // Create a Normalized vector from this.
                var radians = (directionBetweenTargets + alpha) % (Math.PI * 2);
                // With isosceles triangle, determine the other two angles:
                var otherAngles = ((Math.PI * 2) - radians) / 2.0;
                var distanceToTravelBeforeInterception = (float)(Math.Abs(Math.Cos(otherAngles) * distanceBetweenTargets));
                var timeToReach = distanceToTravelBeforeInterception / _speedManager.GetSpeed();
                var dir = GetDirection().Normalize();
                return new RftVector(CurrentLocation.X + (dir.X * timeToReach), CurrentLocation.Y + (dir.Y * timeToReach));
            }

            // Solve with quadratic formula
            var a = Math.Pow(chaserSpeed, 2) - Math.Pow(this._speedManager.GetSpeed(), 2);
            var b = 2 * distanceBetweenTargets * this._speedManager.GetSpeed() * Math.Cos(alpha);
            var c = -Math.Pow(distanceBetweenTargets, 2);

            var discriminant = Math.Pow(b, 2) - (4 * a * c);
            if (discriminant < 0)
                return null;

            var time = (Math.Sqrt(discriminant) - b) / (2 * a);
            var normalizedDirection = GetDirection().Normalize();
            var x = _initialLocation.X +
                    (this._speedManager.GetSpeed() * time) * Math.Cos(Math.Atan2(normalizedDirection.Y, normalizedDirection.X));
            var y = _initialLocation.Y +
                    (this._speedManager.GetSpeed() * time) * Math.Sin(Math.Atan2(normalizedDirection.Y, normalizedDirection.X));

            return new RftVector((float)x, (float)y);
        }

        public RftVector GetDirection()
        {
            if (_destination == null)
            {
                return new RftVector(0, 0);
            }

            return (GetExpectedDestination() - _initialLocation);
        }

        public GameTick GetExpectedArrival()
        {
            if (_destination == null || _speedManager.GetSpeed() < 0.001)
            {
                return null;
            }
            RftVector direction = GetDirection();
            int ticksToArrive = (int) Math.Floor(direction.Length() / _speedManager.GetSpeed());
            return _startTime.Advance(ticksToArrive);
        }

        public IEntity GetDestination()
        {
            return _destination;
        }

        public RftVector GetInitialPosition()
        {
            return this._initialLocation;
        }

        public void SetDestination(IEntity destination)
        {
            this._destination = destination;
        }

        public IEntity GetSource()
        {
            return _source;
        }

        public void OnTargeted(IEntity targetedBy)
        {
            OnLocationTargeted?.Invoke(this, new OnLocationTargetedEventArgs()
            {
                Destination = Parent,
                TargetedBy = targetedBy,
            });
        }

        public event EventHandler<OnRegisterCombatEventArgs>? OnRegisterCombatEffects;
        public event EventHandler<OnLocationTargetedEventArgs>? OnLocationTargeted;
    }
}


text:
using System;
using Subterfuge.Remake.Core.Players;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class DrillerCarrier : EntityComponent
    {
        private Player _owner;
        private int _drillers;
        private bool _isCaptured;
        private bool _isDestroyed;

        /// <summary>
        /// Creates a new driller carrier
        /// </summary>
        /// <param name="drillerCount">The number of drillers in the carrier</param>
        /// <param name="owner">The owner of the carrier</param>
        /// <param name="parent"></param>
        public DrillerCarrier(IEntity parent, int drillerCount, Player owner) : base(parent)
        {
            this._drillers = drillerCount;
            this._owner = owner;
        }

        /// <summary>
        /// Gets the number of drillers on the carrier
        /// </summary>
        /// <returns></returns>
        public int GetDrillerCount()
        {
            return _drillers;
        }

        /// <summary>
        /// Sets the number of drillers on the carrier
        /// </summary>
        /// <param name="drillerCount">The number of drillers to set</param>
        public void SetDrillerCount(int drillerCount)
        {
            this._drillers = drillerCount;
        }

        public int AlterDrillers(int delta)
        {
            var initialAmount = _drillers;
            _drillers = Math.Max(0, _drillers + delta);
            return _drillers - initialAmount;
        }

        /// <summary>
        /// Check if the carrier contains the specified number of drillers
        /// </summary>
        /// <param name="drillers">The number of drillers to check for</param>
        /// <returns>If the carrier contains the number of drillers</returns>
        public bool HasDrillers(int drillers)
        {
            return this._drillers >= drillers;
        }

        /// <summary>
        /// Sets the driller carrier to have a new owner.
        /// </summary>
        /// <param name="newOwner">The new owner of the carrier</param>
        /// <param name="newDrillerCount">The new driller count of the carrier</param>
        public void SetNewOwner(Player newOwner, int newDrillerCount)
        {
            _drillers = newDrillerCount;
            _owner = newOwner;
            _isCaptured = false;
        }

        public Player? GetOwner()
        {
            return this._owner;
        }

        public void SetOwner(Player player)
        {
            this._owner = player;
        }

        /// <summary>
        /// Checks if the carrier is dead
        /// This should only be true if the carrier is not existing in the game.
        /// </summary>
        /// <returns>If the carrier is alive</returns>
        public bool IsCaptured()
        {
            return _isCaptured;
        }

        public void SetCaptured(bool isCaptured)
        {
            this._isCaptured = isCaptured;
        }

        public bool IsDestroyed()
        {
            return _isDestroyed;
        }

        /// <summary>
        /// Destroys the outpost.
        /// </summary>
        /// <returns>The number of drillers lost.</returns>
        public int Destroy()
        {
            _isDestroyed = true;
            var drillersDestroyed = _drillers;
            _drillers = 0;
            return drillersDestroyed;
        }

        public void UndoDestroy(int drillersToRestore)
        {
            _isDestroyed = false;
            _drillers = drillersToRestore;
        }
    }
}

repo_name: Subterfuge-Revived/Remake-Core

repo_language: C#

mime_type: text/plain


text:
using System;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class IdentityManager : EntityComponent
    {
        private readonly string _id;
        private string _name;

        public IdentityManager(IEntity parent, string id, string name) : base(parent)
        {
            this._id = id;
            this._name = name;
        }

        public IdentityManager(IEntity parent, string id = null) : base(parent)
        {
            this._id = id;
            this._name = Guid.NewGuid().ToString();
        }

        public void SetName(string name)
        {
            this._name = name;
        }

        public string GetId()
        {
            return this._id;
        }

        public string GetName()
        {
            return this._name;
        }
    }
}


text:
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class DrillerProducer : EntityComponent
    {
        public ResourceProducer Producer { get; set; }

        public DrillerProducer(
            IEntity parent,
            TimeMachine timeMachine
        ) : base(parent)
        {
            Producer = new ResourceProducer(parent,
                timeMachine,
                Constants.TicksPerProduction,
                Constants.BaseFactoryProductionAmount,
                ProducedResourceType.Driller
            );
        }
    }
}


text:
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class ShieldProducer : EntityComponent
    {
        public ResourceProducer Producer { get; set; }

        public ShieldProducer(
            IEntity parent,
            TimeMachine timeMachine
        ) : base(parent)
        {
            Producer = new ResourceProducer(
                parent,
                timeMachine,
                Constants.BASE_SHIELD_REGENERATION_TICKS,
                1,
                ProducedResourceType.Shield
            );
        }
    }
}


text:
using System;
using System.Collections.Generic;
using System.Linq;
using Subterfuge.Remake.Core.Entities.Specialists;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Components
{
    /// <summary>
    /// Specialist management class to facilitate adding and removing specialists from ISpecialistCarrier classes.
    /// </summary>
    public class SpecialistManager : EntityComponent, ISpecialistEventPublisher
    {
        /// <summary>
        /// The maximum number of specialists that can be stored in this carrier
        /// </summary>
        private int _capacity;

        /// <summary>
        /// A list of specialists in the carrier
        /// </summary>
        List<Specialist> _specialists = new List<Specialist>();

        public bool CanLoadCapturedSpecialists { get; set; } = false;
        private int _canHireAtLocation = 0;

        public event EventHandler<OnSpecialistHireEventArgs>? OnSpecialistHire;
        public event EventHandler<OnSpecialistsCapturedEventArgs>? OnSpecialistCapture;
        public event EventHandler<OnSpecialistPromotionEventArgs>? OnSpecialistPromotion;
        public event EventHandler<OnSpecialistTransferEventArgs>? OnSpecialistTransfer;

        /// <summary>
        /// Constructor with a specific capacity
        /// </summary>
        /// <param name="parent">The parent entity</param>
        /// <param name="capacity">The capacity of the manager</param>
        public SpecialistManager(IEntity parent, int capacity = 3) : base(parent)
        {
            _capacity = capacity;
        }

        /// <summary>
        /// Gets a list of the held specialists
        /// </summary>
        /// <returns>A list of the held specialists</returns>
        public List<Specialist> GetSpecialists()
        {
            return _specialists;
        }

        /// <summary>
        /// Determines if a specialist can be added to this location
        /// </summary>
        /// <returns>If a specialist can be added</returns>
        public bool CanAddSpecialists(int specsToAdd = 1)
        {
            return GetUncapturedSpecialistCount() + specsToAdd <= _capacity;
        }

        public bool CanAddSpecialist(Specialist specialist)
        {
            if (GetUncapturedSpecialistCount() < _capacity)
                return false;

            if (Parent is Sub && specialist.IsCaptured() && !CanLoadCapturedSpecialists)
                return false;

            return true;
        }

        public void AllowHireFromLocation()
        {
            _canHireAtLocation++;
        }

        public void DisallowHireFromLocation()
        {
            _canHireAtLocation--;
        }

        public bool HireSpecialist(Specialist specialist, TimeMachine timeMachine)
        {
            if (_canHireAtLocation == 0 && !specialist.IsHero)
                return false;

            if (GetUncapturedSpecialistCount() < _capacity)
            {
                _specialists.Add(specialist);
                // Must register event listener before triggering other events.
                specialist.ArriveAtLocation(Parent, timeMachine);
                OnSpecialistHire?.Invoke(this, new OnSpecialistHireEventArgs()
                {
                    Direction = TimeMachineDirection.FORWARD,
                    HiredSpecialist = specialist,
                    HireLocation = Parent,
                });
                return true;
            }
            return false;
        }

        public bool UndoHireSpecialist(Specialist specialist, TimeMachine timeMachine)
        {
            _specialists.Remove(specialist);
            // Reverse must trigger events before unregistering listeners
            OnSpecialistHire?.Invoke(this, new OnSpecialistHireEventArgs()
            {
                Direction = TimeMachineDirection.REVERSE,
                HiredSpecialist = specialist,
                HireLocation = Parent,
            });
            specialist.LeaveLocation(Parent, timeMachine);
            return true;
        }

        /// <summary>
        /// Get the capacity
        /// </summary>
        /// <returns>The capacity of specialists</returns>
        public int GetCapacity()
        {
            return _capacity;
        }

        /// <summary>
        /// Sets the capacity
        /// </summary>
        /// <param name="capacity">The capacity to set</param>
        public int AlterCapacity(int delta)
        {
            var initialCapacity = _capacity;
            _capacity = Math.Max(0, _capacity + delta);
            return _capacity - initialCapacity;
        }

        /// <summary>
        /// Returns the current number of specialists
        /// </summary>
        /// <returns>The current number of specialists</returns>
        public int GetUncapturedSpecialistCount()
        {
            return _specialists.Where(it => !it.IsCaptured()).ToList().Count;
        }

        public int GetTotalSpecialistCount()
        {
            return _specialists.Count;
        }

        public int GetCapturedSpecialistCount()
        {
            return _specialists.Where(it => it.IsCaptured()).ToList().Count;
        }

        /// <summary>
        /// Transfers all of the specialists from this specialist manager to the target specialist manager.
        /// </summary>
        /// <param name="specialistManager"></param>
        public bool TransferSpecialistsTo(SpecialistManager specialistManager, TimeMachine timeMachine)
        {
            if (specialistManager.CanAddSpecialists(this._specialists.Count))
            {
                List<Specialist> toRemove = new List<Specialist>(_specialists);
                _specialists.Clear();
                specialistManager._specialists.AddRange(toRemove);

                toRemove.ForEach(spec =>
                {
                    spec.LeaveLocation(Parent, timeMachine);
                    spec.ArriveAtLocation(specialistManager.Parent, timeMachine);
                });

                OnSpecialistTransfer?.Invoke(this , new OnSpecialistTransferEventArgs()
                {
                    AddedTo = specialistManager.Parent,
                    Direction = TimeMachineDirection.FORWARD,
                    RemovedFrom = this.Parent,
                    specialist = toRemove,
                });

                return true;
            }
            return false;
        }

        /// <summary>
        /// Transfers all of the specialists from this specialist manager to the target specialist manager.
        /// </summary>
        /// <param name="destinationSpecialistManager">Specialist manager to transfer subs to</param>
        /// <param name="specialistIds">List of specialist Ids to transfer</param>
        public bool TransferSpecialistsById(SpecialistManager destinationSpecialistManager, List<string> specialistIds, TimeMachine timeMachine)
        {
            var specialistsMatchingId = _specialists.Where(it => specialistIds.Contains(it.GetId())).ToList();
            if (destinationSpecialistManager.CanAddSpecialists(specialistsMatchingId.Count))
            {
                _specialists = _specialists.Where(it => !specialistIds.Contains(it.GetId())).ToList();
                destinationSpecialistManager._specialists.AddRange(specialistsMatchingId);

                specialistsMatchingId.ForEach(spec =>
                {
                    spec.LeaveLocation(Parent, timeMachine);
                    spec.ArriveAtLocation(destinationSpecialistManager.Parent, timeMachine);
                });

                OnSpecialistTransfer?.Invoke(this , new OnSpecialistTransferEventArgs()
                {
                    AddedTo = destinationSpecialistManager.Parent,
                    Direction = TimeMachineDirection.FORWARD,
                    RemovedFrom = this.Parent,
                    specialist = specialistsMatchingId,
                });

                return true;
            }

            return false;
        }

        public void CaptureAllForward(TimeMachine timeMachine, IEntity captureLocation)
        {
            foreach(Specialist s in _specialists)
            {
                s.SetCaptured(true, timeMachine, captureLocation);
            }

            OnSpecialistCapture?.Invoke(this, new OnSpecialistsCapturedEventArgs()
            {
                Direction = TimeMachineDirection.FORWARD,
                Location = captureLocation,
                TimeMachine = timeMachine,
            });
        }

        /// <summary>
        /// Sets all of the specialists within this specialist manager to be captured.
        /// </summary>
        public void KillAll()
        {
            _specialists.Clear();
        }

        public void ReviveAll(List<Specialist> killedSpecialists)
        {
            _specialists.AddRange(killedSpecialists);
        }

        /// <summary>
        /// Uncaptures all of the specialists within this specialist manager.
        /// </summary>
        public void UncaptureAll(TimeMachine timeMachine, IEntity captureLocation)
        {
            foreach(Specialist s in _specialists)
            {
                s.SetCaptured(false, timeMachine, captureLocation);
            }
        }

        /// <summary>
        /// Gets all of the specialists belonging to a specific player.
        /// </summary>
        /// <param name="player"></param>
        /// <returns></returns>
        public List<Specialist> GetPlayerSpecialists(Player player)
        {
            List<Specialist> playerSpecs = new List<Specialist>();
            foreach(Specialist s in _specialists)
            {
                if (Equals(s.GetOwner(), player))
                {
                    playerSpecs.Add(s);
                }
            }

            return playerSpecs;
        }
    }
}


text:
using System;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class ResourceProducer : IResourceProductionEventPublisher
    {
	    private GameTick _lastProductionTick = null;
        private GameTick _nextProductionTick;
        private int _ticksPerProductionCycle;
        private int _nextValueToProduce;
        private ProducedResourceType _typeToProduce;
        private bool _isPaused = false;
        private TimeMachine _timeMachine;
        private IEntity _productionLocation;

        public bool IgnoresCapacity { get; set; }

        public ResourceProducer(
            IEntity productionLocation,
            TimeMachine timeMachine,
            int ticksPerProductionCycle,
            int baseValuePerProduction,
            ProducedResourceType producedType
        )
        {
	        _productionLocation = productionLocation;
	        _ticksPerProductionCycle = ticksPerProductionCycle;
	        _nextValueToProduce = baseValuePerProduction;
	        this._nextProductionTick = timeMachine.GetCurrentTick().Advance(_ticksPerProductionCycle);
	        _typeToProduce = producedType;
	        _timeMachine = timeMachine;

            timeMachine.OnTick += ProductionTickListener;
        }

         public void SetPaused(bool isPaused)
	    {
		    _isPaused = isPaused;
	    }

	    public void ChangeAmountProducedPerCycle(int delta)
	    {
		    _nextValueToProduce = Math.Max(0, _nextValueToProduce + delta);
	    }

	    public int GetAmountProducedPerCycle()
	    {
		    return _nextValueToProduce;
	    }

	    public void ChangeTicksPerProductionCycle(int delta)
	    {
		    _ticksPerProductionCycle = Math.Max(0, _ticksPerProductionCycle + delta);
	    }

	    public GameTick GetNextProductionTick()
	    {
		    return _nextProductionTick;
	    }

	    public int GetTicksPerProductionCycle()
	    {
		    return _ticksPerProductionCycle;
	    }

        private void ProductionTickListener(object sender, OnTickEventArgs tickEventArgs)
        {
	        if (tickEventArgs.Direction == TimeMachineDirection.FORWARD)
			{
				if (_isPaused)
				{
					_nextProductionTick = _nextProductionTick.Advance(1);
					return;
				}

				if (_nextProductionTick == tickEventArgs.CurrentTick)
				{
					var resourceProduction = new ResourceProductionEvent(
						tickEventArgs.CurrentTick,
						locationProducedAt: _productionLocation,
						producedType: _typeToProduce,
						_nextValueToProduce,
						IgnoresCapacity
					);
					_lastProductionTick = tickEventArgs.CurrentTick;
					_timeMachine.AddEvent(resourceProduction);

					var productionEvent = new ProductionEventArgs()
					{
						TickProducedAt = tickEventArgs.CurrentTick,
						Direction = tickEventArgs.Direction,
						NextProduction = _nextProductionTick,
						ProducedResourceType = _typeToProduce,
						TimeMachine = _timeMachine,
						ProductionEvent = resourceProduction
					};

					_nextProductionTick = _nextProductionTick.Advance(_ticksPerProductionCycle);
					OnResourceProduced?.Invoke(this, productionEvent);
				}
			}
	        else
	        {
		        if (_isPaused)
		        {
			        _nextProductionTick = _nextProductionTick.Rewind(1);
			        return;
		        }
	        }
		}

        public void SetNextProductionTick(GameTick nextProduction)
        {
	        _nextProductionTick = nextProduction;
        }

        public event EventHandler<ProductionEventArgs>? OnResourceProduced;
    }
}


text:
namespace Subterfuge.Remake.Core.Entities.Components
{

    public abstract class EntityComponent
    {
        public IEntity Parent { get; set; }

        protected EntityComponent(IEntity parent)
        {
            Parent = parent;
        }
    }
}


text:
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class NeptuniumProducer : EntityComponent
    {
        public ResourceProducer Producer { get; set; }

        public NeptuniumProducer(
            IEntity parent,
            TimeMachine timeMachine
        ) : base(parent)
        {
            Producer = new ResourceProducer(
                parent,
                timeMachine,
                (int)(1440 / GameTick.MinutesPerTick),
                1,
                ProducedResourceType.Neptunium
            );
        }
    }
}


text:
using System;
using System.Collections.Generic;
using System.Numerics;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class VisionManager : EntityComponent, IVisionEventPublisher
    {
        private float _visionRange;
        private PositionManager position;
        private List<IEntity> _lastCheckedEntitiesInRange = new List<IEntity>();

        public event EventHandler<OnVisionRangeChangeEventArgs> OnVisionRangeChange;
        public event EventHandler<OnEntityEnterVisionRangeEventArgs> OnEntityEnterVisionRange;
        public event EventHandler<OnEntityLeaveVisionRangeEventArgs> OnEntityLeaveVisionRange;

        public VisionManager(IEntity parent, float visionRange) : base(parent)
        {
            this._visionRange = visionRange;
            this.position = parent.GetComponent<PositionManager>();
        }

        /// <summary>
        /// Gets the range of vision for the current object
        /// </summary>
        /// <returns>The object's vision range</returns>
        public float GetVisionRange()
        {
            return this._visionRange;
        }

        /// <summary>
        /// Sets the vision range to the specified value.
        /// </summary>
        /// <param name="newVisionRange">The new vision range.</param>
        /// <param name="state">The game state</param>
        /// <param name="tick">The current tick</param>
        public void SetVisionRange(float newVisionRange, IGameState state)
        {
            var previousVisionRange = this._visionRange;
            this._visionRange = newVisionRange;

            OnVisionRangeChange?.Invoke(this, new OnVisionRangeChangeEventArgs()
            {
                NewVisionRange = newVisionRange,
                PreviousVisionRange = previousVisionRange,
                VisionManager = this,
            });

            // Update what the vision manager can see.
            GetEntitiesInVisionRange(state);
        }

        /// <summary>
        /// Determines if the position is in vision of this object.
        /// </summary>
        /// <param name="tick">The tick to check</param>
        /// <param name="positionManager">The position manager</param>
        /// <returns>If the object is in the vision range.</returns>
        public bool IsInVisionRange(PositionManager positionManager)
        {
            var pos = position.CurrentLocation;
            return Vector2.Distance(pos.ToVector2(), positionManager.CurrentLocation.ToVector2()) < _visionRange;
        }

        public List<IEntity> GetEntitiesInVisionRange(IGameState state)
        {
            var currentPosition = this.position.CurrentLocation;
            var currentEntitiesInRange = state.EntitesInRange(this._visionRange, currentPosition);

            // Check entities that have left the vision range (or that have died/etc.)
            foreach (IEntity e in _lastCheckedEntitiesInRange)
            {
                if (!currentEntitiesInRange.Contains(e))
                {
                    OnEntityLeaveVisionRange?.Invoke(this, new OnEntityLeaveVisionRangeEventArgs()
                    {
                        EntityLeavingVision = e,
                        VisionManager = this,
                    });
                }
            }

            // Check new entities in range
            foreach (IEntity e in currentEntitiesInRange)
            {
                if (!_lastCheckedEntitiesInRange.Contains(e))
                {
                    OnEntityEnterVisionRange?.Invoke(this, new OnEntityEnterVisionRangeEventArgs()
                    {
                        EntityInVision = e,
                        VisionManager = this,
                    });
                }
            }

            _lastCheckedEntitiesInRange = currentEntitiesInRange;
            return _lastCheckedEntitiesInRange;
        }
    }
}


text:
using System;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class ShieldManager : EntityComponent, IShieldEventPublisher
    {
        /// <summary>
        /// The outposts shields
        /// </summary>
        int _shields;

        /// <summary>
        /// If the outpost's shields are active.
        /// </summary>
        bool _shieldActive;

        /// <summary>
        /// The maximum number of shields the outpost can have.
        /// </summary>
        int _shieldCapacity;

        public event EventHandler<OnToggleShieldEventArgs> OnToggleShield;
        public event EventHandler<OnShieldCapacityChangeEventArgs> OnShieldCapacityChange;
        public event EventHandler<OnShieldValueChangeEventArgs> OnShieldValueChange;

        public ShieldManager(
            IEntity parent,
            int shieldCapacity,
            int initialShields = 0
            ) : base(parent)
        {
            if (initialShields > shieldCapacity)
            {
                _shields = shieldCapacity;
                _shieldActive = true;
                _shieldCapacity = shieldCapacity;
            }
            else
            {
                _shields = initialShields;
                _shieldActive = true;
                _shieldCapacity = shieldCapacity;
            }
        }

        public int GetShields()
        {
            return this._shields;
        }

        public void SetShields(int shieldValue)
        {
            var previousValue = _shields;
            this._shields = Math.Max(0, Math.Min(shieldValue, this._shieldCapacity));

            OnShieldValueChange?.Invoke(this, new OnShieldValueChangeEventArgs()
            {
                ShieldDelta = this._shields - previousValue,
                ShieldManager = this,
            });
        }

        public void AlterShieldCapacity(int delta)
        {
            _shieldCapacity = Math.Max(0, _shieldCapacity + delta);
        }

        public int AlterShields(int shieldDelta)
        {
            var initial = _shields;
            _shields = Math.Max(0, Math.Min(this._shieldCapacity, this._shields + shieldDelta));

            OnShieldValueChange?.Invoke(this, new OnShieldValueChangeEventArgs()
            {
                ShieldDelta = this._shields - initial,
                ShieldManager = this,
            });

            return this._shields - initial;
        }

        public void ToggleShield()
        {
            this._shieldActive = !this._shieldActive;
            OnToggleShield?.Invoke(this, new OnToggleShieldEventArgs()
            {
                ShieldManager = this
            });
        }

        public bool IsShieldActive()
        {
            return this._shieldActive;
        }

        public int GetShieldCapacity()
        {
            return this._shieldCapacity;
        }

        public void SetShieldCapacity(int capactiy)
        {
            var previousCapacity = _shieldCapacity;
            this._shieldCapacity = capactiy;

            this._shields = Math.Min(_shields, _shieldCapacity);

            OnShieldCapacityChange?.Invoke(this, new OnShieldCapacityChangeEventArgs()
            {
                CapacityDelta = this._shieldCapacity - previousCapacity,
                ShieldManager = this,
            });
        }
    }
}


text:
using System;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class SpeedManager : EntityComponent, ISpeedEventPublisher
    {
        private float _speed;

        public event EventHandler<OnSpeedChangedEventArgs> OnSpeedChanged;

        public SpeedManager(IEntity parent, float initialSpeed) : base(parent)
        {
            _speed = initialSpeed;
        }

        public float GetSpeed()
        {
            return _speed;
        }

        public void IncreaseSpeed(float increaseBy)
        {
            var previousSpeed = _speed;
            _speed += increaseBy;
            OnSpeedChanged?.Invoke(this, new OnSpeedChangedEventArgs()
            {
                PreviousSpeed = previousSpeed,
                NewSpeed = _speed,
                SpeedManager = this,
            });
        }

        public void DecreaseSpeed(float decreaseBy)
        {
            var previousSpeed = _speed;
            _speed -= decreaseBy;
            OnSpeedChanged?.Invoke(this, new OnSpeedChangedEventArgs()
            {
                PreviousSpeed = previousSpeed,
                NewSpeed = _speed,
                SpeedManager = this,
            });
        }

        public void SetSpeed(float newSpeed)
        {
            var previousSpeed = _speed;
            _speed = newSpeed;
            OnSpeedChanged?.Invoke(this, new OnSpeedChangedEventArgs()
            {
                PreviousSpeed = previousSpeed,
                NewSpeed = newSpeed,
                SpeedManager = this,
            });
        }
    }
}


text:
using System;
using System.Linq;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.GameEvents.PlayerTriggeredEvents;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Components
{
    public class SubLauncher : EntityComponent, ISubLaunchEventPublisher
    {

        /// <summary>
        /// The number of drillers at the outpost
        /// </summary>
        private readonly DrillerCarrier _drillerCarrier;
        private readonly SpecialistManager _specialistManager;

        public event EventHandler<OnSubLaunchEventArgs> OnSubLaunched;

        public bool CanTargetSubs { get; set; } = false;

        public SubLauncher(IEntity parent) : base(parent)
        {
            _drillerCarrier = parent.GetComponent<DrillerCarrier>();
            _specialistManager = parent.GetComponent<SpecialistManager>();
        }

        public Sub LaunchSub(
            TimeMachine timeMachine,
            LaunchEvent launchEvent
        ) {
            // Determine any specialist effects if a specialist left the sub.
            LaunchEventData launchData = launchEvent.GetEventData();
            GameState state = timeMachine.GetState();
            Entity source = state.GetEntity(launchData.SourceId);
            Entity destination = state.GetEntity(launchData.DestinationId);

            if (destination is Sub && CanTargetSubs == false)
            {
                if (!CanTargetSubs || !launchData.SpecialistIds.Contains(SpecialistTypeId.Pirate.ToString()))
                {
                    // Error. Attempting to target a sub when you cannot target subs or have not brought the pirate with you.
                    return null;
                }
            }

            if (destination != null && _drillerCarrier.HasDrillers(launchData.DrillerCount))
            {
                _drillerCarrier.AlterDrillers(launchData.DrillerCount * -1);
                Sub launchedSub = new Sub(launchEvent.GetEventId(), source, destination, state.GetCurrentTick(), launchData.DrillerCount, this._drillerCarrier.GetOwner(), timeMachine);
                this._specialistManager.TransferSpecialistsById(launchedSub.GetComponent<SpecialistManager>(), launchData.SpecialistIds.ToList(), timeMachine);
                state.AddSub(launchedSub);
                launchEvent.SetLaunchedSub(launchedSub);

                OnSubLaunched?.Invoke(this, new OnSubLaunchEventArgs()
                {
                    Destination = destination,
                    LaunchedSub = launchedSub,
                    LaunchEvent = launchEvent,
                    Source = source,
                    Direction = TimeMachineDirection.FORWARD
                });
                destination.GetComponent<PositionManager>().OnTargeted(launchedSub);

                return launchedSub;
            }

            return null;
        }

        public void UndoLaunch(TimeMachine timeMachine, LaunchEvent launchEvent)
        {
            // Determine any specialist effects if a specialist left the sub.
            LaunchEventData launchData = launchEvent.GetEventData();

            Sub launchedSub = launchEvent.GetActiveSub();
            if (launchedSub != null && timeMachine.GetState().SubExists(launchedSub))
            {
                _drillerCarrier.AlterDrillers(launchData.DrillerCount);
                launchedSub.GetComponent<SpecialistManager>().TransferSpecialistsTo(_specialistManager, timeMachine);
                timeMachine.GetState().RemoveSub(launchEvent.GetActiveSub());

                OnSubLaunched?.Invoke(this, new OnSubLaunchEventArgs()
                {
                    LaunchedSub = launchedSub,
                    LaunchEvent = launchEvent,
                    Source = Parent,
                    Direction = TimeMachineDirection.REVERSE
                });

            }
        }
    }
}


text:
using System;
using System.Collections.Generic;
using System.Linq;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists
{
    public class SpecialistPool
    {
        /// <summary>
        /// Holds a collection of all of the specialists being used in the active game.
        /// </summary>
        private List<SpecialistTypeId> _specialistHireOrder = new List<SpecialistTypeId>();
        private List<SpecialistTypeId> _hiredSpecialists = new List<SpecialistTypeId>();
        private int currentHireIndex = 0;

        private Player _player;

        public SpecialistPool(
            Player player,
            SeededRandom seededRandom,
            List<SpecialistTypeId> playerSpecialistDeck
        )
        {
            _player = player;

            // Create the specialist hire order.
            // Create N lists where N is the number of offered specialists. This ensures that the elements in the list are divisible by N
            for (int i = 0; i < Constants.SPECIALISTS_OFFERED_PER_CYCLE; i++)
            {
                _specialistHireOrder
                    .AddRange(playerSpecialistDeck.OrderBy(it => seededRandom.NextRand(0, 1000)));
            }
        }

        /// <summary>
        /// Look at the N next specialists in the pool
        /// </summary>
        /// <returns></returns>
        public List<SpecialistTypeId> PeekOffers()
        {
            var index = (currentHireIndex * Constants.SPECIALISTS_OFFERED_PER_CYCLE) % _specialistHireOrder.Count;
            return _specialistHireOrder.GetRange(index, Constants.SPECIALISTS_OFFERED_PER_CYCLE);
        }

        public Specialist HireSpecialist(SpecialistTypeId specialistTypeId) {
            if (PeekOffers().Contains(specialistTypeId))
            {
                currentHireIndex++;
                _hiredSpecialists.Add(specialistTypeId);
                return SpecialistFactory.CreateSpecialist(specialistTypeId, _player.SpecialistPool.currentHireIndex, _player);
            }
            return null;
        }

        public void UndoSpecialistHire()
        {
            currentHireIndex--;
        }
    }
}


text:
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Specialists.Heroes;
using Subterfuge.Remake.Core.Entities.Specialists.Specialists;
using Subterfuge.Remake.Core.Players;

namespace Subterfuge.Remake.Core.Entities.Specialists
{
    public class SpecialistFactory
    {
        public static Specialist CreateSpecialist(
            SpecialistTypeId specialistTypeId,
            int hireNumber,
            Player owner
        ) {
            switch (specialistTypeId)
            {
                case SpecialistTypeId.Queen:
                    return new Queen(owner);
                case SpecialistTypeId.Advisor:
                    return new Advisor(owner);
                case SpecialistTypeId.Foreman:
                    return new Foreman(owner);
                case SpecialistTypeId.Helmsman:
                    return new Helmsman(owner);
                case SpecialistTypeId.Infiltrator:
                    return new Infiltrator(owner);
                case SpecialistTypeId.Smuggler:
                    return new Smuggler(owner);
                case SpecialistTypeId.Veteran:
                    return new Veteran(owner);
                default:
                    return null;
            }
        }
    }
}


text:
using System;
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.GameEvents.Base;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists
{
    /// <summary>
    /// Base class for a specialist.
    /// </summary>
    public abstract class Specialist
    {
        /// <summary>
        /// The name of the specialist
        /// </summary>
        /// TODO: Generate the id from a known seed.
        private readonly string _specialistId = Guid.NewGuid().ToString();

        /// <summary>
        /// The player who owns the specialist
        /// </summary>
        protected Player _owner;

        /// <summary>
        /// Is the specialist captured by another player?
        /// </summary>
        protected bool _isCaptured = false;

        /// <summary>
        /// Determines the level of the effects applied
        /// </summary>
        protected int _level = 1;

        public bool IsHero { get; }

        /// <summary>
        /// Abstract constructor for a specialist. All inherited specialist classes require implementing this.
        /// </summary>
        /// <param name="name">The name of the specialist</param>
        /// <param name="priority">The specialist priority</param>
        /// <param name="owner">The player that owns the specialist</param>
        public Specialist(
            Player owner,
            bool isHero
        ) {
            _owner = owner;
            IsHero = isHero;
        }

        /// <summary>
        /// Returns the specialist id.
        /// </summary>
        /// <returns>The specialist's id</returns>
        public string GetId()
        {
            // TODO: Do something else here.
            return GetSpecialistId().ToString();
        }

        /// <summary>
        /// Gets the owner of the specialist.
        /// </summary>
        /// <returns>The specialist's owner</returns>
        public Player GetOwner()
        {
            return this._owner;
        }

        /// <summary>
        /// Sets the owner of the specialist
        /// </summary>
        /// <param name="newOwner">The new owner of the specialist</param>
        public void SetOwner(Player newOwner)
        {
            this._owner = newOwner;
        }

        /// <summary>
        /// Captures the specialist
        /// </summary>
        /// <param name="timeMachine"></param>
        /// <param name="captureLocation"></param>
        public void SetCaptured(
            bool isCaptured,
            TimeMachine timeMachine,
            IEntity captureLocation
        ) {
            this._isCaptured = isCaptured;
            OnCapture(isCaptured, captureLocation, timeMachine);
        }

        /// <summary>
        /// Check if the specialist is captured
        /// </summary>
        /// <returns>If the specialist if captured</returns>
        public bool IsCaptured()
        {
            return _isCaptured;
        }

        public int GetLevel()
        {
            return _level;
        }

        public void Promote()
        {
            _level++;
        }

        public void UndoPromote()
        {
            _level--;
        }

        public abstract void ArriveAtLocation(IEntity entity, TimeMachine timeMachine);
        public abstract void LeaveLocation(IEntity entity, TimeMachine timeMachine);
        public abstract void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine);

        public abstract SpecialistTypeId GetSpecialistId();

        public abstract String GetDescription();
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Tinkerer : Specialist
    {
        private List<int> DecreasedProductionTicks = new List<int>()
        {
            0,
            Constants.TicksPerProduction / 10, // 10%
            Constants.TicksPerProduction / 5 // 20%
        };

        public Tinkerer(Player owner, bool isHero) : base(owner, isHero)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {

            if (!_isCaptured)
            {
                entity.GetComponent<DrillerProducer>().Producer.IgnoresCapacity = true;
                entity.GetComponent<DrillerProducer>().Producer.ChangeTicksPerProductionCycle(-1 * DecreasedProductionTicks[_level]);
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<DrillerProducer>().Producer.IgnoresCapacity = false;
                entity.GetComponent<DrillerProducer>().Producer.ChangeTicksPerProductionCycle(DecreasedProductionTicks[_level]);
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            if (isCaptured)
            {
                entity.GetComponent<DrillerProducer>().Producer.IgnoresCapacity = false;
                entity.GetComponent<DrillerProducer>().Producer.ChangeTicksPerProductionCycle(DecreasedProductionTicks[_level]);
            }
            else
            {
                entity.GetComponent<DrillerProducer>().Producer.IgnoresCapacity = true;
                entity.GetComponent<DrillerProducer>().Producer.ChangeTicksPerProductionCycle(DecreasedProductionTicks[_level]);
            }
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Tinkerer;
        }

        public override string GetDescription()
        {
            return $"The factory where the Tinkerer is stationed is allowed th exceed the driller cap. " +
                   $"Increases driller production rate by {DecreasedProductionTicks} ticks.";
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
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;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Foreman : Specialist
    {

        public List<int> ExtraDrillersPerLevel = new List<int>() { 4, 6, 6 };

        public List<int> TickReductionPerLevel = new List<int>()
        {
            0,
            0,
            Constants.TicksPerProduction / 4
        };

        public Foreman(Player owner) : base(owner, false)
        {
        }

        private void ProduceMore(object? sender, ProductionEventArgs productionEventArgs)
        {
            productionEventArgs.ProductionEvent.ValueProduced += ExtraDrillersPerLevel[_level];
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                if (entity is Factory)
                {
                    entity.GetComponent<DrillerProducer>().Producer.OnResourceProduced += ProduceMore;

                    if (_level == 3)
                    {
                        entity.GetComponent<DrillerProducer>().Producer.ChangeTicksPerProductionCycle(-1 * TickReductionPerLevel[_level]);
                    }
                }
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                if (entity is Factory)
                {
                    entity.GetComponent<DrillerProducer>().Producer.OnResourceProduced -= ProduceMore;
                    if (_level == 3)
                    {
                        entity.GetComponent<DrillerProducer>().Producer.ChangeTicksPerProductionCycle(TickReductionPerLevel[_level]);
                    }
                }
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<DrillerProducer>().Producer.OnResourceProduced -= ProduceMore;
            if (_level == 3)
            {
                entity.GetComponent<DrillerProducer>().Producer.ChangeTicksPerProductionCycle(TickReductionPerLevel[_level]);
            }
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Foreman;
        }

        public override string GetDescription()
        {
            return $"Produces an additional {ExtraDrillersPerLevel} drillers while at a factory." +
                   $"Decreases production by {TickReductionPerLevel} ticks while at a factory.";
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class SignalJammer : Specialist
    {
        private List<float> SpeedReductionPerLevel = new List<float>() { 0.10f, 0.20f, 0.30f };

        public SignalJammer(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnLocationTargeted += OnTargetedBySub;
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnLocationTargeted -= OnTargetedBySub;
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<PositionManager>().OnLocationTargeted -= OnTargetedBySub;
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.SignalJammer;
        }

        public override string GetDescription()
        {
            return $"When the Signal Jammer's location is targeted by an enemy, the enemy is slowed by {SpeedReductionPerLevel}.";
        }

        public void OnTargetedBySub(object? sender, OnLocationTargetedEventArgs eventArgs)
        {
            eventArgs.TargetedBy.GetComponent<SpeedManager>().DecreaseSpeed(0.15f * _level);
        }
    }
}


text:
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Assassin : Specialist
    {
        public Assassin(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects += KillSpecialistRegisterCombatEffects;
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= KillSpecialistRegisterCombatEffects;
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= KillSpecialistRegisterCombatEffects;
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Assasin;
        }

        public override string GetDescription()
        {
            return "Kills all Specialists in combat.";
        }

        public void KillSpecialistRegisterCombatEffects(object? sender, OnRegisterCombatEventArgs combatArgs)
        {
            var friendlyEntity = combatArgs.CombatEvent.GetEntityOwnedBy(_owner);
            var enemyEntity = combatArgs.CombatEvent.GetEnemyEntity(_owner);

            combatArgs.CombatEvent.AddEffectToCombat(new KillSpecialistsEffect(
                combatArgs.CombatEvent.OccursAt,
                enemyEntity
            ));
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Base;
using Subterfuge.Remake.Core.GameEvents.Combat;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class NoOpSpecialist : Specialist
    {
        public NoOpSpecialist(
            Player owner
        ) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Advisor;
        }

        public override string GetDescription()
        {
            return $"Does nothing";
        }
    }
}


text:
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Bolster : Specialist
    {
        public Bolster(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SpecialistManager>().GetSpecialists().ForEach(it => it.Promote());
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SpecialistManager>().GetSpecialists().ForEach(it => it.UndoPromote());
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<SpecialistManager>().GetSpecialists().ForEach(it => it.UndoPromote());
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Bolster;
        }

        public override string GetDescription()
        {
            return $"All specialists at Bolster's location are promoted by 1 level.";
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Escort : Specialist
    {
        public List<int> deltaPerLevel = new List<int>() { 1, 2, 3 };

        public Escort(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SpecialistManager>().AlterCapacity(deltaPerLevel[_level]);
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SpecialistManager>().AlterCapacity(-1 * deltaPerLevel[_level]);
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<SpecialistManager>().AlterCapacity(-1 * deltaPerLevel[_level]);
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Escort;
        }

        public override string GetDescription()
        {
            return $"The Escort can carry an additional {deltaPerLevel} specialists.";
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Pirate : Specialist
    {
        public List<int> damagePerLevel = new List<int>() { 0, 15, 25 };

        public Pirate(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SubLauncher>().CanTargetSubs = true;
                if (entity is Sub)
                {
                    entity.GetComponent<PositionManager>().OnRegisterCombatEffects += DamageIfOnSub;
                }
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SubLauncher>().CanTargetSubs = false;
                if (entity is Sub)
                {
                    entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= DamageIfOnSub;
                }
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<SubLauncher>().CanTargetSubs = false;
            if (entity is Sub)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= DamageIfOnSub;
            }
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Pirate;
        }

        public override string GetDescription()
        {
            return $"The Pirate can directly target enemy subs." +
                   $"Returns home after combat." +
                   $"When attacking enemy subs, deals an additional {damagePerLevel} damage";
        }

        public void DamageIfOnSub(object? sender, OnRegisterCombatEventArgs registerCombatEventArgs)
        {
            var friendlyEntity = registerCombatEventArgs.CombatEvent.GetEntityOwnedBy(_owner);

            registerCombatEventArgs.CombatEvent.AddEffectToCombat(new AlterDrillerEffect(
                registerCombatEventArgs.CombatEvent,
                friendlyEntity,
                0,
                damagePerLevel[_level] * -1
            ));
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Smuggler : Specialist
    {
        public List<float> SpeedPerLevel = new List<float>() { 0.5f, 1.0f, 1.5f };

        public Smuggler(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured && entity is Sub)
            {
                var destinationOwner = GetOwnerOfSubDestination(entity);

                if (Equals(destinationOwner, _owner))
                {
                    entity.GetComponent<SpeedManager>().IncreaseSpeed(SpeedPerLevel[_level]);
                }
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            // Do nothing.
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            // Slow the sub down if we were captured while travelling to an outpost we own.
            if (!isCaptured && entity is Sub)
            {
                var captureDestinationOwner = GetOwnerOfSubDestination(entity);

                if (Equals(captureDestinationOwner, _owner))
                {
                    entity.GetComponent<SpeedManager>().DecreaseSpeed(SpeedPerLevel[_level]);
                }
            }
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Smuggler;
        }

        public override string GetDescription()
        {
            return $"When targeting a friendly outpost, increases speed by {SpeedPerLevel} units.";
        }

        private Player GetOwnerOfSubDestination(IEntity sub)
        {
            return sub
                .GetComponent<PositionManager>()
                .GetDestination()
                .GetComponent<DrillerCarrier>()
                .GetOwner();
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Base;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Helmsman : Specialist
    {
        private List<float> speedPerLevel = new List<float>() { 0.5f, 1.0f, 1.5f };

        public Helmsman(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SpeedManager>().IncreaseSpeed(speedPerLevel[_level]);
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SpeedManager>().DecreaseSpeed(speedPerLevel[_level]);
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<SpeedManager>().DecreaseSpeed(speedPerLevel[_level]);
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Helmsman;
        }

        public override string GetDescription()
        {
            return $"The Helmsman travels {speedPerLevel} units faster.";
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Scrutineer : Specialist
    {

        public List<int> TickReductionPerLevel = new List<int>() { Constants.BASE_SHIELD_REGENERATION_TICKS / 4, Constants.BASE_SHIELD_REGENERATION_TICKS / 2, Constants.BASE_SHIELD_REGENERATION_TICKS / 2 };

        public Scrutineer(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<ShieldProducer>().Producer.ChangeTicksPerProductionCycle(TickReductionPerLevel[_level]);
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects += RegisterPostCombatShieldRegeneration;
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<ShieldProducer>().Producer.ChangeTicksPerProductionCycle( TickReductionPerLevel[_level]);
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= RegisterPostCombatShieldRegeneration;
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<ShieldProducer>().Producer.ChangeTicksPerProductionCycle( TickReductionPerLevel[_level]);
            entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= RegisterPostCombatShieldRegeneration;
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Scrutineer;
        }

        public override string GetDescription()
        {
            return $"Reduces the shield regeneration rate by {TickReductionPerLevel} ticks." +
                   $"Regenerates all shields after a successful combat.";
        }

        private void RegisterPostCombatShieldRegeneration(object positionManager, OnRegisterCombatEventArgs postCombatEventArgs)
        {
            if (GetLevel() >= 3)
            {
                postCombatEventArgs.CombatEvent.AddEffectToCombat(new RegenerateShieldPostCombatEffect(
                    postCombatEventArgs.CombatEvent,
                    _owner,
                    1.0f
                ));
            }
        }
    }
}


text:
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.Entities.Positions;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Infiltrator : Specialist
    {
        public Infiltrator(Player owner) : base(owner, false)
        {
        }
        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects += OnRegisterCombatEffects;
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= OnRegisterCombatEffects;
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= OnRegisterCombatEffects;
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Infiltrator;
        }

        public override string GetDescription()
        {
            return $"When participating in combat, the Infiltrator destroys {{15, 20, All}} enemy shields.";
        }

        private void OnRegisterCombatEffects(object positionManager, OnRegisterCombatEventArgs registerCombatEventArgs)
        {
            var friendlyEntity = registerCombatEventArgs.CombatEvent.GetEntityOwnedBy(_owner);
            var enemyEntity = registerCombatEventArgs.CombatEvent.GetEnemyEntity(_owner);

            registerCombatEventArgs.CombatEvent.AddEffectToCombat(new AlterShieldEffect(
                registerCombatEventArgs.CombatEvent,
                friendlyEntity,
                GetShieldDelta(enemyEntity)
            ));

            if (GetLevel() >= 2)
            {
                registerCombatEventArgs.CombatEvent.AddEffectToCombat(new RegenerateShieldPostCombatEffect(
                    registerCombatEventArgs.CombatEvent,
                    _owner,
                    1.0f
                ));
            }
        }

        private int GetShieldDelta(IEntity targetLocation)
        {
            return _level switch
            {
                1 => 15,
                2 => 25,
                3 => targetLocation.GetComponent<ShieldManager>().GetShields(),
                _ => 0
            };
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Sapper : Specialist
    {
        public List<float> haltDrillerPercent = new List<float>() { 0, 0.50f, 1.00f };

        public Sapper(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (_isCaptured)
            {
                ToggleEntityShieldProduction(true, entity);
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {

            if (_isCaptured)
            {
                ToggleEntityShieldProduction(false, entity);
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            if (isCaptured)
            {
                ToggleEntityShieldProduction(true, entity);
            }
            else
            {
                ToggleEntityShieldProduction(false, entity);
            }
        }

        public void ToggleEntityShieldProduction(bool shouldPause, IEntity entity)
        {
            if (!Equals(entity.GetComponent<DrillerCarrier>().GetOwner(), _owner))
            {
                entity.GetComponent<ShieldProducer>().Producer.SetPaused(shouldPause);

                // Also alter the production events.
                entity.GetComponent<DrillerProducer>().Producer.OnResourceProduced += ReduceProductionAmount;
            }
        }

        public void ReduceProductionAmount(object? sender, ProductionEventArgs productionEventArgs)
        {
            productionEventArgs.ProductionEvent.ValueProduced -= (int)(productionEventArgs.ProductionEvent.ValueProduced * haltDrillerPercent[_level]);
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Sapper;
        }

        public override string GetDescription()
        {
            return $"While captured at an enemy base, prevents shield regeneration. " +
                   $"Also halts driller production by {haltDrillerPercent} percent.";
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Veteran : Specialist
    {
        private List<int> drillersPerLevel = new List<int>() { 10, 20, 20 };
        private List<float> ExtraDrillersPerLevel = new List<float>() { 0, 0, 0.2f };

        public Veteran(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects += AlterDrilleresOnCombat;
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= AlterDrilleresOnCombat;
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= AlterDrilleresOnCombat;
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Veteran;
        }

        public override string GetDescription()
        {
            return $"Destroy an additional {drillersPerLevel} drillers in combat. " +
                   $"Increase friendly drillers in combat by {ExtraDrillersPerLevel} percent.";
        }

        private void AlterDrilleresOnCombat(object sender, OnRegisterCombatEventArgs eventArgs)
        {
            var friendlyEntity = eventArgs.CombatEvent.GetEntityOwnedBy(_owner);
            var friendlyDrillers = friendlyEntity.GetComponent<DrillerCarrier>().GetDrillerCount();
            var extraDrillers = (int)(ExtraDrillersPerLevel[_level] * friendlyDrillers);

            eventArgs.CombatEvent.AddEffectToCombat(new AlterDrillerEffect(
                eventArgs.CombatEvent,
                friendlyEntity,
                0,
                -1 * (drillersPerLevel[_level] + extraDrillers)
            ));
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Theif : Specialist
    {

        private List<float> _stealPerLevel = new List<float>() { 0.1f, 0.15f, 0.20f };

        public Theif(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects += StealEnemyDrillers;
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= StealEnemyDrillers;
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= StealEnemyDrillers;
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Theif;
        }

        private void StealEnemyDrillers(object? combat, OnRegisterCombatEventArgs combatEventArgs)
        {
            var friendlyEntity = combatEventArgs.CombatEvent.GetEntityOwnedBy(_owner);
            var enemyCarrier = combatEventArgs.CombatEvent.GetEnemyEntity(_owner).GetComponent<DrillerCarrier>();

            combatEventArgs.CombatEvent.AddEffectToCombat(new AlterDrillerEffect(
                combatEventArgs.CombatEvent,
                friendlyEntity,
                (int)(enemyCarrier.GetDrillerCount() * _stealPerLevel[_level]),
                (int)(enemyCarrier.GetDrillerCount() * _stealPerLevel[_level] * -1)
            ));
        }

        public override string GetDescription()
        {
            return $"Steals {_stealPerLevel} percent of enemy drillers on combat.";
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Base;
using Subterfuge.Remake.Core.GameEvents.SpecialistEvents;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Advisor : Specialist
    {
        private List<int> capacityIncreaseDelta = new List<int>() { 1, 2, 2 };

        public Advisor(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity location, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                location.GetComponent<SpecialistManager>().AllowHireFromLocation();
                location.GetComponent<SpecialistManager>().AlterCapacity(capacityIncreaseDelta[_level]);
            }
        }

        public override void LeaveLocation(IEntity location, TimeMachine timeMachine)
        {
            // Don't care if captured, when leaving should always alter spec capacity at friendly locations.
            if (Equals(location.GetComponent<DrillerCarrier>().GetOwner(), _owner))
            {
                location.GetComponent<SpecialistManager>().DisallowHireFromLocation();
                location.GetComponent<SpecialistManager>().AlterCapacity(capacityIncreaseDelta[_level] * -1);
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<SpecialistManager>().DisallowHireFromLocation();
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Advisor;
        }

        public override string GetDescription()
        {
            return $"Allows hiring specialists from the Advisor's location." +
                   $"Carry {capacityIncreaseDelta} additional specialists with the Advisor.";
        }
    }
}


text:
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Martyr : Specialist
    {
        public Martyr(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects += ExplodeOnCombatEffects;
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= ExplodeOnCombatEffects;
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= ExplodeOnCombatEffects;
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Martyr;
        }

        public override string GetDescription()
        {
            return $"When participating in combat, the Martyr explodes, destroying everything in it's wake.";
        }

        public void ExplodeOnCombatEffects(object? sender, OnRegisterCombatEventArgs registerCombat)
        {
            var friendlyEntity = registerCombat.CombatEvent.GetEntityOwnedBy(_owner);

            registerCombat.CombatEvent.AddEffectToCombat(new EntityExplodeEffect(
                registerCombat.CombatEvent.OccursAt,
                friendlyEntity,
                registerCombat.CurrentState
            ));
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Inspector : Specialist
    {
        private List<int> shieldsPerLevel = new List<int>() { 10, 20, 30 };

        public Inspector(Player owner) : base(owner, false)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                // TODO: This won't work. Need an undo action here.
                var shieldManager = entity.GetComponent<ShieldManager>();
                shieldManager.SetShields(shieldManager.GetShieldCapacity());
                shieldManager.AlterShieldCapacity(shieldsPerLevel[_level]);
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {

            if (!_isCaptured)
            {
                var shieldManager = entity.GetComponent<ShieldManager>();
                shieldManager.AlterShieldCapacity(shieldsPerLevel[_level] * -1);
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                var shieldManager = entity.GetComponent<ShieldManager>();
                shieldManager.AlterShieldCapacity(shieldsPerLevel[_level] * -1);
            }
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Inspector;
        }

        public override string GetDescription()
        {
            return $"The Inspector increases the shield capacity at it's location by {shieldsPerLevel}." +
                   $"Regenerates all shields when arriving at friendly outposts.";
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Saboteur : Specialist
    {
        public List<float> slowPercent = new List<float>() { 0, 0.1f, 0.2f };

        public Saboteur(Player owner) : base(owner, false)
        {
        }
        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {

            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects += RedirectOnCombatEffects;
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= RedirectOnCombatEffects;
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<PositionManager>().OnRegisterCombatEffects -= RedirectOnCombatEffects;
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Saboteur;
        }

        public override string GetDescription()
        {
            return $"Redirects enemy subs on combat. Slows redirected subs by {slowPercent} units.";
        }

        public void RedirectOnCombatEffects(object? sender, OnRegisterCombatEventArgs combatArgs)
        {
            var enemyEntity = combatArgs.CombatEvent.GetEnemyEntity(_owner);

            combatArgs.CombatEvent.AddEffectToCombat(new RedirectEntityEffect(
                combatArgs.CombatEvent.OccursAt,
                enemyEntity
            ));

            combatArgs.CombatEvent.AddEffectToCombat(new SlowTargetEffect(
                combatArgs.CombatEvent.OccursAt,
                enemyEntity,
                slowPercent[_level]
            ));
        }
    }
}


text:
using System.Collections.Generic;
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Specialists
{
    public class Warden : Specialist
    {
        private List<int> ExtraDrillersPerLevel = new List<int>() { 0, 1, 2 };

        public Warden(Player owner) : base(owner, true)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SpecialistManager>().AllowHireFromLocation();
                entity.GetComponent<SpecialistManager>().CanLoadCapturedSpecialists = true;
                entity.GetComponent<DrillerProducer>().Producer.OnResourceProduced += ProducePerCapturedSpecialist;
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!_isCaptured)
            {
                entity.GetComponent<SpecialistManager>().DisallowHireFromLocation();
                entity.GetComponent<SpecialistManager>().CanLoadCapturedSpecialists = false;
                entity.GetComponent<DrillerProducer>().Producer.OnResourceProduced -= ProducePerCapturedSpecialist;
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            entity.GetComponent<SpecialistManager>().DisallowHireFromLocation();
            entity.GetComponent<SpecialistManager>().CanLoadCapturedSpecialists = false;
            entity.GetComponent<DrillerProducer>().Producer.OnResourceProduced -= ProducePerCapturedSpecialist;
            // Kill player.
            entity.GetComponent<DrillerCarrier>().GetOwner().SetEliminated(true);
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Warden;
        }

        public override string GetDescription()
        {
            return $"The Warden can transport captured specialists. " +
                   $"While at a factory, each captured specialist produces an extra {ExtraDrillersPerLevel} drillers. " +
                   $"If the Warden dies, the owner is eliminated.";
        }

        public void ProducePerCapturedSpecialist(object? sender, ProductionEventArgs productionEventArgs)
        {
            // Get total number of captured specs are current location.
            var capturedTotal = productionEventArgs
                .ProductionEvent
                .ProductionLocation
                .GetComponent<SpecialistManager>()
                .GetCapturedSpecialistCount();

            productionEventArgs.ProductionEvent.ValueProduced += capturedTotal * GetExtraDrillersPerSpecialist();
        }

        private int GetExtraDrillersPerSpecialist()
        {
            return ExtraDrillersPerLevel[_level];
        }
    }
}


text:
using Subterfuge.Remake.Api.Network;
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;

namespace Subterfuge.Remake.Core.Entities.Specialists.Heroes
{
    public class Queen: Specialist
    {
        private int shieldDelta = 25;

        public Queen(Player owner) : base(owner, true)
        {
        }

        public override void ArriveAtLocation(IEntity entity, TimeMachine timeMachine)
        {
            if (!this._isCaptured)
            {
                entity.GetComponent<SpecialistManager>().AllowHireFromLocation();
                entity.GetComponent<ShieldManager>().AlterShieldCapacity(shieldDelta);
            }
        }

        public override void LeaveLocation(IEntity entity, TimeMachine timeMachine)
        {

            if (!this._isCaptured)
            {
                entity.GetComponent<SpecialistManager>().DisallowHireFromLocation();
                entity.GetComponent<ShieldManager>().AlterShieldCapacity(shieldDelta * -1);
            }
        }

        public override void OnCapture(bool isCaptured, IEntity entity, TimeMachine timeMachine)
        {
            // TODO: Check for princesses to take over.
            // Otherwise, kill player.
            entity.GetComponent<SpecialistManager>().DisallowHireFromLocation();
            entity.GetComponent<DrillerCarrier>().GetOwner().SetEliminated(true);
        }

        public override SpecialistTypeId GetSpecialistId()
        {
            return SpecialistTypeId.Queen;
        }

        public override string GetDescription()
        {
            return $"Adds ${shieldDelta} shields to the Queen's Location. If the Queen dies, the owner is eliminated.";
        }
    }
}


text:
using Subterfuge.Remake.Core.Entities.Components;
using Subterfuge.Remake.Core.GameEvents.Combat.CombatEvents;
using Subterfuge.Remake.Core.GameEvents.EventPublishers;
using Subterfuge.Remake.Core.Players;
using Subterfuge.Remake.Core.Timing;
using Subterfuge.Remake.Core.Topologies;

namespace Subterfuge.Remake.Core.Entities.Positions
{
	/// <summary>
	/// Outpost class
	/// </summary>
    public abstract class Outpost : Entity
    {
        /// <summary>
        /// Outpost constructor
        /// </summary>
        /// <param name="id">The outpost id</param>
        /// <param name="outpostStartPosition">The position of the outpost</param>
        /// <param name="outpostOwner">The owner of the outpost</param>
        /// <param name="visionRadius">The vision radius</param>
        public Outpost(
            string id,
            RftVector outpostStartPosition,
            TimeMachine timeMachine,
            Player outpostOwner = null,
            float visionRadius = Constants.BaseOutpostVisionRadius
        ) {
            // Ensure unowned outposts start with 0 dillers
            int initialDrillers = outpostOwner != null ? Constants.InitialDrillersPerOutpost : 0;

            AddComponent(new DrillerCarrier(this, initialDrillers, outpostOwner));
            AddComponent(new SpeedManager(this, 0.0f));
            AddComponent(new PositionManager(this, outpostStartPosition, new GameTick(), timeMachine));
            AddComponent(new SpecialistManager(this, 100));
            AddComponent(new IdentityManager(this, id));
            AddComponent(new ShieldManager(this, Constants.InitialMaxShieldsPerOutpost));
            AddComponent(new SubLauncher(this));
            AddComponent(new VisionManager(this, visionRadius));
            AddComponent(new ShieldProducer(this, timeMachine));
        }

        /// <summary>
        /// Outpost constructor; should only be used when it is necessary to change the type of an outpost (i.e. when converting an outpost to a mine)
        /// </summary>
        /// <param name="o">The outpost to copy components from</param>
        public Outpost(Outpost o, TimeMachine timeMachine)
        {
            AddComponent(o.GetComponent<DrillerCarrier>());
            AddComponent(o.GetComponent<SpeedManager>());
            AddComponent(o.GetComponent<PositionManager>());
            AddComponent(o.GetComponent<SpecialistManager>());
            AddComponent(o.GetComponent<IdentityManager>());
            AddComponent(o.GetComponent<ShieldManager>());
            AddComponent(o.GetComponent<SubLauncher>());
            AddComponent(o.GetComponent<VisionManager>());
            AddComponent(new ShieldProducer(this, timeMachine));
        }

        /// <summary>
        /// Gets the outpost type
        /// </summary>
        /// <returns>The type of outpost</returns>
        public abstract OutpostType GetOutpostType();
    }
}


text:
namespace Subterfuge.Remake.Core.Entities.Positions
{
    /// <summary>
    /// An enum of the types of outposts.
    /// </summary>
    public enum OutpostType
    {
        Generator,
        Factory,
        Mine,
        Watchtower
    }
}