// Reassigns some allied soldiers in order to redistribute them.
        private static void TryReassignAllArmy()
        {
            int armiesCount = UnitSystem.inst.ArmyCount();

            for (int i = 0; i < armiesCount; i++)
            {
                UnitSystem.Army army    = UnitSystem.inst.GetAmry(i);
                IMoveTarget     target  = army.moveTarget;
                Vector3         armyPos = army.GetPos();

                bool alliedSoldier = army.TeamID() == 0 && army.armyType == UnitSystem.ArmyType.Default;
                bool valid         = !army.IsInvalid();

                // Only valid allied soldiers can be reassigned.
                if (!alliedSoldier || !valid)
                {
                    continue;
                }

                IMoveTarget newTarget = GetTargetForArmy(army, settings.patrolRadius.Value);

                // If the current target enemy is not a viking, disabled through settings, or invalid (dying or getting
                // on a boat), always reassign.
                bool targetIsViking = target != null && enemyAssignments.ContainsKey(target);
                if (!targetIsViking || !TargetTypeEnabled(target) || TargetTypeInvalid(target))
                {
                    MoveArmyToTarget(army, newTarget);
                }
                else if (target != null && target is UnitSystem.Army)
                {
                    if (newTarget is UnitSystem.Army && !AtMinimumAssignment(target) &&
                        LessThanMinimumAssignment(newTarget))
                    {
                        MoveArmyToTarget(army, newTarget);
                    }
                    else if (newTarget is SiegeMonster && LessThanMinimumAssignment(newTarget))
                    {
                        // Ogres are always prioritized by always pulling soldiers away from viking squads.
                        MoveArmyToTarget(army, newTarget);
                    }
                }
                else if (target != null && target is SiegeMonster)
                {
                    if (!AtMinimumAssignment(target) && LessThanMinimumAssignment(newTarget))
                    {
                        MoveArmyToTarget(army, newTarget);
                    }
                }
            }
        }
            public static void Postfix(General __instance)
            {
                UnitSystem.Army army = __instance.army;
                if (!settings.enabled.Value || army.TeamID() != 0 || army.armyType != UnitSystem.ArmyType.Default ||
                    army.IsInvalid())
                {
                    return;
                }

                if (VikingInvasion())
                {
                    try
                    {
                        if (!army.moving && ArmyIdle(army))
                        {
                            if (!originalPos.ContainsKey(army))
                            {
                                // At the beginning of an invasion, record the soldier squad's original position so it
                                // can be returned at the end.
                                originalPos[army] = army.GetPos();
                            }
                            AssignTargetToArmyAndMove(army, settings.patrolRadius.Value);
                        }
                    }
                    catch {}
                }
                else
                {
                    if (originalPos.ContainsKey(army))
                    {
                        // At the end of a viking invasion, move all soldiers back to their original position.
                        IMoveTarget target = World.inst.GetCellData(originalPos[army]);
                        if (target != null)
                        {
                            OrdersManager.inst.MoveTo(army, target);
                        }
                        originalPos.Remove(army);
                    }
                }
            }
        private static IMoveTarget GetNextViking(Vector3 pos, float range)
        {
            // Refer to SiegeMonster::ClosestMonster and UnitSystem::GetClosestDamageable.
            float       rangeSquared           = range * range;
            float       currentClosestDistance = float.MaxValue;
            int         currentLowestAssigned  = int.MaxValue;
            IMoveTarget nextViking             = null;

            foreach (IMoveTarget viking in enemyAssignments.Keys)
            {
                UnitSystem.Army army = viking as UnitSystem.Army;
                SiegeMonster    ogre = viking as SiegeMonster;
                if ((army == null && ogre == null) ||
                    (army != null && (army.IsInvalid() || !OnSameLandmass(pos, army.GetPos()))) ||
                    (ogre != null && (ogre.IsInvalid() || !OnSameLandmass(pos, ogre.GetPos()))) ||
                    !TargetTypeEnabled(viking))
                {
                    continue;
                }

                // Select the closest viking squad or ogre with the least points to obey the assignment and distribution
                // rules.
                int   assigned        = enemyAssignments[viking];
                float distanceSquared = Mathff.DistSqrdXZ(pos, viking.GetPos());

                if (distanceSquared > rangeSquared || assigned > currentLowestAssigned)
                {
                    continue;
                }

                if (nextViking == null || assigned < currentLowestAssigned ||
                    (distanceSquared < currentClosestDistance && assigned == currentLowestAssigned))
                {
                    nextViking             = viking;
                    currentClosestDistance = distanceSquared;
                    currentLowestAssigned  = assigned;
                }
            }
            return(nextViking);
        }
        private static IMoveTarget GetTargetForArmy(UnitSystem.Army army, float range)
        {
            if (!originalPos.ContainsKey(army))
            {
                return(null);
            }

            // Soldiers will patrol a radius around its starting point specified by range.
            IMoveTarget target = null;

            target = GetNextTarget(originalPos[army], range);

            if (target == null)
            {
                // If there are no more targets, return the soldier squad to its original position if it's not already
                // there.
                if (originalPos[army] != army.GetPos())
                {
                    target = World.inst.GetCellData(originalPos[army]);
                }
            }
            return(target);
        }
        // Takes into account if move target is a Transport Ship. Since Transport Ship is not IProjectileHitable, this
        // returns the ShipBase, which is IProjectileHitable.
        private static System.Object GetAttackTarget(UnitSystem.Army army, ArcherGeneral archerGeneral)
        {
            if (army.locked && army.moveTarget != null)
            {
                System.Object moveTarget = army.moveTarget;
                Vector3       targetPos;

                // Refer to ProjectileDefense::GetTarget for getting target position.
                if (moveTarget is TroopTransportShip)
                {
                    // Prevents friendly fire.
                    if (((TroopTransportShip)moveTarget).TeamID() != 0)
                    {
                        ShipBase shipBase = ((TroopTransportShip)moveTarget).shipBase;
                        moveTarget = shipBase;
                        targetPos  = shipBase.GetPos();
                    }
                    else
                    {
                        return(null);
                    }
                }
                else if (moveTarget is SiegeMonster)
                {
                    SiegeMonster ogre = (SiegeMonster)moveTarget;
                    if (ogre.IsInvalid())
                    {
                        // Ogre died or got on a ship, stop tracking.
                        army.moveTarget = null;
                        return(null);
                    }
                    targetPos = ogre.GetPos();
                }
                else if (moveTarget is IProjectileHitable)
                {
                    if (moveTarget is UnitSystem.Army && ((UnitSystem.Army)moveTarget).IsInvalid())
                    {
                        // Army died or got on a ship, stop tracking.
                        army.moveTarget = null;
                        return(null);
                    }
                    targetPos = ((IProjectileHitable)moveTarget).GetPosition();
                }
                else
                {
                    return(null);
                }

                // If moveTarget is not in range, returning null will make archer seek another target until moveTarget
                // is in range. This is because moveTarget will not change until it is dead, or the player clicked on a
                // different target.
                // Refer to ArcherGeneral::Update and ProjectileDefense::GetTarget for range calculation.
                Vector3 archerPos   = army.GetPos();
                float   attackRange = archerGeneral.FullRange(archerGeneral.attackRange) + 1f;
                if (!ProjectileDefense.TestRange(targetPos, archerPos, attackRange))
                {
                    return(null);
                }
                return(moveTarget);
            }
            else
            {
                // If not on ship, don't change the behavior.
                return(army.moveTarget);
            }
        }