Search in sources :

Example 36 with Territory

use of games.strategy.engine.data.Territory in project triplea by triplea-game.

the class MoveValidator method validateParatroops.

private static MoveValidationResult validateParatroops(final boolean nonCombat, final GameData data, final Collection<Unit> units, final Route route, final PlayerID player, final MoveValidationResult result) {
    if (!TechAttachment.isAirTransportable(player)) {
        return result;
    }
    if (units.stream().noneMatch(Matches.unitIsAirTransportable()) || units.stream().noneMatch(Matches.unitIsAirTransport())) {
        return result;
    }
    if (nonCombat && !isAirTransportableCanMoveDuringNonCombat(data)) {
        return result.setErrorReturnResult("Paratroops may not move during NonCombat");
    }
    if (!getEditMode(data)) {
        // if we can move without using paratroop tech, do so this allows moving a bomber/infantry from one friendly
        // territory to another
        final List<Unit> paratroopsRequiringTransport = getParatroopsRequiringTransport(units, route);
        if (paratroopsRequiringTransport.isEmpty()) {
            return result;
        }
        final List<Unit> airTransports = CollectionUtils.getMatches(units, Matches.unitIsAirTransport());
        // TODO kev change below to mapAirTransports (or modify mapTransports to handle air cargo)
        // Map<Unit, Unit> airTransportsAndParatroops = MoveDelegate.mapTransports(route, paratroopsRequiringTransport,
        // airTransports);
        final Map<Unit, Unit> airTransportsAndParatroops = TransportUtils.mapTransportsToLoad(paratroopsRequiringTransport, airTransports);
        for (final Unit paratroop : airTransportsAndParatroops.keySet()) {
            if (Matches.unitHasMoved().test(paratroop)) {
                result.addDisallowedUnit("Cannot paratroop units that have already moved", paratroop);
            }
            final Unit transport = airTransportsAndParatroops.get(paratroop);
            if (Matches.unitHasMoved().test(transport)) {
                result.addDisallowedUnit("Cannot move then transport paratroops", transport);
            }
        }
        final Territory routeEnd = route.getEnd();
        for (final Unit paratroop : paratroopsRequiringTransport) {
            if (Matches.unitHasMoved().test(paratroop)) {
                result.addDisallowedUnit("Cannot paratroop units that have already moved", paratroop);
            }
            if (Matches.isTerritoryFriendly(player, data).test(routeEnd) && !isAirTransportableCanMoveDuringNonCombat(data)) {
                result.addDisallowedUnit("Paratroops must advance to battle", paratroop);
            }
            if (!nonCombat && Matches.isTerritoryFriendly(player, data).test(routeEnd) && isAirTransportableCanMoveDuringNonCombat(data)) {
                result.addDisallowedUnit("Paratroops may only airlift during Non-Combat Movement Phase", paratroop);
            }
        }
        if (!Properties.getParatroopersCanAttackDeepIntoEnemyTerritory(data)) {
            for (final Territory current : CollectionUtils.getMatches(route.getMiddleSteps(), Matches.territoryIsLand())) {
                if (Matches.isTerritoryEnemy(player, data).test(current)) {
                    return result.setErrorReturnResult("Must stop paratroops in first enemy territory");
                }
            }
        }
    }
    return result;
}
Also used : Territory(games.strategy.engine.data.Territory) TripleAUnit(games.strategy.triplea.TripleAUnit) Unit(games.strategy.engine.data.Unit)

Example 37 with Territory

use of games.strategy.engine.data.Territory in project triplea by triplea-game.

the class MoveValidator method validateCombat.

