AbilityUtils: refactor Party types (#8967)

This commit is contained in:
Hans Mackowiak
2025-10-23 23:04:22 +02:00
committed by GitHub
parent 4cb581152e
commit 4063ac55e6
6 changed files with 68 additions and 59 deletions

View File

@@ -547,7 +547,14 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
if (!isCreature() && !isKindred()) { if (!isCreature() && !isKindred()) {
return false; return false;
} }
return !Collections.disjoint(getCreatureTypes(), Constant.OUTLAW_TYPES); return Constant.OUTLAW_TYPES.stream().anyMatch(s -> hasCreatureType(s));
}
@Override
public boolean isParty() {
if (!isCreature() && !isKindred()) {
return false;
}
return Constant.PARTY_TYPES.stream().anyMatch(s -> hasCreatureType(s));
} }
@Override @Override
@@ -916,6 +923,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
"Pirate", "Pirate",
"Rogue", "Rogue",
"Warlock"); "Warlock");
public static final Set<String> PARTY_TYPES = Sets.newHashSet(
"Cleric",
"Rogue",
"Warrior",
"Wizard");
} }
public static class Predicates { public static class Predicates {
public static Predicate<String> IS_LAND_TYPE = CardType::isALandType; public static Predicate<String> IS_LAND_TYPE = CardType::isALandType;

View File

@@ -64,6 +64,7 @@ public interface CardTypeView extends Iterable<String>, Serializable {
boolean isSaga(); boolean isSaga();
boolean isHistoric(); boolean isHistoric();
boolean isOutlaw(); boolean isOutlaw();
boolean isParty();
CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes); CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes);
} }

View File

@@ -93,6 +93,8 @@ public class ForgeScript {
} }
} else if (property.equals("Outlaw")) { } else if (property.equals("Outlaw")) {
return type.isOutlaw(); return type.isOutlaw();
} else if (property.equals("Party")) {
return type.isParty();
} else if (property.startsWith("non")) { } else if (property.startsWith("non")) {
// ... Other Card types // ... Other Card types
return !type.hasStringType(property.substring(3)); return !type.hasStringType(property.substring(3));

View File

@@ -4,6 +4,7 @@ import com.google.common.collect.*;
import com.google.common.math.IntMath; import com.google.common.math.IntMath;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.CardType; import forge.card.CardType;
import forge.card.CardTypeView;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.card.mana.ManaAtom; import forge.card.mana.ManaAtom;
@@ -42,6 +43,7 @@ import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class AbilityUtils { public class AbilityUtils {
private final static ImmutableList<String> cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE"); private final static ImmutableList<String> cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE");
@@ -2601,61 +2603,65 @@ public class AbilityUtils {
} }
if (sq[0].contains("Party")) { if (sq[0].contains("Party")) {
CardCollection adventurers = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), Set<String> chosenParty = Sets.newHashSet();
"Creature.Cleric,Creature.Rogue,Creature.Warrior,Creature.Wizard", player, c, ctb); int wildcard = 0;
ListMultimap<String, Card> multityped = MultimapBuilder.hashKeys().arrayListValues().build();
Set<String> partyTypes = Sets.newHashSet("Cleric", "Rogue", "Warrior", "Wizard"); List<Card> chosenMulti = Lists.newArrayList();
int partySize = 0;
HashMap<String, Card> chosenParty = new HashMap<>();
List<Card> wildcard = Lists.newArrayList();
HashMap<Card, Set<String>> multityped = new HashMap<>();
// Figure out how to count each class separately. // Figure out how to count each class separately.
for (Card card : adventurers) { for (Card card : player.getCardsIn(ZoneType.Battlefield)) {
// cards with all creature types will just return full list if (!card.isCreature()) {
Set<String> creatureTypes = card.getType().getCreatureTypes();
creatureTypes.retainAll(partyTypes);
if (creatureTypes.size() == 4) {
wildcard.add(card);
if (wildcard.size() >= 4) {
break;
}
continue; continue;
} else if (creatureTypes.size() == 1) { }
String type = (String)(creatureTypes.toArray()[0]); CardTypeView type = card.getType();
Set<String> creatureTypes;
if (!chosenParty.containsKey(type)) { // extra logic for "all creature types" cards
chosenParty.put(type, card); if (type.hasAllCreatureTypes()) {
// one of the party types could be excluded, so check each of them separate
creatureTypes = CardType.Constant.PARTY_TYPES.stream().filter(p -> type.hasCreatureType(p)).collect(Collectors.toSet());
} else { // shortcut for others
creatureTypes = type.getCreatureTypes();
creatureTypes.retainAll(CardType.Constant.PARTY_TYPES);
}
switch (creatureTypes.size()) {
case 0:
continue;
case 4:
wildcard++;
break;
case 1:
chosenParty.addAll(creatureTypes);
break;
default:
for (String t : creatureTypes) {
multityped.put(t, card);
} }
} else { }
multityped.put(card, creatureTypes);
// found enough
if (chosenParty.size() + wildcard >= 4) {
break;
} }
} }
partySize = Math.min(chosenParty.size() + wildcard.size(), 4); if (chosenParty.size() + wildcard < 4) {
multityped.keySet().removeAll(chosenParty);
if (partySize < 4) { // sort by amount of members
partyTypes.removeAll(chosenParty.keySet()); Multimaps.asMap(multityped).entrySet().stream()
.sorted(Map.Entry.<String, List<Card>>comparingByValue(Comparator.<List<Card>>comparingInt(Collection::size)))
// Here I'm left with just the party types that I haven't selected. .forEach(e -> {
for (Card multi : multityped.keySet()) { e.getValue().removeAll(chosenMulti);
Set<String> types = multityped.get(multi); if (e.getValue().size() > 0) {
types.retainAll(partyTypes); chosenParty.add(e.getKey());
chosenMulti.add(e.getValue().get(0));
for (String type : types) { }
chosenParty.put(type, multi); });
partyTypes.remove(type);
break;
}
}
} }
partySize = Math.min(chosenParty.size() + wildcard.size(), 4); return doXMath(Math.min(chosenParty.size() + wildcard, 4), expr, c, ctb);
return doXMath(partySize, expr, c, ctb);
} }
// TODO make AI part to understand Sunburst better so this isn't needed // TODO make AI part to understand Sunburst better so this isn't needed

