This commit is contained in:
Alessandro Coli
2019-07-14 13:35:09 +02:00
15 changed files with 321 additions and 236 deletions

View File

@@ -181,20 +181,6 @@ public class PlayerControllerAi extends PlayerController {
return selecteds;
}
@Override
public <T extends GameEntity> List<T> chooseFromTwoListsForEffect(FCollectionView<T> optionList1, FCollectionView<T> optionList2,
boolean optional, DelayedReveal delayedReveal, SpellAbility sa, String title, Player targetedPlayer) {
if (delayedReveal != null) {
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
}
T selected1 = chooseSingleEntityForEffect(optionList1, null, sa, title, optional, targetedPlayer);
T selected2 = chooseSingleEntityForEffect(optionList2, null, sa, title, optional || selected1!=null, targetedPlayer);
List<T> selecteds = new ArrayList<T>();
if ( selected1 != null ) { selecteds.add(selected1); }
if ( selected2 != null ) { selecteds.add(selected2); }
return selecteds;
}
@Override
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title,
Map<String, Object> params) {
@@ -1275,4 +1261,17 @@ public class PlayerControllerAi extends PlayerController {
return chosenAmount;
}
@Override
public CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap, SpellAbility sa, String title) {
CardCollection choices = new CardCollection();
for (String mapKey: validMap.keySet()) {
CardCollection cc = validMap.get(mapKey);
cc.removeAll(choices);
choices.add(ComputerUtilCard.getBestAI(cc)); // TODO: should the AI limit itself here with the max number of cards in hand?
}
return choices;
}
}

View File

@@ -62,6 +62,7 @@ public enum SpellApiToAi {
.put(ApiType.Destroy, DestroyAi.class)
.put(ApiType.DestroyAll, DestroyAllAi.class)
.put(ApiType.Dig, DigAi.class)
.put(ApiType.DigMultiple, DigMultipleAi.class)
.put(ApiType.DigUntil, DigUntilAi.class)
.put(ApiType.Discard, DiscardAi.class)
.put(ApiType.DrainMana, DrainManaAi.class)

View File

@@ -0,0 +1,101 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class DigMultipleAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
Player opp = ai.getWeakestOpponent();
final Card host = sa.getHostCard();
Player libraryOwner = ai;
if (sa.usesTargeting()) {
sa.resetTargets();
if (!opp.canBeTargetedBy(sa)) {
return false;
} else {
sa.getTargets().add(opp);
}
libraryOwner = opp;
}
// return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
if ("Never".equals(sa.getParam("AILogic"))) {
return false;
} else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) {
return false;
}
}
// don't deck yourself
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
return false;
}
}
// Don't use draw abilities before main 2 if possible
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
if (SpellAbilityAi.playReusable(ai, sa)) {
return true;
}
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
&& !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
return !ComputerUtil.preventRunAwayActivations(sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent();
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
}

View File