private static MoveValidationResult validateCombat(final GameData data, final Collection<Unit> units, final Route route, final PlayerID player, final MoveValidationResult result) {
    if (getEditMode(data)) {
        return result;
    }
    for (final Territory t : route.getSteps()) {
        if (!Matches.territoryOwnerRelationshipTypeCanMoveIntoDuringCombatMove(player).test(t)) {
            return result.setErrorReturnResult("Cannot move into territories owned by " + t.getOwner().getName() + " during Combat Movement Phase");
        }
    }
    // owned territory. do not allow unless each unit can blitz the current territory.
    if (!route.getStart().isWater() && Matches.isAtWar(route.getStart().getOwner(), data).test(player) && (route.anyMatch(Matches.isTerritoryEnemy(player, data)) && !route.allMatchMiddleSteps(Matches.isTerritoryEnemy(player, data).negate(), false))) {
        if (!Matches.territoryIsBlitzable(player, data).test(route.getStart()) && (units.isEmpty() || !units.stream().allMatch(Matches.unitIsAir()))) {
            return result.setErrorReturnResult("Cannot blitz out of a battle further into enemy territory");
        }
        for (final Unit u : CollectionUtils.getMatches(units, Matches.unitCanBlitz().negate().and(Matches.unitIsNotAir()))) {
            result.addDisallowedUnit("Not all units can blitz out of empty enemy territory", u);
        }
    }
    // Do not allow unless the territory is blitzable.
    if (!route.getStart().isWater() && !Matches.isAtWar(route.getStart().getOwner(), data).test(player) && (route.anyMatch(Matches.isTerritoryEnemy(player, data)) && !route.allMatchMiddleSteps(Matches.isTerritoryEnemy(player, data).negate(), false))) {
        if (!Matches.territoryIsBlitzable(player, data).test(route.getStart()) && (units.isEmpty() || !units.stream().allMatch(Matches.unitIsAir()))) {
            return result.setErrorReturnResult("Cannot blitz out of a battle into enemy territory");
        }
    }
    // Don't allow aa guns (and other disallowed units) to move in combat unless they are in a transport
    if (units.stream().anyMatch(Matches.unitCanNotMoveDuringCombatMove()) && (!route.getStart().isWater() || !route.getEnd().isWater())) {
        for (final Unit unit : CollectionUtils.getMatches(units, Matches.unitCanNotMoveDuringCombatMove())) {
            result.addDisallowedUnit("Cannot move AA guns in combat movement phase", unit);
        }
    }
    // If there is a neutral in the middle must stop unless all are air or getNeutralsBlitzable
    if (route.hasNeutralBeforeEnd()) {
        if ((units.isEmpty() || !units.stream().allMatch(Matches.unitIsAir())) && !isNeutralsBlitzable(data)) {
            return result.setErrorReturnResult("Must stop land units when passing through neutral territories");
        }
    }
    if (units.stream().anyMatch(Matches.unitIsLand()) && route.hasSteps()) {
        // Check all the territories but the end, if there are enemy territories, make sure they are blitzable
        // if they are not blitzable, or we aren't all blitz units fail
        int enemyCount = 0;
        boolean allEnemyBlitzable = true;
        for (final Territory current : route.getMiddleSteps()) {
            if (current.isWater()) {
                continue;
            }
            if (data.getRelationshipTracker().isAtWar(current.getOwner(), player) || AbstractMoveDelegate.getBattleTracker(data).wasConquered(current)) {
                enemyCount++;
                allEnemyBlitzable &= Matches.territoryIsBlitzable(player, data).test(current);
            }
        }
        if (enemyCount > 0 && !allEnemyBlitzable) {
            if (nonParatroopersPresent(player, units)) {
                return result.setErrorReturnResult("Cannot blitz on that route");
            }
        } else if (allEnemyBlitzable && !(route.getStart().isWater() || route.getEnd().isWater())) {
            final Predicate<Unit> blitzingUnit = Matches.unitCanBlitz().or(Matches.unitIsAir());
            final Predicate<Unit> nonBlitzing = blitzingUnit.negate();
            final Collection<Unit> nonBlitzingUnits = CollectionUtils.getMatches(units, nonBlitzing);
            // remove any units that gain blitz due to certain abilities
            nonBlitzingUnits.removeAll(UnitAttachment.getUnitsWhichReceivesAbilityWhenWith(units, "canBlitz", data));
            final Predicate<Territory> territoryIsNotEnd = Matches.territoryIs(route.getEnd()).negate();
            final Predicate<Territory> nonFriendlyTerritories = Matches.isTerritoryFriendly(player, data).negate();
            final Predicate<Territory> notEndOrFriendlyTerrs = nonFriendlyTerritories.and(territoryIsNotEnd);
            final Predicate<Territory> foughtOver = Matches.territoryWasFoughOver(AbstractMoveDelegate.getBattleTracker(data));
            final Predicate<Territory> notEndWasFought = territoryIsNotEnd.and(foughtOver);
            final boolean wasStartFoughtOver = AbstractMoveDelegate.getBattleTracker(data).wasConquered(route.getStart()) || AbstractMoveDelegate.getBattleTracker(data).wasBlitzed(route.getStart());
            nonBlitzingUnits.addAll(CollectionUtils.getMatches(units, Matches.unitIsOfTypes(TerritoryEffectHelper.getUnitTypesThatLostBlitz((wasStartFoughtOver ? route.getAllTerritories() : route.getSteps())))));
            for (final Unit unit : nonBlitzingUnits) {
                // TODO: Need to actually test if the unit is being air transported or land transported
                if ((Matches.unitIsAirTransportable().test(unit) && units.stream().anyMatch(Matches.unitIsAirTransport())) || (Matches.unitIsLandTransportable().test(unit) && units.stream().anyMatch(Matches.unitIsLandTransport()))) {
                    continue;
                }
                final TripleAUnit taUnit = (TripleAUnit) unit;
                if (wasStartFoughtOver || taUnit.getWasInCombat() || route.anyMatch(notEndOrFriendlyTerrs) || route.anyMatch(notEndWasFought)) {
                    result.addDisallowedUnit(NOT_ALL_UNITS_CAN_BLITZ, unit);
                }
            }
        }
    }
    if (units.stream().anyMatch(Matches.unitIsAir())) {
        // check aircraft
        if (route.hasSteps() && (!Properties.getNeutralFlyoverAllowed(data) || isNeutralsImpassable(data))) {
            if (route.getMiddleSteps().stream().anyMatch(Matches.territoryIsNeutralButNotWater())) {
                return result.setErrorReturnResult("Air units cannot fly over neutral territories");
            }
        }
    }
    // make sure no conquered territories on route
    if (MoveValidator.hasConqueredNonBlitzedNonWaterOnRoute(route, data)) {
        // unless we are all air or we are in non combat OR the route is water (was a bug in convoy zone movement)
        if (units.isEmpty() || !units.stream().allMatch(Matches.unitIsAir())) {
            // what if we are paratroopers?
            return result.setErrorReturnResult("Cannot move through newly captured territories");
        }
    }
    // See if they've already been in combat
    if (units.stream().anyMatch(Matches.unitWasInCombat()) && units.stream().anyMatch(Matches.unitWasUnloadedThisTurn())) {
        final Collection<Territory> end = Collections.singleton(route.getEnd());
        if (!end.isEmpty() && end.stream().allMatch(Matches.isTerritoryEnemyAndNotUnownedWaterOrImpassableOrRestricted(player, data)) && !route.getEnd().getUnits().isEmpty()) {
            return result.setErrorReturnResult("Units cannot participate in multiple battles");
        }
    }
    // See if we are doing invasions in combat phase, with units or transports that can't do invasion.
    if (route.isUnload() && Matches.isTerritoryEnemy(player, data).test(route.getEnd())) {
        for (final Unit unit : CollectionUtils.getMatches(units, Matches.unitCanInvade().negate())) {
            result.addDisallowedUnit(unit.getType().getName() + " can't invade from " + TripleAUnit.get(unit).getTransportedBy().getType().getName(), unit);
        }
    }
    return result;
}
Also used : Territory(games.strategy.engine.data.Territory) Collection(java.util.Collection) ResourceCollection(games.strategy.engine.data.ResourceCollection) TripleAUnit(games.strategy.triplea.TripleAUnit) Unit(games.strategy.engine.data.Unit) TripleAUnit(games.strategy.triplea.TripleAUnit) Predicate(java.util.function.Predicate)