View File

@@ -131,8 +131,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
} }
} else if (sa.hasParam("ChooseEach")) { } else if (sa.hasParam("ChooseEach")) {
final String s = sa.getParam("ChooseEach"); final String s = sa.getParam("ChooseEach");
final String[] types = s.equals("Party") ? new String[]{"Cleric","Rogue","Warrior","Wizard"} final Collection<String> types = s.equals("Party") ? CardType.Constant.PARTY_TYPES : Arrays.asList(s.split(" & "));
: s.split(" & ");
for (final String type : types) { for (final String type : types) {
CardCollection valids = CardLists.filter(pChoices, CardPredicates.isType(type)); CardCollection valids = CardLists.filter(pChoices, CardPredicates.isType(type));
if (!valids.isEmpty()) { if (!valids.isEmpty()) {

View File

@@ -2,7 +2,6 @@ package forge.game.card;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.StaticData; import forge.StaticData;
import forge.card.CardDb; import forge.card.CardDb;
import forge.card.ColorSet; import forge.card.ColorSet;
@@ -720,17 +719,6 @@ public class CardProperty {
return false; return false;
} }
} }
} else if (property.equals("Party")) {
boolean isParty = false;
Set<String> partyTypes = Sets.newHashSet("Cleric", "Rogue", "Warrior", "Wizard");
Set<String> cTypes = card.getType().getCreatureTypes();
for (String t : partyTypes) {
if (cTypes.contains(t)) {
isParty = true;
break;
}
}
return isParty;
} else if (property.startsWith("sharesCreatureTypeWith")) { } else if (property.startsWith("sharesCreatureTypeWith")) {
if (property.equals("sharesCreatureTypeWith")) { if (property.equals("sharesCreatureTypeWith")) {
if (!card.sharesCreatureTypeWith(source)) { if (!card.sharesCreatureTypeWith(source)) {