@@ -60,6 +60,7 @@ public enum ApiType {
Destroy (DestroyEffect.class),
DestroyAll (DestroyAllEffect.class),
Dig (DigEffect.class),
DigMultiple (DigMultipleEffect.class),
DigUntil (DigUntilEffect.class),
Discard (DiscardEffect.class),
DrainMana (DrainManaEffect.class),

View File

@@ -66,8 +66,6 @@ public class DigEffect extends SpellAbilityEffect {
int destZone1ChangeNum = 1;
final boolean mitosis = sa.hasParam("Mitosis");
String changeValid = sa.hasParam("ChangeValid") ? sa.getParam("ChangeValid") : "";
//andOrValid is for cards with "creature card and/or a land card"
String andOrValid = sa.hasParam("AndOrValid") ? sa.getParam("AndOrValid") : "";
final boolean anyNumber = sa.hasParam("AnyNumber");
final int libraryPosition2 = sa.hasParam("LibraryPosition2") ? Integer.parseInt(sa.getParam("LibraryPosition2")) : -1;
@@ -173,28 +171,18 @@ public class DigEffect extends SpellAbilityEffect {
if (!noMove) {
CardCollection movedCards;
CardCollection andOrCards;
for (final Card c : top) {
rest.add(c);
}
CardCollection valid;
if (mitosis) {
valid = sharesNameWithCardOnBattlefield(game, top);
andOrCards = new CardCollection();
}
else if (!changeValid.isEmpty()) {
if (changeValid.contains("ChosenType")) {
changeValid = changeValid.replace("ChosenType", host.getChosenType());
}
valid = CardLists.getValidCards(top, changeValid.split(","), host.getController(), host, sa);
if (!andOrValid.equals("")) {
andOrCards = CardLists.getValidCards(top, andOrValid.split(","), host.getController(), host, sa);
andOrCards.removeAll((Collection<?>)valid);
valid.addAll(andOrCards); //pfps need to add andOr cards to valid to have set of all valid cards set up
}
else {
andOrCards = new CardCollection();
}
}
else {
// If all the cards are valid choices, no need for a separate reveal dialog to the chooser. pfps??
@@ -202,7 +190,6 @@ public class DigEffect extends SpellAbilityEffect {
delayedReveal = null;
}
valid = top;
andOrCards = new CardCollection();
}
if (forceRevealToController) {
@@ -282,16 +269,13 @@ public class DigEffect extends SpellAbilityEffect {
chooser.getController().tempShowCards(top);
}
List<Card> chosen = new ArrayList<Card>();
if (!andOrValid.equals("")) {
valid.removeAll(andOrCards); //pfps remove andOr cards to get two two choices set up correctly
chosen = chooser.getController().chooseFromTwoListsForEffect(valid, andOrCards, optional, delayedReveal, sa, prompt, p);
} else {
int max = anyNumber ? valid.size() : Math.min(valid.size(),destZone1ChangeNum);
int min = (anyNumber || optional) ? 0 : max;
if ( max > 0 ) { // if max is 0 don't make a choice
chosen = chooser.getController().chooseEntitiesForEffect(valid, min, max, delayedReveal, sa, prompt, p);
}
int max = anyNumber ? valid.size() : Math.min(valid.size(),destZone1ChangeNum);
int min = (anyNumber || optional) ? 0 : max;
if ( max > 0 ) { // if max is 0 don't make a choice
chosen = chooser.getController().chooseEntitiesForEffect(valid, min, max, delayedReveal, sa, prompt, p);
}
chooser.getController().endTempShowCards();
movedCards.addAll(chosen);
}

View File

@@ -0,0 +1,167 @@
package forge.game.ability.effects;
import java.util.Collections;
import java.util.Map;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
public class DigMultipleEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
// TODO Auto-generated method stub
final Card host = sa.getHostCard();
final Player player = sa.getActivatingPlayer();
final Game game = player.getGame();
Player chooser = player;
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
final ZoneType srcZone = sa.hasParam("SourceZone") ? ZoneType.smartValueOf(sa.getParam("SourceZone")) : ZoneType.Library;
final ZoneType destZone1 = sa.hasParam("DestinationZone") ? ZoneType.smartValueOf(sa.getParam("DestinationZone")) : ZoneType.Hand;
final ZoneType destZone2 = sa.hasParam("DestinationZone2") ? ZoneType.smartValueOf(sa.getParam("DestinationZone2")) : ZoneType.Library;
int libraryPosition = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : -1;
final int libraryPosition2 = sa.hasParam("LibraryPosition2") ? Integer.parseInt(sa.getParam("LibraryPosition2")) : -1;
String changeValid = sa.hasParam("ChangeValid") ? sa.getParam("ChangeValid") : "";
CardZoneTable table = new CardZoneTable();
for (final Player p : getTargetPlayers(sa)) {
if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) {
continue;
}
final CardCollection top = new CardCollection();
final CardCollection rest = new CardCollection();
final PlayerZone sourceZone = p.getZone(srcZone);
numToDig = Math.min(numToDig, sourceZone.size());
for (int i = 0; i < numToDig; i++) {
top.add(sourceZone.get(i));
}
if (top.isEmpty()) {
continue;
}
rest.addAll(top);
if (sa.hasParam("Reveal")) {
game.getAction().reveal(top, p, false);
} else {
// reveal cards first
game.getAction().revealTo(top, player);
}
Map<String, CardCollection> validMap = Maps.newHashMap();
for (final String valid : changeValid.split(",")) {
CardCollection list = CardLists.getValidCards(top, valid, host.getController(), host, sa);
if (!list.isEmpty()) {
validMap.put(valid, list);
}
}
if (validMap.isEmpty()) {
chooser.getController().notifyOfValue(sa, null, "No valid cards");
continue;
}
CardCollection chosen = chooser.getController().chooseCardsForEffectMultiple(validMap, sa, "Choose cards");
if (!chosen.isEmpty()) {
game.getAction().reveal(chosen, chooser, true,
chooser + " picked " + (chosen.size() == 1 ? "this card" : "these cards") + " from ");
}
for (Card c : chosen) {
final ZoneType origin = c.getZone().getZoneType();
final PlayerZone zone = c.getOwner().getZone(destZone1);
if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) {
if (libraryPosition == -1 || libraryPosition > zone.size()) {
libraryPosition = zone.size();
}
c = game.getAction().moveTo(zone, c, libraryPosition, sa);
}
else {
c = game.getAction().moveTo(zone, c, sa);
if (destZone1.equals(ZoneType.Battlefield)) {
if (sa.hasParam("Tapped")) {
c.setTapped(true);
}
}
}
if (!origin.equals(c.getZone().getZoneType())) {
table.put(origin, c.getZone().getZoneType(), c);
}
if (sa.hasParam("ExileFaceDown")) {
c.turnFaceDown(true);
}
if (sa.hasParam("Imprint")) {
host.addImprintedCard(c);
}
if (sa.hasParam("ForgetOtherRemembered")) {
host.clearRemembered();
}
if (sa.hasParam("RememberChanged")) {
host.addRemembered(c);
}
rest.remove(c);
}
// now, move the rest to destZone2
if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck || destZone2 == ZoneType.SchemeDeck
|| destZone2 == ZoneType.Graveyard) {
CardCollection afterOrder = rest;
if (sa.hasParam("RestRandomOrder")) {
CardLists.shuffle(afterOrder);
}
if (libraryPosition2 != -1) {
// Closest to top
Collections.reverse(afterOrder);
}
for (final Card c : afterOrder) {
final ZoneType origin = c.getZone().getZoneType();
Card m;
if (destZone2 == ZoneType.Library) {
m = game.getAction().moveToLibrary(c, libraryPosition2, sa);
}
else {
m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa);
}
if (m != null && !origin.equals(m.getZone().getZoneType())) {
table.put(origin, m.getZone().getZoneType(), m);
}
}
}
else {
// just move them randomly
for (int i = 0; i < rest.size(); i++) {
Card c = rest.get(i);
final ZoneType origin = c.getZone().getZoneType();
final PlayerZone toZone = c.getOwner().getZone(destZone2);
c = game.getAction().moveTo(toZone, c, sa);
if (!origin.equals(c.getZone().getZoneType())) {
table.put(origin, c.getZone().getZoneType(), c);
}
}
}
}
//table trigger there
table.triggerChangesZoneAll(game);
}
}

