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