mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 10:48:00 +00:00
Merge branch 'ai' into 'master'
Add ReplaceDamageAi See merge request core-developers/forge!5512
This commit is contained in:
@@ -473,7 +473,7 @@ public class AiAttackController {
|
|||||||
if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) {
|
if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return ai.getPoisonCounters() + totalPoison > 9;
|
return ai.canReceiveCounters(CounterEnumType.POISON) && ai.getPoisonCounters() + totalPoison > 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doAssault(final Player ai) {
|
private boolean doAssault(final Player ai) {
|
||||||
|
|||||||
@@ -1719,7 +1719,7 @@ public class ComputerUtil {
|
|||||||
final Player p = (Player) o;
|
final Player p = (Player) o;
|
||||||
|
|
||||||
if (source.hasKeyword(Keyword.INFECT)) {
|
if (source.hasKeyword(Keyword.INFECT)) {
|
||||||
if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getPoisonCounters()) {
|
if (p.canReceiveCounters(CounterEnumType.POISON) && ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= 10 - p.getPoisonCounters()) {
|
||||||
threatened.add(p);
|
threatened.add(p);
|
||||||
}
|
}
|
||||||
} else if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getLife()) {
|
} else if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getLife()) {
|
||||||
|
|||||||
@@ -302,6 +302,7 @@ public class ComputerUtilCombat {
|
|||||||
public static int lifeThatWouldRemain(final Player ai, final Combat combat) {
|
public static int lifeThatWouldRemain(final Player ai, final Combat combat) {
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
|
|
||||||
|
if (ai.canLoseLife()) {
|
||||||
final List<Card> attackers = combat.getAttackersOf(ai);
|
final List<Card> attackers = combat.getAttackersOf(ai);
|
||||||
final List<Card> unblocked = Lists.newArrayList();
|
final List<Card> unblocked = Lists.newArrayList();
|
||||||
|
|
||||||
@@ -321,9 +322,6 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
damage += sumDamageIfUnblocked(unblocked, ai);
|
damage += sumDamageIfUnblocked(unblocked, ai);
|
||||||
|
|
||||||
if (!ai.canLoseLife()) {
|
|
||||||
damage = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ai.getLife() - damage;
|
return ai.getLife() - damage;
|
||||||
@@ -671,7 +669,6 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
int flankingMagnitude = 0;
|
int flankingMagnitude = 0;
|
||||||
if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
|
if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
|
||||||
|
|
||||||
flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
|
flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
|
||||||
|
|
||||||
if (flankingMagnitude >= blocker.getNetToughness()) {
|
if (flankingMagnitude >= blocker.getNetToughness()) {
|
||||||
|
|||||||
@@ -361,8 +361,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
for (Card c: topN) {
|
for (Card c: topN) {
|
||||||
if (ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c)) {
|
if (ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c)) {
|
||||||
toBottom.add(c);
|
toBottom.add(c);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
toTop.add(c);
|
toTop.add(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -525,8 +524,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if (copySA instanceof Spell) {
|
if (copySA instanceof Spell) {
|
||||||
Spell spell = (Spell) copySA;
|
Spell spell = (Spell) copySA;
|
||||||
((PlayerControllerAi) player.getController()).getAi().canPlayFromEffectAI(spell, true, true);
|
((PlayerControllerAi) player.getController()).getAi().canPlayFromEffectAI(spell, true, true);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
getAi().canPlaySa(copySA);
|
getAi().canPlaySa(copySA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -884,9 +882,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
byte chosenColorMask = MagicColor.fromName(c);
|
byte chosenColorMask = MagicColor.fromName(c);
|
||||||
if ((colors.getColor() & chosenColorMask) != 0) {
|
if ((colors.getColor() & chosenColorMask) != 0) {
|
||||||
return chosenColorMask;
|
return chosenColorMask;
|
||||||
} else {
|
|
||||||
return Iterables.getFirst(colors, (byte)0);
|
|
||||||
}
|
}
|
||||||
|
return Iterables.getFirst(colors, (byte)0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -905,10 +902,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if ((colors.getColor() & chosenColorMask) != 0) {
|
if ((colors.getColor() & chosenColorMask) != 0) {
|
||||||
return chosenColorMask;
|
return chosenColorMask;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return Iterables.getFirst(colors, MagicColor.WHITE);
|
return Iterables.getFirst(colors, MagicColor.WHITE);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICardFace chooseSingleCardFace(SpellAbility sa, String message,
|
public ICardFace chooseSingleCardFace(SpellAbility sa, String message,
|
||||||
@@ -1370,8 +1365,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt,
|
public int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt, int max) {
|
||||||
int max) {
|
|
||||||
// TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.)
|
// TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.)
|
||||||
int chosenAmount = 0;
|
int chosenAmount = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -142,9 +142,9 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.Repeat, RepeatAi.class)
|
.put(ApiType.Repeat, RepeatAi.class)
|
||||||
.put(ApiType.RepeatEach, RepeatEachAi.class)
|
.put(ApiType.RepeatEach, RepeatEachAi.class)
|
||||||
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
|
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
|
||||||
.put(ApiType.ReplaceDamage, AlwaysPlayAi.class)
|
.put(ApiType.ReplaceDamage, ReplaceDamageAi.class)
|
||||||
.put(ApiType.ReplaceMana, AlwaysPlayAi.class)
|
.put(ApiType.ReplaceMana, AlwaysPlayAi.class)
|
||||||
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
|
.put(ApiType.ReplaceSplitDamage, ReplaceDamageAi.class)
|
||||||
.put(ApiType.ReplaceToken, AlwaysPlayAi.class)
|
.put(ApiType.ReplaceToken, AlwaysPlayAi.class)
|
||||||
.put(ApiType.RestartGame, RestartGameAi.class)
|
.put(ApiType.RestartGame, RestartGameAi.class)
|
||||||
.put(ApiType.Reveal, RevealAi.class)
|
.put(ApiType.Reveal, RevealAi.class)
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
SpellAbility topStack = game.getStack().peekAbility();
|
SpellAbility topStack = game.getStack().peekAbility();
|
||||||
if (topStack.getApi() == ApiType.Sacrifice) {
|
if (topStack.getApi() == ApiType.Sacrifice) {
|
||||||
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
|
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
|
||||||
String num = topStack.getParam("Amount");
|
String num = topStack.getParamOrDefault("Amount", "1");
|
||||||
num = (num == null) ? "1" : num;
|
|
||||||
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
||||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||||
ai.getWeakestOpponent(), topStack.getHostCard(), topStack);
|
ai.getWeakestOpponent(), topStack.getHostCard(), topStack);
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
final SpellAbility topStack = game.getStack().peekAbility();
|
final SpellAbility topStack = game.getStack().peekAbility();
|
||||||
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source, sa)) {
|
if (sa.hasParam("Choices") && !topStack.matchesValid(topStack.getHostCard(), sa.getParam("Choices").split(","))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final ApiType threatApi = topStack.getApi();
|
final ApiType threatApi = topStack.getApi();
|
||||||
@@ -111,8 +111,6 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
||||||
@@ -178,7 +176,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
final Card source = si.getSourceCard();
|
final Card source = si.getSourceCard();
|
||||||
final SpellAbility abilityOnStack = si.getSpellAbility(true);
|
final SpellAbility abilityOnStack = si.getSpellAbility(true);
|
||||||
|
|
||||||
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard(), sa)) {
|
if (sa.hasParam("Choices") && !abilityOnStack.matchesValid(source, sa.getParam("Choices").split(","))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final ApiType threatApi = abilityOnStack.getApi();
|
final ApiType threatApi = abilityOnStack.getApi();
|
||||||
|
|||||||
@@ -330,7 +330,6 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
*/
|
*/
|
||||||
private Card dealDamageChooseTgtC(final Player ai, final SpellAbility sa, final int d, final boolean noPrevention,
|
private Card dealDamageChooseTgtC(final Player ai, final SpellAbility sa, final int d, final boolean noPrevention,
|
||||||
final Player pl, final boolean mandatory) {
|
final Player pl, final boolean mandatory) {
|
||||||
|
|
||||||
// wait until stack is empty (prevents duplicate kills)
|
// wait until stack is empty (prevents duplicate kills)
|
||||||
if (!sa.isTrigger() && !ai.getGame().getStack().isEmpty()) {
|
if (!sa.isTrigger() && !ai.getGame().getStack().isEmpty()) {
|
||||||
//TODO:all removal APIs require a check to prevent duplicate kill/bounce/exile/etc.
|
//TODO:all removal APIs require a check to prevent duplicate kill/bounce/exile/etc.
|
||||||
@@ -345,7 +344,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
final Player activator = sa.getActivatingPlayer();
|
final Player activator = sa.getActivatingPlayer();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
|
List<Card> hPlay = getTargetableCards(mandatory ? pl : ai, sa, pl, tgt, activator, source, game);
|
||||||
|
|
||||||
// Filter MustTarget requirements
|
// Filter MustTarget requirements
|
||||||
StaticAbilityMustTarget.filterMustTargetCards(ai, hPlay, sa);
|
StaticAbilityMustTarget.filterMustTargetCards(ai, hPlay, sa);
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import forge.game.keyword.Keyword;
|
|||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerCollection;
|
||||||
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
@@ -43,7 +45,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(Player ai, SpellAbility sa, String aiLogic) {
|
protected boolean checkAiLogic(Player ai, SpellAbility sa, String aiLogic) {
|
||||||
if (aiLogic.startsWith("ManaRitual")) {
|
if (aiLogic.startsWith("ManaRitual")) {
|
||||||
return doManaRitualLogic(ai, sa);
|
return doManaRitualLogic(ai, sa, false);
|
||||||
} else if ("Always".equals(aiLogic)) {
|
} else if ("Always".equals(aiLogic)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -110,14 +112,32 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
if (logic.startsWith("ManaRitual")) {
|
||||||
|
return doManaRitualLogic(aiPlayer, sa, true);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dark Ritual and other similar instants/sorceries that add mana to mana pool
|
// Dark Ritual and other similar instants/sorceries that add mana to mana pool
|
||||||
public static boolean doManaRitualLogic(Player ai, SpellAbility sa) {
|
public static boolean doManaRitualLogic(Player ai, SpellAbility sa, boolean fromTrigger) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
|
if (sa.usesTargeting()) { // Rousing Refrain
|
||||||
|
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
if (targetableOpps.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Player mostCards = targetableOpps.max(PlayerPredicates.compareByZoneSize(ZoneType.Hand));
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(mostCards);
|
||||||
|
if (fromTrigger) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CardCollection manaSources = ComputerUtilMana.getAvailableManaSources(ai, true);
|
CardCollection manaSources = ComputerUtilMana.getAvailableManaSources(ai, true);
|
||||||
int numManaSrcs = manaSources.size();
|
int numManaSrcs = manaSources.size();
|
||||||
int manaReceived = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa) : 1;
|
int manaReceived = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa) : 1;
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.player.PlayerCollection;
|
|
||||||
import forge.game.player.PlayerPredicates;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
@@ -512,15 +510,6 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else if ("ManaRitual".equals(sa.getParam("AILogic"))) {
|
|
||||||
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
|
||||||
if (targetableOpps.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Player mostCards = targetableOpps.max(PlayerPredicates.compareByZoneSize(ZoneType.Hand));
|
|
||||||
sa.resetTargets();
|
|
||||||
sa.getTargets().add(mostCards);
|
|
||||||
return mandatory || ManaEffectAi.doManaRitualLogic(ai, sa.getSubAbility());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFight) {
|
if (isFight) {
|
||||||
|
|||||||
34
forge-ai/src/main/java/forge/ai/ability/ReplaceDamageAi.java
Normal file
34
forge-ai/src/main/java/forge/ai/ability/ReplaceDamageAi.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class ReplaceDamageAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
|
for (Card c : options) {
|
||||||
|
// TODO check if enough shields to prevent trigger
|
||||||
|
if (c.hasSVar("MustBeBlocked")) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
// TODO check if target can receive counters
|
||||||
|
if (c.hasKeyword(Keyword.INFECT)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (c.isCommander()) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if (c.hasKeyword(Keyword.LIFELINK) || c.hasSVar("LikeLifeLink")) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ComputerUtilCard.getBestAI(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -230,8 +230,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa);
|
final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa);
|
||||||
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
|
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
|
||||||
String num = sa.getParam("Amount");
|
String num = sa.getParamOrDefault("Amount", "1");
|
||||||
num = (num == null) ? "1" : num;
|
|
||||||
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
||||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||||
ai.getWeakestOpponent(), topStack.getHostCard(), sa);
|
ai.getWeakestOpponent(), topStack.getHostCard(), sa);
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (AbilitySub sub : chosen) {
|
for (AbilitySub sub : chosen) {
|
||||||
// Clone the chosen, just in case the some subAb gets chosen multiple times
|
// Clone the chosen, just in case the same subAb gets chosen multiple times
|
||||||
AbilitySub clone = (AbilitySub)sub.copy();
|
AbilitySub clone = (AbilitySub)sub.copy();
|
||||||
|
|
||||||
// update ActivatingPlayer
|
// update ActivatingPlayer
|
||||||
@@ -250,7 +250,6 @@ public class CharmEffect extends SpellAbilityEffect {
|
|||||||
// add Clone to Tail of sa
|
// add Clone to Tail of sa
|
||||||
sa.appendSubAbility(clone);
|
sa.appendSubAbility(clone);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class ManaEffect extends SpellAbilityEffect {
|
|||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
AbilityManaPart abMana = sa.getManaPart();
|
AbilityManaPart abMana = sa.getManaPart();
|
||||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
final List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
|
||||||
|
|
||||||
// Spells are not undoable
|
// Spells are not undoable
|
||||||
sa.setUndoable(sa.isAbility() && sa.isUndoable() && tgtPlayers.size() < 2);
|
sa.setUndoable(sa.isAbility() && sa.isUndoable() && tgtPlayers.size() < 2);
|
||||||
|
|||||||
@@ -3541,7 +3541,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
public final CardTypeView getType() {
|
public final CardTypeView getType() {
|
||||||
return getType(currentState);
|
return getType(currentState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final CardTypeView getType(CardState state) {
|
public final CardTypeView getType(CardState state) {
|
||||||
if (changedCardTypes.isEmpty() && changedCardTypesCharacterDefining.isEmpty()) {
|
if (changedCardTypes.isEmpty() && changedCardTypesCharacterDefining.isEmpty()) {
|
||||||
return state.getType();
|
return state.getType();
|
||||||
@@ -3580,10 +3579,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean clearChangedCardKeywords() {
|
|
||||||
return clearChangedCardKeywords(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean clearChangedCardColors() {
|
public boolean clearChangedCardColors() {
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
|
|
||||||
@@ -3648,7 +3643,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
public final void removeChangedCardTypes(final long timestamp) {
|
public final void removeChangedCardTypes(final long timestamp) {
|
||||||
removeChangedCardTypes(timestamp, true);
|
removeChangedCardTypes(timestamp, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void removeChangedCardTypes(final long timestamp, final boolean updateView) {
|
public final void removeChangedCardTypes(final long timestamp, final boolean updateView) {
|
||||||
boolean removed = false;
|
boolean removed = false;
|
||||||
removed |= changedCardTypes.remove(timestamp) != null;
|
removed |= changedCardTypes.remove(timestamp) != null;
|
||||||
@@ -4268,6 +4262,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean clearChangedCardKeywords() {
|
||||||
|
return clearChangedCardKeywords(false);
|
||||||
|
}
|
||||||
public final boolean clearChangedCardKeywords(final boolean updateView) {
|
public final boolean clearChangedCardKeywords(final boolean updateView) {
|
||||||
if (changedCardKeywords.isEmpty()) {
|
if (changedCardKeywords.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -3197,10 +3197,6 @@ public class CardFactoryUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.getHostCard().isInstant() || this.getHostCard().hasKeyword(Keyword.FLASH)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getHostCard().getFirstSpellAbility().canCastTiming(this.getHostCard(), this.getActivatingPlayer());
|
return this.getHostCard().getFirstSpellAbility().canCastTiming(this.getHostCard(), this.getActivatingPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import forge.game.card.Card;
|
|||||||
|
|
||||||
public class KeywordCollection implements Iterable<KeywordInterface> {
|
public class KeywordCollection implements Iterable<KeywordInterface> {
|
||||||
|
|
||||||
|
|
||||||
private transient KeywordCollectionView view;
|
private transient KeywordCollectionView view;
|
||||||
// don't use enumKeys it causes a slow down
|
// don't use enumKeys it causes a slow down
|
||||||
private final Multimap<Keyword, KeywordInterface> map = MultimapBuilder.hashKeys()
|
private final Multimap<Keyword, KeywordInterface> map = MultimapBuilder.hashKeys()
|
||||||
|
|||||||
@@ -342,8 +342,7 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
// no first strikers, skip this step
|
// no first strikers, skip this step
|
||||||
if (!combat.assignCombatDamage(true)) {
|
if (!combat.assignCombatDamage(true)) {
|
||||||
givePriorityToPlayer = false;
|
givePriorityToPlayer = false;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
combat.dealAssignedDamage();
|
combat.dealAssignedDamage();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -355,8 +354,7 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
|
|
||||||
if (!combat.assignCombatDamage(false)) {
|
if (!combat.assignCombatDamage(false)) {
|
||||||
givePriorityToPlayer = false;
|
givePriorityToPlayer = false;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
combat.dealAssignedDamage();
|
combat.dealAssignedDamage();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
Name:Jeska's Will
|
Name:Jeska's Will
|
||||||
ManaCost:2 R
|
ManaCost:2 R
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ Charm | Cost$ 2 R | MinCharmNum$ 1 | CharmNum$ X | Choices$ DBHandTarget,DBExile | AdditionalDescription$ If you control a commander as you cast this spell, you may choose both.
|
A:SP$ Charm | Cost$ 2 R | MinCharmNum$ 1 | CharmNum$ X | Choices$ DBMana,DBExile | AdditionalDescription$ If you control a commander as you cast this spell, you may choose both.
|
||||||
SVar:DBHandTarget:DB$ Pump | ValidTgts$ Opponent | SubAbility$ DBMana | AILogic$ ManaRitual | SpellDescription$ Add {R} for each card in target opponent's hand.
|
SVar:DBMana:DB$ Mana | Defined$ You | ValidTgts$ Opponent | AILogic$ ManaRitual | Produced$ R | Amount$ Z | SpellDescription$ Add {R} for each card in target opponent's hand.
|
||||||
SVar:DBMana:DB$ Mana | Produced$ R | Amount$ Z | StackDescription$ None
|
|
||||||
SVar:DBExile:DB$ Dig | Defined$ You | DigNum$ 3 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile the top three cards of your library. You may play them this turn.
|
SVar:DBExile:DB$ Dig | Defined$ You | DigNum$ 3 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile the top three cards of your library. You may play them this turn.
|
||||||
SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | SubAbility$ DBCleanup | ForgetOnMoved$ Exile
|
SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | SubAbility$ DBCleanup | ForgetOnMoved$ Exile
|
||||||
SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play the exiled cards this turn.
|
SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play the exiled cards this turn.
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ Name:Rousing Refrain
|
|||||||
ManaCost:3 R R
|
ManaCost:3 R R
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
K:Suspend:3:1 R
|
K:Suspend:3:1 R
|
||||||
A:SP$ Pump | Cost$ 3 R R | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | SubAbility$ DBMana | AILogic$ ManaRitual | StackDescription$ SpellDescription | SpellDescription$ Add {R} for each card in target opponent's hand.
|
A:SP$ Mana | Cost$ 3 R R | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | SubAbility$ DBMana | AILogic$ ManaRitual | Produced$ R | Amount$ Z | PersistentMana$ True | Defined$ You | SubAbility$ DBChange | StackDescription$ SpellDescription | SpellDescription$ Until end of turn, you don't lose this mana as steps and phases end.
|
||||||
SVar:DBMana:DB$ Mana | Produced$ R | Amount$ Z | PersistentMana$ True | SubAbility$ DBChange | StackDescription$ SpellDescription | SpellDescription$ Until end of turn, you don't lose this mana as steps and phases end.
|
|
||||||
SVar:Z:TargetedPlayer$CardsInHand
|
SVar:Z:TargetedPlayer$CardsInHand
|
||||||
SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | WithCountersType$ TIME | WithCountersAmount$ 3 | SpellDescription$ Exile CARDNAME with three time counters on it.
|
SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | WithCountersType$ TIME | WithCountersAmount$ 3 | SpellDescription$ Exile CARDNAME with three time counters on it.
|
||||||
Oracle:Add {R} for each card in target opponent's hand. Until end of turn, you don't lose this mana as steps and phases end. Exile Rousing Refrain with three time counters on it.\nSuspend 3—{1}{R} (Rather than cast this card from your hand, you may pay {1}{R} and exile it with three time counters on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost.)
|
Oracle:Add {R} for each card in target opponent's hand. Until end of turn, you don't lose this mana as steps and phases end. Exile Rousing Refrain with three time counters on it.\nSuspend 3—{1}{R} (Rather than cast this card from your hand, you may pay {1}{R} and exile it with three time counters on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost.)
|
||||||
|
|||||||
@@ -119,8 +119,7 @@ public abstract class GuiDownloadService implements Runnable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
//handle special case of zip service
|
//handle special case of zip service
|
||||||
if (onReadyToStart != null) {
|
if (onReadyToStart != null) {
|
||||||
onReadyToStart.run();
|
onReadyToStart.run();
|
||||||
@@ -147,8 +146,7 @@ public abstract class GuiDownloadService implements Runnable {
|
|||||||
progressBar.setDescription("All items have been downloaded.");
|
progressBar.setDescription("All items have been downloaded.");
|
||||||
btnStart.setText("OK");
|
btnStart.setText("OK");
|
||||||
btnStart.setCommand(cmdClose);
|
btnStart.setCommand(cmdClose);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
progressBar.setMaximum(files.size());
|
progressBar.setMaximum(files.size());
|
||||||
progressBar.setDescription(files.size() == 1 ? "1 item found." : files.size() + " items found.");
|
progressBar.setDescription(files.size() == 1 ? "1 item found." : files.size() + " items found.");
|
||||||
//for(Entry<String, String> kv : cards.entrySet()) System.out.printf("Will get %s from %s%n", kv.getKey(), kv.getValue());
|
//for(Entry<String, String> kv : cards.entrySet()) System.out.printf("Will get %s from %s%n", kv.getKey(), kv.getValue());
|
||||||
@@ -223,8 +221,7 @@ public abstract class GuiDownloadService implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sb.append(String.format("%02d remaining.", t2Go / 1000));
|
sb.append(String.format("%02d remaining.", t2Go / 1000));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
sb.append(String.format("%d of %d items finished! Skipped " + skipped + " items. Please close!",
|
sb.append(String.format("%d of %d items finished! Skipped " + skipped + " items. Please close!",
|
||||||
count, files.size()));
|
count, files.size()));
|
||||||
finish();
|
finish();
|
||||||
@@ -303,8 +300,7 @@ public abstract class GuiDownloadService implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if file is not found and this is a JPG, give PNG a shot...
|
// if file is not found and this is a JPG, give PNG a shot...
|
||||||
if ((conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) && (url.endsWith(".jpg")))
|
if ((conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) && (url.endsWith(".jpg"))) {
|
||||||
{
|
|
||||||
fullborder = false;
|
fullborder = false;
|
||||||
isJPG = false;
|
isJPG = false;
|
||||||
conn.disconnect();
|
conn.disconnect();
|
||||||
@@ -342,8 +338,7 @@ public abstract class GuiDownloadService implements Runnable {
|
|||||||
System.out.println(" Connection failed for url: " + url);
|
System.out.println(" Connection failed for url: " + url);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
System.out.println(" Can't create folder: " + base.getAbsolutePath());
|
System.out.println(" Can't create folder: " + base.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user