* Copy spell for each other permanents that it could target - fixed that AI can freeze the game, fixed wrong highlighting;

This commit is contained in:
Oleg Agafonov 2020-12-17 06:37:17 +04:00
parent a90fc283d4
commit 926f5f0621
6 changed files with 138 additions and 42 deletions

View file

@ -0,0 +1,80 @@
package org.mage.test.cards.single.tor;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.player.TestPlayer;
import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
/**
* @author JayDi85
*/
public class RadiateTest extends CardTestPlayerBaseWithAIHelps {
@Test
public void test_Play_Manual() {
// Choose target instant or sorcery spell that targets only a single permanent or player. Copy that spell
// for each other permanent or player the spell could target. Each copy targets a different one of those
// permanents and players.
addCard(Zone.HAND, playerA, "Radiate", 6); // {3}{R}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
//
addCard(Zone.HAND, playerA, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
//
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 2);
addCard(Zone.BATTLEFIELD, playerB, "Kitesail Corsair", 2);
// cast bolt and copy spell for each another target
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Radiate", "Lightning Bolt", "Lightning Bolt");
checkStackSize("before radiate", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true);
// must have: 2x for corsairs, 2x for bears, 1x for A
checkStackSize("after radiate", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 1 + 5);
addTarget(playerA, TestPlayer.TARGET_SKIP);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, 6); // 6 lands
assertPermanentCount(playerB, 0);
assertLife(playerA, 20 - 3);
assertLife(playerB, 20 - 3);
}
@Test
public void test_Play_AI() {
// possible bug: game freeze or Target wasn't handled... TargetWithAdditionalFilter
// Choose target instant or sorcery spell that targets only a single permanent or player. Copy that spell
// for each other permanent or player the spell could target. Each copy targets a different one of those
// permanents and players.
addCard(Zone.HAND, playerA, "Radiate", 6); // {3}{R}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
//
addCard(Zone.HAND, playerA, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
//
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 2);
addCard(Zone.BATTLEFIELD, playerB, "Kitesail Corsair", 2);
// cast bolt and copy spell for each another target
// must call commands manually cause it's a bad scenario and AI don't cast it itself
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Radiate", "Lightning Bolt", "Lightning Bolt");
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); // but AI can choose targets
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, 6); // 6 lands
assertPermanentCount(playerB, 0);
assertLife(playerA, 20 - 3);
assertLife(playerB, 20 - 3);
}
}

View file

