Some cleanup (#6712)

This commit is contained in:
tool4ever
2024-12-22 11:27:29 +01:00
committed by GitHub
parent 96b2b2cbe4
commit 48a33e37c3
19 changed files with 67 additions and 94 deletions

View File

@@ -18,7 +18,6 @@
package forge.ai;
import com.esotericsoftware.minlog.Log;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.AiCardMemory.MemorySet;
@@ -64,6 +63,7 @@ import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.concurrent.CompletableFuture;
@@ -2208,8 +2208,6 @@ public class AiController {
return activePlayerSAs;
}
List<SpellAbility> result = Lists.newArrayList();
// filter list by ApiTypes
List<SpellAbility> discard = filterListByApi(activePlayerSAs, ApiType.Discard);
List<SpellAbility> mandatoryDiscard = filterList(discard, SpellAbilityPredicates.isMandatory());
@@ -2225,37 +2223,33 @@ public class AiController {
List<SpellAbility> pump = filterListByApi(activePlayerSAs, ApiType.Pump);
List<SpellAbility> pumpAll = filterListByApi(activePlayerSAs, ApiType.PumpAll);
List<SpellAbility> result = Lists.newArrayList(activePlayerSAs);
// do mandatory discard early if hand is empty or has DiscardMe card
boolean discardEarly = false;
CardCollectionView playerHand = player.getCardsIn(ZoneType.Hand);
if (playerHand.isEmpty() || playerHand.anyMatch(CardPredicates.hasSVar("DiscardMe"))) {
discardEarly = true;
if (!playerHand.isEmpty() && !playerHand.anyMatch(CardPredicates.hasSVar("DiscardMe"))) {
result.addAll(mandatoryDiscard);
mandatoryDiscard.clear();
}
// token should be added first so they might get the pump bonus
result.addAll(token);
result.addAll(pump);
result.addAll(pumpAll);
// do Evolve Trigger before other PutCounter SpellAbilities
// do putCounter before Draw/Discard because it can cause a Draw Trigger
result.addAll(evolve);
result.addAll(putCounter);
result.addAll(putCounterAll);
// optional Discard, probably combined with Draw
result.addAll(discard);
// do Draw before Discard
result.addAll(draw);
result.addAll(discard); // optional Discard, probably combined with Draw
if (!discardEarly) {
result.addAll(putCounterAll);
// do putCounter before Draw/Discard because it can cause a Draw Trigger
result.addAll(putCounter);
// do Evolve Trigger before other PutCounter SpellAbilities
result.addAll(evolve);
// token should be added first so they might get the pump bonus
result.addAll(pumpAll);
result.addAll(pump);
result.addAll(token);
result.addAll(mandatoryDiscard);
}
result.addAll(activePlayerSAs);
//need to reverse because of magic stack
Collections.reverse(result);
return result;
}
@@ -2267,6 +2261,10 @@ public class AiController {
}
// TODO move to more common place
public static <T extends TriggerReplacementBase> List<T> filterList(List<T> input, Function<SpellAbility, Object> pred, Object value) {
return filterList(input, trb -> pred.apply(trb.ensureAbility()) == value);
}
public static List<SpellAbility> filterListByApi(List<SpellAbility> input, ApiType type) {
return filterList(input, SpellAbilityPredicates.isApi(type));
}
@@ -2303,12 +2301,11 @@ public class AiController {
public ReplacementEffect chooseSingleReplacementEffect(List<ReplacementEffect> list) {
// no need to choose anything
if (list.size() <= 1) {
return Iterables.getFirst(list, null);
return list.get(0);
}
ReplacementType mode = Iterables.getFirst(list, null).getMode();
ReplacementType mode = list.get(0).getMode();
// replace lifegain effects
if (mode.equals(ReplacementType.GainLife)) {
List<ReplacementEffect> noGain = filterListByAiLogic(list, "NoLife");
List<ReplacementEffect> loseLife = filterListByAiLogic(list, "LoseLife");
@@ -2317,16 +2314,16 @@ public class AiController {
if (!noGain.isEmpty()) {
// no lifegain is better than lose life
return Iterables.getFirst(noGain, null);
return noGain.get(0);
} else if (!loseLife.isEmpty()) {
// lose life before double life to prevent lose double
return Iterables.getFirst(loseLife, null);
return loseLife.get(0);
} else if (!lichDraw.isEmpty()) {
// lich draw before double life to prevent to draw to much
return Iterables.getFirst(lichDraw, null);
return lichDraw.get(0);
} else if (!doubleLife.isEmpty()) {
// other than that, do double life
return Iterables.getFirst(doubleLife, null);
return doubleLife.get(0);
}
} else if (mode.equals(ReplacementType.DamageDone)) {
List<ReplacementEffect> prevention = filterList(list, CardTraitPredicates.hasParam("Prevent"));
@@ -2334,7 +2331,7 @@ public class AiController {
// TODO when Protection is done as ReplacementEffect do them
// before normal prevention
if (!prevention.isEmpty()) {
return Iterables.getFirst(prevention, null);
return prevention.get(0);
}
} else if (mode.equals(ReplacementType.Destroy)) {
List<ReplacementEffect> shield = filterList(list, CardTraitPredicates.hasParam("ShieldCounter"));
@@ -2344,30 +2341,35 @@ public class AiController {
// Indestructible umbra armor is the best
if (!umbraArmorIndestructible.isEmpty()) {
return Iterables.getFirst(umbraArmorIndestructible, null);
return umbraArmorIndestructible.get(0);
}
// then it might be better to remove shield counter if able?
if (!shield.isEmpty()) {
return Iterables.getFirst(shield, null);
return shield.get(0);
}
// TODO get the RunParams for Affected to check if the creature already dealt combat damage for Regeneration effects
// is using a Regeneration Effect better than using a Umbra Armor?
if (!regeneration.isEmpty()) {
return Iterables.getFirst(regeneration, null);
return regeneration.get(0);
}
if (!umbraArmor.isEmpty()) {
// sort them by cmc
umbraArmor.sort(Comparator.comparing(CardTraitBase::getHostCard, Comparator.comparing(Card::getCMC)));
return Iterables.getFirst(umbraArmor, null);
return umbraArmor.get(0);
}
} else if (mode.equals(ReplacementType.Draw)) {
List<ReplacementEffect> winGame = filterList(list, SpellAbility::getApi, ApiType.WinsGame);
if (!winGame.isEmpty()) {
return winGame.get(0);
}
}
// TODO always lower counters with Vorinclex first, might turn it from 1 to 0 as final
return Iterables.getFirst(list, null);
return list.get(0);
}
}

View File

@@ -248,10 +248,10 @@ public class CreatureEvaluator implements Function<Card, Integer> {
value -= subValue(10, "echo-unpaid");
}
if (t.isKeyword(Keyword.FADING)) {
value -= subValue(20 / (Math.max(1, c.getCounters(CounterEnumType.FADE))), "fading");
value -= subValue(20 / (Math.max(1, c.isInPlay() ? c.getCounters(CounterEnumType.FADE) : c.getKeywordMagnitude(Keyword.FADING))), "fading");
}
if (t.isKeyword(Keyword.VANISHING)) {
value -= subValue(20 / (Math.max(1, c.getCounters(CounterEnumType.TIME))), "vanishing");
value -= subValue(20 / (Math.max(1, c.isInPlay() ? c.getCounters(CounterEnumType.TIME) : c.getKeywordMagnitude(Keyword.VANISHING))), "vanishing");
}
SpellAbility ab = t.ensureAbility();

View File

@@ -253,7 +253,7 @@ public final class CardRulesPredicates {
}
public static Predicate<CardRules> canBePartnerCommanderWith(final CardRules commander) {
return (rules) -> rules.canBePartnerCommanders(commander);
return rules -> rules.canBePartnerCommanders(commander);
}
private static class LeafString extends PredicateString<CardRules> {

View File

@@ -5,7 +5,6 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
@@ -45,7 +44,7 @@ public class ActivateAbilityEffect extends SpellAbilityEffect {
List<Card> list = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
for (Card c : list) {
List<SpellAbility> possibleAb = Lists.newArrayList(c.getAllPossibleAbilities(p, true));
List<SpellAbility> possibleAb = c.getAllPossibleAbilities(p, true);
if (isManaAb) {
possibleAb.retainAll((FCollection<SpellAbility>)c.getManaAbilities());
}

View File

@@ -880,13 +880,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
* a {@link forge.game.spellability.SpellAbility} object.
*/
private void changeHiddenOriginResolve(final SpellAbility sa) {
List<Player> fetchers;
if (sa.hasParam("DefinedPlayer")) {
fetchers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa);
} else {
fetchers = Lists.newArrayList(sa.getActivatingPlayer());
}
List<Player> fetchers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa);
Player chooser = null;
if (sa.hasParam("Chooser")) {
@@ -1059,7 +1053,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
handleCastWhileSearching(fetchList, decider);
}
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(decider);
runParams.put(AbilityKey.Target, Lists.newArrayList(player));
runParams.put(AbilityKey.Target, player);
game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false);
}
if (searchedLibrary && sa.hasParam("Searched")) {

View File

@@ -22,7 +22,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
return Lang.joinHomogenous(getTargetPlayers(sa)) + " names a card.";
}
@@ -58,21 +57,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
continue;
}
String chosen;
//This section was used for Momir Avatar, which no longer uses it - commented out 7/28/2021
//if (randomChoice) {
//String numericAmount = "X";
//final int validAmount = StringUtils.isNumeric(numericAmount) ? Integer.parseInt(numericAmount) :
// AbilityUtils.calculateAmount(host, numericAmount, sa);
// Momir needs PaperCard
//Collection<PaperCard> cards = StaticData.instance().getCommonCards().getUniqueCards();
//Predicate<PaperCard> cpp = Predicates.and(
// Predicates.compose(CardRulesPredicates.IS_CREATURE, PaperCard.FN_GET_RULES),
// Predicates.compose(CardRulesPredicates.cmc(ComparableOp.EQUALS, validAmount), PaperCard.FN_GET_RULES));
//cards = Lists.newArrayList(Iterables.filter(cards, cpp));
//if (!cards.isEmpty()) { chosen = Aggregates.random(cards).getName();
//} else {
// chosen = "";
//}
if (chooseFromDefined) {
CardCollection choices = AbilityUtils.getDefinedCards(host, sa.getParam("ChooseFromDefinedCards"), sa);
choices = CardLists.getValidCards(choices, valid, host.getController(), host, sa);

View File

@@ -2,12 +2,11 @@ package forge.game.ability.effects;
import java.util.List;
import com.google.common.collect.Lists;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.CardTranslation;
@@ -23,9 +22,9 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
protected String getStackDescription(SpellAbility sa) {
Card object1 = null;
Card object2 = null;
List<Card> tgts = null;
CardCollectionView tgts = null;
if (sa.usesTargeting()) {
tgts = Lists.newArrayList(sa.getTargets().getTargetCards());
tgts = sa.getTargets().getTargetCards();
if (tgts.size() > 0) {
object1 = tgts.get(0);
}
@@ -57,9 +56,9 @@ public class ControlExchangeEffect extends SpellAbilityEffect {
Card object1 = null;
Card object2 = null;
List<Card> tgts = null;
CardCollectionView tgts = null;
if (sa.usesTargeting()) {
tgts = Lists.newArrayList(sa.getTargets().getTargetCards());
tgts = sa.getTargets().getTargetCards();
if (tgts.size() > 0) {
object1 = tgts.get(0);
}

View File

@@ -72,10 +72,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
return;
}
List<Player> controllers = Lists.newArrayList(sa.getActivatingPlayer());
if (sa.hasParam("Controller")) {
controllers = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa);
}
List<Player> controllers = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa);
boolean isOptional = sa.hasParam("Optional");

View File

@@ -65,7 +65,7 @@ public class DrawEffect extends SpellAbilityEffect {
final List<Player> tgts = getTargetPlayersWithDuplicates(true, "Defined", sa);
for (final Player p : Sets.newHashSet(tgts)) {
for (final Player p : Sets.newLinkedHashSet(tgts)) {
if (!p.isInGame()) {
continue;
}

View File

@@ -98,7 +98,7 @@ public class FlipOntoBattlefieldEffect extends SpellAbilityEffect {
// as well as the current card attachments that are visually located next to the requested card or are assumed to be near it.
Player controller = c.getController();
ArrayList<Card> attachments = Lists.newArrayList();
ArrayList<Card> cardsOTB = Lists.newArrayList(CardLists.filter(
CardCollection cardsOTB = CardLists.filter(
controller.getCardsIn(ZoneType.Battlefield), card -> {
if (card.isAttachedToEntity(c)) {
attachments.add(card);
@@ -114,7 +114,7 @@ public class FlipOntoBattlefieldEffect extends SpellAbilityEffect {
}
return card.sharesCardTypeWith(c);
}
));
);
// Chance to hit an attachment
float hitAttachment = 0.50f;

View File

@@ -26,7 +26,7 @@ public class LifeGainEffect extends SpellAbilityEffect {
sb.append(Lang.joinHomogenous(getDefinedPlayersOrTargeted(sa)));
if (sb.length() == 0 && spellDesc != null) {
return (spellDesc);
return spellDesc;
} else {
sb.append(getDefinedPlayersOrTargeted(sa).size() > 1 ? " gain " : " gains ");
if (!StringUtils.isNumeric(amountStr) && spellDesc != null && spellDesc.contains("life equal to")) {

View File

@@ -29,7 +29,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
List<ZoneType> restartZones = new ArrayList<>(Arrays.asList(ZoneType.Battlefield,
ZoneType.Library, ZoneType.Graveyard, ZoneType.Hand, ZoneType.Exile));
ZoneType leaveZone = ZoneType.smartValueOf(sa.hasParam("RestrictFromZone") ? sa.getParam("RestrictFromZone") : null);
ZoneType leaveZone = ZoneType.smartValueOf(sa.getParam("RestrictFromZone"));
restartZones.remove(leaveZone);
String leaveRestriction = sa.getParamOrDefault("RestrictFromValid", "Card");

View File

@@ -252,7 +252,7 @@ public class AbilityManaPart implements java.io.Serializable {
eff.setColor(MagicColor.COLORLESS);
eff.setGamePieceType(GamePieceType.EFFECT);
String cantcounterstr = "Event$ Counter | ValidCard$ Card.IsRemembered | Description$ That spell can't be countered.";
String cantcounterstr = "Event$ Counter | ValidSA$ Spell.IsRemembered | Description$ That spell can't be countered.";
ReplacementEffect re = ReplacementHandler.parseReplacement(cantcounterstr, eff, true);
re.setLayer(ReplacementLayer.CantHappen);
eff.addReplacementEffect(re);

View File

@@ -453,11 +453,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
public void setActivatingPlayer(final Player player) {
// trickle down activating player
boolean updated = false;
// don't use equals because player might be from simulation
if (player == null || player != activatingPlayer) {
activatingPlayer = player;
updated = true;
}
if (subAbility != null) {
subAbility.setActivatingPlayer(player);

View File

@@ -17,7 +17,6 @@
*/
package forge.game.trigger;
import java.util.List;
import java.util.Map;
import forge.game.ability.AbilityKey;
@@ -60,9 +59,8 @@ public class TriggerSearchedLibrary extends Trigger {
return false;
}
if (hasParam("SearchOwnLibrary")) {
@SuppressWarnings("unchecked")
List<Player> targets = (List<Player>) runParams.get(AbilityKey.Target);
if (!targets.contains(runParams.get(AbilityKey.Player))) {
Player target = (Player) runParams.get(AbilityKey.Target);
if (!target.equals(runParams.get(AbilityKey.Player))) {
return false;
}
}

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Human Knight
PT:4/4
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME enters or attacks, create a Virtuous Role token attached to another target creature you control. (If you control another Role on it, put that one into the graveyard. Enchanted creature gets +1/+1 for each enchantment you control.)
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters or attacks, create a Virtuous Role token attached to another target creature you control. (If you control another Role on it, put that one into the graveyard. Enchanted creature gets +1/+1 for each enchantment you control.)
SVar:TrigToken:DB$ Token | TokenScript$ role_virtuous | AttachedTo$ Targeted | ValidTgts$ Creature.YouCtrl+Other
SVar:TrigToken:DB$ Token | TokenScript$ role_virtuous | AttachedTo$ Targeted | ValidTgts$ Creature.YouCtrl+Other | TokenOwner$ You
T:Mode$ DamageDone | ValidSource$ Creature.YouCtrl+enchanted | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever an enchanted creature you control deals combat damage to a player, draw a card.
SVar:TrigDraw:DB$ Draw
SVar:HasAttackEffect:TRUE

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Goblin Rogue
PT:2/2
K:etbCounter:P1P1:X
SVar:X:Count$xPaid
A:AB$ ChangeZone | Cost$ 2 | Defined$ BottomOfLibrary | Origin$ Library | Destination$ Graveyard | RememberChanged$ True | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Put the bottom card of your library into your graveyard. If it's a creature card with power less than or equal to CARDNAME's power, put it onto the battlefield.
A:AB$ ChangeZone | Cost$ 2 | Defined$ BottomOfLibrary | Origin$ Library | Destination$ Graveyard | RememberChanged$ True | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Put the bottom card of your library into your graveyard. If it's a creature card with power less than or equal to NICKNAME's power, put it onto the battlefield.
SVar:DBChangeZone:DB$ ChangeZone | Defined$ Remembered | Origin$ All | Destination$ Battlefield | Hidden$ True | ConditionDefined$ Remembered | ConditionPresent$ Card.Creature+powerLEY | ConditionCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Y:Count$CardPower

View File

@@ -2,5 +2,7 @@ Name:Veiled Apparition
ManaCost:1 U
Types:Enchantment
T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Opponent | TriggerZones$ Battlefield | IsPresent$ Card.Self+Enchantment | Execute$ TrigAnimate | TriggerDescription$ When an opponent casts a spell, if CARDNAME is an enchantment, CARDNAME becomes a 3/3 Illusion creature with flying and "At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1}{U}."
SVar:TrigAnimate:DB$ Animate | Defined$ Self | Power$ 3 | Toughness$ 3 | Keywords$ Flying & UpkeepCost:1 U | Types$ Creature,Illusion | RemoveCardTypes$ True | RemoveCreatureTypes$ True | Duration$ Permanent
SVar:TrigAnimate:DB$ Animate | Defined$ Self | Power$ 3 | Toughness$ 3 | Keywords$ Flying | Triggers$ UpkeepCostTrigger | Types$ Creature,Illusion | RemoveCardTypes$ True | RemoveCreatureTypes$ True | Duration$ Permanent
SVar:UpkeepCostTrigger:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1}{U}.
SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 1 U
Oracle:When an opponent casts a spell, if Veiled Apparition is an enchantment, Veiled Apparition becomes a 3/3 Illusion creature with flying and "At the beginning of your upkeep, sacrifice Veiled Apparition unless you pay {1}{U}."