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);
                }
            }
        }
        private void RecurseUnitRange(UnitRangeParameters parms, IList <MovementCoordSet> currCoords, string visitedCoords, Coordinate lastWarpUsed)
        {
            //Base case
            //Don't exceed the maximum range and don't go off the map
            if (currCoords.Any(c => c.RemainingMov < 0 ||
                               c.Coordinate.X < 1 ||
                               c.Coordinate.Y < 1 ||
                               c.Coordinate.X > this.Map.TileWidth ||
                               c.Coordinate.Y > this.Map.TileHeight)
                )
            {
                return;
            }

            for (int i = 0; i < currCoords.Count; i++)
            {
                MovementCoordSet currCoord = currCoords[i];
                Tile             tile      = this.Map.GetTileByCoord(currCoords[i].Coordinate);

                //If there is a Unit occupying this tile, check for affiliation collisions
                //Check if this tile blocks units of a certain affiliation
                if (UnitIsBlocked(parms.Unit, tile.UnitData.Unit, parms.IgnoresAffiliations) ||
                    (tile.TerrainTypeObj.RestrictAffiliations.Any() && !tile.TerrainTypeObj.RestrictAffiliations.Contains(parms.Unit.AffiliationObj.Grouping))
                    )
                {
                    return;
                }

                //Test that the unit can move to this tile
                int moveCost;
                if (!tile.TerrainTypeObj.MovementCosts.TryGetValue(parms.Unit.GetUnitMovementType(), out moveCost))
                {
                    throw new UnmatchedMovementTypeException(parms.Unit.GetUnitMovementType(), tile.TerrainTypeObj.MovementCosts.Keys.ToList());
                }

                //Apply movement cost modifiers
                TerrainTypeMovementCostSetEffect      movCostSet  = parms.MoveCostSets.FirstOrDefault(s => tile.TerrainTypeObj.Groupings.Contains(s.TerrainTypeGrouping));
                TerrainTypeMovementCostModifierEffect moveCostMod = parms.MoveCostModifiers.FirstOrDefault(s => tile.TerrainTypeObj.Groupings.Contains(s.TerrainTypeGrouping));
                if (movCostSet != null && (movCostSet.CanOverride99MoveCost || moveCost < 99))
                {
                    moveCost = movCostSet.Value;
                }
                else if (moveCostMod != null && moveCost < 99)
                {
                    moveCost += moveCostMod.Value;
                }

                if (tile.UnitData.Unit != null && tile.UnitData.Unit.AffiliationObj.Grouping == parms.Unit.AffiliationObj.Grouping && moveCost < 99)
                {
                    //If tile is occupied by an ally, test if they have a skill that sets the move cost
                    //Only applies if value is less than natural move cost for unit
                    OriginAllyMovementCostSetEffect allyMovCostSet = tile.UnitData.Unit.SkillList.Select(s => s.Effect).OfType <OriginAllyMovementCostSetEffect>().FirstOrDefault();
                    if (allyMovCostSet != null && allyMovCostSet.MovementCost < moveCost)
                    {
                        moveCost = allyMovCostSet.MovementCost;
                    }
                }

                //Min/max value enforcement
                moveCost = Math.Max(0, moveCost);
                if (moveCost >= 99)
                {
                    return;
                }

                //Don't check or subtract move cost for the starting tile
                if (visitedCoords.Length > 0)
                {
                    if (moveCost > currCoord.RemainingMov)
                    {
                        return;
                    }
                    currCoord.RemainingMov = currCoord.RemainingMov - moveCost;
                }
            }

            //If none of the tiles fail the move cost checks above...
            //Add current anchor tile to visited coords
            visitedCoords += "_" + currCoords.First().Coordinate.ToString() + "_";

            //Document tile movement
            IList <Tile> tiles = currCoords.Select(c => this.Map.GetTileByCoord(c.Coordinate)).ToList();

            if (!tiles.Any(t => t.TerrainTypeObj.CannotStopOn))
            {
                foreach (Tile tile in tiles)
                {
                    if (!parms.Unit.Ranges.Movement.Contains(tile.Coordinate))
                    {
                        parms.Unit.Ranges.Movement.Add(tile.Coordinate);
                    }
                }
            }

            //Units may move onto obstructed tiles, but no further.
            if (tiles.Any(t => UnitIsBlocked(parms.Unit, t.UnitData.ObstructingUnits, parms.IgnoresAffiliations)))
            {
                return;
            }


            //Navigate in each cardinal direction, do not repeat tiles in this path
            //Left
            //Coordinate left = new Coordinate(currCoord.X - 1, currCoord.Y);
            IList <MovementCoordSet> left = currCoords.Select(c => new MovementCoordSet(c.RemainingMov, new Coordinate(c.Coordinate.X - 1, c.Coordinate.Y))).ToList();

            if (!visitedCoords.Contains("_" + left.First().Coordinate.ToString() + "_"))
            {
                RecurseUnitRange(parms, left, visitedCoords, lastWarpUsed);
            }

            //Right
            //Coordinate right = new Coordinate(currCoord.X + 1, currCoord.Y);
            IList <MovementCoordSet> right = currCoords.Select(c => new MovementCoordSet(c.RemainingMov, new Coordinate(c.Coordinate.X + 1, c.Coordinate.Y))).ToList();

            if (!visitedCoords.Contains("_" + right.First().Coordinate.ToString() + "_"))
            {
                RecurseUnitRange(parms, right, visitedCoords, lastWarpUsed);
            }

            //Up
            //Coordinate up = new Coordinate(currCoord.X, currCoord.Y - 1);
            IList <MovementCoordSet> up = currCoords.Select(c => new MovementCoordSet(c.RemainingMov, new Coordinate(c.Coordinate.X, c.Coordinate.Y - 1))).ToList();

            if (!visitedCoords.Contains("_" + up.First().Coordinate.ToString() + "_"))
            {
                RecurseUnitRange(parms, up, visitedCoords, lastWarpUsed);
            }

            //Down
            //Coordinate down = new Coordinate(currCoord.X, currCoord.Y + 1);
            IList <MovementCoordSet> down = currCoords.Select(c => new MovementCoordSet(c.RemainingMov, new Coordinate(c.Coordinate.X, c.Coordinate.Y + 1))).ToList();

            if (!visitedCoords.Contains("_" + down.First().Coordinate.ToString() + "_"))
            {
                RecurseUnitRange(parms, down, visitedCoords, lastWarpUsed);
            }


            //If any tile is a warp entrance, calculate the remaining range from each warp exit too.
            IEnumerable <Tile> warps = tiles.Where(t => t.TerrainTypeObj.WarpType == WarpType.Entrance || t.TerrainTypeObj.WarpType == WarpType.Dual &&
                                                   (lastWarpUsed == null || t.Coordinate != lastWarpUsed));

            foreach (Tile warp in warps)
            {
                //Calculate warp cost
                WarpMovementCostSetEffect      warpCostSet = parms.WarpCostSets.FirstOrDefault(s => warp.TerrainTypeObj.Groupings.Contains(s.TerrainTypeGrouping));
                WarpMovementCostModifierEffect warpCostMod = parms.WarpCostModifiers.FirstOrDefault(s => warp.TerrainTypeObj.Groupings.Contains(s.TerrainTypeGrouping));

                int warpCost = warp.TerrainTypeObj.WarpCost;
                if (warpCostSet != null)
                {
                    warpCost = warpCostSet.Value;
                }
                else if (warpCostMod != null)
                {
                    warpCost += warpCostMod.Value;
                }

                if (warpCost < 0)
                {
                    warpCost = 0;
                }

                foreach (Tile warpExit in warp.WarpData.WarpGroup.Where(t => warp.Coordinate != t.Coordinate && (t.TerrainTypeObj.WarpType == WarpType.Exit || t.TerrainTypeObj.WarpType == WarpType.Dual)))
                {
                    //Calculate range from warp exit in all possible unit orientations, starting with the anchor tile
                    Coordinate currAnchor = currCoords.First().Coordinate;
                    for (int y = 0; y < parms.Unit.Location.UnitSize; y++)
                    {
                        for (int x = 0; x < parms.Unit.Location.UnitSize; x++)
                        {
                            RecurseUnitRange(parms,
                                             currCoords.Select(c => new MovementCoordSet(c.RemainingMov - warpCost, new Coordinate(warpExit.Coordinate.X + Math.Abs(currAnchor.X - c.Coordinate.X) - x, warpExit.Coordinate.Y + Math.Abs(currAnchor.Y - c.Coordinate.Y) - y))).ToList(),
                                             string.Empty,
                                             warpExit.Coordinate);
                        }
                    }
                }
            }
        }