Example 38 with Territory

use of games.strategy.engine.data.Territory in project triplea by triplea-game.

the class MoveValidator method validateBasic.

private static MoveValidationResult validateBasic(final GameData data, final Collection<Unit> units, final Route route, final PlayerID player, final Collection<Unit> transportsToLoad, final Map<Unit, Collection<Unit>> newDependents, final MoveValidationResult result) {
    final boolean isEditMode = getEditMode(data);
    // make sure transports in the destination
    if (route.getEnd() != null && !route.getEnd().getUnits().containsAll(transportsToLoad) && !units.containsAll(transportsToLoad)) {
        return result.setErrorReturnResult("Transports not found in route end");
    }
    if (!isEditMode) {
        // Make sure all units are at least friendly
        for (final Unit unit : CollectionUtils.getMatches(units, Matches.enemyUnit(player, data))) {
            result.addDisallowedUnit("Can only move friendly units", unit);
        }
        // Ensure all air transports are included
        for (final Unit airTransport : newDependents.keySet()) {
            if (!units.contains(airTransport)) {
                for (final Unit unit : newDependents.get(airTransport)) {
                    if (units.contains(unit)) {
                        result.addDisallowedUnit("Not all units have enough movement", unit);
                    }
                }
            }
        }
        // Ignore transported units
        Collection<Unit> moveTest = new ArrayList<>(units);
        if (route.getStart().isWater()) {
            moveTest = MoveValidator.getNonLand(units);
        }
        final Map<Unit, Collection<Unit>> dependentsMap = getDependents(CollectionUtils.getMatches(units, Matches.unitCanTransport()));
        final Set<Unit> dependents = dependentsMap.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
        dependents.addAll(newDependents.values().stream().flatMap(Collection::stream).collect(Collectors.toSet()));
        moveTest.removeAll(dependents);
        // Can only move owned units except transported units or allied air on carriers
        for (final Unit unit : CollectionUtils.getMatches(moveTest, Matches.unitIsOwnedBy(player).negate())) {
            if (!(UnitAttachment.get(unit.getType()).getCarrierCost() > 0 && data.getRelationshipTracker().isAllied(player, unit.getOwner()))) {
                result.addDisallowedUnit("Can only move own troops", unit);
            }
        }
        // Check if units have enough movement accounting for land transports
        // Land transports can either:
        // 1. Transport units on a 1-to-1 basis (have no capacity set)
        // 2. Transport like sea transports using capacity and cost
        data.acquireReadLock();
        try {
            int numLandTransportsWithoutCapacity = getNumLandTransportsWithoutCapacity(units, player);
            final IntegerMap<Unit> landTransportsWithCapacity = getLandTransportsWithCapacity(units, player);
            moveTest = TransportUtils.sortByTransportCostDescending(moveTest);
            for (final Unit unit : moveTest) {
                if (!Matches.unitHasEnoughMovementForRoute(route).test(unit)) {
                    boolean unitOk = false;
                    if (Matches.unitIsOwnedBy(player).negate().test(unit) && Matches.alliedUnit(player, data).test(unit) && Matches.unitTypeCanLandOnCarrier().test(unit.getType()) && moveTest.stream().anyMatch(Matches.unitIsAlliedCarrier(unit.getOwner(), data))) {
                        // this is so that if the unit is owned by any ally and it is cargo, then it will not count.
                        // (shouldn't it be a dependent in this case??)
                        unitOk = true;
                    } else if (Matches.unitHasNotMoved().test(unit) && Matches.unitIsLandTransportable().test(unit)) {
                        if (numLandTransportsWithoutCapacity > 0) {
                            numLandTransportsWithoutCapacity--;
                            unitOk = true;
                        } else {
                            for (final Unit transport : landTransportsWithCapacity.keySet()) {
                                final int cost = UnitAttachment.get((unit).getType()).getTransportCost();
                                if (cost <= landTransportsWithCapacity.getInt(transport)) {
                                    landTransportsWithCapacity.add(transport, -cost);
                                    unitOk = true;
                                    break;
                                }
                            }
                        }
                    }
                    if (!unitOk) {
                        result.addDisallowedUnit("Not all units have enough movement", unit);
                    }
                }
            }
        } finally {
            data.releaseReadLock();
        }
        // if there is a neutral in the middle must stop unless all are air or getNeutralsBlitzable
        if (route.hasNeutralBeforeEnd()) {
            if ((units.isEmpty() || !units.stream().allMatch(Matches.unitIsAir())) && !isNeutralsBlitzable(data)) {
                return result.setErrorReturnResult("Must stop land units when passing through neutral territories");
            }
        }
        // a territory effect can disallow unit types in
        if (units.stream().anyMatch(Matches.unitIsOfTypes(TerritoryEffectHelper.getUnitTypesForUnitsNotAllowedIntoTerritory(route.getSteps())))) {
            return result.setErrorReturnResult("Territory Effects disallow some units into " + (route.numberOfSteps() > 1 ? "these territories" : "this territory"));
        }
        // Check requiresUnitsToMove conditions
        Collection<Unit> requiresUnitsToMoveList = moveTest;
        if (route.isUnload()) {
            requiresUnitsToMoveList = units;
        }
        for (final Territory t : route.getAllTerritories()) {
            if (!requiresUnitsToMoveList.stream().allMatch(Matches.unitHasRequiredUnitsToMove(t, data))) {
                return result.setErrorReturnResult(t.getName() + " doesn't have the required units to allow moving the selected units into it");
            }
        }
    }
    // make sure that no non sea non transportable no carriable units end at sea
    if (route.getEnd() != null && route.getEnd().isWater()) {
        for (final Unit unit : MoveValidator.getUnitsThatCantGoOnWater(units)) {
            result.addDisallowedUnit("Not all units can end at water", unit);
        }
    }
    // if we are water make sure no land
    if (units.stream().anyMatch(Matches.unitIsSea())) {
        if (route.hasLand()) {
            for (final Unit unit : CollectionUtils.getMatches(units, Matches.unitIsSea())) {
                result.addDisallowedUnit("Sea units cannot go on land", unit);
            }
        }
    }
    // test for stack limits per unit
    if (route.getEnd() != null) {
        final Collection<Unit> unitsWithStackingLimits = CollectionUtils.getMatches(units, Matches.unitHasMovementLimit().or(Matches.unitHasAttackingLimit()));
        for (final Territory t : route.getSteps()) {
            final Collection<Unit> unitsAllowedSoFar = new ArrayList<>();
            if (Matches.isTerritoryEnemyAndNotUnownedWater(player, data).test(t) || t.getUnits().anyMatch(Matches.unitIsEnemyOf(data, player))) {
                for (final Unit unit : unitsWithStackingLimits) {
                    final UnitType ut = unit.getType();
                    int maxAllowed = UnitAttachment.getMaximumNumberOfThisUnitTypeToReachStackingLimit("attackingLimit", ut, t, player, data);
                    maxAllowed -= CollectionUtils.countMatches(unitsAllowedSoFar, Matches.unitIsOfType(ut));
                    if (maxAllowed > 0) {
                        unitsAllowedSoFar.add(unit);
                    } else {
                        result.addDisallowedUnit("UnitType " + ut.getName() + " has reached stacking limit", unit);
                    }
                }
                if (!PlayerAttachment.getCanTheseUnitsMoveWithoutViolatingStackingLimit("attackingLimit", units, t, player, data)) {
                    return result.setErrorReturnResult("Units Cannot Go Over Stacking Limit");
                }
            } else {
                for (final Unit unit : unitsWithStackingLimits) {
                    final UnitType ut = unit.getType();
                    int maxAllowed = UnitAttachment.getMaximumNumberOfThisUnitTypeToReachStackingLimit("movementLimit", ut, t, player, data);
                    maxAllowed -= CollectionUtils.countMatches(unitsAllowedSoFar, Matches.unitIsOfType(ut));
                    if (maxAllowed > 0) {
                        unitsAllowedSoFar.add(unit);
                    } else {
                        result.addDisallowedUnit("UnitType " + ut.getName() + " has reached stacking limit", unit);
                    }
                }
                if (!PlayerAttachment.getCanTheseUnitsMoveWithoutViolatingStackingLimit("movementLimit", units, t, player, data)) {
                    return result.setErrorReturnResult("Units Cannot Go Over Stacking Limit");
                }
            }
        }
    }
    // Don't allow move through impassable territories
    if (!isEditMode && route.anyMatch(Matches.territoryIsImpassable())) {
        return result.setErrorReturnResult(CANT_MOVE_THROUGH_IMPASSABLE);
    }
    if (canCrossNeutralTerritory(data, route, player, result).getError() != null) {
        return result;
    }
    if (isNeutralsImpassable(data) && !isNeutralsBlitzable(data) && !route.getMatches(Matches.territoryIsNeutralButNotWater()).isEmpty()) {
        return result.setErrorReturnResult(CANNOT_VIOLATE_NEUTRALITY);
    }
    return result;
}
Also used : Territory(games.strategy.engine.data.Territory) UnitType(games.strategy.engine.data.UnitType) ArrayList(java.util.ArrayList) Collection(java.util.Collection) ResourceCollection(games.strategy.engine.data.ResourceCollection) TripleAUnit(games.strategy.triplea.TripleAUnit) Unit(games.strategy.engine.data.Unit)