View File

@@ -115,7 +115,6 @@ public abstract class PlayerController {
Map<String, Object> params);
public abstract <T extends GameEntity> List<T> chooseEntitiesForEffect(FCollectionView<T> optionList, int min, int max, DelayedReveal delayedReveal, SpellAbility sa, String title, Player relatedPlayer);
public abstract <T extends GameEntity> List<T> chooseFromTwoListsForEffect(FCollectionView<T> optionList1, FCollectionView<T> optionList2, boolean optional, DelayedReveal delayedReveal, SpellAbility sa, String title, Player relatedPlayer);
public abstract boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message);
public abstract boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode bidlife, String string, int bid, Player winner);
@@ -273,4 +272,7 @@ public abstract class PlayerController {
public abstract List<OptionalCostValue> chooseOptionalCosts(SpellAbility choosen, List<OptionalCostValue> optionalCostValues);
public abstract boolean confirmMulliganScry(final Player p);
public abstract CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap,
SpellAbility sa, String title);
}

View File

@@ -180,12 +180,6 @@ public class PlayerControllerForTests extends PlayerController {
return null;
}
@Override
public <T extends GameEntity> List<T> chooseFromTwoListsForEffect(FCollectionView<T> optionList1, FCollectionView<T> optionList2, boolean optional, DelayedReveal delayedReveal, SpellAbility sa, String title, Player targetedPlayer) {
// this isn't used
return null;
}
@Override
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
@@ -703,4 +697,9 @@ public class PlayerControllerForTests extends PlayerController {
return 0;
}
@Override
public CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap, SpellAbility sa, String title) {
// TODO Auto-generated method stub
return new CardCollection();
}
}

