...
 
Commits (133)
......@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.27-SNAPSHOT</version>
<version>1.6.28-SNAPSHOT</version>
</parent>
<artifactId>forge-ai</artifactId>
......
......@@ -99,6 +99,8 @@ public class ComputerUtil {
sa.resetPaidHash();
}
sa = GameActionUtil.addExtraKeywordCost(sa);
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
}
......@@ -208,9 +210,9 @@ public class ComputerUtil {
}
// this is used for AI's counterspells
public static final boolean playStack(final SpellAbility sa, final Player ai, final Game game) {
public static final boolean playStack(SpellAbility sa, final Player ai, final Game game) {
sa.setActivatingPlayer(ai);
if (!ComputerUtilCost.canPayCost(sa, ai))
if (!ComputerUtilCost.canPayCost(sa, ai))
return false;
final Card source = sa.getHostCard();
......@@ -220,6 +222,9 @@ public class ComputerUtil {
sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setHostCard(game.getAction().moveToStack(source, sa));
}
sa = GameActionUtil.addExtraKeywordCost(sa);
final Cost cost = sa.getPayCosts();
if (cost == null) {
ComputerUtilMana.payManaCost(ai, sa);
......@@ -249,13 +254,15 @@ public class ComputerUtil {
}
public static final boolean playSpellAbilityWithoutPayingManaCost(final Player ai, final SpellAbility sa, final Game game) {
final SpellAbility newSA = sa.copyWithNoManaCost();
SpellAbility newSA = sa.copyWithNoManaCost();
newSA.setActivatingPlayer(ai);
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
return false;
}
newSA = GameActionUtil.addExtraKeywordCost(newSA);
final Card source = newSA.getHostCard();
if (newSA.isSpell() && !source.isCopiedSpell()) {
source.setCastSA(newSA);
......@@ -275,7 +282,7 @@ public class ComputerUtil {
return true;
}
public static final void playNoStack(final Player ai, final SpellAbility sa, final Game game) {
public static final void playNoStack(final Player ai, SpellAbility sa, final Game game) {
sa.setActivatingPlayer(ai);
// TODO: We should really restrict what doesn't use the Stack
if (ComputerUtilCost.canPayCost(sa, ai)) {
......@@ -287,6 +294,8 @@ public class ComputerUtil {
sa.setHostCard(game.getAction().moveToStack(source, sa));
}
sa = GameActionUtil.addExtraKeywordCost(sa);
final Cost cost = sa.getPayCosts();
if (cost == null) {
ComputerUtilMana.payManaCost(ai, sa);
......@@ -797,7 +806,7 @@ public class ComputerUtil {
final int max = Math.min(remaining.size(), amount);
if (exceptSelf) {
if (exceptSelf && max < remaining.size()) {
removedSelf = remaining.remove(source.getHostCard());
}
......
......@@ -25,6 +25,7 @@ import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.cost.*;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid;
......@@ -180,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) {
......@@ -1253,4 +1240,38 @@ public class PlayerControllerAi extends PlayerController {
// Always true?
return true;
}
@Override
public int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt,
int max) {
// TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.)
int chosenAmount = 0;
Cost costSoFar = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero;
for (int i = 0; i < max; i++) {
costSoFar.add(cost);
SpellAbility fullCostSa = sa.copyWithDefinedCost(costSoFar);
if (ComputerUtilCost.canPayCost(fullCostSa, player)) {
chosenAmount++;
} else {
break;
}
}
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;
}
}
......@@ -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)
......
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;
}
}
......@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.27-SNAPSHOT</version>
<version>1.6.28-SNAPSHOT</version>
</parent>
<artifactId>forge-core</artifactId>
......
......@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.27-SNAPSHOT</version>
<version>1.6.28-SNAPSHOT</version>
</parent>
<artifactId>forge-game</artifactId>
......
......@@ -21,6 +21,8 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
......@@ -29,7 +31,9 @@ import forge.game.card.CardPlayOption.PayManaCost;
import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.spellability.*;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
......@@ -250,11 +254,6 @@ public final class GameActionUtil {
if (keyword.startsWith("Buyback")) {
final Cost cost = new Cost(keyword.substring(8), false);
costs.add(new OptionalCostValue(OptionalCost.Buyback, cost));
} else if (keyword.equals("Conspire")) {
final String conspireCost = "tapXType<2/Creature.SharesColorWith/" +
"untapped creature you control that shares a color with " + source.getName() + ">";
final Cost cost = new Cost(conspireCost, false);
costs.add(new OptionalCostValue(OptionalCost.Conspire, cost));
} else if (keyword.startsWith("Entwine")) {
String[] k = keyword.split(":");
final Cost cost = new Cost(k[1], false);
......@@ -301,15 +300,11 @@ public final class GameActionUtil {
}
final SpellAbility result = sa.copy();
for (OptionalCostValue v : list) {
// need to copy cost, otherwise it does alter the original
result.setPayCosts(result.getPayCosts().copy().add(v.getCost()));
result.getPayCosts().add(v.getCost());
result.addOptionalCost(v.getType());
// add some extra logic, try to move it to other parts
switch (v.getType()) {
case Conspire:
result.addConspireInstance();
break;
case Retrace:
case Jumpstart:
result.getRestrictions().setZone(ZoneType.Graveyard);
......@@ -363,6 +358,99 @@ public final class GameActionUtil {
return abilities;
}
public static SpellAbility addExtraKeywordCost(final SpellAbility sa) {
if (!sa.isSpell() || sa.isCopied()) {
return sa;
}
SpellAbility result = null;
final Card host = sa.getHostCard();
final Player activator = sa.getActivatingPlayer();
final PlayerController pc = activator.getController();
host.getGame().getAction().checkStaticAbilities(false);
boolean reset = false;
for (KeywordInterface ki : host.getKeywords()) {
final String o = ki.getOriginal();
if (o.equals("Conspire")) {
Trigger tr = Iterables.getFirst(ki.getTriggers(), null);
if (tr != null) {
final String conspireCost = "tapXType<2/Creature.SharesColorWith/" +
"untapped creature you control that shares a color with " + host.getName() + ">";
final Cost cost = new Cost(conspireCost, false);
String str = "Pay for Conspire? " + cost.toSimpleString();
boolean v = pc.addKeywordCost(sa, cost, ki, str);
tr.setSVar("Conspire", v ? "1" : "0");
if (v) {
if (result == null) {
result = sa.copy();
}
result.getPayCosts().add(cost);
reset = true;
}
}
} else if (o.startsWith("Replicate")) {
Trigger tr = Iterables.getFirst(ki.getTriggers(), null);
if (tr != null) {
String costStr = o.split(":")[1];
final Cost cost = new Cost(costStr, false);
String str = "Choose Amount for Replicate: " + cost.toSimpleString();
int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE);
tr.setSVar("ReplicateAmount", String.valueOf(v));
tr.getOverridingAbility().setSVar("ReplicateAmount", String.valueOf(v));
for (int i = 0; i < v; i++) {
if (result == null) {
result = sa.copy();
}
result.getPayCosts().add(cost);
reset = true;
}
}
}
}
if (host.isCreature()) {
String kw = "As an additional cost to cast creature spells," +
" you may pay any amount of mana. If you do, that creature enters " +
"the battlefield with that many additional +1/+1 counters on it.";
for (final Card c : activator.getZone(ZoneType.Battlefield)) {
for (KeywordInterface ki : c.getKeywords()) {
if (kw.equals(ki.getOriginal())) {
final Cost cost = new Cost(ManaCost.ONE, false);
String str = "Choose Amount for " + c.getName() + ": " + cost.toSimpleString();
int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE);
if (v > 0) {
host.addReplacementEffect(CardFactoryUtil.makeEtbCounter("etbCounter:P1P1:" + v, host, false));
if (result == null) {
result = sa.copy();
}
for (int i = 0; i < v; i++) {
result.getPayCosts().add(cost);
}
}
}
}
}
}
// reset active Trigger
if (reset) {
host.getGame().getTriggerHandler().resetActiveTriggers(false);
}
return result != null ? result : sa;
}
private static boolean hasUrzaLands(final Player p) {
final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield);
return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine")))
......
......@@ -1843,7 +1843,11 @@ public class AbilityUtils {
public static SpellAbility addSpliceEffects(final SpellAbility sa) {
final Card source = sa.getHostCard();
final Player player = sa.getActivatingPlayer();
if (!sa.isSpell() || source.isCopiedSpell()) {
return sa;
}
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
if (hand.isEmpty()) {
......
......@@ -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),
......
......@@ -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);
}
......
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);
}
}
......@@ -1905,13 +1905,14 @@ public class Card extends GameEntity implements Comparable<Card> {
}
sb.append(keywordsToText(getUnhiddenKeywords(state)));
// Process replacement effects first so that ETB tabbed can be printed
// Process replacement effects first so that "enters the battlefield tapped"
// and "as ~ enters the battlefield, choose...", etc can be printed
// here. The rest will be printed later.
StringBuilder replacementEffects = new StringBuilder();
for (final ReplacementEffect replacementEffect : state.getReplacementEffects()) {
if (!replacementEffect.isSecondary()) {
String text = replacementEffect.toString();
if (text.equals("CARDNAME enters the battlefield tapped.")) {
if (text.contains("enters the battlefield")) {
sb.append(text).append("\r\n");
} else {
replacementEffects.append(text).append("\r\n");
......
......@@ -2125,7 +2125,7 @@ public class CardFactoryUtil {
}
private static ReplacementEffect makeEtbCounter(final String kw, final Card card, final boolean intrinsic)
public static ReplacementEffect makeEtbCounter(final String kw, final Card card, final boolean intrinsic)
{
String parse = kw;
......@@ -2361,11 +2361,12 @@ public class CardFactoryUtil {
inst.addTrigger(parsedTrigger);
inst.addTrigger(parsedTrigReturn);
} else if (keyword.equals("Conspire")) {
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | Conspire$ True | Secondary$ True | TriggerDescription$ Copy CARDNAME if its conspire cost was paid";
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Conspire | Secondary$ True | TriggerDescription$ Copy CARDNAME if its conspire cost was paid";
final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ 1";
final Trigger conspireTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic);
conspireTrigger.setOverridingAbility(AbilityFactory.getAbility(abString, card));
conspireTrigger.setSVar("Conspire", "0");
inst.addTrigger(conspireTrigger);
} else if (keyword.startsWith("Cumulative upkeep")) {
final String[] k = keyword.split(":");
......@@ -2942,6 +2943,17 @@ public class CardFactoryUtil {
+ " exile CARDNAME. | Secondary$ True";
final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
inst.addTrigger(myTrigger);
} else if (keyword.startsWith("Replicate")) {
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ ReplicateAmount | Secondary$ True | TriggerDescription$ Copy CARDNAME for each time you paid its replicate cost";
final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ ReplicateAmount";
final Trigger replicateTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic);
final SpellAbility replicateAbility = AbilityFactory.getAbility(abString, card);
replicateAbility.setSVar("ReplicateAmount", "0");
replicateTrigger.setOverridingAbility(replicateAbility);
replicateTrigger.setSVar("ReplicateAmount", "0");
inst.addTrigger(replicateTrigger);
} else if (keyword.startsWith("Ripple")) {
final String[] k = keyword.split(":");
final String num = k[1];
......@@ -4225,9 +4237,6 @@ public class CardFactoryUtil {
sa.setTemporary(!intrinsic);
inst.addSpellAbility(sa);
} else if (keyword.startsWith("Replicate")) {
card.getFirstSpellAbility().addAnnounceVar("Replicate");
} else if (keyword.startsWith("Scavenge")) {
final String[] k = keyword.split(":");
final String manacost = k[1];
......
......@@ -19,6 +19,7 @@ import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
import forge.game.replacement.ReplacementEffect;
......@@ -47,7 +48,6 @@ public abstract class PlayerController {
DeclareBlocker,
Echo,
Multikicker,
Replicate,
CumulativeUpkeep,
}
......@@ -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);
......@@ -182,6 +181,11 @@ public abstract class PlayerController {
public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard);
public abstract boolean payManaOptional(Card card, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose);
public abstract int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt, int max);
public boolean addKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt) {
return chooseNumberForKeywordCost(sa, cost, keyword, prompt, 1) == 1;
}
public abstract int chooseNumber(SpellAbility sa, String title, int min, int max);
public abstract int chooseNumber(SpellAbility sa, String title, List<Integer> values, Player relatedPlayer);
public int chooseNumber(SpellAbility sa, String string, int min, int max, Map<String, Object> params) {
......@@ -268,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);
}
......@@ -5,7 +5,6 @@ package forge.game.spellability;
*
*/
public enum OptionalCost {
Conspire("Conspire"),
Buyback("Buyback"),
Entwine("Entwine"),
Kicker1("Kicker"),
......
......@@ -144,7 +144,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private CardCollection tappedForConvoke = new CardCollection();
private Card sacrificedAsOffering = null;
private Card sacrificedAsEmerge = null;
private int conspireInstances = 0;
private AbilityManaPart manaPart = null;
......@@ -301,9 +300,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
public boolean canPlayWithOptionalCost(OptionalCostValue opt) {
SpellAbility saCopy = this.copy();
saCopy = GameActionUtil.addOptionalCosts(saCopy, Lists.newArrayList(opt));
return saCopy.canPlay();
return GameActionUtil.addOptionalCosts(this, Lists.newArrayList(opt)).canPlay();
}
public boolean isPossible() {
......@@ -1700,19 +1697,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return ForgeScript.spellAbilityHasProperty(this, property, sourceController, source, spellAbility);
}
// Methods enabling multiple instances of conspire
public void addConspireInstance() {
conspireInstances++;
}
public void subtractConspireInstance() {
conspireInstances--;
}
public int getConspireInstances() {
return conspireInstances;
} // End of Conspire methods
public boolean isCumulativeupkeep() {
return cumulativeupkeep;
}
......
......@@ -32,7 +32,6 @@ import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices;
......@@ -216,18 +215,6 @@ public class TriggerSpellAbilityCast extends Trigger {
}
}
if (hasParam("Conspire")) {
if (!spellAbility.isOptionalCostPaid(OptionalCost.Conspire)) {
return false;
}
if (spellAbility.getConspireInstances() == 0) {
return false;
} else {
spellAbility.subtractConspireInstance();
//System.out.println("Conspire instances left = " + spellAbility.getConspireInstances());
}
}
if (hasParam("Outlast")) {
if (!spellAbility.isOutlast()) {
return false;
......
......@@ -28,24 +28,19 @@ import java.util.concurrent.LinkedBlockingDeque;
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import forge.GameCommand;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.GameObject;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardPredicates;
import forge.game.cost.Cost;
import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.event.GameEventSpellAbilityCast;
......@@ -54,11 +49,6 @@ import forge.game.event.GameEventSpellResolved;
import forge.game.event.GameEventZone;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.Spell;
......@@ -282,72 +272,8 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
si = push(sp);
}
else {
if (sp.isSpell() && source.isCreature() && Iterables.any(activator.getCardsIn(ZoneType.Battlefield),
CardPredicates.hasKeyword("As an additional cost to cast creature spells," +
" you may pay any amount of mana. If you do, that creature enters " +
"the battlefield with that many additional +1/+1 counters on it."))) {
final Cost costPseudoKicker = new Cost(ManaCost.ONE, false);
boolean hasPaid = false;
do {
int mkMagnitude = source.getPseudoKickerMagnitude();
String prompt = TextUtil.concatWithSpace("Additional Cost for",source.toString(),"\r\nTimes Kicked:", String.valueOf(mkMagnitude),"\r\n");
hasPaid = activator.getController().payManaOptional(source, costPseudoKicker, sp, prompt, ManaPaymentPurpose.Multikicker);
if (hasPaid) {
source.addPseudoMultiKickerMagnitude(1);
totManaSpent += 1;
}
} while (hasPaid);
if (source.getPseudoKickerMagnitude() > 0) {
String abStr = "DB$ PutCounter | Defined$ Self | ETB$ True | CounterType$ P1P1 | CounterNum$ "
+ source.getPseudoKickerMagnitude() + " | SubAbility$ ChorusDBETBCounters";
String dbStr = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield"
+ "| Defined$ ReplacedCard";
source.setSVar("ChorusETBCounters", abStr);
source.setSVar("ChorusDBETBCounters", dbStr);
String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield "
+ "| ReplaceWith$ ChorusETBCounters | Secondary$ True | Description$ CARDNAME"
+ " enters the battlefield with " + source.getPseudoKickerMagnitude() + " +1/+1 counters.";
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, source, false);
re.setLayer(ReplacementLayer.Other);
source.addReplacementEffect(re);
}
}
// The ability is added to stack HERE
si = push(sp);
if (sp.isSpell() && (source.hasStartOfKeyword("Replicate")
|| ((source.isInstant() || source.isSorcery()) && Iterables.any(activator.getCardsIn(ZoneType.Battlefield),
CardPredicates.hasKeyword("Each instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost."))))) {
Integer magnitude = sp.getSVarInt("Replicate");
if (magnitude == null) {
magnitude = 0;
final Cost costReplicate = new Cost(source.getManaCost(), false);
boolean hasPaid = false;
int replicateCMC = source.getManaCost().getCMC();
do {
String prompt = TextUtil.concatWithSpace("Replicate for", source.toString(),"\r\nTimes Replicated:", magnitude.toString(),"\r\n");
hasPaid = activator.getController().payManaOptional(source, costReplicate, sp, prompt, ManaPaymentPurpose.Replicate);
if (hasPaid) {
magnitude++;
totManaSpent += replicateCMC;
}
} while (hasPaid);
}
// Replicate Trigger
String effect = TextUtil.concatWithSpace("DB$ CopySpellAbility | Cost$ 0 | Defined$ Parent | Amount$", magnitude.toString());
AbilitySub sa = (AbilitySub) AbilityFactory.getAbility(effect, source);
sa.setParent(sp);
sa.setDescription("Replicate - " + source);
sa.setTrigger(true);
sa.setCopied(true);
addSimultaneousStackEntry(sa);
}
}
}
......
......@@ -6,7 +6,7 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms1024m</build.min.memory>
<build.max.memory>-Xmx1536m</build.max.memory>
<alpha-version>1.6.26.001</alpha-version>
<alpha-version>1.6.27.001</alpha-version>
<sign.keystore>keystore</sign.keystore>
<sign.alias>alias</sign.alias>
<sign.storepass>storepass</sign.storepass>
......@@ -19,7 +19,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.27-SNAPSHOT</version>
<version>1.6.28-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-android</artifactId>
......
......@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.27-SNAPSHOT</version>
<version>1.6.28-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-desktop</artifactId>
......
......@@ -1042,7 +1042,7 @@ public final class CMatchUI
if (delayedReveal != null) {
reveal(delayedReveal.getMessagePrefix(), delayedReveal.getCards()); //TODO: Merge this into search dialog
}
return (List<GameEntityView>) order(title,"Selected", min, max, optionList, null, null, false);
return (List<GameEntityView>) order(title,"Selected", optionList.size() - max, optionList.size() - min, optionList, null, null, false);
}
@Override
......
......@@ -29,6 +29,7 @@ import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid;
......@@ -179,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;
......@@ -695,4 +690,16 @@ public class PlayerControllerForTests extends PlayerController {
return false;
}
@Override
public int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt,
int max) {
// TODO Auto-generated method stub
return 0;
}
@Override
public CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap, SpellAbility sa, String title) {
// TODO Auto-generated method stub
return new CardCollection();
}
}
......@@ -6,13 +6,13 @@
<packaging.type>jar</packaging.type>
<build.min.memory>-Xms128m</build.min.memory>
<build.max.memory>-Xmx2048m</build.max.memory>
<alpha-version>1.6.26.001</alpha-version>
<alpha-version>1.6.27.001</alpha-version>
</properties>
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.27-SNAPSHOT</version>
<version>1.6.28-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-ios</artifactId>
......
......@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.27-SNAPSHOT</version>
<version>1.6.28-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile-dev</artifactId>
......
......@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.27-SNAPSHOT</version>
<version>1.6.28-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile</artifactId>
......
......@@ -34,7 +34,7 @@ import java.util.List;
import java.util.Stack;
public class Forge implements ApplicationListener {
public static final String CURRENT_VERSION = "1.6.26.001";
public static final String CURRENT_VERSION = "1.6.27.001";
private static final ApplicationListener app = new Forge();
private static Clipboard clipboard;
......
......@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.27-SNAPSHOT</version>
<version>1.6.28-SNAPSHOT</version>
</parent>
<artifactId>forge-gui</artifactId>
......