@ -80,7 +80,7 @@ public abstract class CopySpellForEachItCouldTargetEffect<T extends MageItem> ex
return false;
}
// collect objects that can be targeted
// generate copies for each possible target, but do not put it to stack (use must choose targets in custom order later)
Spell copy = spell.copySpell(source.getControllerId(), game);
modifyCopy(copy, game, source);
Target sampleTarget = targetsToBeChanged.iterator().next().getTarget(copy);
@ -135,28 +135,33 @@ public abstract class CopySpellForEachItCouldTargetEffect<T extends MageItem> ex
}
}
// allow the copies' controller to choose the order that they go on the stack
// allows controller of the copies to choose spells order on stack (by using targeting GUI)
for (Player player : game.getPlayers().values()) {
if (playerTargetCopyMap.containsKey(player.getId())) {
Map<UUID, Spell> targetCopyMap = playerTargetCopyMap.get(player.getId());
if (targetCopyMap != null) {
while (!targetCopyMap.isEmpty()) {
// all checks must be make for new copied spell, not original (controller can be changed)
Spell spellSample = targetCopyMap.values().stream().findFirst().get();
FilterInPlay<T> setFilter = filter.copy();
setFilter.add(new FromSetPredicate(targetCopyMap.keySet()));
setFilter.add(new FromSetPredicate(targetCopyMap.keySet())); // allows only unselected targets
Target target = new TargetWithAdditionalFilter(sampleTarget, setFilter);
target.setNotTarget(false); // it is targeted, not chosen
target.setMinNumberOfTargets(0);
target.setMinNumberOfTargets(0); // if not selected then it uses first target (see below), same for AI
target.setMaxNumberOfTargets(1);
target.setTargetName(filter.getMessage() + " that " + spell.getLogName()
target.setTargetName(filter.getMessage() + " that " + spellSample.getLogName()
+ " could target (" + targetCopyMap.size() + " remaining)");
// shortcut if there's only one possible target remaining
if (targetCopyMap.size() > 1
&& target.canChoose(spell.getId(), player.getId(), game)) {
&& target.canChoose(spellSample.getId(), player.getId(), game)) {
// The original "source" is not applicable here due to the spell being a copy. ie: Zada, Hedron Grinder
player.chooseTarget(Outcome.Neutral, target, spell.getSpellAbility(), game); // not source, but the spell that is copied
Outcome outcome = spellSample.getSpellAbility().getAllEffects().getOutcome(spellSample.getSpellAbility());
player.chooseTarget(outcome, target, spellSample.getSpellAbility(), game); // not source, but the spell that is copied
}
Collection<UUID> chosenIds = target.getTargets();
if (chosenIds.isEmpty()) {
// uses first target on cancel/non-selected
chosenIds = targetCopyMap.keySet();
}
List<UUID> toDelete = new ArrayList<>();

View file

@ -1,7 +1,5 @@
package mage.filter.common;
import java.util.UUID;
import mage.MageItem;
import mage.filter.FilterImpl;
import mage.filter.FilterInPlay;
@ -10,8 +8,9 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class FilterCreatureOrPlayer extends FilterImpl<MageItem> implements FilterInPlay<MageItem> {
@ -42,20 +41,24 @@ public class FilterCreatureOrPlayer extends FilterImpl<MageItem> implements Filt
@Override
public boolean match(MageItem o, Game game) {
if (o instanceof Player) {
return playerFilter.match((Player) o, game);
} else if (o instanceof Permanent) {
return creatureFilter.match((Permanent) o, game);
if (super.match(o, game)) {
if (o instanceof Player) {
return playerFilter.match((Player) o, game);
} else if (o instanceof Permanent) {
return creatureFilter.match((Permanent) o, game);
}
}
return false;
}
@Override
public boolean match(MageItem o, UUID sourceId, UUID playerId, Game game) {
if (o instanceof Player) {
return playerFilter.match((Player) o, sourceId, playerId, game);
} else if (o instanceof Permanent) {
return creatureFilter.match((Permanent) o, sourceId, playerId, game);
if (super.match(o, game)) { // process predicates
if (o instanceof Player) {
return playerFilter.match((Player) o, sourceId, playerId, game);
} else if (o instanceof Permanent) {
return creatureFilter.match((Permanent) o, sourceId, playerId, game);
}
}
return false;
}

View file

@ -1,12 +1,12 @@
package mage.filter.common;
import java.util.UUID;
import mage.MageItem;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
* @author JRHerlehy Created on 4/8/18.
*/
@ -33,22 +33,26 @@ public class FilterCreaturePlayerOrPlaneswalker extends FilterPermanentOrPlayer
@Override
public boolean match(MageItem o, Game game) {
if (o instanceof Player) {
return playerFilter.match((Player) o, game);
} else if (o instanceof Permanent) {
return creatureFilter.match((Permanent) o, game)
|| planeswalkerFilter.match((Permanent) o, game);
if (super.match(o, game)) {
if (o instanceof Player) {
return playerFilter.match((Player) o, game);
} else if (o instanceof Permanent) {
return creatureFilter.match((Permanent) o, game)
|| planeswalkerFilter.match((Permanent) o, game);
}
}
return false;
}
@Override
public boolean match(MageItem o, UUID sourceId, UUID playerId, Game game) {
if (o instanceof Player) {
return playerFilter.match((Player) o, sourceId, playerId, game);
} else if (o instanceof Permanent) {
return creatureFilter.match((Permanent) o, sourceId, playerId, game)
|| planeswalkerFilter.match((Permanent) o, sourceId, playerId, game);
if (super.match(o, game)) { // process predicates
if (o instanceof Player) {
return playerFilter.match((Player) o, sourceId, playerId, game);
} else if (o instanceof Permanent) {
return creatureFilter.match((Permanent) o, sourceId, playerId, game)
|| planeswalkerFilter.match((Permanent) o, sourceId, playerId, game);
}
}
return false;
}

View file

@ -1,7 +1,5 @@
package mage.filter.common;
import java.util.UUID;
import mage.MageItem;
import mage.filter.FilterImpl;
import mage.filter.FilterInPlay;
@ -11,6 +9,8 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
* @author nantuko
*/
@ -46,20 +46,24 @@ public class FilterPermanentOrPlayer extends FilterImpl<MageItem> implements Fil
@Override
public boolean match(MageItem o, Game game) {
if (o instanceof Player) {
return playerFilter.match((Player) o, game);
} else if (o instanceof Permanent) {
return permanentFilter.match((Permanent) o, game);
if (super.match(o, game)) {
if (o instanceof Player) {
return playerFilter.match((Player) o, game);
} else if (o instanceof Permanent) {
return permanentFilter.match((Permanent) o, game);
}
}
return false;
}
@Override
public boolean match(MageItem o, UUID sourceId, UUID playerId, Game game) {
if (o instanceof Player) {
return playerFilter.match((Player) o, sourceId, playerId, game);
} else if (o instanceof Permanent) {
return permanentFilter.match((Permanent) o, sourceId, playerId, game);
if (super.match(o, game)) { // process predicates
if (o instanceof Player) {
return playerFilter.match((Player) o, sourceId, playerId, game);
} else if (o instanceof Permanent) {
return permanentFilter.match((Permanent) o, sourceId, playerId, game);
}
}
return false;
}

View file

@ -38,7 +38,7 @@ public class FilterPermanentOrPlayerWithCounter extends FilterPermanentOrPlayer
@Override
public boolean match(MageItem o, UUID sourceId, UUID playerId, Game game) {
if (super.match(o, sourceId, playerId, game)) {
if (super.match(o, sourceId, playerId, game)) { // same as parent class, so can call with full params
if (o instanceof Player) {
return !((Player) o).getCounters().isEmpty();
} else if (o instanceof Permanent) {