View File

@@ -1,6 +1,6 @@
Name:Benefaction of Rhonas
ManaCost:2 G
Types:Sorcery
A:SP$ Dig | Cost$ 2 G | DigNum$ 5 | Reveal$ True | ChangeNum$ 2 | ChangeValid$ Creature | AndOrValid$ Enchantment | DestinationZone2$ Graveyard | Optional$ True | SpellDescription$ Reveal the top five cards of your library. You may put a creature card and/or an enchantment card from among them into your hand. Put the rest into your graveyard.
A:SP$ DigMultiple | Cost$ 2 G | DigNum$ 5 | Reveal$ True | ChangeValid$ Creature,Enchantment | DestinationZone2$ Graveyard | SpellDescription$ Reveal the top five cards of your library. You may put a creature card and/or an enchantment card from among them into your hand. Put the rest into your graveyard.
SVar:Picture:http://www.wizards.com/global/images/magic/general/benefaction_of_rhonas.jpg
Oracle:Reveal the top five cards of your library. You may put a creature card and/or an enchantment card from among them into your hand. Put the rest into your graveyard.

View File

@@ -1,8 +1,8 @@
Name:Domri's Ambush
ManaCost:R G
Types:Sorcery
A:SP$ PutCounter | Cost$ R G | ValidTgts$ Creature.YouCtrl | CounterType$ P1P1 | TgtPrompt$ Select target creature you control to put a +1/+1 counter | SubAbility$ DBDamage | AILogic$ Fight | SpellDescription$ Put a +1/+1 counter on target creature you control. Then that creature deals damage equal to its power to target creature or planeswalker you don't control.
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature.YouDontCtrl,Planeswalker.YouDontCtrl | TgtPrompt$ Select target creature or planeswalker you don't control | AILogic$ PowerDmg | NumDmg$ X | References$ X
A:SP$ PutCounter | Cost$ R G | ValidTgts$ Creature.YouCtrl | CounterType$ P1P1 | TgtPrompt$ Select target creature you control to put a +1/+1 counter | SubAbility$ DBDamage | AILogic$ PowerDmg | SpellDescription$ Put a +1/+1 counter on target creature you control. Then that creature deals damage equal to its power to target creature or planeswalker you don't control.
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature.YouDontCtrl,Planeswalker.YouDontCtrl | TgtPrompt$ Select target creature or planeswalker you don't control | AILogic$ PowerDmg | NumDmg$ X | References$ X | DamageSource$ ParentTarget
SVar:X:ParentTargeted$CardPower
DeckHas:Ability$Counters
Oracle:Put a +1/+1 counter on target creature you control. Then that creature deals damage equal to its power to target creature or planeswalker you don't control.

View File

@@ -1,6 +1,6 @@
Name:Gift of the Gargantuan
ManaCost:2 G
Types:Sorcery
A:SP$ Dig | Cost$ 2 G | DigNum$ 4 | ChangeValid$ Creature | AndOrValid$ Land | ChangeNum$ 2 | Optional$ True | SpellDescription$ Look at the top four cards of your library. You may reveal a creature card and/or a land card from among them and put the revealed cards into your hand. Put the rest on the bottom of your library in any order.
A:SP$ DigMultiple | Cost$ 2 G | DigNum$ 4 | ChangeValid$ Creature,Land | SpellDescription$ Look at the top four cards of your library. You may reveal a creature card and/or a land card from among them and put the revealed cards into your hand. Put the rest on the bottom of your library in any order.
SVar:Picture:http://www.wizards.com/global/images/magic/general/gift_of_the_gargantuan.jpg
Oracle:Look at the top four cards of your library. You may reveal a creature card and/or a land card from among them and put the revealed cards into your hand. Put the rest on the bottom of your library in any order.

View File

@@ -0,0 +1,9 @@
Name:Kaalia, Zenith Seeker
ManaCost:R W B
Types:Legendary Creature Human Cleric
PT:3/3
K:Flying
K:Vigilance
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDigMulti | TriggerDescription$ When CARDNAME enters the battlefield, look at the top six cards of your library. You may reveal an Angel card, a Demon card, and/or a Dragon card from among them and put them into your hand. Put the rest on the bottom of your library in a random order.
SVar:TrigDigMulti:DB$ DigMultiple | DigNum$ 6 | ChangeValid$ Card.Angel,Card.Demon,Card.Dragon | SourceZone$ Library | DestinationZone$ Hand | DestinationZone2$ Library | LibraryPosition$ -1 | RestRandomOrder$ True
Oracle:Flying, vigilance\nWhen Kaalia, Zenith Seeker enters the battlefield, look at the top six cards of your library. You may reveal an Angel card, a Demon card, and/or a Dragon card from among them and put them into your hand. Put the rest on the bottom of your library in a random order.

