/// <summary> /// Helper function used by the <c>...RadiusTeleportEffect</c>s. Tests each tile in <paramref name="targetTiles"/> to ensure that <paramref name="unit"/> is capable of teleporting there, then adds valid tiles to the <paramref name="unit"/>'s movement range. /// </summary> /// <exception cref="UnmatchedMovementTypeException"></exception> protected void AddTeleportTargetsToUnitRange(Unit unit, IList <Tile> targetTiles) { IList <TerrainTypeMovementCostSetEffect> moveCostSets = unit.SkillList.Select(s => s.Effect).OfType <TerrainTypeMovementCostSetEffect>().ToList(); foreach (Tile tile in targetTiles) { //Ensure that this unit can move to this tile int moveCost; if (!tile.TerrainTypeObj.MovementCosts.TryGetValue(unit.GetUnitMovementType(), out moveCost)) { throw new UnmatchedMovementTypeException(unit.GetUnitMovementType(), tile.TerrainTypeObj.MovementCosts.Keys.ToList()); } //If unit is blocked from this tile, check for a skill that would allow it to access it if (moveCost == 99) { TerrainTypeMovementCostSetEffect movCostSet = moveCostSets.FirstOrDefault(s => tile.TerrainTypeObj.Groupings.Contains(s.TerrainTypeGrouping)); if (movCostSet == null || !movCostSet.CanOverride99MoveCost) { continue; } } //Check for an enemy unit already occupying this tile if (tile.UnitData.Unit != null && tile.UnitData.Unit.AffiliationObj.Grouping != unit.AffiliationObj.Grouping) { continue; } //If no issues arose, add the tile to the unit's movement range if (!unit.Ranges.Movement.Contains(tile.Coordinate)) { unit.Ranges.Movement.Add(tile.Coordinate); } } }
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); } } } } }