...
 
Commits (83)
......@@ -150,12 +150,15 @@ public class AiCardMemory {
*/
public boolean isRememberedCardByName(String cardName, MemorySet set) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName)) {
return true;
if (memorySet != null) {
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName)) {
return true;
}
}
}
......@@ -174,12 +177,15 @@ public class AiCardMemory {
*/
public boolean isRememberedCardByName(String cardName, MemorySet set, Player owner) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
return true;
if (memorySet != null) {
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
return true;
}
}
}
......@@ -197,7 +203,12 @@ public class AiCardMemory {
if (c == null)
return false;
getMemorySet(set).add(c);
Set<Card> memorySet = getMemorySet(set);
if (memorySet != null) {
memorySet.add(c);
}
return true;
}
......@@ -216,7 +227,12 @@ public class AiCardMemory {
return false;
}
getMemorySet(set).remove(c);
Set<Card> memorySet = getMemorySet(set);
if (memorySet != null) {
memorySet.remove(c);
}
return true;
}
......@@ -229,12 +245,15 @@ public class AiCardMemory {
*/
public boolean forgetAnyCardWithName(String cardName, MemorySet set) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName)) {
return forgetCard(c, set);
if (memorySet != null) {
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName)) {
return forgetCard(c, set);
}
}
}
......@@ -251,15 +270,18 @@ public class AiCardMemory {
*/
public boolean forgetAnyCardWithName(String cardName, MemorySet set, Player owner) {
Set<Card> memorySet = getMemorySet(set);
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
return forgetCard(c, set);
if (memorySet != null) {
Iterator<Card> it = memorySet.iterator();
while (it.hasNext()) {
Card c = it.next();
if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
return forgetCard(c, set);
}
}
}
return false;
}
......@@ -269,14 +291,16 @@ public class AiCardMemory {
* @return true, if the given memory set contains no remembered cards.
*/
public boolean isMemorySetEmpty(MemorySet set) {
return getMemorySet(set).isEmpty();
return set == null ? true : getMemorySet(set).isEmpty();
}
/**
* Clears the given memory set.
*/
public void clearMemorySet(MemorySet set) {
getMemorySet(set).clear();
if (set != null) {
getMemorySet(set).clear();
}
}
/**
......
......@@ -655,10 +655,10 @@ public class AiController {
return false;
}
AiCardMemory.MemorySet memSet;
AiCardMemory.MemorySet memSet = null;
if (phaseType == null && forNextSpell) {
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL;
} else {
} else if (phaseType != null) {
switch (phaseType) {
case MAIN2:
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
......
......@@ -1612,7 +1612,7 @@ public class ComputerUtil {
}
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
if (saviour.getParam("CounterType").equals("P1P1")) {
if (saviour != null && saviour.getParam("CounterType").equals("P1P1")) {
toughness = AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("CounterNum"), saviour);
} else {
return threatened;
......
......@@ -228,7 +228,7 @@ public class ComputerUtilCard {
Card cheapest = null;
for (Card c : all) {
if (cheapest == null || cheapest.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
if (cheapest == null || c.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
cheapest = c;
}
}
......@@ -926,7 +926,7 @@ public class ComputerUtilCard {
}
else if (logic.equals("MostProminentInComputerDeckButGreen")) {
List<String> prominence = ComputerUtilCard.getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
if (prominence.get(0) == MagicColor.Constant.GREEN) {
if (prominence.get(0).equals(MagicColor.Constant.GREEN)) {
chosen.add(prominence.get(1));
} else {
chosen.add(prominence.get(0));
......@@ -1878,7 +1878,7 @@ public class ComputerUtilCard {
// A special case which checks that this creature will attack if it's the AI's turn
if (needsToPlay.equalsIgnoreCase("WillAttack")) {
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
if (sa != null && game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(sa.getActivatingPlayer(), card) ?
AiPlayDecision.WillPlay : AiPlayDecision.BadEtbEffects;
} else {
......
......@@ -631,7 +631,9 @@ public abstract class GameState {
// Note: triggers may fire during combat declarations ("whenever X attacks, ...", etc.)
if (newPhase == PhaseType.COMBAT_DECLARE_ATTACKERS || newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS) {
boolean toDeclareBlockers = newPhase == PhaseType.COMBAT_DECLARE_BLOCKERS;
handleCombat(game, newPlayerTurn, newPlayerTurn.getSingleOpponent(), toDeclareBlockers);
if (newPlayerTurn != null) {
handleCombat(game, newPlayerTurn, newPlayerTurn.getSingleOpponent(), toDeclareBlockers);
}
}
game.getStack().setResolving(false);
......@@ -891,7 +893,9 @@ public abstract class GameState {
}
}
sa.setActivatingPlayer(c.getController());
if (sa != null) {
sa.setActivatingPlayer(c.getController());
}
handleScriptedTargetingForSA(game, sa, tgtID);
if (putOnStack) {
......
......@@ -733,6 +733,7 @@ public class PlayerControllerAi extends PlayerController {
return true;
}
}
break;
case "BetterTgtThanRemembered":
if (source.getRememberedCount() > 0) {
Card rem = (Card) source.getFirstRemembered();
......@@ -746,6 +747,7 @@ public class PlayerControllerAi extends PlayerController {
}
return false;
}
break;
default:
break;
}
......@@ -939,6 +941,10 @@ public class PlayerControllerAi extends PlayerController {
final Card source = sa.getHostCard();
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
emptyAbility.setActivatingPlayer(player);
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
for (String sVar : sa.getSVars()) {
emptyAbility.setSVar(sVar, sa.getSVar(sVar));
}
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
ComputerUtil.playNoStack(player, emptyAbility, game); // AI needs something to resolve to pay that cost
return true;
......
......@@ -172,7 +172,7 @@ public class SpecialCardAi {
}
}
return best.getName();
return best != null ? best.getName() : "";
}
}
......@@ -1231,7 +1231,7 @@ public class SpecialCardAi {
// no options with smaller CMC, so discard the one that is harder to cast for the one that is
// easier to cast right now, but only if the best card in the library is at least CMC 3
// (probably not worth it to grab low mana cost cards this way)
if (maxCMC != null && maxCMC.getCMC() < bestInLib.getCMC() && bestInLib.getCMC() >= 3) {
if (maxCMC != null && bestInLib != null && maxCMC.getCMC() < bestInLib.getCMC() && bestInLib.getCMC() >= 3) {
return maxCMC;
}
// We appear to be playing Reanimator (or we have a reanimator card in hand already), so it's
......
......@@ -70,6 +70,7 @@ public enum SpellApiToAi {
.put(ApiType.EachDamage, DamageEachAi.class)
.put(ApiType.Effect, EffectAi.class)
.put(ApiType.Encode, EncodeAi.class)
.put(ApiType.EndCombatPhase, EndTurnAi.class)
.put(ApiType.EndTurn, EndTurnAi.class)
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
.put(ApiType.ExchangeLifeVariant, LifeExchangeVariantAi.class)
......
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
......@@ -79,37 +84,22 @@ public class ManifestAi extends SpellAbilityAi {
return false;
}
}
return true;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
static boolean shouldManyfest(final Card card, final Player ai, final SpellAbility sa) {
final Game game = ai.getGame();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
// Library is empty, no Manifest
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
if (library.isEmpty())
return false;
// try not to mill himself with Manifest
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
return false;
}
// check to ensure that there are no replacement effects that prevent creatures ETBing from library
// (e.g. Grafdigger's Cage)
Card topCopy = CardUtil.getLKICopy(library.getFirst());
Card topCopy = CardUtil.getLKICopy(card);
topCopy.turnFaceDownNoUpdate();
topCopy.setManifested(true);
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "Moved");
repParams.put("Affected", topCopy);
repParams.put("Origin", ZoneType.Library);
repParams.put("Origin", card.getZone().getZoneType());
repParams.put("Destination", ZoneType.Battlefield);
repParams.put("Source", sa.getHostCard());
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(repParams, ReplacementLayer.Other);
......@@ -117,27 +107,62 @@ public class ManifestAi extends SpellAbilityAi {
return false;
}
// if the AI can see the top card of the library, check it
final Card topCard = library.getFirst();
if (topCard.mayPlayerLook(ai)) {
if (card.mayPlayerLook(ai)) {
// try to avoid manifest a non Permanent
if (!topCard.isPermanent())
if (!card.isPermanent())
return false;
// do not manifest a card with X in its cost
if (topCard.getManaCost().countX() > 0)
if (card.getManaCost().countX() > 0)
return false;
// try to avoid manifesting a creature with zero or less thoughness
if (topCard.isCreature() && topCard.getNetToughness() <= 0)
if (card.isCreature() && card.getNetToughness() <= 0)
return false;
// card has ETBTrigger or ETBReplacement
if (topCard.hasETBTrigger(false) || topCard.hasETBReplacement()) {
if (card.hasETBTrigger(false) || card.hasETBReplacement()) {
return false;
}
}
return true;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Game game = ai.getGame();
final Card host = sa.getHostCard();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.hasParam("Choices") || sa.hasParam("ChoiceZone")) {
ZoneType choiceZone = ZoneType.Hand;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
CardCollection choices = new CardCollection(game.getCardsIn(choiceZone));
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), ai, host);
}
if (choices.isEmpty()) {
return false;
}
} else {
// Library is empty, no Manifest
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
if (library.isEmpty())
return false;
// try not to mill himself with Manifest
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
return false;
}
if (!shouldManyfest(library.getFirst(), ai, sa)) {
return false;
}
}
// Probably should be a little more discerning on playing during OPPs turn
if (SpellAbilityAi.playReusable(ai, sa)) {
return true;
......@@ -152,4 +177,26 @@ public class ManifestAi extends SpellAbilityAi {
return MyRandom.getRandom().nextFloat() < .8;
}
protected Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
if (Iterables.size(options) <= 1) {
return Iterables.getFirst(options, null);
}
CardCollection filtered = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
if (shouldManyfest(input, ai, sa)) {
return false;
}
return true;
}
});
if (!filtered.isEmpty()) {
return ComputerUtilCard.getBestAI(filtered);
}
if (isOptional) {
return null;
}
return Iterables.getFirst(options, null);
}
}
......@@ -283,7 +283,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
return "n/a";
}
final String toReturn = MagicColor.toLongString(myColor);
if (toReturn == MagicColor.Constant.COLORLESS && myColor != 0) {
if (toReturn.equals(MagicColor.Constant.COLORLESS) && myColor != 0) {
return "multi";
}
return toReturn;
......
......@@ -306,7 +306,7 @@ public class BoosterGenerator {
// 1 out of ~30 normal and mythic rares are foil,
// match that.
// If not special card, make it always foil.
if ((MyRandom.getRandom().nextInt(30) == 1) || (foilSlot != BoosterSlots.SPECIAL)) {
if ((MyRandom.getRandom().nextInt(30) == 1) || (!foilSlot.equals(BoosterSlots.SPECIAL))) {
foilCardGeneratedAndHeld.add(generateFoilCard(ps));
} else {
// Otherwise it's not foil (even though this is the
......
......@@ -106,11 +106,8 @@ public final class FileUtil {
File source = new File(sourceFilename);
if (!source.exists()) { return; } //if source doesn't exist, nothing to copy
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream(source);
os = new FileOutputStream(new File(destFilename));
try (InputStream is = new FileInputStream(source);
OutputStream os = new FileOutputStream(new File(destFilename))){
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
......@@ -120,15 +117,6 @@ public final class FileUtil {
catch (Exception e) {
e.printStackTrace();
}
finally {
try {
is.close();
os.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
public static void writeFile(String filename, String text) {
......@@ -136,10 +124,8 @@ public final class FileUtil {
}
public static void writeFile(File file, String text) {
try {
PrintWriter p = new PrintWriter(file);
try (PrintWriter p = new PrintWriter(file)) {
p.print(text);
p.close();
} catch (final Exception ex) {
throw new RuntimeException("FileUtil : writeFile() error, problem writing file - " + file + " : " + ex);
}
......@@ -174,12 +160,10 @@ public final class FileUtil {
* a {@link java.util.List} object.
*/
public static void writeFile(File file, Collection<?> data) {
try {
PrintWriter p = new PrintWriter(file);
try (PrintWriter p = new PrintWriter(file)) {
for (Object o : data) {
p.println(o);
}
p.close();
} catch (final Exception ex) {
throw new RuntimeException("FileUtil : writeFile() error, problem writing file - " + file + " : " + ex);
}
......@@ -291,10 +275,11 @@ public final class FileUtil {
ThreadUtil.executeWithTimeout(new Callable<Void>() {
@Override
public Void call() throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
String line;
while ((line = in.readLine()) != null) {
lines.add(line);
try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()))) {
String line;
while ((line = in.readLine()) != null) {
lines.add(line);
}
}
return null;
}
......
......@@ -311,6 +311,7 @@ public final class GameActionUtil {
break;
case Flash:
result.getRestrictions().setInstantSpeed(true);
break;
default:
break;
}
......
......@@ -370,7 +370,7 @@ public class GameFormat implements Comparable<GameFormat> {
rarities = Lists.newArrayList();
for (String s: Arrays.asList(strCars.split(", "))) {
cr = CardRarity.smartValueOf(s);
if (cr.name() != "Unknown") {
if (!cr.name().equals("Unknown")) {
rarities.add(cr);
}
}
......
......@@ -1002,6 +1002,10 @@ public class StaticEffect {
affectedCard.removeTextChangeState(getTimestamp());
}
if (params.containsKey("Goad")) {
affectedCard.removeGoad(getTimestamp());
}
affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
}
return affectedCards;
......
......@@ -68,6 +68,7 @@ public enum ApiType {
EachDamage (DamageEachEffect.class),
Effect (EffectEffect.class),
Encode (EncodeEffect.class),
EndCombatPhase (EndCombatPhaseEffect.class),
EndTurn (EndTurnEffect.class),
ExchangeLife (LifeExchangeEffect.class),
ExchangeLifeVariant (LifeExchangeVariantEffect.class),
......
......@@ -180,7 +180,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
sb.append(num).append(" of those ").append(type).append(" card(s)");
} else {
sb.append(destination.equals("Exile") ? " exiles " : " puts ");
if (type == "Card") {
if (type.equals("Card")) {
sb.append(num);
} else {
sb.append(num).append(" ").append(type);
......@@ -208,7 +208,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
sb.append(" of ").append(fetchPlayer);
if (fetchPlayer != "their") {
if (!fetchPlayer.equals("their")) {
sb.append("'s");
}
sb.append(" library");
......
......@@ -8,16 +8,20 @@ import com.google.common.collect.Lists;
import forge.GameCommand;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.event.GameEventCombatChanged;
import forge.game.player.Player;
import forge.game.spellability.Ability;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.collect.FCollectionView;
public class ControlGainEffect extends SpellAbilityEffect {
/* (non-Javadoc)
......@@ -81,6 +85,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
final boolean bNoRegen = sa.hasParam("NoRegen");
final boolean remember = sa.hasParam("RememberControlled");
final boolean forget = sa.hasParam("ForgetControlled");
final boolean attacking = sa.hasParam("Attacking");
final List<String> destroyOn = sa.hasParam("DestroyTgt") ? Arrays.asList(sa.getParam("DestroyTgt").split(",")) : null;
final List<String> keywords = sa.hasParam("AddKWs") ? Arrays.asList(sa.getParam("AddKWs").split(" & ")) : null;
final List<String> lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null;
......@@ -164,6 +169,10 @@ public class ControlGainEffect extends SpellAbilityEffect {
game.getEndOfTurn().addUntil(loseControl);
tgtC.setSVar("SacMe", "6");
}
if (lose.contains("EndOfCombat")) {
game.getEndOfCombat().addUntil(loseControl);
tgtC.setSVar("SacMe", "6");
}
if (lose.contains("StaticCommandCheck")) {
String leftVar = sa.getSVar(sa.getParam("StaticCommandCheckSVar"));
String rightVar = sa.getParam("StaticCommandSVarCompare");
......@@ -211,6 +220,22 @@ public class ControlGainEffect extends SpellAbilityEffect {
}
game.getAction().controllerChangeZoneCorrection(tgtC);
if (attacking) {
final Combat combat = game.getCombat();
if ( null != combat ) {
final FCollectionView<GameEntity> e = combat.getDefenders();
final GameEntity defender = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(e, sa,
"Declare a defender for " + tgtC);
if (defender != null) {
combat.addAttacker(tgtC, defender);
game.getCombat().getBandOfAttacker(tgtC).setBlocked(false);
game.fireEvent(new GameEventCombatChanged());
}
}
}
} // end foreach target
}
......
package forge.game.ability.effects;
import com.google.common.collect.Lists;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
public class EndCombatPhaseEffect extends SpellAbilityEffect {
/*
* (non-Javadoc)
* @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
*/
@Override
public void resolve(SpellAbility sa) {
Game game = sa.getActivatingPlayer().getGame();
// Steps taken from gatherer's rulings on Time Stop.
// 1) All spells and abilities on the stack are exiled. This includes
// Time Stop, though it will continue to resolve. It also includes
// spells and abilities that can't be countered.
for (final Card c : Lists.newArrayList(game.getStackZone().getCards())) {
game.getAction().exile(c, sa);
}
game.getStack().clear();
game.getStack().clearSimultaneousStack();
game.getTriggerHandler().clearWaitingTriggers();
// 2) All attacking and blocking creatures are removed from combat.
//game.getPhaseHandler().endCombat();
// 3) State-based actions are checked. No player gets priority, and no
// triggered abilities are put onto the stack.
game.getAction().checkStateEffects(true);
// 4) The current phase and/or step ends. The game skips straight to the
// cleanup step. The cleanup step happens in its entirety.
game.getPhaseHandler().endCombatPhaseByEffect();
}
/*
* (non-Javadoc)
* @see forge.game.ability.SpellAbilityEffect#getStackDescription(forge.game.spellability.SpellAbility)
*/
@Override
protected String getStackDescription(SpellAbility sa) {
return "End the combat phase.";
}
}
package forge.game.ability.effects;
import com.google.common.collect.Lists;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
......@@ -22,7 +24,7 @@ public class EndTurnEffect extends SpellAbilityEffect {
// 1) All spells and abilities on the stack are exiled. This includes
// Time Stop, though it will continue to resolve. It also includes
// spells and abilities that can't be countered.
for (final Card c : game.getStackZone().getCards()) {
for (final Card c : Lists.newArrayList(game.getStackZone().getCards())) {
game.getAction().exile(c, sa);
}
game.getStack().clear();
......@@ -41,11 +43,9 @@ public class EndTurnEffect extends SpellAbilityEffect {
game.getPhaseHandler().endTurnByEffect();
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected String getStackDescription(SpellAbility sa) {
return "End the turn.";
......
......@@ -18,6 +18,7 @@ public class ManifestEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card source = sa.getHostCard();
final Player activator = sa.getActivatingPlayer();
final Game game = source.getGame();
// Usually a number leaving possibility for X, Sacrifice X land: Manifest X creatures.
final int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(source,
......@@ -28,7 +29,22 @@ public class ManifestEffect extends SpellAbilityEffect {
for (final Player p : getTargetPlayers(sa, "DefinedPlayer")) {
if (sa.usesTargeting() || p.canBeTargetedBy(sa)) {
CardCollection tgtCards;
if ("TopOfLibrary".equals(defined)) {
if (sa.hasParam("Choices") || sa.hasParam("ChoiceZone")) {
ZoneType choiceZone = ZoneType.Hand;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
CardCollection choices = new CardCollection(game.getCardsIn(choiceZone));
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, source);
}
if (choices.isEmpty()) {
continue;
}
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : "Choose cards to manifest ";
tgtCards = new CardCollection(activator.getController().chooseEntitiesForEffect(choices, amount, amount, null, sa, title, p));
} else if ("TopOfLibrary".equals(defined)) {
tgtCards = p.getTopXCardsFromLibrary(amount);
} else {
tgtCards = getTargetCards(sa);
......
......@@ -255,6 +255,8 @@ public class PumpEffect extends SpellAbilityEffect {
replaced = host.getChosenType();
} else if (defined.equals("CardUIDSource")) {
replaced = "CardUID_" + String.valueOf(host.getId());
} else if (defined.equals("ActivatorName")) {
replaced = sa.getActivatingPlayer().getName();
}
for (int i = 0; i < keywords.size(); i++) {
keywords.set(i, TextUtil.fastReplace(keywords.get(i), defined, replaced));
......
......@@ -119,8 +119,9 @@ public class SacrificeEffect extends SpellAbilityEffect {
if (sa.hasParam("Random")) {
choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size()), new CardCollection());
}
else {
} else if (sa.hasParam("OptionalSacrifice") && !p.getController().confirmAction(sa, null, "Do you want to sacrifice?")) {
choosenToSacrifice = CardCollection.EMPTY;
} else {
boolean isOptional = sa.hasParam("Optional");
boolean isStrict = sa.hasParam("StrictAmount");
int minTargets = isOptional ? 0 : amount;
......
......@@ -2700,7 +2700,9 @@ public class Card extends GameEntity implements Comparable<Card> {
//only permanents and spells have controllers [108.4],
//so a card really only has a controller while it's on the stack or battlefield.
//everywhere else, just use the owner [108.4a].
return owner;
if (owner != null) {
return owner;
}
}
Entry<Long, Player> lastEntry = tempControllers.lastEntry();
......@@ -5333,6 +5335,11 @@ public class Card extends GameEntity implements Comparable<Card> {
if (source.getController().equals(chosenPlayer)) {
return true;
}
} else if (kw.startsWith("Protection from opponent of ")) {
final String playerName = kw.substring("Protection from opponent of ".length());
if (source.getController().isOpponentOf(playerName)) {
return true;
}
} else if (kw.startsWith("Protection from ")) {
final String protectType = CardType.getSingularType(kw.substring("Protection from ".length()));
if (source.getType().hasStringType(protectType)) {
......
......@@ -375,7 +375,7 @@ public class AttackConstraints {
}
for (final Entry<Card, GameEntity> attacker : attackers.entrySet()) {
final AttackRestriction restriction = restrictions.get(attacker.getKey());
if (!restriction.canAttack(attacker.getKey(), attackers)) {
if (restriction != null && !restriction.canAttack(attacker.getKey(), attackers)) {
// Violating a restriction!
return -1;
}
......@@ -384,7 +384,9 @@ public class AttackConstraints {
int violations = 0;
for (final Card possibleAttacker : possibleAttackers) {
final AttackRequirement requirement = requirements.get(possibleAttacker);
violations += requirement.countViolations(attackers.get(possibleAttacker), attackers);
if (requirement != null) {
violations += requirement.countViolations(attackers.get(possibleAttacker), attackers);
}
}
return violations;
......
......@@ -161,7 +161,7 @@ public class CostAdjustment {
if (sa.isSpell()) {
if (((Spell) sa).isCastFaceDown()) {
// Turn face down to apply cost modifiers correctly
originalCard.setState(CardStateName.FaceDown, false);
originalCard.turnFaceDownNoUpdate();
isStateChangeToFaceDown = true;
}
} // isSpell
......@@ -240,6 +240,7 @@ public class CostAdjustment {
// Reset card state (if changed)
if (isStateChangeToFaceDown) {
originalCard.setFaceDown(false);
originalCard.setState(CardStateName.Original, false);
}
}
......@@ -451,11 +452,8 @@ public class CostAdjustment {
if (activator == null ) {
return false;
}
CardCollection list = CardLists.filterControlledBy(activator.getGame().getStack().getSpellsCastThisTurn(), activator);
if (params.containsKey("ValidCard")) {
list = CardLists.getValidCards(list, params.get("ValidCard"), hostCard.getController(), hostCard);
}
if (list.size() > 0) {
List<Card> list = CardUtil.getThisTurnCast(params.get("ValidCard"), hostCard);
if (CardLists.filterControlledBy(list, activator).size() > 0) {
return false;
}
}
......
......@@ -17,6 +17,7 @@
*/
package forge.game.cost;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
......@@ -95,7 +96,10 @@ public class CostSacrifice extends CostPartWithList {
CardCollectionView typeList = activator.getCardsIn(ZoneType.Battlefield);
typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source, ability);
final Integer amount = this.convertAmount();
Integer amount = this.convertAmount();
if (amount == null) {
amount = AbilityUtils.calculateAmount(source, getAmount(), ability);
}
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
......
......@@ -707,4 +707,13 @@ public class ManaCostBeingPaid {
}
return result;
}
public boolean hasAnyKind(int kind) {
for (ManaCostShard s : unpaidShards.keySet()) {
if (s.isOfKind(kind)) {
return true;
}
}
return false;
}
}
......@@ -274,12 +274,12 @@ public class PhaseHandler implements java.io.Serializable {
break;
case COMBAT_BEGIN:
combat = new Combat(playerTurn);
//PhaseUtil.verifyCombat();
break;
case COMBAT_DECLARE_ATTACKERS:
if (!playerTurn.hasLost()) {
combat = new Combat(playerTurn);
game.getStack().freezeStack();
declareAttackersTurnBasedAction();
game.getStack().unfreezeStack();
......@@ -1134,6 +1134,17 @@ public class PhaseHandler implements java.io.Serializable {
devModeSet(phase0, player0, endCombat, 0);
}
public final void endCombatPhaseByEffect() {
if (!inCombat()) {
return;
}
endCombat();
game.getAction().checkStateEffects(true);
setPhase(PhaseType.COMBAT_END);
// End Combat always happens
game.getEndOfCombat().executeUntil();
advanceToNextPhase();
}
public final void endTurnByEffect() {
endCombat();
......
......@@ -353,6 +353,17 @@ public class Player extends GameEntity implements Comparable<Player> {
return other != this && other != null && (other.teamNumber < 0 || other.teamNumber != teamNumber);
}
public boolean isOpponentOf(String other) {
Player otherPlayer = null;
for (Player p : game.getPlayers()) {
if (p.getName().equals(other)) {
otherPlayer = p;
break;
}
}
return isOpponentOf(otherPlayer);
}
public final boolean setLife(final int newLife, final Card source) {
boolean change = false;
// rule 118.5
......
......@@ -253,12 +253,11 @@ public class PlayerProperty {
}
} else if (property.startsWith("hasFewer")) {
final Player controller = "Active".equals(property.split("Than")[1]) ? game.getPhaseHandler().getPlayerTurn() : sourceController;
if (property.substring(8).startsWith("CreaturesInYard")) {
final CardCollectionView oppList = CardLists.filter(player.getCardsIn(ZoneType.Graveyard), Presets.CREATURES);
final CardCollectionView yourList = CardLists.filter(controller.getCardsIn(ZoneType.Graveyard), Presets.CREATURES);
if (oppList.size() >= yourList.size()) {
return false;
}
final ZoneType zt = property.substring(8).startsWith("CreaturesInYard") ? ZoneType.Graveyard : ZoneType.Battlefield;
final CardCollectionView oppList = CardLists.filter(player.getCardsIn(zt), Presets.CREATURES);
final CardCollectionView yourList = CardLists.filter(controller.getCardsIn(zt), Presets.CREATURES);
if (oppList.size() >= yourList.size()) {
return false;
}
} else if (property.startsWith("withMost")) {
final String kind = property.substring(8);
......
......@@ -90,7 +90,7 @@ public class ReplacementHandler {
if (cause != null && cause.isReplacementAbility()) {
final ReplacementEffect re = cause.getReplacementEffect();
// only return for same layer
if (layer.equals(re.getLayer())) {
if ("Moved".equals(re.getParam("Event")) && layer.equals(re.getLayer())) {
return re.getOtherChoices();
}
}
......
......@@ -166,7 +166,7 @@ public class StaticAbility extends CardTraitBase implements Comparable<StaticAbi
layers.add(StaticAbilityLayer.RULES);
}
if (hasParam("IgnoreEffectCost")) {
if (hasParam("IgnoreEffectCost") || hasParam("Goad")) {
layers.add(StaticAbilityLayer.RULES);
}
......
......@@ -19,6 +19,7 @@ package forge.game.staticability;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
......@@ -81,8 +82,7 @@ public class StaticAbilityCantBeCast {
if (params.containsKey("NumLimitEachTurn") && activator != null) {
int limit = Integer.parseInt(params.get("NumLimitEachTurn"));
String valid = params.containsKey("ValidCard") ? params.get("ValidCard") : "Card";
List<Card> thisTurnCast = CardLists.getValidCards(card.getGame().getStack().getSpellsCastThisTurn(),
valid, card.getController(), card);
List<Card> thisTurnCast = CardUtil.getThisTurnCast(valid, card);
if (CardLists.filterControlledBy(thisTurnCast, activator).size() < limit) {
return false;
}
......
......@@ -727,6 +727,10 @@ public final class StaticAbilityContinuous {
}
}
if (layer == StaticAbilityLayer.RULES && params.containsKey("Goad")) {
affectedCard.addGoad(se.getTimestamp(), hostCard.getController());
}
if (mayLookAt != null) {
for (Player p : mayLookAt) {
affectedCard.setMayLookAt(p, true);
......
......@@ -41,6 +41,7 @@ import forge.game.ability.ApiType;