From 6f42fcdc2603a5973292e7c646fe6d2788b05fb5 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Tue, 24 May 2022 22:36:57 +0200 Subject: [PATCH 1/9] Make charm choices for triggers more like other SA --- forge-ai/src/main/java/forge/ai/ComputerUtil.java | 14 ++++++-------- .../src/main/java/forge/ai/PlayerControllerAi.java | 5 +++++ .../src/main/java/forge/ai/ability/CharmAi.java | 9 --------- .../src/main/java/forge/game/zone/MagicStack.java | 11 ----------- .../src/main/java/forge/player/HumanPlay.java | 14 ++++++-------- 5 files changed, 17 insertions(+), 36 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 83520cd8c55..5aeb2b21e7c 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -131,10 +131,9 @@ public class ComputerUtil { sa = GameActionUtil.addExtraKeywordCost(sa); - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - if (!CharmEffect.makeChoices(sa)) { - return false; - } + if (sa.getApi() == ApiType.Charm && !sa.isCopied() && !CharmEffect.makeChoices(sa)) { + // 603.3c If no mode is chosen, the ability is removed from the stack. + return false; } if (chooseTargets != null) { chooseTargets.run(); @@ -295,10 +294,9 @@ public class ComputerUtil { if (newSA.isSpell() && !source.isCopiedSpell()) { newSA.setHostCard(game.getAction().moveToStack(source, newSA)); - if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) { - if (!CharmEffect.makeChoices(newSA)) { - return false; - } + if (newSA.getApi() == ApiType.Charm && !sa.isCopied() && !CharmEffect.makeChoices(newSA)) { + // 603.3c If no mode is chosen, the ability is removed from the stack. + return false; } } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 990ab51e3df..4d944408df0 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -34,6 +34,7 @@ import forge.game.GameObject; import forge.game.GameType; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; +import forge.game.ability.effects.CharmEffect; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; @@ -1064,6 +1065,10 @@ public class PlayerControllerAi extends PlayerController { } private boolean prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory) { + if (sa.getApi() == ApiType.Charm && !sa.isCopied() && !CharmEffect.makeChoices(sa)) { + // 603.3c If no mode is chosen, the ability is removed from the stack. + return false; + } if (sa.hasParam("TargetingPlayer")) { Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0); sa.setTargetingPlayer(targetingPlayer); diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java index a78406df498..15dc3b6ad26 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java @@ -255,15 +255,6 @@ public class CharmAi extends SpellAbilityAi { return Aggregates.random(opponents); } - @Override - protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) { - // already done by chooseOrderOfSimultaneousStackEntry - if (sa.getChosenList() != null) { - return true; - } - return super.doTriggerAINoCost(aiPlayer, sa, mandatory); - } - @Override public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) { // choices were already targeted diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 48efbd76acd..f81487fd80c 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -41,7 +41,6 @@ import forge.game.GameObject; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; -import forge.game.ability.effects.CharmEffect; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardUtil; @@ -826,19 +825,10 @@ public class MagicStack /* extends MyObservable */ implements Iterable activePlayerSAs = Lists.newArrayList(); - final List failedSAs = Lists.newArrayList(); for (int i = 0; i < simultaneousStackEntryList.size(); i++) { SpellAbility sa = simultaneousStackEntryList.get(i); Player activator = sa.getActivatingPlayer(); - if (sa.getApi() == ApiType.Charm) { - if (!CharmEffect.makeChoices(sa)) { - // 603.3c If no mode is chosen, the ability is removed from the stack. - failedSAs.add(sa); - continue; - } - } - if (activator == null) { if (sa.getHostCard().getController().equals(activePlayer)) { activePlayerSAs.add(sa); @@ -850,7 +840,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable Date: Fri, 8 Jul 2022 17:45:33 +0200 Subject: [PATCH 2/9] Clean up --- forge-ai/src/main/java/forge/ai/ComputerUtil.java | 4 ++-- .../src/main/java/forge/ai/PlayerControllerAi.java | 5 ++--- .../java/forge/game/ability/effects/CharmEffect.java | 5 +++++ .../src/main/java/forge/game/card/CardProperty.java | 2 +- .../java/forge/game/spellability/AbilityManaPart.java | 4 ++-- .../src/main/java/forge/game/trigger/TriggerScry.java | 3 +-- .../src/main/java/forge/game/zone/MagicStack.java | 11 ++++------- forge-gui/res/cardsfolder/m/maze_of_ith.txt | 1 + forge-gui/src/main/java/forge/player/HumanPlay.java | 11 ++++++----- 9 files changed, 24 insertions(+), 22 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 5aeb2b21e7c..675bf34f629 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -131,7 +131,7 @@ public class ComputerUtil { sa = GameActionUtil.addExtraKeywordCost(sa); - if (sa.getApi() == ApiType.Charm && !sa.isCopied() && !CharmEffect.makeChoices(sa)) { + if (sa.getApi() == ApiType.Charm && !CharmEffect.makeChoices(sa)) { // 603.3c If no mode is chosen, the ability is removed from the stack. return false; } @@ -294,7 +294,7 @@ public class ComputerUtil { if (newSA.isSpell() && !source.isCopiedSpell()) { newSA.setHostCard(game.getAction().moveToStack(source, newSA)); - if (newSA.getApi() == ApiType.Charm && !sa.isCopied() && !CharmEffect.makeChoices(newSA)) { + if (newSA.getApi() == ApiType.Charm && !CharmEffect.makeChoices(newSA)) { // 603.3c If no mode is chosen, the ability is removed from the stack. return false; } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 826710bf432..05d1f0a0a87 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -1065,9 +1065,8 @@ public class PlayerControllerAi extends PlayerController { } private boolean prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory) { - if (sa.getApi() == ApiType.Charm && !sa.isCopied() && !CharmEffect.makeChoices(sa)) { - // 603.3c If no mode is chosen, the ability is removed from the stack. - return false; + if (sa.getApi() == ApiType.Charm) { + return CharmEffect.makeChoices(sa); } if (sa.hasParam("TargetingPlayer")) { Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java index 6e484d4a1a7..1f5902bc90a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java @@ -156,6 +156,11 @@ public class CharmEffect extends SpellAbilityEffect { } public static boolean makeChoices(SpellAbility sa) { + // CR 700.2g + if (sa.isCopied()) { + return true; + } + //this resets all previous choices sa.setSubAbility(null); diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index cc63af4777c..0777d3772f5 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -607,7 +607,7 @@ public class CardProperty { return false; } } else if (property.startsWith("Cloned")) { - if ((card.getCloneOrigin() == null) || !card.getCloneOrigin().equals(source)) { + if (card.getCloneOrigin() == null || !card.getCloneOrigin().equals(source)) { return false; } } else if (property.startsWith("SharesCMCWith")) { diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index c1f5dc6e9d7..abb22e116ea 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -179,7 +179,7 @@ public class AbilityManaPart implements java.io.Serializable { if (source.isLand() && root.isManaAbility() && root.getPayCosts() != null && root.getPayCosts().hasTapCost()) { player.setTappedLandForManaThisTurn(true); } - } // end produceMana(String) + } /** *

@@ -348,7 +348,7 @@ public class AbilityManaPart implements java.io.Serializable { // "can't" zone restriction – shouldn't be mixed with other restrictions if (restriction.startsWith("CantCastSpellFrom")) { - if (!sa.isSpell()) { // + if (!sa.isSpell()) { return true; } final ZoneType badZone = ZoneType.smartValueOf(restriction.substring(17)); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerScry.java b/forge-game/src/main/java/forge/game/trigger/TriggerScry.java index 5512ddbc89a..05a90284011 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerScry.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerScry.java @@ -64,8 +64,7 @@ public class TriggerScry extends Trigger { /** {@inheritDoc} */ @Override public final void setTriggeringObjects(final SpellAbility sa, Map runParams) { - sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player); - sa.setTriggeringObjectsFrom(runParams, AbilityKey.ScryNum); + sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player, AbilityKey.ScryNum); } @Override diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index f81487fd80c..4fc81955212 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -830,13 +830,10 @@ public class MagicStack /* extends MyObservable */ implements Iterable Date: Sat, 9 Jul 2022 17:29:41 +0200 Subject: [PATCH 3/9] No SpellAbilityRestriction in Subs (performance + fix AI mayplay Charms) --- forge-ai/src/main/java/forge/ai/ability/CharmAi.java | 6 ++---- .../src/main/java/forge/game/ability/AbilityFactory.java | 6 ++++-- .../src/main/java/forge/game/spellability/SpellAbility.java | 5 ++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java index 15dc3b6ad26..ebb6a5b94a6 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java @@ -90,6 +90,8 @@ public class CharmAi extends SpellAbilityAi { for (AbilitySub sub : choices) { sub.setActivatingPlayer(ai); sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); + + // TODO checks from MayPlay fail for subs currently if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { chosenList.add(sub); if (chosenList.size() == num) { @@ -101,8 +103,6 @@ public class CharmAi extends SpellAbilityAi { // Second pass using doTrigger(false) to fulfill minimum choice choices.removeAll(chosenList); for (AbilitySub sub : choices) { - sub.setActivatingPlayer(ai); - sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); if (aic.doTrigger(sub, false)) { chosenList.add(sub); if (chosenList.size() == min) { @@ -114,8 +114,6 @@ public class CharmAi extends SpellAbilityAi { if (chosenList.size() < min) { choices.removeAll(chosenList); for (AbilitySub sub : choices) { - sub.setActivatingPlayer(ai); - sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); if (aic.doTrigger(sub, true)) { chosenList.add(sub); if (chosenList.size() == min) { diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index a34a1019d38..9396f9ff2df 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -426,7 +426,9 @@ public final class AbilityFactory { private static final void makeRestrictions(final SpellAbility sa) { // SpellAbilityRestrictions should be added in here final SpellAbilityRestriction restrict = sa.getRestrictions(); - restrict.setRestrictions(sa.getMapParams()); + if (restrict != null) { + restrict.setRestrictions(sa.getMapParams()); + } } /** @@ -438,7 +440,7 @@ public final class AbilityFactory { * a {@link forge.game.spellability.SpellAbility} object. */ private static final void makeConditions(final SpellAbility sa) { - // SpellAbilityRestrictions should be added in here + // SpellAbilityConditions should be added in here final SpellAbilityCondition condition = sa.getConditions(); condition.setConditions(sa.getMapParams()); } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index ab9d7fc1426..1cdb04d8176 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -138,7 +138,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit /** The pay costs. */ private Cost payCosts; - private SpellAbilityRestriction restrictions = new SpellAbilityRestriction(); + private SpellAbilityRestriction restrictions; private SpellAbilityCondition conditions = new SpellAbilityCondition(); private AbilitySub subAbility; @@ -214,6 +214,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit view0 = new SpellAbilityView(this); } view = view0; + if (!(this instanceof AbilitySub)) { + restrictions = new SpellAbilityRestriction(); + } } @Override From 207a29079e8b57f7b6119e7f20bfebe0bb98123e Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sat, 9 Jul 2022 17:30:06 +0200 Subject: [PATCH 4/9] Fabricate fix --- forge-ai/src/main/java/forge/ai/ability/CharmAi.java | 4 ---- .../src/main/java/forge/game/ability/AbilityFactory.java | 2 +- .../forge/game/ability/effects/ChooseGenericEffect.java | 2 +- .../src/main/java/forge/game/card/CardFactoryUtil.java | 8 ++++++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java index ebb6a5b94a6..957dcb8f03d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java @@ -89,9 +89,6 @@ public class CharmAi extends SpellAbilityAi { // First pass using standard canPlayAi() for good choices for (AbilitySub sub : choices) { sub.setActivatingPlayer(ai); - sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); - - // TODO checks from MayPlay fail for subs currently if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { chosenList.add(sub); if (chosenList.size() == num) { @@ -229,7 +226,6 @@ public class CharmAi extends SpellAbilityAi { } else { // Standard canPlayAi() sub.setActivatingPlayer(ai); - sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { chosenList.add(sub); if (chosenList.size() == min) { diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index 9396f9ff2df..e8b400abf19 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -277,7 +277,7 @@ public final class AbilityFactory { } } - if (api == ApiType.Charm || api == ApiType.GenericChoice || api == ApiType.AssignGroup) { + if (api == ApiType.Charm || api == ApiType.GenericChoice || api == ApiType.AssignGroup) { final String key = "Choices"; if (mapParams.containsKey(key)) { List names = Lists.newArrayList(mapParams.get(key).split(",")); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java index 5f95d0c4dd8..c1724471c82 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java @@ -50,7 +50,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect { List saToRemove = Lists.newArrayList(); for (SpellAbility saChoice : abilities) { - if (!saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer()) ) { + if (saChoice.getRestrictions() != null && !saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer())) { saToRemove.add(saChoice); } else if (saChoice.hasParam("UnlessCost")) { // generic check for if the cost can be paid diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 3f454c027cf..636794f1037 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1149,7 +1149,7 @@ public class CardFactoryUtil { final String choose = "DB$ GenericChoice | AILogic$ " + name; final String counter = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ " + n + - " | IsPresent$ Card.StrictlySelf | SpellDescription$ Put " + " | SpellDescription$ Put " + Lang.nounWithNumeral(n, "+1/+1 counter") + " on it."; final String token = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo | TokenOwner$ You " + " | SpellDescription$ Create " @@ -1160,7 +1160,11 @@ public class CardFactoryUtil { SpellAbility saChoose = AbilityFactory.getAbility(choose, card); List list = Lists.newArrayList(); - list.add((AbilitySub)AbilityFactory.getAbility(counter, card)); + AbilitySub putCounter = (AbilitySub)AbilityFactory.getAbility(counter, card); + SpellAbilityRestriction restriction = new SpellAbilityRestriction(); + restriction.setIsPresent("Card.StrictlySelf"); + putCounter.setRestrictions(restriction); + list.add(putCounter); list.add((AbilitySub)AbilityFactory.getAbility(token, card)); saChoose.setAdditionalAbilityList("Choices", list); saChoose.setIntrinsic(intrinsic); From 2e78de34b4e04309fa57314a89a406972453c372 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 10 Jul 2022 00:11:26 +0200 Subject: [PATCH 5/9] check for canReceiveCounters --- .../java/forge/game/card/CardFactoryUtil.java | 2 +- .../java/forge/game/card/CardProperty.java | 20 +++++++++++-------- .../spellability/SpellAbilityRestriction.java | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 636794f1037..31ae3d89ee8 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1162,7 +1162,7 @@ public class CardFactoryUtil { List list = Lists.newArrayList(); AbilitySub putCounter = (AbilitySub)AbilityFactory.getAbility(counter, card); SpellAbilityRestriction restriction = new SpellAbilityRestriction(); - restriction.setIsPresent("Card.StrictlySelf"); + restriction.setIsPresent("Card.StrictlySelf+canReceiveCounters P1P1"); putCounter.setRestrictions(restriction); list.add(putCounter); list.add((AbilitySub)AbilityFactory.getAbility(token, card)); diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 0777d3772f5..3c831061e65 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1475,6 +1475,18 @@ public class CardProperty { if (!card.getManaCost().getShortString().equals(property.substring(8))) { return false; } + } else if (property.startsWith("canReceiveCounters")) { + if (!card.canReceiveCounters(CounterType.getType(property.split(" ")[1]))) { + return false; + } + } else if (property.equals("HasCounters")) { + if (!card.hasCounters()) { + return false; + } + } else if (property.equals("NoCounters")) { + if (card.hasCounters()) { + return false; + } } // syntax example: countersGE9 P1P1 or countersLT12TIME (greater number @@ -1807,14 +1819,6 @@ public class CardProperty { if (!card.hasNoAbilities()) { return false; } - } else if (property.equals("HasCounters")) { - if (!card.hasCounters()) { - return false; - } - } else if (property.equals("NoCounters")) { - if (card.hasCounters()) { - return false; - } } else if (property.equals("castKeyword")) { SpellAbility castSA = card.getCastSA(); if (castSA == null) { diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java index 8f72ba69673..583830f2dc0 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -443,7 +443,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { if (getPresentDefined() != null) { list = AbilityUtils.getDefinedObjects(sa.getHostCard(), getPresentDefined(), sa); } else { - list = new FCollection(game.getCardsIn(getPresentZone())); + list = new FCollection<>(game.getCardsIn(getPresentZone())); } final int left = Iterables.size(Iterables.filter(list, GameObjectPredicates.restriction(getIsPresent().split(","), activator, c, sa))); From d247b22e0af57e331055bc504e1fb89d5fac4716 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 10 Jul 2022 15:07:39 +0200 Subject: [PATCH 6/9] Clean up Fabricate --- .../java/forge/game/card/CardFactoryUtil.java | 26 +++++++------------ .../java/forge/game/card/CardProperty.java | 4 --- .../java/forge/player/HumanCostDecision.java | 3 +++ 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 31ae3d89ee8..e9e779c77db 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1147,33 +1147,25 @@ public class CardFactoryUtil { + " | ValidCard$ Card.Self | Secondary$ True" + " | TriggerDescription$ Fabricate " + n + " (" + inst.getReminderText() + ")"; - final String choose = "DB$ GenericChoice | AILogic$ " + name; - final String counter = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ " + n + - " | SpellDescription$ Put " - + Lang.nounWithNumeral(n, "+1/+1 counter") + " on it."; - final String token = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo | TokenOwner$ You " - + " | SpellDescription$ Create " + final String token = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo | ConditionPresent$ Card.StrictlySelf | ConditionCompare$ EQ1" + + " | UnlessCost$ AddCounter<" + n + "/P1P1> | UnlessPayer$ You | UnlessResolveSubs$ WhenNotPaid | UnlessAI$ " + name + + " | SpellDescription$ Fabricate - Create " + Lang.nounWithNumeral(n, "1/1 colorless Servo artifact creature token") + "."; + final String token2 = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo | ConditionPresent$ Card.StrictlySelf | ConditionCompare$ EQ0"; final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); - SpellAbility saChoose = AbilityFactory.getAbility(choose, card); - - List list = Lists.newArrayList(); - AbilitySub putCounter = (AbilitySub)AbilityFactory.getAbility(counter, card); - SpellAbilityRestriction restriction = new SpellAbilityRestriction(); - restriction.setIsPresent("Card.StrictlySelf+canReceiveCounters P1P1"); - putCounter.setRestrictions(restriction); - list.add(putCounter); - list.add((AbilitySub)AbilityFactory.getAbility(token, card)); - saChoose.setAdditionalAbilityList("Choices", list); + SpellAbility saChoose = AbilityFactory.getAbility(token, card); saChoose.setIntrinsic(intrinsic); + AbilitySub saNoChoose = (AbilitySub) AbilityFactory.getAbility(token2, card); + saChoose.setSubAbility(saNoChoose); + trigger.setOverridingAbility(saChoose); inst.addTrigger(trigger); } else if (keyword.startsWith("Fading")) { - String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Secondary$ True " + + String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Secondary$ True" + " | TriggerDescription$ At the beginning of your upkeep, remove a fade counter from CARDNAME. If you can't, sacrifice CARDNAME."; final String removeCounterStr = "DB$ RemoveCounter | Defined$ Self | CounterType$ FADE | CounterNum$ 1 | RememberRemoved$ True"; diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 3c831061e65..0143e6a5657 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1475,10 +1475,6 @@ public class CardProperty { if (!card.getManaCost().getShortString().equals(property.substring(8))) { return false; } - } else if (property.startsWith("canReceiveCounters")) { - if (!card.canReceiveCounters(CounterType.getType(property.split(" ")[1]))) { - return false; - } } else if (property.equals("HasCounters")) { if (!card.hasCounters()) { return false; diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index cb06d43414d..e3969bf35b4 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -667,6 +667,9 @@ public class HumanCostDecision extends CostDecisionMakerBase { final int c = cost.getAbilityAmount(ability); if (cost.payCostFromSource()) { + if (ability.getDescription().contains("Fabricate") && !controller.confirmPayment(cost, Localizer.getInstance().getMessage("lblPutNTypeCounterOnTarget", String.valueOf(c), cost.getCounter().getName(), ability.getHostCard().getName()), ability)) { + return null; + } cost.setLastPaidAmount(c); return PaymentDecision.number(c); } From 8f4c5039ed95932e0565670d4f16d93ca618deea Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 10 Jul 2022 15:07:47 +0200 Subject: [PATCH 7/9] Cleanup AI --- .../main/java/forge/ai/ComputerUtilCost.java | 68 +++++++++++++++ .../ai/ability/ChooseGenericEffectAi.java | 83 +------------------ 2 files changed, 69 insertions(+), 82 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 46f379218ce..2f39a64fbf5 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -7,6 +7,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import forge.ai.AiCardMemory.MemorySet; import forge.ai.ability.AnimateAi; +import forge.ai.ability.TokenAi; import forge.card.ColorSet; import forge.game.Game; import forge.game.ability.AbilityUtils; @@ -14,6 +15,7 @@ import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.card.CardPredicates.Presets; import forge.game.combat.Combat; +import forge.game.combat.CombatUtil; import forge.game.cost.*; import forge.game.keyword.Keyword; import forge.game.phase.PhaseType; @@ -692,6 +694,72 @@ public class ComputerUtilCost { return false; } else if ("LowPriority".equals(aiLogic) && MyRandom.getRandom().nextInt(100) < 67) { return false; + } else if (aiLogic != null && aiLogic.startsWith("Fabricate")) { + final int n = Integer.valueOf(aiLogic.substring("Fabricate".length())); + + // if host would leave the play or if host is useless, create tokens + if (source.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(payer, source)) { + return false; + } + + // need a copy for one with extra +1/+1 counter boost, + // without causing triggers to run + final Card copy = CardUtil.getLKICopy(source); + copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n); + copy.setZone(source.getZone()); + + // if host would put into the battlefield attacking + Combat combat = source.getGame().getCombat(); + if (combat != null && combat.isAttacking(source)) { + final Player defender = combat.getDefenderPlayerByAttacker(source); + if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true)) { + return true; + } + return false; + } + + // if the host has haste and can attack + if (CombatUtil.canAttack(copy)) { + for (final Player opp : payer.getOpponents()) { + if (CombatUtil.canAttack(copy, opp) && + opp.canLoseLife() && + !ComputerUtilCard.canBeBlockedProfitably(opp, copy, true)) + return true; + } + } + + // TODO check for trigger to turn token ETB into +1/+1 counter for host + // TODO check for trigger to turn token ETB into damage or life loss for opponent + // in this cases Token might be prefered even if they would not survive + final Card tokenCard = TokenAi.spawnToken(payer, sa); + + // Token would not survive + if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) { + return true; + } + + // Special Card logic, this one try to median its power with the number of artifacts + if ("Marionette Master".equals(source.getName())) { + CardCollection list = CardLists.filter(payer.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS); + return list.size() >= copy.getNetPower(); + } else if ("Cultivator of Blades".equals(source.getName())) { + // Cultivator does try to median with number of Creatures + CardCollection list = payer.getCreaturesInPlay(); + return list.size() >= copy.getNetPower(); + } + + // evaluate Creature with +1/+1 + int evalCounter = ComputerUtilCard.evaluateCreature(copy); + + final CardCollection tokenList = new CardCollection(source); + for (int i = 0; i < n; ++i) { + tokenList.add(TokenAi.spawnToken(payer, sa)); + } + + // evaluate Host with Tokens + int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList); + + return evalToken < evalCounter; } // Check for shocklands and similar ETB replacement effects diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java index e81059371c7..596ee8f6671 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java @@ -9,7 +9,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import forge.ai.ComputerUtilAbility; -import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCost; import forge.ai.SpecialCardAi; import forge.ai.SpellAbilityAi; @@ -19,12 +18,9 @@ import forge.game.Game; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates.Presets; import forge.game.card.CardUtil; import forge.game.card.CounterEnumType; import forge.game.combat.Combat; -import forge.game.combat.CombatUtil; import forge.game.cost.Cost; import forge.game.keyword.Keyword; import forge.game.phase.PhaseHandler; @@ -43,7 +39,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi { protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) { return true; - } else if (aiLogic.startsWith("Fabricate") || "Riot".equals(aiLogic)) { + } else if ("Riot".equals(aiLogic)) { return true; } else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) { for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) { @@ -265,83 +261,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi { } // if unsure, random? return Aggregates.random(spells); - } else if (logic.startsWith("Fabricate")) { - final int n = Integer.valueOf(logic.substring("Fabricate".length())); - if(spells.size() < 2) { - // If the creature is no longer on the battlefield, the option - // to add counters is already removed at this point. Return the - // only available option: create servo tokens. - return spells.get(0); - } - SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1); - - // check for something which might prevent the counters to be placed on host - if (!host.canReceiveCounters(CounterEnumType.P1P1)) { - return tokenSA; - } - - // if host would leave the play or if host is useless, create tokens - if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) { - return tokenSA; - } - - // need a copy for one with extra +1/+1 counter boost, - // without causing triggers to run - final Card copy = CardUtil.getLKICopy(host); - copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n); - copy.setZone(host.getZone()); - - // if host would put into the battlefield attacking - if (combat != null && combat.isAttacking(host)) { - final Player defender = combat.getDefenderPlayerByAttacker(host); - if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true)) { - return counterSA; - } - return tokenSA; - } - - // if the host has haste and can attack - if (CombatUtil.canAttack(copy)) { - for (final Player opp : player.getOpponents()) { - if (CombatUtil.canAttack(copy, opp) && - opp.canLoseLife() && - !ComputerUtilCard.canBeBlockedProfitably(opp, copy, true)) - return counterSA; - } - } - - // TODO check for trigger to turn token ETB into +1/+1 counter for host - // TODO check for trigger to turn token ETB into damage or life loss for opponent - // in this cases Token might be prefered even if they would not survive - final Card tokenCard = TokenAi.spawnToken(player, tokenSA); - - // Token would not survive - if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) { - return counterSA; - } - - // Special Card logic, this one try to median its power with the number of artifacts - if ("Marionette Master".equals(sourceName)) { - CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS); - return list.size() >= copy.getNetPower() ? counterSA : tokenSA; - } else if ("Cultivator of Blades".equals(sourceName)) { - // Cultivator does try to median with number of Creatures - CardCollection list = player.getCreaturesInPlay(); - return list.size() >= copy.getNetPower() ? counterSA : tokenSA; - } - - // evaluate Creature with +1/+1 - int evalCounter = ComputerUtilCard.evaluateCreature(copy); - - final CardCollection tokenList = new CardCollection(host); - for (int i = 0; i < n; ++i) { - tokenList.add(TokenAi.spawnToken(player, tokenSA)); - } - - // evaluate Host with Tokens - int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList); - - return evalToken >= evalCounter ? tokenSA : counterSA; } else if ("CombustibleGearhulk".equals(logic)) { Player controller = sa.getActivatingPlayer(); List zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile"); From aff63c531553e6b3e94ecb446b385a21db516a4f Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 10 Jul 2022 15:53:23 +0200 Subject: [PATCH 8/9] Use canPay check --- .../java/forge/game/card/CardFactoryUtil.java | 8 ++------ .../java/forge/game/cost/CostPutCounter.java | 18 +++++++++--------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index e9e779c77db..02207cf1abf 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1147,20 +1147,16 @@ public class CardFactoryUtil { + " | ValidCard$ Card.Self | Secondary$ True" + " | TriggerDescription$ Fabricate " + n + " (" + inst.getReminderText() + ")"; - final String token = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo | ConditionPresent$ Card.StrictlySelf | ConditionCompare$ EQ1" - + " | UnlessCost$ AddCounter<" + n + "/P1P1> | UnlessPayer$ You | UnlessResolveSubs$ WhenNotPaid | UnlessAI$ " + name + final String token = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo" + + " | UnlessCost$ AddCounter<" + n + "/P1P1> | UnlessPayer$ You | UnlessAI$ " + name + " | SpellDescription$ Fabricate - Create " + Lang.nounWithNumeral(n, "1/1 colorless Servo artifact creature token") + "."; - final String token2 = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo | ConditionPresent$ Card.StrictlySelf | ConditionCompare$ EQ0"; final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); SpellAbility saChoose = AbilityFactory.getAbility(token, card); saChoose.setIntrinsic(intrinsic); - AbilitySub saNoChoose = (AbilitySub) AbilityFactory.getAbility(token2, card); - saChoose.setSubAbility(saNoChoose); - trigger.setOverridingAbility(saChoose); inst.addTrigger(trigger); diff --git a/forge-game/src/main/java/forge/game/cost/CostPutCounter.java b/forge-game/src/main/java/forge/game/cost/CostPutCounter.java index 8b8ed940eb5..ca4d4fd2b45 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPutCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostPutCounter.java @@ -141,16 +141,16 @@ public class CostPutCounter extends CostPartWithList { public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) { final Card source = ability.getHostCard(); if (this.payCostFromSource()) { - return source.canReceiveCounters(this.counter); - } else { - // 3 Cards have Put a -1/-1 Counter on a Creature you control. - List typeList = CardLists.getValidCards(source.getGame().getCardsIn(ZoneType.Battlefield), - this.getType().split(";"), payer, source, ability); - - typeList = CardLists.filter(typeList, CardPredicates.canReceiveCounters(this.counter)); - - return !typeList.isEmpty(); + return source.isInPlay() && source.canReceiveCounters(this.counter); } + + // 3 Cards have Put a -1/-1 Counter on a Creature you control. + List typeList = CardLists.getValidCards(source.getGame().getCardsIn(ZoneType.Battlefield), + this.getType().split(";"), payer, source, ability); + + typeList = CardLists.filter(typeList, CardPredicates.canReceiveCounters(this.counter)); + + return !typeList.isEmpty(); } /* From 2a7ccd69d6cf34a0a01e7b8a73dff1042b519ede Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 10 Jul 2022 16:03:27 +0200 Subject: [PATCH 9/9] Just check for UnlessCost --- forge-gui/src/main/java/forge/player/HumanCostDecision.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index e3969bf35b4..1647515cac3 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -667,7 +667,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { final int c = cost.getAbilityAmount(ability); if (cost.payCostFromSource()) { - if (ability.getDescription().contains("Fabricate") && !controller.confirmPayment(cost, Localizer.getInstance().getMessage("lblPutNTypeCounterOnTarget", String.valueOf(c), cost.getCounter().getName(), ability.getHostCard().getName()), ability)) { + // UnlessCost so player might not want to pay (Fabricate) + if (ability.hasParam("UnlessCost") && !controller.confirmPayment(cost, Localizer.getInstance().getMessage("lblPutNTypeCounterOnTarget", String.valueOf(c), cost.getCounter().getName(), ability.getHostCard().getName()), ability)) { return null; } cost.setLastPaidAmount(c);