//a method that sorts a list of units depending on the sort type and return a chained sorted list of the sorted units
        private ChainedSortedList <float, Unit> SortUnits(List <Unit> units, UnitSortTypes sortType, float offsetRadius, AttackModes attackMode = AttackModes.none, FactionEntity targetEntity = null, Vector3 targetPosition = default, bool playerCommand = false)
        {
            ChainedSortedList <float, Unit> sortedUnits = new ChainedSortedList <float, Unit>(); //this will hold the sorted list of the units.

            //attack only:
            //change the target?
            bool canChangeTarget = (attackMode == AttackModes.full || attackMode == AttackModes.change) ? true : false;
            //assigned attack only?
            bool assignedAttackOnly = (attackMode == AttackModes.full || attackMode == AttackModes.assigned) ? true : false;

            foreach (Unit unit in units)                       //go through the list of units.
            {
                if (unit.MovementComp.CanMove() == false)      //if the unit can't move
                {
                    if (sortType == UnitSortTypes.attackRange) //if this is an attack movement, just check if the target is in range or not
                    {
                        LaunchNonMovementAttackLocal(unit, targetEntity, playerCommand);

                        continue;
                    }
                    else
                    {
                        if (playerCommand == true && unit.FactionID == GameManager.PlayerFactionID) //player notification message
                        {
                            ErrorMessageHandler.OnErrorMessage(ErrorMessage.notMovable, unit);
                        }
                        continue;
                    }
                }

                float sortKey = unit.MovementComp.GetAgentRadius();                                                                //sort key is set to agent radius by default

                if (sortType != UnitSortTypes.radius)                                                                              //if we're not sorting by radius <=> we're not sorting for a simple movement but for an attack movement and therefore:
                {
                    if (CanProceedAttackMovement(unit, playerCommand, assignedAttackOnly, canChangeTarget, targetEntity) == false) //do not proceed if the attack is not possible
                    {
                        continue;
                    }

                    sortKey = unit.AttackComp.RangeType.GetStoppingDistance(targetEntity == null ? EntityTypes.none : targetEntity.Type); //update sort key

                    if (Vector3.Distance(unit.transform.position, targetPosition) < sortKey + offsetRadius)                               //if the attack unit is already inside its target range
                    {
                        //attack directly
                        EnqueueMovementTask(new MovementTask
                        {
                            unit                 = unit,
                            targetPosition       = unit.transform.position,
                            lookAtPosition       = targetPosition,
                            target               = targetEntity,
                            targetMode           = InputMode.attack,
                            attack               = true,
                            attackTarget         = targetEntity,
                            attackTargetPosition = targetPosition,
                        });       //add a new movement task to the movement queue to attack directly
                        continue; //move to next unit
                    }
                }

                unit.MovementComp.TriggerTargetPositionCollider(false); //disable the target position collider first so that it won't intefer in the movement calculations
                sortedUnits.Add(sortKey, unit);                         //add the new alongside its radius as the key.
            }

            return(sortedUnits);
        }
        //the PrepareMove method prepares the movement of a list of units by sorting them based on their radius, distance to target and generating target positions
        public void PrepareMove(List <Unit> units, Vector3 destination, float offsetRadius, Entity target, InputMode targetMode, bool playerCommand, bool attackMovement = false, AttackModes attackMode = AttackModes.none, FactionEntity targetEntity = null)
        {
            //create a new chained sorted list where units will be sorted based on their radius or stopping distance towards the target unit/building (in case of an attack)
            ChainedSortedList <float, Unit> radiusSortedUnits = SortUnits(units, attackMovement ? UnitSortTypes.attackRange : UnitSortTypes.radius, offsetRadius, attackMode, targetEntity, destination, playerCommand);

            float currOffsetRadius = offsetRadius;                           //set the current offset radius (we might need the same original offsetRadius value later)

            AudioClip movementOrderAudio = null;                             //we'll just pick one of the movement order audios from the units list

            if (playerCommand && (!targetEntity || attackMovement == false)) //if this is a player command and it's not an attack movement with an assigned target
            {
                SpawnTargetEffect(!attackMovement, destination);
            }

            foreach (KeyValuePair <float, List <Unit> > currUnits in radiusSortedUnits) //for each different list of units that share the same radius
            {
                if (attackMovement == true)                                             //if this is an attack movement
                {
                    currOffsetRadius += currUnits.Key;                                  //increment the offset radius by the attack range (which is represented by the key in case)
                }
                //sort units in ascending order of the distance to the target position
                currUnits.Value.Sort((x, y) => Vector3.Distance(x.transform.position, destination).CompareTo(Vector3.Distance(y.transform.position, destination)));

                UnitMovement movementRef = currUnits.Value[0].MovementComp; //reference movement component

                Vector3 unitsDirection = Vector3.zero;                      //direction of units regarding the target position
                foreach (Unit unit in currUnits.Value)
                {
                    unitsDirection += (destination - unit.transform.position).normalized;
                }

                unitsDirection /= currUnits.Value.Count;

                Formations currentFormation = GetFormation(currUnits.Value[0], attackMovement); //current movement formation for this units group

                List <Vector3> targetPositions = GenerateTargetPositions(currentFormation,
                                                                         destination, currUnits.Value.Count,
                                                                         movementRef.GetAgentRadius(),
                                                                         movementRef.GetAgentAreaMask(),
                                                                         movementRef.CanFly(),
                                                                         ref currOffsetRadius, unitsDirection);   //get the target positions for the current units list

                for (int i = 0; i < currUnits.Value.Count; i++)                                                   //go through the sorted units list
                {
                    if (targetPositions.Count == 0)                                                               //no more target positions available?
                    {
                        if (playerCommand == true && currUnits.Value[i].FactionID == GameManager.PlayerFactionID) //if this is a player command then inform the player
                        {
                            ErrorMessageHandler.OnErrorMessage(ErrorMessage.targetPositionNotFound, currUnits.Value[i]);
                        }
                        continue;
                    }

                    switch (currentFormation)
                    {
                    case Formations.circular:
                        if (attackMovement == true)                                                                                                                                           //only pick the closest target position in case this is an attack movement type and we're using the circular formation
                        {
                            targetPositions.Sort((x, y) => Vector3.Distance(x, currUnits.Value[i].transform.position).CompareTo(Vector3.Distance(y, currUnits.Value[i].transform.position))); //sort the target positions list depending on the distance to the actual unit to move
                        }
                        break;

                    case Formations.rectangular:                                                                                                                                          //if this is a rectangular formation, then the closest unit always gets to the farthest position
                        targetPositions.Sort((x, y) => Vector3.Distance(y, currUnits.Value[i].transform.position).CompareTo(Vector3.Distance(x, currUnits.Value[i].transform.position))); //sort the target positions list depending on the distance to the actual unit to move
                        break;
                    }

                    if (movementOrderAudio == null)                                                                                                                       //if the movement order audio hasn't been set yet
                    {
                        movementOrderAudio = attackMovement == true ? currUnits.Value[i].AttackComp.GetOrderAudio() : currUnits.Value[i].MovementComp.GetMvtOrderAudio(); //set it.
                    }
                    EnqueueMovementTask(new MovementTask
                    {
                        unit                 = currUnits.Value[i],
                        targetPosition       = targetPositions[0],
                        lookAtPosition       = targetPositions[0] + unitsDirection,
                        target               = target,
                        targetMode           = targetMode,
                        attack               = attackMovement,
                        attackTarget         = targetEntity,
                        attackTargetPosition = destination,
                    }); //add a new movement task to the movement queue

                    targetPositions.RemoveAt(0);
                }
            }

            if (playerCommand)                                                                 //if this is a player command
            {
                AudioManager.Play(gameMgr.GetGeneralAudioSource(), movementOrderAudio, false); //play the movement order audio
            }
        }