/// <summary>
        /// Returns a list of MemberLocations, sorted by distance to the specified leader.
        /// </summary>
        private List <MemberLocation> GetMemberLocations(CreatureBoardAsset leaderAsset)
        {
            if (lastMemberLocations != null)
            {
                return(lastMemberLocations);
            }
            List <MemberLocation> memberLocations = new List <MemberLocation>();

            foreach (string memberId in Data.Members)
            {
                MemberLocation     memberLocation     = new MemberLocation();
                CreatureBoardAsset creatureBoardAsset = Talespire.Minis.GetCreatureBoardAsset(memberId);
                if (creatureBoardAsset == null)
                {
                    continue;
                }
                memberLocation.Asset            = creatureBoardAsset;
                memberLocation.Name             = creatureBoardAsset.GetOnlyCreatureName();
                memberLocation.Position         = creatureBoardAsset.PlacedPosition;
                memberLocation.DistanceToLeader = (leaderAsset.PlacedPosition - creatureBoardAsset.PlacedPosition).magnitude;
                memberLocations.Add(memberLocation);
            }
            memberLocations = memberLocations.OrderBy(x => x.DistanceToLeader).ToList();
            return(memberLocations);
        }
        void PositionMember(MemberLocation memberLocation, Vector3 referencePosition, Vector3 totalDeltaMove, ref float offset)
        {
            Vector3 delta       = totalDeltaMove * offset / totalDeltaMove.magnitude;
            Vector3 newPosition = referencePosition - delta;

            if (Data.Look == LookTowardMode.Movement)
            {
                memberLocation.Asset.RotateToFacePosition(newPosition);
            }
            memberLocation.Asset.MoveCreatureTo(newPosition);
            offset += Talespire.Convert.FeetToTiles(memberLocation.Asset.GetBaseRadiusFeet() + Data.Spacing);
        }
        MemberLocation RemoveMemberClosestTo(List <MemberLocation> sourceMemberLocations, Vector3 targetPosition)
        {
            MemberLocation member = sourceMemberLocations.OrderBy(x => (x.Position - targetPosition).magnitude).FirstOrDefault();

            if (member == null)
            {
                Talespire.Log.Error($"RemoveMemberClosestTo / member == null");
            }

            sourceMemberLocations.Remove(member);
            return(member);
        }
        void MoveMembersByProximity(List <MemberLocation> destinationMembers, List <MemberLocation> sourceMemberLocations, Vector3 targetPosition)
        {
            if (sourceMemberLocations.Count == 0)
            {
                return;
            }
            MemberLocation member = RemoveMemberClosestTo(sourceMemberLocations, targetPosition);

            if (member != null)
            {
                destinationMembers.Add(member);
                MoveMembersByProximity(destinationMembers, sourceMemberLocations, member.Position /* was targetPosition */);
            }
        }
        public void ArrangeInCircle(float radiusFeet, float arcAngleDegrees, float spacingFeet, float rotationOffsetDegrees)
        {
            CreatureBoardAsset leaderAsset = OwnerCreature;

            if (Guard.IsNull(leaderAsset, "leaderAsset"))
            {
                return;
            }

            List <MemberLocation> memberLocations = GetMemberLocations(leaderAsset);

            if (memberLocations == null || memberLocations.Count == 0)
            {
                return;
            }

            float baseDiameterFeet = GetLargestBaseDiameterFeet(memberLocations);

            int minisToPlaceTotal = memberLocations.Count;

            GetCircleFormationVariables(radiusFeet, arcAngleDegrees, spacingFeet, baseDiameterFeet, minisToPlaceTotal, out Vector3 startingPoint, out float circumferenceFeet, out int numMinisCanFitInThisCircle, out float angleBetweenMinis, out float degreesToRotate);
            List <MemberLocation> allMembers = new List <MemberLocation>(memberLocations);

            int minisToPlaceThisCircle = Math.Min(allMembers.Count, numMinisCanFitInThisCircle);

            while (allMembers.Count > 0)
            {
                Vector3 newOffset   = Quaternion.Euler(0, rotationOffsetDegrees + degreesToRotate, 0) * startingPoint; // rotate it
                Vector3 newPosition = leaderAsset.PlacedPosition + newOffset;                                          // calculate rotated point

                MemberLocation member = RemoveMemberClosestTo(allMembers, newPosition);

                Vector3 delta = newPosition - member.Asset.PlacedPosition;
                Talespire.Minis.MoveRelative(member.Asset.CreatureId.ToString(), delta);
                minisToPlaceTotal--;
                minisToPlaceThisCircle--;

                degreesToRotate += angleBetweenMinis;
                if (minisToPlaceThisCircle <= 0 && allMembers.Count > 0)
                {
                    radiusFeet += baseDiameterFeet + spacingFeet;
                    GetCircleFormationVariables(radiusFeet, arcAngleDegrees, spacingFeet, baseDiameterFeet, minisToPlaceTotal, out startingPoint, out circumferenceFeet, out numMinisCanFitInThisCircle, out angleBetweenMinis, out degreesToRotate);
                    minisToPlaceThisCircle = Math.Min(allMembers.Count, numMinisCanFitInThisCircle);
                }
            }
            UpdateLook();
        }
        public void ArrangeInRectangle(int columns, int spacingFeet, float percentVariance, int rotationDegrees)
        {
            CreatureBoardAsset leaderAsset = OwnerCreature;

            if (Guard.IsNull(leaderAsset, "leaderAsset"))
            {
                return;
            }

            List <MemberLocation> memberLocations = GetMemberLocations(leaderAsset);

            if (memberLocations == null || memberLocations.Count == 0)
            {
                return;
            }
            float baseDiameterFeet = GetLargestBaseDiameterFeet(memberLocations);

            float startingX   = memberLocations[0].Position.x;
            float startingZ   = memberLocations[0].Position.z;
            int   rowIndex    = 0;
            int   columnIndex = 0;

            Vector3 centerPoint     = leaderAsset.PlacedPosition;
            int     minisInLastRow  = memberLocations.Count % columns;
            int     lastRowIndex    = (int)Math.Floor((double)memberLocations.Count / columns);
            float   normalRowLength = (columns - 1) * GetSpaceTiles(spacingFeet, baseDiameterFeet);
            float   lengthLastRow   = (minisInLastRow - 1) * GetSpaceTiles(spacingFeet, baseDiameterFeet);

            List <MemberLocation> allMembers = new List <MemberLocation>(memberLocations);

            while (allMembers.Count > 0)
            {
                float x;

                float gaggleFactor = 1;
                if (rowIndex == 0 && columnIndex == 0)
                {
                    gaggleFactor = 0;
                }
                if (rowIndex == lastRowIndex)
                {
                    if (minisInLastRow == 1)
                    {
                        x = startingX + normalRowLength / 2f + gaggleFactor * GetGaggleVariance(spacingFeet, percentVariance);
                    }
                    else
                    {
                        float lastRowStretchFactor = normalRowLength / lengthLastRow;
                        x = startingX + columnIndex * GetSpaceTiles(spacingFeet, baseDiameterFeet) * lastRowStretchFactor + gaggleFactor * GetGaggleVariance(spacingFeet, percentVariance);
                    }
                }
                else
                {
                    x = startingX + columnIndex * GetSpaceTiles(spacingFeet, baseDiameterFeet) + gaggleFactor * GetGaggleVariance(spacingFeet, percentVariance);
                }

                float   z           = startingZ + rowIndex * GetSpaceTiles(spacingFeet, baseDiameterFeet) + gaggleFactor * GetGaggleVariance(spacingFeet, percentVariance);
                Vector3 newPosition = new Vector3(x, leaderAsset.PlacedPosition.y, z);

                if (rotationDegrees != 0)
                {
                    Vector3 offset    = centerPoint - newPosition;
                    Vector3 newOffset = Quaternion.Euler(0, rotationDegrees + 180, 0) * offset; // rotate it
                    newPosition = centerPoint + newOffset;                                      // calculate rotated point
                }

                MemberLocation member = RemoveMemberClosestTo(allMembers, newPosition);
                Vector3        delta  = newPosition - member.Position;
                Talespire.Minis.MoveRelative(member.Asset.CreatureId.ToString(), delta);
                columnIndex++;
                if (columnIndex >= columns)
                {
                    columnIndex = 0;
                    rowIndex++;
                }
            }

            CacheLastMemberLocations(memberLocations, leaderAsset);
            UpdateLook();
        }