View File

@@ -4,7 +4,7 @@ Types:Legendary Planeswalker Kiora
Loyalty:4
A:AB$ Untap | Cost$ AddCounter<1/LOYALTY> | ValidTgts$ Creature | TgtPrompt$ Choose target creature | TargetMin$ 0 | TargetMax$ 1 | Planeswalker$ True | SubAbility$ DBUntap | SpellDescription$ Untap up to one target creature and up to one target land.
SVar:DBUntap:DB$ Untap | ValidTgts$ Land | TgtPrompt$ Choose target land | TargetMin$ 0 | TargetMax$ 1
A:AB$ Dig | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | DigNum$ 4 | Reveal$ True | ChangeNum$ 2 | ChangeValid$ Creature | AndOrValid$ Land | DestinationZone2$ Graveyard | Optional$ True | SpellDescription$ Reveal the top four cards of your library. You may put a creature card and/or a land card from among them into your hand. Put the rest into your graveyard.
A:AB$ DigMultiple | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | DigNum$ 4 | Reveal$ True | ChangeValid$ Creature,Land | DestinationZone2$ Graveyard | Optional$ True | SpellDescription$ Reveal the top four cards of your library. You may put a creature card and/or a land card from among them into your hand. Put the rest into your graveyard.
A:AB$ Effect | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem - Kiora, Master of the Depths | Image$ emblem_kiora_master_of_the_depths | Triggers$ TrigFight | SVars$ DBFight | Duration$ Permanent | AILogic$ Always | SubAbility$ DBToken | SpellDescription$ You get an emblem with "Whenever a creature enters the battlefield under your control, you may have it fight target creature." Then create three 8/8 blue Octopus creature tokens.
SVar:TrigFight:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | Execute$ DBFight | OptionalDecider$ You | TriggerZones$ Command | TriggerDescription$ Whenever a creature enters the battlefield under your control, you may have it fight target creature.
SVar:DBFight:DB$ Fight | Defined$ TriggeredCardLKICopy | ValidTgts$ Creature | TgtPrompt$ Choose target creature

View File

