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()) {
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
@@ -916,6 +923,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
"Pirate",
"Rogue",
"Warlock");
public static final Set<String> PARTY_TYPES = Sets.newHashSet(
"Cleric",
"Rogue",
"Warrior",
"Wizard");
}
public static class Predicates {
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 isHistoric();
boolean isOutlaw();
boolean isParty();
CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes);
}

View File

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

View File

@@ -4,6 +4,7 @@ import com.google.common.collect.*;
import com.google.common.math.IntMath;
import forge.card.CardStateName;
import forge.card.CardType;
import forge.card.CardTypeView;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
@@ -42,6 +43,7 @@ import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class AbilityUtils {
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")) {
CardCollection adventurers = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield),
"Creature.Cleric,Creature.Rogue,Creature.Warrior,Creature.Wizard", player, c, ctb);
Set<String> partyTypes = Sets.newHashSet("Cleric", "Rogue", "Warrior", "Wizard");
int partySize = 0;
HashMap<String, Card> chosenParty = new HashMap<>();
List<Card> wildcard = Lists.newArrayList();
HashMap<Card, Set<String>> multityped = new HashMap<>();
Set<String> chosenParty = Sets.newHashSet();
int wildcard = 0;
ListMultimap<String, Card> multityped = MultimapBuilder.hashKeys().arrayListValues().build();
List<Card> chosenMulti = Lists.newArrayList();
// Figure out how to count each class separately.
for (Card card : adventurers) {
// cards with all creature types will just return full list
Set<String> creatureTypes = card.getType().getCreatureTypes();
creatureTypes.retainAll(partyTypes);
if (creatureTypes.size() == 4) {
wildcard.add(card);
if (wildcard.size() >= 4) {
break;
}
for (Card card : player.getCardsIn(ZoneType.Battlefield)) {
if (!card.isCreature()) {
continue;
} else if (creatureTypes.size() == 1) {
String type = (String)(creatureTypes.toArray()[0]);
}
CardTypeView type = card.getType();
Set<String> creatureTypes;
if (!chosenParty.containsKey(type)) {
chosenParty.put(type, card);
// extra logic for "all creature types" cards
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) {
partyTypes.removeAll(chosenParty.keySet());
// Here I'm left with just the party types that I haven't selected.
for (Card multi : multityped.keySet()) {
Set<String> types = multityped.get(multi);
types.retainAll(partyTypes);
for (String type : types) {
chosenParty.put(type, multi);
partyTypes.remove(type);
break;
}
}
// sort by amount of members
Multimaps.asMap(multityped).entrySet().stream()
.sorted(Map.Entry.<String, List<Card>>comparingByValue(Comparator.<List<Card>>comparingInt(Collection::size)))
.forEach(e -> {
e.getValue().removeAll(chosenMulti);
if (e.getValue().size() > 0) {
chosenParty.add(e.getKey());
chosenMulti.add(e.getValue().get(0));
}
});
}
partySize = Math.min(chosenParty.size() + wildcard.size(), 4);
return doXMath(partySize, expr, c, ctb);
return doXMath(Math.min(chosenParty.size() + wildcard, 4), expr, c, ctb);
}
// 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")) {
final String s = sa.getParam("ChooseEach");
final String[] types = s.equals("Party") ? new String[]{"Cleric","Rogue","Warrior","Wizard"}
: s.split(" & ");
final Collection<String> types = s.equals("Party") ? CardType.Constant.PARTY_TYPES : Arrays.asList(s.split(" & "));
for (final String type : types) {
CardCollection valids = CardLists.filter(pChoices, CardPredicates.isType(type));
if (!valids.isEmpty()) {

View File

@@ -2,7 +2,6 @@ package forge.game.card;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.StaticData;
import forge.card.CardDb;
import forge.card.ColorSet;
@@ -720,17 +719,6 @@ public class CardProperty {
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")) {
if (property.equals("sharesCreatureTypeWith")) {
if (!card.sharesCreatureTypeWith(source)) {