Example 39 with Territory

use of games.strategy.engine.data.Territory in project triplea by triplea-game.

the class MoveValidator method onlyIgnoredUnitsOnPath.

/**
 * Checks that there only transports, subs and/or allies on the route except at the end. AA and factory dont count as
 * enemy.
 */
static boolean onlyIgnoredUnitsOnPath(final Route route, final PlayerID player, final GameData data, final boolean ignoreRouteEnd) {
    final Predicate<Unit> subOnly = Matches.unitIsInfrastructure().or(Matches.unitIsSub()).or(Matches.enemyUnit(player, data).negate());
    final Predicate<Unit> transportOnly = Matches.unitIsInfrastructure().or(Matches.unitIsTransportButNotCombatTransport()).or(Matches.unitIsLand()).or(Matches.enemyUnit(player, data).negate());
    final Predicate<Unit> transportOrSubOnly = Matches.unitIsInfrastructure().or(Matches.unitIsTransportButNotCombatTransport()).or(Matches.unitIsLand()).or(Matches.unitIsSub()).or(Matches.enemyUnit(player, data).negate());
    final boolean getIgnoreTransportInMovement = isIgnoreTransportInMovement(data);
    final boolean getIgnoreSubInMovement = isIgnoreSubInMovement(data);
    final List<Territory> steps;
    if (ignoreRouteEnd) {
        steps = route.getMiddleSteps();
    } else {
        steps = route.getSteps();
    }
    // this sea zone (not sure if we need !ignoreRouteEnd here).
    if (steps.isEmpty() && route.numberOfStepsIncludingStart() == 1 && !ignoreRouteEnd) {
        steps.add(route.getStart());
    }
    boolean validMove = false;
    for (final Territory current : steps) {
        if (current.isWater()) {
            if (getIgnoreTransportInMovement && getIgnoreSubInMovement && current.getUnits().allMatch(transportOrSubOnly)) {
                validMove = true;
                continue;
            }
            if (getIgnoreTransportInMovement && !getIgnoreSubInMovement && current.getUnits().allMatch(transportOnly)) {
                validMove = true;
                continue;
            }
            if (!getIgnoreTransportInMovement && getIgnoreSubInMovement && current.getUnits().allMatch(subOnly)) {
                validMove = true;
                continue;
            }
            return false;
        }
    }
    return validMove;
}
Also used : Territory(games.strategy.engine.data.Territory) TripleAUnit(games.strategy.triplea.TripleAUnit) Unit(games.strategy.engine.data.Unit)