@@ -1,152 +0,0 @@
package forge.match.input;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.player.PlayerControllerHuman;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import forge.util.ITriggerEvent;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.game.zone.Zone;
import forge.FThreads;
public class InputSelectFromTwoLists<T extends GameEntity> extends InputSelectManyBase<T> {
private final FCollectionView<T> valid1, valid2;
private final FCollection<T> validBoth;
private FCollectionView<T> validChoices;
protected final FCollection<T> selected = new FCollection<T>();
protected final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
protected Iterable<PlayerZoneUpdate> zonesShown; // want to hide these zones when input done
public InputSelectFromTwoLists(final PlayerControllerHuman controller, final boolean optional,
final FCollectionView<T> list1, final FCollectionView<T> list2, final SpellAbility sa0) {
super(controller, optional?0:1, 2, sa0);
valid1 = list1;
valid2 = list2;
validBoth = new FCollection<T>(valid1);
for ( T v : valid2 ) { validBoth.add(v); }
validChoices = validBoth;
setSelectables();
for (final GameEntity c : validChoices) {
final Zone cz = (c instanceof Card) ? ((Card) c).getZone() : null ;
if ( cz != null ) {
zonesToUpdate.add(new PlayerZoneUpdate(cz.getPlayer().getView(),cz.getZoneType()));
}
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public void run() {
controller.getGui().updateZones(zonesToUpdate);
zonesShown = controller.getGui().tempShowZones(controller.getPlayer().getView(),zonesToUpdate);
}
});
}
private void setSelectables() {
ArrayList<CardView> vCards = new ArrayList<CardView>();
getController().getGui().clearSelectables();
for ( T c : validChoices ) {
if ( c instanceof Card ) {
vCards.add(((Card)c).getView()) ;
}
}
getController().getGui().setSelectables(vCards);
}
private void setValid() {
boolean selected1 = false, selected2 = false;
for ( T s : selected ) {
if ( valid1.contains(s) ) { selected1 = true; }
if ( valid2.contains(s) ) { selected2 = true; }
}
validChoices = selected1 ? ( selected2 ? FCollection.<T>getEmpty() : valid2 ) : ( selected2 ? valid1 : validBoth );
setSelectables();
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public void run() {
getController().getGui().updateZones(zonesToUpdate);
}
});
}
@Override
protected boolean onCardSelected(final Card c, final List<Card> otherCardsToSelect, final ITriggerEvent triggerEvent) {
if (!selectEntity(c)) {
return false;
}
refresh();
return true;
}
@Override
public String getActivateAction(final Card card) {
if (validChoices.contains(card)) {
if (selected.contains(card)) {
return "unselect card";
}
return "select card";
}
return null;
}
@Override
protected void onPlayerSelected(final Player p, final ITriggerEvent triggerEvent) {
if (!selectEntity(p)) {
return;
}
refresh();
}
@Override
public final Collection<T> getSelected() {
return selected;
}
@SuppressWarnings("unchecked")
protected boolean selectEntity(final GameEntity c) {
if (!validChoices.contains(c) && !selected.contains(c)) {
return false;
}
final boolean entityWasSelected = selected.contains(c);
if (entityWasSelected) {
selected.remove(c);
}
else {
selected.add((T)c);
}
setValid();
onSelectStateChanged(c, !entityWasSelected);
return true;
}
// might re-define later
@Override
protected boolean hasEnoughTargets() { return selected.size() >= min; }
@Override
protected boolean hasAllTargets() { return selected.size() >= max; }
@Override
protected String getMessage() {
return max == Integer.MAX_VALUE
? String.format(message, selected.size())
: String.format(message, max - selected.size());
}
@Override
protected void onStop() {
getController().getGui().hideZones(getController().getPlayer().getView(),zonesShown);
getController().getGui().clearSelectables();
super.onStop();
}
}

View File

@@ -511,41 +511,6 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return results;
}
@Override
public <T extends GameEntity> List<T> chooseFromTwoListsForEffect(final FCollectionView<T> optionList1, final FCollectionView<T> optionList2,
boolean optional, final DelayedReveal delayedReveal, final SpellAbility sa, final String title, final Player targetedPlayer) {
// Human is supposed to read the message and understand from it what to choose
// useful details for debugging problems with the mass select logic
Sentry.getContext().addExtra("Card", sa.getCardView().toString());
Sentry.getContext().addExtra("SpellAbility", sa.toString());
if (delayedReveal != null) {
tempShow(delayedReveal.getCards());
}
tempShow(optionList1);
tempShow(optionList2);
if (useSelectCardsInput(optionList1) && useSelectCardsInput(optionList2)) {
final InputSelectFromTwoLists<T> input = new InputSelectFromTwoLists<T>(this, optional, optionList1, optionList2, sa);
input.setCancelAllowed(optional);
input.setMessage(MessageUtil.formatMessage(title, player, targetedPlayer));
input.showAndWait();
endTempShowCards();
return (List<T>) input.getSelected();
}
final GameEntityView result1 = getGui().chooseSingleEntityForEffect(title, GameEntityView.getEntityCollection(optionList1), null, optional);
final GameEntityView result2 = getGui().chooseSingleEntityForEffect(title, GameEntityView.getEntityCollection(optionList2), null, (result1==null)?optional:true);
endTempShowCards();
List<T> results = new ArrayList<>();
GameEntity entity1 = convertToEntity(result1);
if (entity1!=null) { results.add((T) entity1); }
GameEntity entity2 = convertToEntity(result2);
if (entity2!=null) { results.add((T) entity2); }
return results;
}
@Override
public int chooseNumber(final SpellAbility sa, final String title, final int min, final int max) {
if (min >= max) {
@@ -2945,5 +2910,14 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return v == null ? 0 : v.intValue();
}
@Override
public CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap, SpellAbility sa, String title) {
CardCollection result = new CardCollection();
for (Map.Entry<String, CardCollection> e : validMap.entrySet()) {
result.addAll(chooseCardsForEffect(e.getValue(), sa, title + " " + e.getKey(), 0, 1, true));
}
return result;
}
}