//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)
        {
            if (units.Count == 0)
            {
                Debug.LogError("[MovementManager] Attempting to move an empty list of units is not allowed!");
                return;
            }

            if (!RTSHelper.IsPlayerFaction(units[0])) //if the source unit is not part of the player's faction then, override the playerCommand value to false even if it was set to true
            {
                playerCommand = false;
            }

            //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);

            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
            {
                float currOffsetRadius = 0.0f;                                          //for attack movement offset

                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
                            ?  currUnits.Value[i].AttackComp.GetOrderAudio()
                            : (!currUnits.Value[i].MovementComp.IsMoving() ? currUnits.Value[i].MovementComp.GetMvtOrderAudio() : null); //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
            }
        }
        //the PrepareMove method prepares the movement of a single unit
        public void PrepareMove(Unit unit, Vector3 destination, float offsetRadius, Entity target, InputMode targetMode, bool playerCommand, bool attackMovement = false, AttackModes attackMode = AttackModes.none, FactionEntity targetEntity = null)
        {
            if (!RTSHelper.IsPlayerFaction(unit)) //if the source unit is not part of the player's faction then, override the playerCommand value to false even if it was set to true
            {
                playerCommand = false;
            }

            if (!unit.MovementComp.CanMove()) //if the unit can't move and this is no attack movement
            {
                if (attackMovement)           //if this is an attack movement, just check if the target is in range or not
                {
                    LaunchNonMovementAttackLocal(unit, targetEntity, playerCommand);
                    return;
                }
                else
                {
                    if (playerCommand == true) //player notification message
                    {
                        ErrorMessageHandler.OnErrorMessage(ErrorMessage.notMovable, unit);
                    }

                    unit.MovementComp.OnInvalidPath(true, false);
                    return; //nothing to do here
                }
            }

            if (attackMovement == true) //if this is an attack movement
            {
                //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;

                if (CanProceedAttackMovement(unit, playerCommand, assignedAttackOnly, canChangeTarget, targetEntity) == false) //do not proceed if the attack is not possible
                {
                    return;
                }

                offsetRadius += unit.AttackComp.GetRange().GetStoppingDistance(targetEntity); //update sort key

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

            unit.MovementComp.TriggerTargetPositionCollider(false);          //disable the target position collider so it won't intefer in the target position collider

            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);
            }

            Vector3 originalDestination = destination; //save the original destination here

            //first check if the actual destination is a valid target position, if it can't be then search for a target position
            if (offsetRadius > 0.0f || IsPositionClear(ref destination, unit.MovementComp.GetAgentRadius(), unit.MovementComp.GetAgentAreaMask(), unit.MovementComp.CanFly()) == false)
            {
                List <Vector3> targetPositions = GenerateTargetPositions(Formations.circular, //it doesn't really matter what formation to use for one unit
                                                                         destination, 1,
                                                                         unit.MovementComp.GetAgentRadius(),
                                                                         unit.MovementComp.GetAgentAreaMask(),
                                                                         unit.MovementComp.CanFly(),
                                                                         ref offsetRadius, Vector3.zero); //get the target positions for the current units list

                if (targetPositions.Count == 0)                                                           //no target positions found?
                {
                    if (playerCommand == true)                                                            //if this is a player command then inform the player
                    {
                        ErrorMessageHandler.OnErrorMessage(ErrorMessage.targetPositionNotFound, unit);
                    }

                    unit.CancelJob(Unit.jobType.all); //cancel all the unit's jobs.
                    return;
                }

                targetPositions.Sort((x, y) => Vector3.Distance(x, unit.transform.position).CompareTo(Vector3.Distance(y, unit.transform.position))); //sort the target positions list depending on the distance to the actual unit to move

                destination = targetPositions[0];                                                                                                     //get the nearest target position
            }


            if (playerCommand && !unit.MovementComp.IsMoving())                                                                                                             //if this is a player command and the unit is not currently moving.
            {
                AudioManager.Play(gameMgr.GetGeneralAudioSource(), attackMovement == true ? unit.AttackComp.GetOrderAudio() : unit.MovementComp.GetMvtOrderAudio(), false); //play the movement order audio
            }
            EnqueueMovementTask(new MovementTask
            {
                unit                 = unit,
                targetPosition       = destination,
                lookAtPosition       = originalDestination,
                target               = target,
                targetMode           = targetMode,
                attack               = attackMovement,
                attackTarget         = targetEntity,
                attackTargetPosition = originalDestination,
            }); //add a new movement task to the movement queue.
        }