From 26d7866a68e5d83f91a09d59b7c5d044c7144af3 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Mon, 21 Aug 2023 14:38:18 +0000 Subject: [PATCH] PlacePanel: Limit max based on unit requirements. (#11887) This change caps the max for each unit type in the place panel to the max that can be placed for that type when considering "units which require units" logic (i.e. factory types). This partially prevents the error "Cannot place more units which require units, than production capacity of territories with the required units" after making the selection in the place panel. Note: That error can still happen when selecting multiple units of different types that surpass the max. --- .../delegate/AbstractPlaceDelegate.java | 19 +++++++++++---- .../triplea/delegate/PlaceDelegateTest.java | 23 +++++++++++++++++++ .../src/test/resources/DelegateTest.xml | 16 +++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/AbstractPlaceDelegate.java b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/AbstractPlaceDelegate.java index 220607ccf00..a5773a194a1 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/delegate/AbstractPlaceDelegate.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/delegate/AbstractPlaceDelegate.java @@ -38,6 +38,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.IntStream; import org.triplea.java.collections.CollectionUtils; import org.triplea.java.collections.IntegerMap; @@ -974,6 +975,11 @@ protected Collection getUnitsToBePlaced( } else { placeableUnits2 = placeableUnits; } + // Limit count of each unit type to the max that can be placed based on unit requirements. + for (UnitType ut : placeableUnits2.stream().map(Unit::getType).collect(Collectors.toSet())) { + var unitsOfType = CollectionUtils.getMatches(placeableUnits2, Matches.unitIsOfType(ut)); + placeableUnits2.removeAll(getUnitsThatCantBePlacedThatRequireUnits(unitsOfType, to)); + } // now check stacking limits return UnitStackingLimitFilter.filterUnits( placeableUnits2, PLACEMENT_LIMIT, player, to, produced.getOrDefault(to, List.of())); @@ -1471,20 +1477,25 @@ private Predicate unitWhichRequiresUnitsHasRequiredUnits( private boolean getCanAllUnitsWithRequiresUnitsBePlacedCorrectly( final Collection units, final Territory to) { + return getUnitsThatCantBePlacedThatRequireUnits(units, to).isEmpty(); + } + + private Collection getUnitsThatCantBePlacedThatRequireUnits( + final Collection units, final Territory to) { if (!Properties.getUnitPlacementRestrictions(getData().getProperties()) || units.stream().noneMatch(Matches.unitRequiresUnitsOnCreation())) { - return true; + return List.of(); } final IntegerMap producersMap = getMaxUnitsToBePlacedMap(units, to, player); final List producers = getAllProducers(to, player, units); if (producers.isEmpty()) { - return false; + return units; } producers.sort(getBestProducerComparator(to, units, player)); final Collection unitsLeftToPlace = new ArrayList<>(units); for (final Territory t : producers) { if (unitsLeftToPlace.isEmpty()) { - return true; + return List.of(); } final int productionHere = producersMap.getInt(t); final List canBePlacedHere = @@ -1499,7 +1510,7 @@ private boolean getCanAllUnitsWithRequiresUnitsBePlacedCorrectly( CollectionUtils.getNMatches(canBePlacedHere, productionHere, it -> true); unitsLeftToPlace.removeAll(placedHere); } - return unitsLeftToPlace.isEmpty(); + return unitsLeftToPlace; } private Comparator getBestProducerComparator( diff --git a/game-app/game-core/src/test/java/games/strategy/triplea/delegate/PlaceDelegateTest.java b/game-app/game-core/src/test/java/games/strategy/triplea/delegate/PlaceDelegateTest.java index 0151881db45..215f00fbab4 100644 --- a/game-app/game-core/src/test/java/games/strategy/triplea/delegate/PlaceDelegateTest.java +++ b/game-app/game-core/src/test/java/games/strategy/triplea/delegate/PlaceDelegateTest.java @@ -1,5 +1,8 @@ package games.strategy.triplea.delegate; +import static games.strategy.triplea.Constants.DAMAGE_FROM_BOMBING_DONE_TO_UNITS_INSTEAD_OF_TERRITORIES; +import static games.strategy.triplea.Constants.UNIT_PLACEMENT_RESTRICTIONS; +import static games.strategy.triplea.delegate.GameDataTestUtil.unitType; import static games.strategy.triplea.delegate.Matches.unitIsOfType; import static games.strategy.triplea.delegate.MockDelegateBridge.newDelegateBridge; import static games.strategy.triplea.delegate.remote.IAbstractPlaceDelegate.BidMode.NOT_BID; @@ -145,6 +148,26 @@ void testCanNotProduceThatManyUnits() { assertEquals(2, response.getMaxUnits()); } + @Test + void testCanNotProduceThatManyUnitsDueToRequiresUnits() { + gameData.getProperties().set(UNIT_PLACEMENT_RESTRICTIONS, true); + // Needed for canProduceXUnits to work. (!) + gameData.getProperties().set(DAMAGE_FROM_BOMBING_DONE_TO_UNITS_INSTEAD_OF_TERRITORIES, true); + final var factory2 = unitType("factory2", gameData); + final var infantry2 = unitType("infantry2", gameData); + + final var threeInfantry2 = create(british, infantry2, 3); + final var fourInfantry2 = create(british, infantry2, 4); + + uk.getUnitCollection().clear(); + assertError(delegate.canUnitsBePlaced(uk, threeInfantry2, british)); + uk.getUnitCollection().addAll(create(british, factory2, 1)); + assertValid(delegate.canUnitsBePlaced(uk, threeInfantry2, british)); + assertError(delegate.canUnitsBePlaced(uk, fourInfantry2, british)); + final PlaceableUnits response = delegate.getPlaceableUnits(fourInfantry2, uk); + assertThat(response.getUnits(), hasSize(3)); + } + @Test void testAlreadyProducedUnits() { delegate.setProduced(Map.of(westCanada, create(british, infantry, 2))); diff --git a/game-app/game-core/src/test/resources/DelegateTest.xml b/game-app/game-core/src/test/resources/DelegateTest.xml index ce876750f4d..cd8c6dea63b 100644 --- a/game-app/game-core/src/test/resources/DelegateTest.xml +++ b/game-app/game-core/src/test/resources/DelegateTest.xml @@ -459,6 +459,8 @@ + + @@ -718,6 +720,20 @@ type="unitType">