private void RecurseItemRange(ItemRangeParameters parms, Coordinate currCoord, int remainingRange, string visitedCoords, ref IList <Coordinate> atkRange, ref IList <Coordinate> utilRange)
        {
            //Base case
            //Don't exceed the maximum range and don't go off the map
            if (remainingRange < 0 ||
                currCoord.X < 1 ||
                currCoord.Y < 1 ||
                currCoord.X > this.Map.TileWidth ||
                currCoord.Y > this.Map.TileHeight
                )
            {
                return;
            }

            Tile tile = this.Map.GetTileByCoord(currCoord);

            //Check if ranges can pass through this tile
            if (tile.TerrainTypeObj.BlocksItems)
            {
                return;
            }

            visitedCoords += "_" + currCoord.ToString() + "_";

            //Check for items that can reach this tile
            if (!parms.Unit.Ranges.Movement.Contains(currCoord))
            {
                int horzDisplacement     = Math.Abs(currCoord.X - parms.StartCoord.X);
                int verticalDisplacement = Math.Abs(currCoord.Y - parms.StartCoord.Y);
                int totalDisplacement    = currCoord.DistanceFrom(parms.StartCoord);

                int pathLength = parms.LargestRange - remainingRange;

                List <UnitItemRange> validRanges = new List <UnitItemRange>();
                validRanges.AddRange(parms.Ranges.Where(r => r.Shape == ItemRangeShape.Standard &&
                                                        ((r.MinRange <= totalDisplacement &&                 //tile greater than min range away from unit
                                                          r.MinRange <= pathLength &&                   //tile greater than min range down the path
                                                          r.MaxRange >= totalDisplacement &&                   //tile less than max range from unit
                                                          r.MaxRange >= pathLength) ||                   //tile less than max range down path
                                                         (totalDisplacement == 1 && pathLength == 1 && r.AllowMeleeRange)                       //unit can specially allow melee range for an item
                                                        )
                                                        ));
                validRanges.AddRange(parms.Ranges.Where(r => r.Shape == ItemRangeShape.Square &&
                                                        (((r.MinRange <= verticalDisplacement || r.MinRange <= horzDisplacement) &&
                                                          r.MaxRange >= verticalDisplacement &&
                                                          r.MaxRange >= horzDisplacement) ||
                                                         (totalDisplacement == 1 && pathLength == 1 && r.AllowMeleeRange)                       //unit can specially allow melee range for an item
                                                        )));
                validRanges.AddRange(parms.Ranges.Where(r => (r.Shape == ItemRangeShape.Cross || r.Shape == ItemRangeShape.Star) &&
                                                        (((horzDisplacement == 0 &&                 //tile vertically within range
                                                           r.MinRange <= verticalDisplacement &&
                                                           r.MaxRange >= verticalDisplacement) ||
                                                          (verticalDisplacement == 0 &&                   //tile horizontally within range
                                                           r.MinRange <= horzDisplacement &&
                                                           r.MaxRange >= horzDisplacement) &&
                                                          totalDisplacement == pathLength) ||                   //straight paths only
                                                         (totalDisplacement == 1 && pathLength == 1 && r.AllowMeleeRange)
                                                        )
                                                        ));
                validRanges.AddRange(parms.Ranges.Where(r => (r.Shape == ItemRangeShape.Saltire || r.Shape == ItemRangeShape.Star) &&
                                                        ((horzDisplacement == verticalDisplacement &&
                                                          r.MinRange <= verticalDisplacement &&
                                                          r.MaxRange >= verticalDisplacement &&
                                                          r.MinRange <= horzDisplacement &&
                                                          r.MaxRange >= horzDisplacement &&
                                                          totalDisplacement == pathLength) ||                      //straight paths only
                                                         (totalDisplacement == 1 && pathLength == 1 && r.AllowMeleeRange)
                                                        )
                                                        ));
                //Add to attacking range
                if (validRanges.Any(r => r.DealsDamage) && !atkRange.Contains(currCoord))
                {
                    atkRange.Add(currCoord);
                }
                //Add to util range
                else if (validRanges.Any(r => !r.DealsDamage) && !utilRange.Contains(currCoord))
                {
                    utilRange.Add(currCoord);
                }
            }

            //Navigate in each cardinal direction, do not repeat tiles in this path
            //Left
            if (parms.RangeDirection == ItemRangeDirection.Northwest || parms.RangeDirection == ItemRangeDirection.Southwest)
            {
                Coordinate left = new Coordinate(currCoord.X - 1, currCoord.Y);
                if (!visitedCoords.Contains("_" + left.ToString() + "_"))
                {
                    RecurseItemRange(parms, left, remainingRange - 1, visitedCoords, ref atkRange, ref utilRange);
                }
            }

            //Right
            if (parms.RangeDirection == ItemRangeDirection.Northeast || parms.RangeDirection == ItemRangeDirection.Southeast)
            {
                Coordinate right = new Coordinate(currCoord.X + 1, currCoord.Y);
                if (!visitedCoords.Contains("_" + right.ToString() + "_"))
                {
                    RecurseItemRange(parms, right, remainingRange - 1, visitedCoords, ref atkRange, ref utilRange);
                }
            }

            //Up
            if (parms.RangeDirection == ItemRangeDirection.Northwest || parms.RangeDirection == ItemRangeDirection.Northeast)
            {
                Coordinate up = new Coordinate(currCoord.X, currCoord.Y - 1);
                if (!visitedCoords.Contains("_" + up.ToString() + "_"))
                {
                    RecurseItemRange(parms, up, remainingRange - 1, visitedCoords, ref atkRange, ref utilRange);
                }
            }

            //Down
            if (parms.RangeDirection == ItemRangeDirection.Southwest || parms.RangeDirection == ItemRangeDirection.Southeast)
            {
                Coordinate down = new Coordinate(currCoord.X, currCoord.Y + 1);
                if (!visitedCoords.Contains("_" + down.ToString() + "_"))
                {
                    RecurseItemRange(parms, down, remainingRange - 1, visitedCoords, ref atkRange, ref utilRange);
                }
            }
        }
        public void CalculateUnitRanges()
        {
            foreach (Unit unit in this.Units)
            {
                try
                {
                    //Ignore hidden units
                    if (!unit.Location.OriginTiles.Any())
                    {
                        continue;
                    }

                    //Calculate movement range
                    int movementVal = unit.Stats.General[this.Map.Constants.UnitMovementStatName].FinalValue;
                    OverrideMovementEffect overrideMovEffect = unit.StatusConditions.Select(s => s.StatusObj.Effect).OfType <OverrideMovementEffect>().FirstOrDefault();
                    if (overrideMovEffect != null)
                    {
                        movementVal = overrideMovEffect.MovementValue;
                    }

                    UnitRangeParameters unitParms = new UnitRangeParameters(unit);
                    RecurseUnitRange(unitParms, unit.Location.OriginTiles.Select(o => new MovementCoordSet(movementVal, o.Coordinate)).ToList(), string.Empty, null);

                    //Calculate item ranges
                    IList <Coordinate> atkRange  = new List <Coordinate>();
                    IList <Coordinate> utilRange = new List <Coordinate>();

                    IList <UnitItemRange> itemRanges = unit.Inventory.Where(i => i != null && i.CanEquip && !i.IsUsePrevented && (i.ModifiedMinRangeValue > 0 || i.ModifiedMaxRangeValue > 0))
                                                       .Select(i => new UnitItemRange(i.ModifiedMinRangeValue, i.ModifiedMaxRangeValue, i.Item.Range.Shape, i.Item.DealsDamage, i.AllowMeleeRange))
                                                       .ToList();

                    //Check for whole map ranges
                    if (itemRanges.Any(r => r.MaxRange >= 99))
                    {
                        bool applyAtk  = itemRanges.Any(r => r.DealsDamage && r.MaxRange >= 99);
                        bool applyUtil = itemRanges.Any(r => !r.DealsDamage && r.MaxRange >= 99);

                        ApplyWholeMapItemRange(unit, applyAtk, applyUtil, ref atkRange, ref utilRange);

                        //Remove all relevant ranges from list
                        //Since we cover the whole map we don't need to address these individually later
                        if (applyAtk)
                        {
                            while (itemRanges.Any(r => r.DealsDamage))
                            {
                                itemRanges.Remove(itemRanges.First(r => r.DealsDamage));
                            }
                        }

                        if (applyUtil)
                        {
                            while (itemRanges.Any(r => !r.DealsDamage))
                            {
                                itemRanges.Remove(itemRanges.First(r => !r.DealsDamage));
                            }
                        }
                    }

                    //Check for regular ranges
                    if (itemRanges.Any())
                    {
                        foreach (Coordinate coord in unit.Ranges.Movement)
                        {
                            foreach (ItemRangeDirection direction in RANGE_DIRECTIONS)
                            {
                                //Calculate attack range
                                ItemRangeParameters rangeParms = new ItemRangeParameters(unit, coord, itemRanges, direction);
                                RecurseItemRange(rangeParms,
                                                 coord,
                                                 rangeParms.LargestRange,
                                                 string.Empty,
                                                 ref atkRange,
                                                 ref utilRange
                                                 );
                            }
                        }
                    }

                    unit.Ranges.Attack  = atkRange;
                    unit.Ranges.Utility = utilRange;
                }
                catch (Exception ex)
                {
                    throw new RangeCalculationException(unit, ex);
                }
            }
        }