Additional fix and simplified for playable abilities (see comments b94344341b)

This commit is contained in:
Oleg Agafonov 2020-06-04 03:21:18 +04:00
parent bd40d90286
commit cce467a5ec
13 changed files with 142 additions and 137 deletions

View file

@ -3,6 +3,7 @@ package mage.player.ai;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.AbilityImpl; import mage.abilities.AbilityImpl;
import mage.abilities.ActivatedAbility;
import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbility;
import mage.abilities.common.PassAbility; import mage.abilities.common.PassAbility;
import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCost;
@ -94,9 +95,9 @@ public class SimulatedPlayer2 extends ComputerPlayer {
} }
protected void simulateOptions(Game game) { protected void simulateOptions(Game game) {
List<Ability> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer); List<ActivatedAbility> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer);
playables = filterAbilities(game, playables, suggested); playables = filterAbilities(game, playables, suggested);
for (Ability ability : playables) { for (ActivatedAbility ability : playables) {
if (ability.getAbilityType() == AbilityType.MANA) { if (ability.getAbilityType() == AbilityType.MANA) {
continue; continue;
} }
@ -186,15 +187,15 @@ public class SimulatedPlayer2 extends ComputerPlayer {
* @param suggested * @param suggested
* @return * @return
*/ */
protected List<Ability> filterAbilities(Game game, List<Ability> playables, List<String> suggested) { protected List<ActivatedAbility> filterAbilities(Game game, List<ActivatedAbility> playables, List<String> suggested) {
if (playables.isEmpty()) { if (playables.isEmpty()) {
return playables; return playables;
} }
if (suggested == null || suggested.isEmpty()) { if (suggested == null || suggested.isEmpty()) {
return playables; return playables;
} }
List<Ability> filtered = new ArrayList<>(); List<ActivatedAbility> filtered = new ArrayList<>();
for (Ability ability : playables) { for (ActivatedAbility ability : playables) {
Card card = game.getCard(ability.getSourceId()); Card card = game.getCard(ability.getSourceId());
if (card != null) { if (card != null) {
for (String s : suggested) { for (String s : suggested) {
@ -212,7 +213,7 @@ public class SimulatedPlayer2 extends ComputerPlayer {
return playables; return playables;
} }
protected List<Ability> filterOptions(Game game, List<Ability> options, Ability ability, List<String> suggested) { protected List<Ability> filterOptions(Game game, List<Ability> options, ActivatedAbility ability, List<String> suggested) {
if (options.isEmpty()) { if (options.isEmpty()) {
return options; return options;
} }

View file

@ -2,6 +2,7 @@
package mage.player.ai; package mage.player.ai;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.common.PassAbility; import mage.abilities.common.PassAbility;
import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.GenericManaCost;
@ -45,16 +46,16 @@ public class MCTSPlayer extends ComputerPlayer {
return new MCTSPlayer(this); return new MCTSPlayer(this);
} }
protected List<Ability> getPlayableAbilities(Game game) { protected List<ActivatedAbility> getPlayableAbilities(Game game) {
List<Ability> playables = getPlayable(game, true); List<ActivatedAbility> playables = getPlayable(game, true);
playables.add(pass); playables.add(pass);
return playables; return playables;
} }
public List<Ability> getPlayableOptions(Game game) { public List<Ability> getPlayableOptions(Game game) {
List<Ability> all = new ArrayList<>(); List<Ability> all = new ArrayList<>();
List<Ability> playables = getPlayableAbilities(game); List<ActivatedAbility> playables = getPlayableAbilities(game);
for (Ability ability: playables) { for (ActivatedAbility ability: playables) {
List<Ability> options = game.getPlayer(playerId).getPlayableOptions(ability, game); List<Ability> options = game.getPlayer(playerId).getPlayableOptions(ability, game);
if (options.isEmpty()) { if (options.isEmpty()) {
if (!ability.getManaCosts().getVariableCosts().isEmpty()) { if (!ability.getManaCosts().getVariableCosts().isEmpty()) {

View file

@ -73,7 +73,7 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
} }
private Ability getAction(Game game) { private Ability getAction(Game game) {
List<Ability> playables = getPlayableAbilities(game); List<ActivatedAbility> playables = getPlayableAbilities(game);
Ability ability; Ability ability;
while (true) { while (true) {
if (playables.size() == 1) { if (playables.size() == 1) {

View file

@ -3,6 +3,7 @@
package mage.player.ai; package mage.player.ai;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbility;
import mage.abilities.common.PassAbility; import mage.abilities.common.PassAbility;
@ -59,10 +60,10 @@ public class SimulatedPlayer extends ComputerPlayer {
return list; return list;
} }
protected void simulateOptions(Game game, Ability previousActions) { protected void simulateOptions(Game game, ActivatedAbility previousActions) {
allActions.add(previousActions); allActions.add(previousActions);
List<Ability> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer); List<ActivatedAbility> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer);
for (Ability ability: playables) { for (ActivatedAbility ability: playables) {
List<Ability> options = game.getPlayer(playerId).getPlayableOptions(ability, game); List<Ability> options = game.getPlayer(playerId).getPlayableOptions(ability, game);
if (options.isEmpty()) { if (options.isEmpty()) {
if (!ability.getManaCosts().getVariableCosts().isEmpty()) { if (!ability.getManaCosts().getVariableCosts().isEmpty()) {

View file

@ -1056,7 +1056,7 @@ public class HumanPlayer extends PlayerImpl {
actingPlayer = game.getPlayer(game.getPriorityPlayerId()); actingPlayer = game.getPlayer(game.getPriorityPlayerId());
} }
if (actingPlayer != null) { if (actingPlayer != null) {
useableAbilities = actingPlayer.getUseableActivatedAbilities(object, zone, game); useableAbilities = actingPlayer.getPlayableActivatedAbilities(object, zone, game);
} }
if (object instanceof Card if (object instanceof Card

View file

@ -425,7 +425,7 @@ public final class SystemUtil {
game.firePriorityEvent(opponent.getId()); game.firePriorityEvent(opponent.getId());
} }
List<Ability> abilities = opponent.getPlayable(game, true); List<ActivatedAbility> abilities = opponent.getPlayable(game, true);
Map<String, String> choices = new HashMap<>(); Map<String, String> choices = new HashMap<>();
abilities.forEach(ability -> { abilities.forEach(ability -> {
MageObject object = ability.getSourceObject(game); MageObject object = ability.getSourceObject(game);
@ -437,10 +437,10 @@ public final class SystemUtil {
choice.setKeyChoices(choices); choice.setKeyChoices(choices);
if (feedbackPlayer.choose(Outcome.Detriment, choice, game) && choice.getChoiceKey() != null) { if (feedbackPlayer.choose(Outcome.Detriment, choice, game) && choice.getChoiceKey() != null) {
String needId = choice.getChoiceKey(); String needId = choice.getChoiceKey();
Optional<Ability> ability = abilities.stream().filter(a -> a.getId().toString().equals(needId)).findFirst(); Optional<ActivatedAbility> ability = abilities.stream().filter(a -> a.getId().toString().equals(needId)).findFirst();
if (ability.isPresent()) { if (ability.isPresent()) {
// TODO: set priority for player? // TODO: set priority for player?
ActivatedAbility activatedAbility = (ActivatedAbility) ability.get(); ActivatedAbility activatedAbility = ability.get();
game.informPlayers(feedbackPlayer.getLogName() + " as another player " + opponent.getLogName() game.informPlayers(feedbackPlayer.getLogName() + " as another player " + opponent.getLogName()
+ " trying to force an activate ability: " + activatedAbility.getGameLogMessage(game)); + " trying to force an activate ability: " + activatedAbility.getGameLogMessage(game));
if (opponent.activateAbility(activatedAbility, game)) { if (opponent.activateAbility(activatedAbility, game)) {

View file

@ -37,7 +37,7 @@ public class CastSplitCardsWithFlashbackTest extends CardTestPlayerBase {
} }
@Test @Test
public void test_Flashback_Split() { public void test_Flashback_SplitLeft() {
// {1}{U} // {1}{U}
// When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. // When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn.
addCard(Zone.HAND, playerA, "Snapcaster Mage", 1); addCard(Zone.HAND, playerA, "Snapcaster Mage", 1);
@ -67,4 +67,36 @@ public class CastSplitCardsWithFlashbackTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Bident of Thassa", 1); assertGraveyardCount(playerB, "Bident of Thassa", 1);
assertPermanentCount(playerB, "Bow of Nylea", 1); assertPermanentCount(playerB, "Bow of Nylea", 1);
} }
@Test
public void test_Flashback_SplitRight() {
// {1}{U}
// When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn.
addCard(Zone.HAND, playerA, "Snapcaster Mage", 1);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
//
// Wear {1}{R} Destroy target artifact.
// Tear {W} Destroy target enchantment.
addCard(Zone.GRAVEYARD, playerA, "Wear // Tear", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerB, "Bident of Thassa", 1); // Legendary Enchantment Artifact
addCard(Zone.BATTLEFIELD, playerB, "Bow of Nylea", 1); // Legendary Enchantment Artifact
// add flashback
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage");
addTarget(playerA, "Wear // Tear");
// cast as flashback
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback {W}", "Bident of Thassa");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerB, "Bident of Thassa", 1);
assertPermanentCount(playerB, "Bow of Nylea", 1);
}
} }

View file

@ -73,7 +73,7 @@ public class RandomPlayer extends ComputerPlayer {
} }
private Ability getAction(Game game) { private Ability getAction(Game game) {
List<Ability> playables = getPlayableAbilities(game); List<ActivatedAbility> playables = getPlayableAbilities(game);
Ability ability; Ability ability;
while (true) { while (true) {
if (playables.size() == 1) { if (playables.size() == 1) {
@ -115,8 +115,8 @@ public class RandomPlayer extends ComputerPlayer {
return ability; return ability;
} }
protected List<Ability> getPlayableAbilities(Game game) { protected List<ActivatedAbility> getPlayableAbilities(Game game) {
List<Ability> playables = getPlayable(game, true); List<ActivatedAbility> playables = getPlayable(game, true);
playables.add(pass); playables.add(pass);
return playables; return playables;
} }

View file

@ -587,14 +587,14 @@ public class TestPlayer implements Player {
if (groups.length > 2 && !checkExecuteCondition(groups, game)) { if (groups.length > 2 && !checkExecuteCondition(groups, game)) {
break; break;
} }
for (Ability ability : computerPlayer.getPlayable(game, true)) { // add wrong action log? for (ActivatedAbility ability : computerPlayer.getPlayable(game, true)) { // add wrong action log?
if (isAbilityHaveTargetNameOrAlias(game, ability, groups[0])) { if (isAbilityHaveTargetNameOrAlias(game, ability, groups[0])) {
int bookmark = game.bookmarkState(); int bookmark = game.bookmarkState();
Ability newAbility = ability.copy(); ActivatedAbility newAbility = ability.copy();
if (groups.length > 1 && !groups[1].equals("target=" + NO_TARGET)) { if (groups.length > 1 && !groups[1].equals("target=" + NO_TARGET)) {
groupsForTargetHandling = groups; groupsForTargetHandling = groups;
} }
if (computerPlayer.activateAbility((ActivatedAbility) newAbility, game)) { if (computerPlayer.activateAbility(newAbility, game)) {
actions.remove(action); actions.remove(action);
groupsForTargetHandling = null; groupsForTargetHandling = null;
foundNoAction = 0; // Reset enless loop check because of no action foundNoAction = 0; // Reset enless loop check because of no action
@ -1078,7 +1078,7 @@ public class TestPlayer implements Player {
}); });
} }
private void printAbilities(Game game, List<Ability> abilities) { private void printAbilities(Game game, List<? extends Ability> abilities) {
System.out.println("Total abilities: " + (abilities != null ? abilities.size() : 0)); System.out.println("Total abilities: " + (abilities != null ? abilities.size() : 0));
if (abilities == null) { if (abilities == null) {
return; return;
@ -1714,6 +1714,8 @@ public class TestPlayer implements Player {
private void chooseStrictModeFailed(String choiceType, Game game, String reason, boolean printAbilities) { private void chooseStrictModeFailed(String choiceType, Game game, String reason, boolean printAbilities) {
if (strictChooseMode && !AICanChooseInStrictMode) { if (strictChooseMode && !AICanChooseInStrictMode) {
if (printAbilities) { if (printAbilities) {
printStart("Available mana for " + computerPlayer.getName());
printMana(game, computerPlayer.getManaAvailable(game));
printStart("Available abilities for " + computerPlayer.getName()); printStart("Available abilities for " + computerPlayer.getName());
printAbilities(game, computerPlayer.getPlayable(game, true)); printAbilities(game, computerPlayer.getPlayable(game, true));
printEnd(); printEnd();
@ -2712,8 +2714,8 @@ public class TestPlayer implements Player {
} }
@Override @Override
public LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { public LinkedHashMap<UUID, ActivatedAbility> getPlayableActivatedAbilities(MageObject object, Zone zone, Game game) {
return computerPlayer.getUseableActivatedAbilities(object, zone, game); return computerPlayer.getPlayableActivatedAbilities(object, zone, game);
} }
@Override @Override
@ -3171,12 +3173,8 @@ public class TestPlayer implements Player {
return computerPlayer.getManaAvailable(game); return computerPlayer.getManaAvailable(game);
} }
public List<Permanent> getAvailableManaProducersWithCost(Game game) {
return computerPlayer.getAvailableManaProducersWithCost(game);
}
@Override @Override
public List<Ability> getPlayable(Game game, boolean hidden) { public List<ActivatedAbility> getPlayable(Game game, boolean hidden) {
return computerPlayer.getPlayable(game, hidden); return computerPlayer.getPlayable(game, hidden);
} }

View file

@ -1023,7 +1023,7 @@ public class PlayerStub implements Player {
} }
@Override @Override
public List<Ability> getPlayable(Game game, boolean hidden) { public List<ActivatedAbility> getPlayable(Game game, boolean hidden) {
return null; return null;
} }
@ -1038,7 +1038,7 @@ public class PlayerStub implements Player {
} }
@Override @Override
public LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { public LinkedHashMap<UUID, ActivatedAbility> getPlayableActivatedAbilities(MageObject object, Zone zone, Game game) {
return null; return null;
} }

View file

@ -14,6 +14,7 @@ import mage.ObjectColor;
import mage.abilities.*; import mage.abilities.*;
import mage.abilities.hint.Hint; import mage.abilities.hint.Hint;
import mage.abilities.hint.HintUtils; import mage.abilities.hint.HintUtils;
import mage.abilities.keyword.FlashbackAbility;
import mage.abilities.mana.ActivatedManaAbilityImpl; import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.repository.PluginClassloaderRegistery; import mage.cards.repository.PluginClassloaderRegistery;
import mage.constants.*; import mage.constants.*;
@ -312,6 +313,21 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
// dynamic // dynamic
all.addAll(cardState.getAbilities()); all.addAll(cardState.getAbilities());
// workaround to add dynamic flashback ability from main card to all parts (example: Snapcaster Mage gives flashback to split card)
if (!this.getId().equals(this.getMainCard().getId())) {
CardState mainCardState = game.getState().getCardState(this.getMainCard().getId());
if (mainCardState != null
&& !mainCardState.hasLostAllAbilities()
&& mainCardState.getAbilities().containsClass(FlashbackAbility.class)) {
FlashbackAbility flash = new FlashbackAbility(this.getManaCost(), this.isInstant() ? TimingRule.INSTANT : TimingRule.SORCERY);
flash.setSourceId(this.getId());
flash.setControllerId(this.getOwnerId());
flash.setSpellAbilityType(this.getSpellAbility().getSpellAbilityType());
flash.setAbilityName(this.getName());
all.add(flash);
}
}
return all; return all;
} }

View file

@ -627,13 +627,13 @@ public interface Player extends MageItem, Copyable<Player> {
ManaOptions getManaAvailable(Game game); ManaOptions getManaAvailable(Game game);
List<Ability> getPlayable(Game game, boolean hidden); List<ActivatedAbility> getPlayable(Game game, boolean hidden);
List<Ability> getPlayableOptions(Ability ability, Game game); List<Ability> getPlayableOptions(Ability ability, Game game);
Map<UUID, Integer> getPlayableObjects(Game game, Zone zone); Map<UUID, Integer> getPlayableObjects(Game game, Zone zone);
LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game); LinkedHashMap<UUID, ActivatedAbility> getPlayableActivatedAbilities(MageObject object, Zone zone, Game game);
boolean addCounters(Counter counter, Game game); boolean addCounters(Counter counter, Game game);

View file

@ -1499,7 +1499,7 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
@Override @Override
public LinkedHashMap<UUID, ActivatedAbility> getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { public LinkedHashMap<UUID, ActivatedAbility> getPlayableActivatedAbilities(MageObject object, Zone zone, Game game) {
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>(); LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
boolean previousState = game.inCheckPlayableState(); boolean previousState = game.inCheckPlayableState();
game.setCheckPlayableState(true); game.setCheckPlayableState(true);
@ -1510,12 +1510,33 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
// collect and filter playable activated abilities // collect and filter playable activated abilities
List<Ability> allPlayable = getPlayable(game, true, zone, false); // GUI: user clicks on card, but it must activate ability from any card's parts (main, left, right)
for (Ability ability : allPlayable) { UUID needId1, needId2, needId3;
if (ability instanceof ActivatedAbility) { if (object instanceof SplitCard) {
if (object.hasAbility(ability, game)) { needId1 = object.getId();
useable.putIfAbsent(ability.getId(), (ActivatedAbility) ability); needId2 = ((SplitCard) object).getLeftHalfCard().getId();
needId3 = ((SplitCard) object).getRightHalfCard().getId();
} else if (object instanceof AdventureCard) {
needId1 = object.getId();
needId2 = ((AdventureCard) object).getMainCard().getId();
needId3 = ((AdventureCard) object).getSpellCard().getId();
} else if (object instanceof AdventureCardSpell) {
needId1 = object.getId();
needId2 = ((AdventureCardSpell) object).getParentCard().getId();
needId3 = object.getId();
} else {
needId1 = object.getId();
needId2 = object.getId();
needId3 = object.getId();
} }
// workaround to find all abilities first and filter it for one object
List<ActivatedAbility> allPlayable = getPlayable(game, true, zone, false);
for (ActivatedAbility ability : allPlayable) {
if (Objects.equals(ability.getSourceId(), needId1)
|| Objects.equals(ability.getSourceId(), needId2)
|| Objects.equals(ability.getSourceId(), needId3)) {
useable.putIfAbsent(ability.getId(), ability);
} }
} }
} finally { } finally {
@ -1524,58 +1545,6 @@ public abstract class PlayerImpl implements Player, Serializable {
return useable; return useable;
} }
// Adds special abilities that are given to non permanents by continuous effects
private void getOtherUseableActivatedAbilities(MageObject object, Zone zone, Game game, Map<UUID, ActivatedAbility> useable) {
Abilities<ActivatedAbility> otherAbilities = game.getState().getActivatedOtherAbilities(object.getId(), zone);
if (otherAbilities != null) {
boolean canUse = !(object instanceof Permanent)
|| ((Permanent) object).canUseActivatedAbilities(game);
for (ActivatedAbility ability : otherAbilities) {
if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
Card card = game.getCard(ability.getSourceId());
if (card != null) {
if (card.isSplitCard() && ability instanceof FlashbackAbility) {
FlashbackAbility flashbackAbility;
// Left Half
if (card.isInstant()) {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(),
TimingRule.INSTANT);
} else {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(),
TimingRule.SORCERY);
}
flashbackAbility.setSourceId(card.getId());
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_LEFT);
flashbackAbility.setAbilityName(((SplitCard) card).getLeftHalfCard().getName());
if (flashbackAbility.canActivate(playerId, game).canActivate()) {
useable.put(flashbackAbility.getId(), flashbackAbility);
}
// Right Half
if (card.isInstant()) {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(),
TimingRule.INSTANT);
} else {
flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(),
TimingRule.SORCERY);
}
flashbackAbility.setSourceId(card.getId());
flashbackAbility.setControllerId(card.getOwnerId());
flashbackAbility.setSpellAbilityType(SpellAbilityType.SPLIT_RIGHT);
flashbackAbility.setAbilityName(((SplitCard) card).getRightHalfCard().getName());
if (flashbackAbility.canActivate(playerId, game).canActivate()) {
useable.put(flashbackAbility.getId(), flashbackAbility);
}
} else {
useable.put(ability.getId(), ability);
}
}
}
}
}
}
protected LinkedHashMap<UUID, ActivatedManaAbilityImpl> getUseableManaAbilities(MageObject object, Zone zone, Game game) { protected LinkedHashMap<UUID, ActivatedManaAbilityImpl> getUseableManaAbilities(MageObject object, Zone zone, Game game) {
LinkedHashMap<UUID, ActivatedManaAbilityImpl> useable = new LinkedHashMap<>(); LinkedHashMap<UUID, ActivatedManaAbilityImpl> useable = new LinkedHashMap<>();
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
@ -3182,7 +3151,16 @@ public abstract class PlayerImpl implements Player, Serializable {
return false; return false;
} }
private void getPlayableFromCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<Ability> output) { private Abilities<ActivatedAbility> getActivatedOnly(Abilities<Ability> list) {
Abilities<ActivatedAbility> res = new AbilitiesImpl<>();
list.stream()
.filter(a -> a instanceof ActivatedAbility)
.map(a -> (ActivatedAbility) a)
.forEach(res::add);
return res;
}
private void getPlayableFromCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<ActivatedAbility> output) {
if (fromZone == null) { if (fromZone == null) {
return; return;
} }
@ -3190,41 +3168,22 @@ public abstract class PlayerImpl implements Player, Serializable {
// BASIC abilities // BASIC abilities
if (card instanceof SplitCard) { if (card instanceof SplitCard) {
SplitCard splitCard = (SplitCard) card; SplitCard splitCard = (SplitCard) card;
getPlayableFromCardSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(), availableMana, output); getPlayableFromCardSingle(game, fromZone, splitCard.getLeftHalfCard(), getActivatedOnly(splitCard.getLeftHalfCard().getAbilities(game)), availableMana, output);
getPlayableFromCardSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(), availableMana, output); getPlayableFromCardSingle(game, fromZone, splitCard.getRightHalfCard(), getActivatedOnly(splitCard.getRightHalfCard().getAbilities(game)), availableMana, output);
getPlayableFromCardSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(game), availableMana, output); getPlayableFromCardSingle(game, fromZone, splitCard, getActivatedOnly(splitCard.getSharedAbilities(game)), availableMana, output);
} else if (card instanceof AdventureCard) { } else if (card instanceof AdventureCard) {
// adventure must use different card characteristics for different spells (main or adventure) // adventure must use different card characteristics for different spells (main or adventure)
AdventureCard adventureCard = (AdventureCard) card; AdventureCard adventureCard = (AdventureCard) card;
getPlayableFromCardSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(), availableMana, output); getPlayableFromCardSingle(game, fromZone, adventureCard.getSpellCard(), getActivatedOnly(adventureCard.getSpellCard().getAbilities(game)), availableMana, output);
getPlayableFromCardSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(game), availableMana, output); getPlayableFromCardSingle(game, fromZone, adventureCard, getActivatedOnly(adventureCard.getSharedAbilities(game)), availableMana, output);
} else { } else {
getPlayableFromCardSingle(game, fromZone, card, card.getAbilities(game), availableMana, output); getPlayableFromCardSingle(game, fromZone, card, getActivatedOnly(card.getAbilities(game)), availableMana, output);
} }
// DYNAMIC ADDED abilities // DYNAMIC ADDED abilities are adds in getAbilities(game)
if (fromZone != Zone.ALL) { // TODO: test revealed cards with dynamic added abilities
// Other activated abilities (added dynamic by effects)
LinkedHashMap<UUID, ActivatedAbility> useable;
if (card instanceof AdventureCard) {
// adventure cards (contains two different cards: main and adventure spell)
useable = new LinkedHashMap<>();
getOtherUseableActivatedAbilities(((AdventureCard) card).getSpellCard(), fromZone, game, useable);
output.addAll(useable.values());
useable = new LinkedHashMap<>();
getOtherUseableActivatedAbilities(card, fromZone, game, useable);
output.addAll(useable.values());
} else {
// all other cards (TODO: check split cards with dynamic added abilities)
useable = new LinkedHashMap<>();
getOtherUseableActivatedAbilities(card, fromZone, game, useable);
output.addAll(useable.values());
}
}
} }
private void getPlayableFromCardSingle(Game game, Zone fromZone, Card card, Abilities<Ability> candidateAbilities, ManaOptions availableMana, List<Ability> output) { private void getPlayableFromCardSingle(Game game, Zone fromZone, Card card, Abilities<ActivatedAbility> candidateAbilities, ManaOptions availableMana, List<ActivatedAbility> output) {
// check "can play" condition as affected controller (BUT play from not own hand zone must be checked as original controller) // check "can play" condition as affected controller (BUT play from not own hand zone must be checked as original controller)
for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) { for (ActivatedAbility ability : candidateAbilities.getActivatedAbilities(Zone.ALL)) {
boolean isPlaySpell = (ability instanceof SpellAbility); boolean isPlaySpell = (ability instanceof SpellAbility);
@ -3300,12 +3259,12 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
@Override @Override
public List<Ability> getPlayable(Game game, boolean hidden) { public List<ActivatedAbility> getPlayable(Game game, boolean hidden) {
return getPlayable(game, hidden, Zone.ALL, true); return getPlayable(game, hidden, Zone.ALL, true);
} }
public List<Ability> getPlayable(Game game, boolean hidden, Zone fromZone, boolean hideDuplicatedAbilities) { public List<ActivatedAbility> getPlayable(Game game, boolean hidden, Zone fromZone, boolean hideDuplicatedAbilities) {
List<Ability> playable = new ArrayList<>(); List<ActivatedAbility> playable = new ArrayList<>();
if (shouldSkipGettingPlayable(game)) { if (shouldSkipGettingPlayable(game)) {
return playable; return playable;
} }
@ -3402,22 +3361,20 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
// eliminate duplicate activated abilities (uses for AI plays) // eliminate duplicate activated abilities (uses for AI plays)
Map<String, Ability> activatedUnique = new HashMap<>(); Map<String, ActivatedAbility> activatedUnique = new HashMap<>();
List<Ability> activatedAll = new ArrayList<>(); List<ActivatedAbility> activatedAll = new ArrayList<>();
// activated abilities from battlefield objects // activated abilities from battlefield objects
if (fromAll || fromZone == Zone.BATTLEFIELD) { if (fromAll || fromZone == Zone.BATTLEFIELD) {
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
List<Ability> battlePlayable = new ArrayList<>(); List<ActivatedAbility> battlePlayable = new ArrayList<>();
getPlayableFromCardAll(game, Zone.BATTLEFIELD, permanent, availableMana, battlePlayable); getPlayableFromCardAll(game, Zone.BATTLEFIELD, permanent, availableMana, battlePlayable);
for (Ability ability : battlePlayable) { for (ActivatedAbility ability : battlePlayable) {
if (ability instanceof ActivatedAbility) {
activatedUnique.putIfAbsent(ability.toString(), ability); activatedUnique.putIfAbsent(ability.toString(), ability);
activatedAll.add(ability); activatedAll.add(ability);
} }
} }
} }
}
// activated abilities from stack objects // activated abilities from stack objects
if (fromAll || fromZone == Zone.STACK) { if (fromAll || fromZone == Zone.STACK) {
@ -3467,7 +3424,7 @@ public abstract class PlayerImpl implements Player, Serializable {
*/ */
@Override @Override
public Map<UUID, Integer> getPlayableObjects(Game game, Zone zone) { public Map<UUID, Integer> getPlayableObjects(Game game, Zone zone) {
List<Ability> playableAbilities = getPlayable(game, true, zone, false); // do not hide duplicated abilities/cards List<ActivatedAbility> playableAbilities = getPlayable(game, true, zone, false); // do not hide duplicated abilities/cards
Map<UUID, Integer> playableObjects = new HashMap<>(); Map<UUID, Integer> playableObjects = new HashMap<>();
for (Ability ability : playableAbilities) { for (Ability ability : playableAbilities) {
if (ability.getSourceId() != null) { if (ability.getSourceId() != null) {
@ -3518,7 +3475,6 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override @Override
public List<Ability> getPlayableOptions(Ability ability, Game game) { public List<Ability> getPlayableOptions(Ability ability, Game game) {
List<Ability> options = new ArrayList<>(); List<Ability> options = new ArrayList<>();
if (ability.isModal()) { if (ability.isModal()) {
addModeOptions(options, ability, game); addModeOptions(options, ability, game);
} else if (!ability.getTargets().getUnchosen().isEmpty()) { } else if (!ability.getTargets().getUnchosen().isEmpty()) {