Example 40 with Territory

use of games.strategy.engine.data.Territory in project triplea by triplea-game.

the class MoveValidator method validateTransport.

private static MoveValidationResult validateTransport(final boolean isNonCombat, final GameData data, final List<UndoableMove> undoableMoves, final Collection<Unit> units, final Route route, final PlayerID player, final Collection<Unit> transportsToLoad, final MoveValidationResult result) {
    final boolean isEditMode = getEditMode(data);
    if (!units.isEmpty() && units.stream().allMatch(Matches.unitIsAir())) {
        return result;
    }
    if (!route.hasWater()) {
        return result;
    }
    // If there are non-sea transports return
    final boolean seaOrNoTransportsPresent = transportsToLoad.isEmpty() || transportsToLoad.stream().anyMatch(Matches.unitIsSea().and(Matches.unitCanTransport()));
    if (!seaOrNoTransportsPresent) {
        return result;
    }
    final Territory routeEnd = route.getEnd();
    final Territory routeStart = route.getStart();
    // if unloading make sure length of route is only 1
    if (!isEditMode && route.isUnload()) {
        if (route.hasMoreThenOneStep()) {
            return result.setErrorReturnResult("Unloading units must stop where they are unloaded");
        }
        for (final Unit unit : TransportTracker.getUnitsLoadedOnAlliedTransportsThisTurn(units)) {
            result.addDisallowedUnit(CANNOT_LOAD_AND_UNLOAD_AN_ALLIED_TRANSPORT_IN_THE_SAME_ROUND, unit);
        }
        final Collection<Unit> transports = TransportUtils.mapTransports(route, units, null).values();
        final boolean isScramblingOrKamikazeAttacksEnabled = Properties.getScrambleRulesInEffect(data) || Properties.getUseKamikazeSuicideAttacks(data);
        final boolean submarinesPreventUnescortedAmphibAssaults = Properties.getSubmarinesPreventUnescortedAmphibiousAssaults(data);
        final Predicate<Unit> enemySubmarineMatch = Matches.unitIsEnemyOf(data, player).and(Matches.unitIsSub());
        final Predicate<Unit> ownedSeaNonTransportMatch = Matches.unitIsOwnedBy(player).and(Matches.unitIsSea()).and(Matches.unitIsNotTransportButCouldBeCombatTransport());
        for (final Unit transport : transports) {
            if (!isNonCombat && route.numberOfStepsIncludingStart() == 2) {
                if (Matches.territoryHasEnemyUnits(player, data).test(routeEnd) || Matches.isTerritoryEnemyAndNotUnownedWater(player, data).test(routeEnd)) {
                    // this is an amphibious assault
                    if (submarinesPreventUnescortedAmphibAssaults && !Matches.territoryHasUnitsThatMatch(ownedSeaNonTransportMatch).test(routeStart) && Matches.territoryHasUnitsThatMatch(enemySubmarineMatch).test(routeStart)) {
                        // stops our unloading for amphibious assault
                        for (final Unit unit : TransportTracker.transporting(transport)) {
                            result.addDisallowedUnit(ENEMY_SUBMARINE_PREVENTING_UNESCORTED_AMPHIBIOUS_ASSAULT_LANDING, unit);
                        }
                    }
                } else if (!AbstractMoveDelegate.getBattleTracker(data).wasConquered(routeEnd)) {
                    // this is an unload to a friendly territory
                    if (isScramblingOrKamikazeAttacksEnabled || !Matches.territoryIsEmptyOfCombatUnits(data, player).test(routeStart)) {
                        // TODO: should we use the battle tracker for this instead?
                        for (final Unit unit : TransportTracker.transporting(transport)) {
                            result.addDisallowedUnit(TRANSPORT_MAY_NOT_UNLOAD_TO_FRIENDLY_TERRITORIES_UNTIL_AFTER_COMBAT_IS_RESOLVED, unit);
                        }
                    }
                }
            }
            // in which they perform their actions. check whether transport has already unloaded
            if (TransportTracker.hasTransportUnloadedInPreviousPhase(transport)) {
                for (final Unit unit : TransportTracker.transporting(transport)) {
                    result.addDisallowedUnit(TRANSPORT_HAS_ALREADY_UNLOADED_UNITS_IN_A_PREVIOUS_PHASE, unit);
                }
            // check whether transport is restricted to another territory
            } else if (TransportTracker.isTransportUnloadRestrictedToAnotherTerritory(transport, route.getEnd())) {
                final Territory alreadyUnloadedTo = getTerritoryTransportHasUnloadedTo(undoableMoves, transport);
                for (final Unit unit : TransportTracker.transporting(transport)) {
                    result.addDisallowedUnit(TRANSPORT_HAS_ALREADY_UNLOADED_UNITS_TO + alreadyUnloadedTo.getName(), unit);
                }
            // Check if the transport has already loaded after being in combat
            } else if (TransportTracker.isTransportUnloadRestrictedInNonCombat(transport)) {
                for (final Unit unit : TransportTracker.transporting(transport)) {
                    result.addDisallowedUnit(TRANSPORT_CANNOT_LOAD_AND_UNLOAD_AFTER_COMBAT, unit);
                }
            }
        }
    }
    // if we are land make sure no water in route except for transport situations
    final Collection<Unit> land = CollectionUtils.getMatches(units, Matches.unitIsLand());
    final Collection<Unit> landAndAir = CollectionUtils.getMatches(units, Matches.unitIsLand().or(Matches.unitIsAir()));
    // make sure we can be transported
    final Predicate<Unit> cantBeTransported = Matches.unitCanBeTransported().negate();
    for (final Unit unit : CollectionUtils.getMatches(land, cantBeTransported)) {
        result.addDisallowedUnit("Not all units can be transported", unit);
    }
    // make sure that the only the first or last territory is land don't want situation where they go sea land sea
    if (!isEditMode && route.hasLand() && !(route.getStart().isWater() || route.getEnd().isWater())) {
        // carried by the air and that the air has enough capacity
        if (nonParatroopersPresent(player, landAndAir)) {
            return result.setErrorReturnResult("Invalid move, only start or end can be land when route has water.");
        }
    }
    // TODO handle this
    if (!isEditMode && !route.getEnd().isWater() && !route.getStart().isWater() && nonParatroopersPresent(player, landAndAir)) {
        return result.setErrorReturnResult("Must stop units at a transport on route");
    }
    if (route.getEnd().isWater() && route.getStart().isWater()) {
        // make sure units and transports stick together
        for (final Unit unit : units) {
            final UnitAttachment ua = UnitAttachment.get(unit.getType());
            // make sure transports dont leave their units behind
            if (ua.getTransportCapacity() != -1) {
                final Collection<Unit> holding = TransportTracker.transporting(unit);
                if (!units.containsAll(holding)) {
                    result.addDisallowedUnit("Transports cannot leave their units", unit);
                }
            }
            // make sure units dont leave their transports behind
            if (ua.getTransportCost() != -1) {
                final Unit transport = TransportTracker.transportedBy(unit);
                if (transport != null && !units.contains(transport)) {
                    result.addDisallowedUnit("Unit must stay with its transport while moving", unit);
                }
            }
        }
    }
    if (route.isLoad()) {
        if (!isEditMode && !route.hasExactlyOneStep() && nonParatroopersPresent(player, landAndAir)) {
            return result.setErrorReturnResult("Units cannot move before loading onto transports");
        }
        final Predicate<Unit> enemyNonSubmerged = Matches.enemyUnit(player, data).and(Matches.unitIsSubmerged().negate());
        if (!Properties.getUnitsCanLoadInHostileSeaZones(data) && route.getEnd().getUnits().anyMatch(enemyNonSubmerged) && nonParatroopersPresent(player, landAndAir) && !onlyIgnoredUnitsOnPath(route, player, data, false) && !AbstractMoveDelegate.getBattleTracker(data).didAllThesePlayersJustGoToWarThisTurn(player, route.getEnd().getUnits().getUnits(), data)) {
            return result.setErrorReturnResult("Cannot load when enemy sea units are present");
        }
        final Map<Unit, Unit> unitsToTransports = TransportUtils.mapTransports(route, land, transportsToLoad);
        if (!isEditMode) {
            for (final Unit baseUnit : land) {
                final TripleAUnit unit = (TripleAUnit) baseUnit;
                if (Matches.unitHasMoved().test(unit)) {
                    result.addDisallowedUnit("Units cannot move before loading onto transports", unit);
                }
                final Unit transport = unitsToTransports.get(unit);
                if (transport == null) {
                    continue;
                }
                if (TransportTracker.hasTransportUnloadedInPreviousPhase(transport)) {
                    result.addDisallowedUnit(TRANSPORT_HAS_ALREADY_UNLOADED_UNITS_IN_A_PREVIOUS_PHASE, unit);
                } else if (TransportTracker.isTransportUnloadRestrictedToAnotherTerritory(transport, route.getEnd())) {
                    Territory alreadyUnloadedTo = getTerritoryTransportHasUnloadedTo(undoableMoves, transport);
                    for (final Unit transportToLoad : transportsToLoad) {
                        final TripleAUnit trn = (TripleAUnit) transportToLoad;
                        if (!TransportTracker.isTransportUnloadRestrictedToAnotherTerritory(trn, route.getEnd())) {
                            final UnitAttachment ua = UnitAttachment.get(unit.getType());
                            if (TransportTracker.getAvailableCapacity(trn) >= ua.getTransportCost()) {
                                alreadyUnloadedTo = null;
                                break;
                            }
                        }
                    }
                    if (alreadyUnloadedTo != null) {
                        result.addDisallowedUnit(TRANSPORT_HAS_ALREADY_UNLOADED_UNITS_TO + alreadyUnloadedTo.getName(), unit);
                    }
                }
            }
        }
        if (!unitsToTransports.keySet().containsAll(land)) {
            // some units didn't get mapped to a transport
            final Collection<UnitCategory> unitsToLoadCategories = UnitSeperator.categorize(land);
            if (unitsToTransports.size() == 0 || unitsToLoadCategories.size() == 1) {
                // set all unmapped units as disallowed if there are no transports or only one unit category
                for (final Unit unit : land) {
                    if (unitsToTransports.containsKey(unit)) {
                        continue;
                    }
                    final UnitAttachment ua = UnitAttachment.get(unit.getType());
                    if (ua.getTransportCost() != -1) {
                        result.addDisallowedUnit("Not enough transports", unit);
                    }
                }
            } else {
                // set all units as unresolved if there is at least one transport and mixed unit categories
                for (final Unit unit : land) {
                    final UnitAttachment ua = UnitAttachment.get(unit.getType());
                    if (ua.getTransportCost() != -1) {
                        result.addUnresolvedUnit("Not enough transports", unit);
                    }
                }
            }
        }
    }
    return result;
}
Also used : UnitAttachment(games.strategy.triplea.attachments.UnitAttachment) Territory(games.strategy.engine.data.Territory) TripleAUnit(games.strategy.triplea.TripleAUnit) Unit(games.strategy.engine.data.Unit) TripleAUnit(games.strategy.triplea.TripleAUnit) UnitCategory(games.strategy.triplea.util.UnitCategory)

Aggregations

Territory (games.strategy.engine.data.Territory)420 Unit (games.strategy.engine.data.Unit)254 TripleAUnit (games.strategy.triplea.TripleAUnit)195 PlayerID (games.strategy.engine.data.PlayerID)164 ArrayList (java.util.ArrayList)160 Test (org.junit.jupiter.api.Test)140 Route (games.strategy.engine.data.Route)137 ITestDelegateBridge (games.strategy.engine.data.ITestDelegateBridge)118 GameData (games.strategy.engine.data.GameData)94 HashSet (java.util.HashSet)87 UnitType (games.strategy.engine.data.UnitType)65 HashMap (java.util.HashMap)65 ScriptedRandomSource (games.strategy.engine.random.ScriptedRandomSource)50 Collection (java.util.Collection)47 IntegerMap (games.strategy.util.IntegerMap)45 Set (java.util.Set)41 ProTerritory (games.strategy.triplea.ai.pro.data.ProTerritory)39 TerritoryAttachment (games.strategy.triplea.attachments.TerritoryAttachment)37 List (java.util.List)36 ProPurchaseTerritory (games.strategy.triplea.ai.pro.data.ProPurchaseTerritory)34