mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 03:38:01 +00:00
Merge branch 'master' into 'oracle-updates-nonfunctional'
update nonfunctional changes branch See merge request core-developers/forge!937
This commit is contained in:
21
checkstyle.xml
Normal file
21
checkstyle.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
|
||||
|
||||
<!--
|
||||
Checkstyle is very configurable.
|
||||
http://checkstyle.sf.net (or in your downloaded distribution).
|
||||
-->
|
||||
|
||||
<module name="Checker">
|
||||
<module name="TreeWalker">
|
||||
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports">
|
||||
<!-- <property name="processJavadoc" value="false"/> -->
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
||||
</module>
|
||||
@@ -3,6 +3,7 @@
|
||||
<name>forge-ai</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>forge-game</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.11-SNAPSHOT</version>
|
||||
<version>1.6.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
@@ -29,4 +29,31 @@
|
||||
<version>3.6.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>checkstyle-validation</id>
|
||||
<phase>validate</phase>
|
||||
<configuration>
|
||||
<configLocation>../checkstyle.xml</configLocation>
|
||||
<includeTestSourceDirectory>true</includeTestSourceDirectory>
|
||||
<encoding>UTF-8</encoding>
|
||||
<consoleOutput>true</consoleOutput>
|
||||
<failsOnError>true</failsOnError>
|
||||
<failOnViolation>true</failOnViolation>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -127,10 +127,12 @@ public class AiController {
|
||||
|
||||
private List<SpellAbility> getPossibleETBCounters() {
|
||||
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||
CardCollectionView ccvPlayerLibrary = player.getCardsIn(ZoneType.Library);
|
||||
|
||||
all.addAll(player.getCardsIn(ZoneType.Exile));
|
||||
all.addAll(player.getCardsIn(ZoneType.Graveyard));
|
||||
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
all.add(player.getCardsIn(ZoneType.Library).get(0));
|
||||
if (!ccvPlayerLibrary.isEmpty()) {
|
||||
all.add(ccvPlayerLibrary.get(0));
|
||||
}
|
||||
|
||||
for (final Player opp : player.getOpponents()) {
|
||||
@@ -153,30 +155,34 @@ public class AiController {
|
||||
|
||||
// look for cards on the battlefield that should prevent the AI from using that spellability
|
||||
private boolean checkCurseEffects(final SpellAbility sa) {
|
||||
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
CardCollectionView ccvGameBattlefield = game.getCardsIn(ZoneType.Battlefield);
|
||||
for (final Card c : ccvGameBattlefield) {
|
||||
if (c.hasSVar("AICurseEffect")) {
|
||||
final String curse = c.getSVar("AICurseEffect");
|
||||
final Card host = sa.getHostCard();
|
||||
if ("NonActive".equals(curse) && !player.equals(game.getPhaseHandler().getPlayerTurn())) {
|
||||
return true;
|
||||
} else if ("DestroyCreature".equals(curse) && sa.isSpell() && host.isCreature()
|
||||
&& !sa.getHostCard().hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||
return true;
|
||||
} else if ("CounterEnchantment".equals(curse) && sa.isSpell() && host.isEnchantment()
|
||||
&& CardFactoryUtil.isCounterable(host)) {
|
||||
return true;
|
||||
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
|
||||
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) {
|
||||
return true;
|
||||
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
|
||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
if (!card.isToken() && card.getName().equals(host.getName())) {
|
||||
return true;
|
||||
} else {
|
||||
final Card host = sa.getHostCard();
|
||||
if ("DestroyCreature".equals(curse) && sa.isSpell() && host.isCreature()
|
||||
&& !host.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||
return true;
|
||||
} else if ("CounterEnchantment".equals(curse) && sa.isSpell() && host.isEnchantment()
|
||||
&& CardFactoryUtil.isCounterable(host)) {
|
||||
return true;
|
||||
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
|
||||
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) {
|
||||
return true;
|
||||
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
|
||||
String hostName = host.getName();
|
||||
for (Card card : ccvGameBattlefield) {
|
||||
if (!card.isToken() && card.getName().equals(hostName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Card card : game.getCardsIn(ZoneType.Graveyard)) {
|
||||
if (card.getName().equals(host.getName())) {
|
||||
return true;
|
||||
for (Card card : game.getCardsIn(ZoneType.Graveyard)) {
|
||||
if (card.getName().equals(hostName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,13 +192,14 @@ public class AiController {
|
||||
}
|
||||
|
||||
public boolean checkETBEffects(final Card card, final SpellAbility sa, final ApiType api) {
|
||||
boolean rightapi = false;
|
||||
|
||||
if (card.isCreature()
|
||||
&& game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers)) {
|
||||
return api == null;
|
||||
}
|
||||
|
||||
boolean rightapi = false;
|
||||
String battlefield = ZoneType.Battlefield.toString();
|
||||
Player activatingPlayer = sa.getActivatingPlayer();
|
||||
|
||||
// Trigger play improvements
|
||||
for (final Trigger tr : card.getTriggers()) {
|
||||
// These triggers all care for ETB effects
|
||||
@@ -202,21 +209,22 @@ public class AiController {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
|
||||
if (!params.get("Destination").equals(battlefield)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (params.containsKey("ValidCard")) {
|
||||
if (!params.get("ValidCard").contains("Self")) {
|
||||
String validCard = params.get("ValidCard");
|
||||
if (!validCard.contains("Self")) {
|
||||
continue;
|
||||
}
|
||||
if (params.get("ValidCard").contains("notkicked")) {
|
||||
if (validCard.contains("notkicked")) {
|
||||
if (sa.isKicked()) {
|
||||
continue;
|
||||
}
|
||||
} else if (params.get("ValidCard").contains("kicked")) {
|
||||
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
|
||||
String s = params.get("ValidCard").split("kicked ")[1];
|
||||
} else if (validCard.contains("kicked")) {
|
||||
if (validCard.contains("kicked ")) { // want a specific kicker
|
||||
String s = validCard.split("kicked ")[1];
|
||||
if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
|
||||
if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
|
||||
} else if (!sa.isKicked()) {
|
||||
@@ -259,7 +267,7 @@ public class AiController {
|
||||
}
|
||||
|
||||
if (sa != null) {
|
||||
exSA.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
exSA.setActivatingPlayer(activatingPlayer);
|
||||
}
|
||||
else {
|
||||
exSA.setActivatingPlayer(player);
|
||||
@@ -267,13 +275,11 @@ public class AiController {
|
||||
exSA.setTrigger(true);
|
||||
|
||||
// for trigger test, need to ignore the conditions
|
||||
if (exSA.getConditions() != null) {
|
||||
SpellAbilityCondition cons = exSA.getConditions();
|
||||
if (cons.getIsPresent() != null) {
|
||||
String pres = cons.getIsPresent();
|
||||
if ("Card.Self".equals(pres) || "Card.StrictlySelf".equals(pres)) {
|
||||
SpellAbilityCondition cons = exSA.getConditions();
|
||||
if (cons != null) {
|
||||
String pres = cons.getIsPresent();
|
||||
if (pres != null && pres.matches("Card\\.(Strictly)?Self")) {
|
||||
cons.setIsPresent(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,21 +303,22 @@ public class AiController {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
|
||||
if (!params.get("Destination").equals(battlefield)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (params.containsKey("ValidCard")) {
|
||||
if (!params.get("ValidCard").contains("Self")) {
|
||||
String validCard = params.get("ValidCard");
|
||||
if (!validCard.contains("Self")) {
|
||||
continue;
|
||||
}
|
||||
if (params.get("ValidCard").contains("notkicked")) {
|
||||
if (validCard.contains("notkicked")) {
|
||||
if (sa.isKicked()) {
|
||||
continue;
|
||||
}
|
||||
} else if (params.get("ValidCard").contains("kicked")) {
|
||||
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
|
||||
String s = params.get("ValidCard").split("kicked ")[1];
|
||||
} else if (validCard.contains("kicked")) {
|
||||
if (validCard.contains("kicked ")) { // want a specific kicker
|
||||
String s = validCard.split("kicked ")[1];
|
||||
if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
|
||||
if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
|
||||
} else if (!sa.isKicked()) { // otherwise just any must be present
|
||||
@@ -327,7 +334,7 @@ public class AiController {
|
||||
|
||||
if (exSA != null) {
|
||||
if (sa != null) {
|
||||
exSA.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
exSA.setActivatingPlayer(activatingPlayer);
|
||||
}
|
||||
else {
|
||||
exSA.setActivatingPlayer(player);
|
||||
@@ -375,8 +382,9 @@ public class AiController {
|
||||
if (landsInPlay.size() + landList.size() > max) {
|
||||
for (Card c : allCards) {
|
||||
for (SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.getPayCosts() != null) {
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
Cost payCosts = sa.getPayCosts();
|
||||
if (payCosts != null) {
|
||||
for (CostPart part : payCosts.getCostParts()) {
|
||||
if (part instanceof CostDiscard) {
|
||||
return null;
|
||||
}
|
||||
@@ -390,10 +398,11 @@ public class AiController {
|
||||
landList = CardLists.filter(landList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
CardCollectionView battlefield = player.getCardsIn(ZoneType.Battlefield);
|
||||
canPlaySpellBasic(c, null);
|
||||
if (c.getType().isLegendary() && !c.getName().equals("Flagstones of Trokair")) {
|
||||
final CardCollectionView list = player.getCardsIn(ZoneType.Battlefield);
|
||||
if (Iterables.any(list, CardPredicates.nameEquals(c.getName()))) {
|
||||
String name = c.getName();
|
||||
if (c.getType().isLegendary() && !name.equals("Flagstones of Trokair")) {
|
||||
if (Iterables.any(battlefield, CardPredicates.nameEquals(name))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -402,7 +411,7 @@ public class AiController {
|
||||
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
|
||||
|
||||
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
||||
CardCollection lands = new CardCollection(player.getCardsIn(ZoneType.Battlefield));
|
||||
CardCollection lands = new CardCollection(battlefield);
|
||||
lands.addAll(hand);
|
||||
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
||||
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
||||
@@ -565,14 +574,17 @@ public class AiController {
|
||||
Collections.sort(all, saComparator); // put best spells first
|
||||
|
||||
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
||||
if (sa.getApi() == ApiType.Counter || sa.getApi() == exceptSA) {
|
||||
ApiType saApi = sa.getApi();
|
||||
|
||||
if (saApi == ApiType.Counter || saApi == exceptSA) {
|
||||
continue;
|
||||
}
|
||||
sa.setActivatingPlayer(player);
|
||||
// TODO: this currently only works as a limited prediction of permanent spells.
|
||||
// Ideally this should cast canPlaySa to determine that the AI is truly able/willing to cast a spell,
|
||||
// but that is currently difficult to implement due to various side effects leading to stack overflow.
|
||||
if (!ComputerUtil.castPermanentInMain1(player, sa) && sa.getHostCard() != null && !sa.getHostCard().isLand() && ComputerUtilCost.canPayCost(sa, player)) {
|
||||
Card host = sa.getHostCard();
|
||||
if (!ComputerUtil.castPermanentInMain1(player, sa) && host != null && !host.isLand() && ComputerUtilCost.canPayCost(sa, player)) {
|
||||
if (sa instanceof SpellPermanent) {
|
||||
return sa;
|
||||
}
|
||||
@@ -800,8 +812,7 @@ public class AiController {
|
||||
}
|
||||
|
||||
// use Surge and Prowl costs when able to
|
||||
if (sa.isSurged() ||
|
||||
(sa.getRestrictions().getProwlTypes() != null && !sa.getRestrictions().getProwlTypes().isEmpty())) {
|
||||
if (sa.isSurged() || sa.isProwl()) {
|
||||
p += 9;
|
||||
}
|
||||
// sort planeswalker abilities with most costly first
|
||||
|
||||
@@ -514,19 +514,26 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
|
||||
if ("SacToReduceCost".equals(ability.getParam("AILogic"))) {
|
||||
String logic = ability.getParamOrDefault("AILogic", "");
|
||||
if ("SacToReduceCost".equals(logic)) {
|
||||
// e.g. Torgaar, Famine Incarnate
|
||||
// TODO: currently returns an empty list, so the AI doesn't sacrifice anything. Trying to make
|
||||
// the AI decide on creatures to sac makes the AI sacrifice them, but the cost is not reduced and the
|
||||
// AI pays the full mana cost anyway (despite sacrificing creatures).
|
||||
return PaymentDecision.card(new CardCollection());
|
||||
} else if (!logic.isEmpty() && !logic.equals("Never")) {
|
||||
// If at least some other AI logic is specified, assume that the AI for that API knows how
|
||||
// to define ChosenX and thus honor that value.
|
||||
// Cards which have no special logic for this yet but which do work in a simple/suboptimal way
|
||||
// are currently conventionally flagged with AILogic$ DoSacrifice.
|
||||
c = AbilityUtils.calculateAmount(source, source.getSVar("ChosenX"), null);
|
||||
} else {
|
||||
// Other cards are assumed to be flagged RemAIDeck for now
|
||||
return null;
|
||||
}
|
||||
|
||||
// Other cards are assumed to be flagged RemAIDeck for now
|
||||
return null;
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
CardCollectionView list = aic.chooseSacrificeType(cost.getType(), ability, c);
|
||||
|
||||
@@ -93,6 +93,7 @@ public enum AiProps { /** */
|
||||
SCRY_EVALTHR_CMC_THRESHOLD ("3"), /** */
|
||||
SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM ("false"), /** */
|
||||
SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF ("1"), /** */
|
||||
SURVEIL_NUM_CARDS_IN_LIBRARY_TO_BAIL ("10"), /** */
|
||||
COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION ("true"), /** */
|
||||
COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION ("true"), /** */
|
||||
CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT ("true"), /** */
|
||||
|
||||
@@ -95,7 +95,9 @@ public class ComputerUtil {
|
||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||
}
|
||||
|
||||
sa.resetPaidHash();
|
||||
if (sa.isCopied()) {
|
||||
sa.resetPaidHash();
|
||||
}
|
||||
|
||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||
CharmEffect.makeChoices(sa);
|
||||
@@ -260,6 +262,10 @@ public class ComputerUtil {
|
||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
newSA.setHostCard(game.getAction().moveToStack(source, sa));
|
||||
|
||||
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
|
||||
CharmEffect.makeChoices(newSA);
|
||||
}
|
||||
}
|
||||
|
||||
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
|
||||
@@ -2629,8 +2635,8 @@ public class ComputerUtil {
|
||||
// and also on Chronozoa
|
||||
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
||||
|| type == CounterType.GOLD || type == CounterType.MUSIC || type == CounterType.PUPA
|
||||
|| type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP
|
||||
|| type == CounterType.SLEIGHT || type == CounterType.WAGE;
|
||||
|| type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP
|
||||
|| type == CounterType.SLUMBER || type == CounterType.SLEIGHT || type == CounterType.WAGE;
|
||||
}
|
||||
|
||||
// this countertypes has no effect
|
||||
|
||||
@@ -1573,10 +1573,10 @@ public class ComputerUtilCard {
|
||||
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp);
|
||||
pumped.addTempPowerBoost(c.getTempPowerBoost() + power + berserkPower);
|
||||
pumped.addTempToughnessBoost(c.getTempToughnessBoost() + toughness);
|
||||
pumped.addChangedCardKeywords(kws, new ArrayList<String>(), false, timestamp);
|
||||
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
|
||||
Set<CounterType> types = c.getCounters().keySet();
|
||||
for(CounterType ct : types) {
|
||||
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), c, true);
|
||||
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true);
|
||||
}
|
||||
//Copies tap-state and extra keywords (auras, equipment, etc.)
|
||||
if (c.isTapped()) {
|
||||
@@ -1596,7 +1596,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
}
|
||||
final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used?
|
||||
pumped.addChangedCardKeywordsInternal(toCopy, Lists.<KeywordInterface>newArrayList(), false, timestamp2, true);
|
||||
pumped.addChangedCardKeywordsInternal(toCopy, null, false, false, timestamp2, true);
|
||||
ComputerUtilCard.applyStaticContPT(ai.getGame(), pumped, new CardCollection(c));
|
||||
return pumped;
|
||||
}
|
||||
@@ -1625,6 +1625,9 @@ public class ComputerUtilCard {
|
||||
if (!params.containsKey("Affected")) {
|
||||
continue;
|
||||
}
|
||||
if (!params.containsKey("AddPower") && !params.containsKey("AddToughness")) {
|
||||
continue;
|
||||
}
|
||||
final String valid = params.get("Affected");
|
||||
if (!vCard.isValid(valid, c.getController(), c, null)) {
|
||||
continue;
|
||||
|
||||
@@ -975,12 +975,20 @@ public class ComputerUtilCombat {
|
||||
final Map<String, String> trigParams = trigger.getMapParams();
|
||||
final Card source = trigger.getHostCard();
|
||||
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)
|
||||
|| !trigParams.containsKey("Execute")) {
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
|
||||
continue;
|
||||
}
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
final Map<String, String> abilityParams = AbilityFactory.getMapParams(ability);
|
||||
|
||||
Map<String, String> abilityParams = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
||||
} else if (trigParams.containsKey("Execute")) {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
abilityParams = AbilityFactory.getMapParams(ability);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump")) {
|
||||
continue;
|
||||
}
|
||||
@@ -1098,12 +1106,20 @@ public class ComputerUtilCombat {
|
||||
final Map<String, String> trigParams = trigger.getMapParams();
|
||||
final Card source = trigger.getHostCard();
|
||||
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)
|
||||
|| !trigParams.containsKey("Execute")) {
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
|
||||
continue;
|
||||
}
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
final Map<String, String> abilityParams = AbilityFactory.getMapParams(ability);
|
||||
|
||||
Map<String, String> abilityParams = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
||||
} else if (trigParams.containsKey("Execute")) {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
abilityParams = AbilityFactory.getMapParams(ability);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
String abType = "";
|
||||
if (abilityParams.containsKey("AB")) {
|
||||
abType = abilityParams.get("AB");
|
||||
@@ -1311,12 +1327,20 @@ public class ComputerUtilCombat {
|
||||
final Map<String, String> trigParams = trigger.getMapParams();
|
||||
final Card source = trigger.getHostCard();
|
||||
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)
|
||||
|| !trigParams.containsKey("Execute")) {
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)) {
|
||||
continue;
|
||||
}
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
final Map<String, String> abilityParams = AbilityFactory.getMapParams(ability);
|
||||
|
||||
Map<String, String> abilityParams = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
||||
} else if (trigParams.containsKey("Execute")) {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
abilityParams = AbilityFactory.getMapParams(ability);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) {
|
||||
continue; // targeted pumping not supported
|
||||
}
|
||||
@@ -1330,7 +1354,14 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
|
||||
if (abilityParams.containsKey("Cost")) {
|
||||
final SpellAbility sa = AbilityFactory.getAbility(ability, source);
|
||||
SpellAbility sa = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
sa = trigger.getOverridingAbility();
|
||||
} else {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
sa = AbilityFactory.getAbility(ability, source);
|
||||
}
|
||||
|
||||
sa.setActivatingPlayer(source.getController());
|
||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||
continue;
|
||||
@@ -1514,12 +1545,20 @@ public class ComputerUtilCombat {
|
||||
final Map<String, String> trigParams = trigger.getMapParams();
|
||||
final Card source = trigger.getHostCard();
|
||||
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)
|
||||
|| !trigParams.containsKey("Execute")) {
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)) {
|
||||
continue;
|
||||
}
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
final Map<String, String> abilityParams = AbilityFactory.getMapParams(ability);
|
||||
|
||||
Map<String, String> abilityParams = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
||||
} else if (trigParams.containsKey("Execute")) {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
abilityParams = AbilityFactory.getMapParams(ability);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) {
|
||||
continue; // targeted pumping not supported
|
||||
}
|
||||
@@ -1552,7 +1591,14 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
|
||||
if (abilityParams.containsKey("Cost")) {
|
||||
final SpellAbility sa = AbilityFactory.getAbility(ability, source);
|
||||
SpellAbility sa = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
sa = trigger.getOverridingAbility();
|
||||
} else {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
sa = AbilityFactory.getAbility(ability, source);
|
||||
}
|
||||
|
||||
sa.setActivatingPlayer(source.getController());
|
||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||
continue;
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import forge.ai.ability.AnimateAi;
|
||||
import forge.card.ColorSet;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
@@ -19,8 +20,10 @@ import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.event.GameEventAttackersDeclared;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.mana.ManaPool;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.PlayerZone;
|
||||
@@ -52,6 +55,14 @@ public abstract class GameState {
|
||||
private int computerLife = -1;
|
||||
private String humanCounters = "";
|
||||
private String computerCounters = "";
|
||||
private String humanManaPool = "";
|
||||
private String computerManaPool = "";
|
||||
private String humanPersistentMana = "";
|
||||
private String computerPersistentMana = "";
|
||||
private int humanLandsPlayed = 0;
|
||||
private int computerLandsPlayed = 0;
|
||||
private int humanLandsPlayedLastTurn = 0;
|
||||
private int computerLandsPlayedLastTurn = 0;
|
||||
|
||||
private boolean puzzleCreatorState = false;
|
||||
|
||||
@@ -60,6 +71,7 @@ public abstract class GameState {
|
||||
|
||||
private final Map<Integer, Card> idToCard = new HashMap<>();
|
||||
private final Map<Card, Integer> cardToAttachId = new HashMap<>();
|
||||
private final Map<Card, Integer> cardToEnchantPlayerId = new HashMap<>();
|
||||
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
||||
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
||||
private final Map<Card, String> cardToChosenType = new HashMap<>();
|
||||
@@ -79,6 +91,8 @@ public abstract class GameState {
|
||||
private String tChangePlayer = "NONE";
|
||||
private String tChangePhase = "NONE";
|
||||
|
||||
private String tAdvancePhase = "NONE";
|
||||
|
||||
private String precastHuman = null;
|
||||
private String precastAI = null;
|
||||
|
||||
@@ -112,6 +126,10 @@ public abstract class GameState {
|
||||
|
||||
sb.append(TextUtil.concatNoSpace("humanlife=", String.valueOf(humanLife), "\n"));
|
||||
sb.append(TextUtil.concatNoSpace("ailife=", String.valueOf(computerLife), "\n"));
|
||||
sb.append(TextUtil.concatNoSpace("humanlandsplayed=", String.valueOf(humanLandsPlayed), "\n"));
|
||||
sb.append(TextUtil.concatNoSpace("ailandsplayed=", String.valueOf(computerLandsPlayed), "\n"));
|
||||
sb.append(TextUtil.concatNoSpace("humanlandsplayedlastturn=", String.valueOf(humanLandsPlayedLastTurn), "\n"));
|
||||
sb.append(TextUtil.concatNoSpace("ailandsplayedlastturn=", String.valueOf(computerLandsPlayedLastTurn), "\n"));
|
||||
sb.append(TextUtil.concatNoSpace("turn=", String.valueOf(turn), "\n"));
|
||||
|
||||
if (!humanCounters.isEmpty()) {
|
||||
@@ -121,6 +139,13 @@ public abstract class GameState {
|
||||
sb.append(TextUtil.concatNoSpace("aicounters=", computerCounters, "\n"));
|
||||
}
|
||||
|
||||
if (!humanManaPool.isEmpty()) {
|
||||
sb.append(TextUtil.concatNoSpace("humanmanapool=", humanManaPool, "\n"));
|
||||
}
|
||||
if (!computerManaPool.isEmpty()) {
|
||||
sb.append(TextUtil.concatNoSpace("aimanapool=", humanManaPool, "\n"));
|
||||
}
|
||||
|
||||
sb.append(TextUtil.concatNoSpace("activeplayer=", tChangePlayer, "\n"));
|
||||
sb.append(TextUtil.concatNoSpace("activephase=", tChangePhase, "\n"));
|
||||
appendCards(humanCardTexts, "human", sb);
|
||||
@@ -147,8 +172,14 @@ public abstract class GameState {
|
||||
}
|
||||
humanLife = human.getLife();
|
||||
computerLife = ai.getLife();
|
||||
humanLandsPlayed = human.getLandsPlayedThisTurn();
|
||||
computerLandsPlayed = ai.getLandsPlayedThisTurn();
|
||||
humanLandsPlayedLastTurn = human.getLandsPlayedLastTurn();
|
||||
computerLandsPlayedLastTurn = ai.getLandsPlayedLastTurn();
|
||||
humanCounters = countersToString(human.getCounters());
|
||||
computerCounters = countersToString(ai.getCounters());
|
||||
humanManaPool = processManaPool(human.getManaPool());
|
||||
computerManaPool = processManaPool(ai.getManaPool());
|
||||
|
||||
tChangePlayer = game.getPhaseHandler().getPlayerTurn() == ai ? "ai" : "human";
|
||||
tChangePhase = game.getPhaseHandler().getPhase().toString();
|
||||
@@ -266,6 +297,12 @@ public abstract class GameState {
|
||||
} else if (c.getEnchantingCard() != null) {
|
||||
newText.append("|Attaching:").append(c.getEnchantingCard().getId());
|
||||
}
|
||||
if (c.getEnchantingPlayer() != null) {
|
||||
// TODO: improve this for game states with more than two players
|
||||
newText.append("|EnchantingPlayer:");
|
||||
Player p = c.getEnchantingPlayer();
|
||||
newText.append(p.getController().isAI() ? "AI" : "HUMAN");
|
||||
}
|
||||
|
||||
if (c.getDamage() > 0) {
|
||||
newText.append("|Damage:").append(c.getDamage());
|
||||
@@ -388,8 +425,10 @@ public abstract class GameState {
|
||||
if (categoryName.startsWith("active")) {
|
||||
if (categoryName.endsWith("player"))
|
||||
tChangePlayer = categoryValue.trim().toLowerCase();
|
||||
if (categoryName.endsWith("phase"))
|
||||
else if (categoryName.endsWith("phase"))
|
||||
tChangePhase = categoryValue.trim().toUpperCase();
|
||||
else if (categoryName.endsWith("phaseadvance"))
|
||||
tAdvancePhase = categoryValue.trim().toUpperCase();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -413,6 +452,20 @@ public abstract class GameState {
|
||||
computerCounters = categoryValue;
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("landsplayed")) {
|
||||
if (isHuman)
|
||||
humanLandsPlayed = Integer.parseInt(categoryValue);
|
||||
else
|
||||
computerLandsPlayed = Integer.parseInt(categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("landsplayedlastturn")) {
|
||||
if (isHuman)
|
||||
humanLandsPlayedLastTurn = Integer.parseInt(categoryValue);
|
||||
else
|
||||
computerLandsPlayedLastTurn = Integer.parseInt(categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) {
|
||||
if (isHuman)
|
||||
humanCardTexts.put(ZoneType.Battlefield, categoryValue);
|
||||
@@ -465,6 +518,21 @@ public abstract class GameState {
|
||||
else
|
||||
precastAI = categoryValue;
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("manapool")) {
|
||||
if (isHuman)
|
||||
humanManaPool = categoryValue;
|
||||
else
|
||||
computerManaPool = categoryValue;
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("persistentmana")) {
|
||||
if (isHuman)
|
||||
humanPersistentMana = categoryValue;
|
||||
else
|
||||
computerPersistentMana = categoryValue;
|
||||
}
|
||||
|
||||
else {
|
||||
System.out.println("Unknown key: " + categoryName);
|
||||
}
|
||||
@@ -485,6 +553,7 @@ public abstract class GameState {
|
||||
|
||||
idToCard.clear();
|
||||
cardToAttachId.clear();
|
||||
cardToEnchantPlayerId.clear();
|
||||
cardToRememberedId.clear();
|
||||
cardToExiledWithId.clear();
|
||||
markedDamage.clear();
|
||||
@@ -493,12 +562,18 @@ public abstract class GameState {
|
||||
cardToScript.clear();
|
||||
cardAttackMap.clear();
|
||||
|
||||
Player newPlayerTurn = tChangePlayer.equals("human") ? human : tChangePlayer.equals("ai") ? ai : null;
|
||||
PhaseType newPhase = tChangePhase.equals("none") ? null : PhaseType.smartValueOf(tChangePhase);
|
||||
Player newPlayerTurn = tChangePlayer.equalsIgnoreCase("human") ? human : tChangePlayer.equalsIgnoreCase("ai") ? ai : null;
|
||||
PhaseType newPhase = tChangePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tChangePhase);
|
||||
PhaseType advPhase = tAdvancePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tAdvancePhase);
|
||||
|
||||
// Set stack to resolving so things won't trigger/effects be checked right away
|
||||
game.getStack().setResolving(true);
|
||||
|
||||
updateManaPool(human, humanManaPool, true, false);
|
||||
updateManaPool(ai, computerManaPool, true, false);
|
||||
updateManaPool(human, humanPersistentMana, false, true);
|
||||
updateManaPool(ai, computerPersistentMana, false, true);
|
||||
|
||||
if (!humanCounters.isEmpty()) {
|
||||
applyCountersToGameEntity(human, humanCounters);
|
||||
}
|
||||
@@ -510,8 +585,8 @@ public abstract class GameState {
|
||||
|
||||
game.getTriggerHandler().setSuppressAllTriggers(true);
|
||||
|
||||
setupPlayerState(humanLife, humanCardTexts, human);
|
||||
setupPlayerState(computerLife, aiCardTexts, ai);
|
||||
setupPlayerState(humanLife, humanCardTexts, human, humanLandsPlayed, humanLandsPlayedLastTurn);
|
||||
setupPlayerState(computerLife, aiCardTexts, ai, computerLandsPlayed, computerLandsPlayedLastTurn);
|
||||
|
||||
handleCardAttachments();
|
||||
handleChosenEntities();
|
||||
@@ -531,9 +606,50 @@ public abstract class GameState {
|
||||
|
||||
game.getStack().setResolving(false);
|
||||
|
||||
// Advance to a certain phase, activating all triggered abilities
|
||||
if (advPhase != null) {
|
||||
game.getPhaseHandler().devAdvanceToPhase(advPhase);
|
||||
}
|
||||
|
||||
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||
}
|
||||
|
||||
private String processManaPool(ManaPool manaPool) {
|
||||
String mana = "";
|
||||
for (final byte c : MagicColor.WUBRGC) {
|
||||
int amount = manaPool.getAmountOfColor(c);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
mana += MagicColor.toShortString(c) + " ";
|
||||
}
|
||||
}
|
||||
|
||||
return mana.trim();
|
||||
}
|
||||
|
||||
private void updateManaPool(Player p, String manaDef, boolean clearPool, boolean persistent) {
|
||||
Game game = p.getGame();
|
||||
if (clearPool) {
|
||||
p.getManaPool().clearPool(false);
|
||||
}
|
||||
|
||||
if (!manaDef.isEmpty()) {
|
||||
final Card dummy = new Card(-777777, game);
|
||||
dummy.setOwner(p);
|
||||
final Map<String, String> produced = Maps.newHashMap();
|
||||
produced.put("Produced", manaDef);
|
||||
if (persistent) {
|
||||
produced.put("PersistentMana", "True");
|
||||
}
|
||||
final AbilityManaPart abMana = new AbilityManaPart(dummy, produced);
|
||||
game.getAction().invoke(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
abMana.produceMana(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCombat(final Game game, final Player attackingPlayer, final Player defendingPlayer, final boolean toDeclareBlockers) {
|
||||
// First we need to ensure that all attackers are declared in the Declare Attackers step,
|
||||
// even if proceeding straight to Declare Blockers
|
||||
@@ -864,6 +980,16 @@ public abstract class GameState {
|
||||
attacher.fortifyCard(attachedTo);
|
||||
}
|
||||
}
|
||||
|
||||
// Enchant players by ID
|
||||
for(Entry<Card, Integer> entry : cardToEnchantPlayerId.entrySet()) {
|
||||
// TODO: improve this for game states with more than two players
|
||||
Card attacher = entry.getKey();
|
||||
Game game = attacher.getGame();
|
||||
Player attachedTo = entry.getValue() == TARGET_AI ? game.getPlayers().get(1) : game.getPlayers().get(0);
|
||||
|
||||
attacher.enchantEntity(attachedTo);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
|
||||
@@ -875,7 +1001,7 @@ public abstract class GameState {
|
||||
}
|
||||
}
|
||||
|
||||
private void setupPlayerState(int life, Map<ZoneType, String> cardTexts, final Player p) {
|
||||
private void setupPlayerState(int life, Map<ZoneType, String> cardTexts, final Player p, final int landsPlayed, final int landsPlayedLastTurn) {
|
||||
// Lock check static as we setup player state
|
||||
|
||||
Map<ZoneType, CardCollectionView> playerCards = new EnumMap<ZoneType, CardCollectionView>(ZoneType.class);
|
||||
@@ -885,6 +1011,9 @@ public abstract class GameState {
|
||||
}
|
||||
|
||||
if (life >= 0) p.setLife(life, null);
|
||||
p.setLandsPlayedThisTurn(landsPlayed);
|
||||
p.setLandsPlayedLastTurn(landsPlayedLastTurn);
|
||||
|
||||
for (Entry<ZoneType, CardCollectionView> kv : playerCards.entrySet()) {
|
||||
PlayerZone zone = p.getZone(kv.getKey());
|
||||
if (kv.getKey() == ZoneType.Battlefield) {
|
||||
@@ -1008,6 +1137,10 @@ public abstract class GameState {
|
||||
} else if (info.startsWith("Attaching:")) {
|
||||
int id = Integer.parseInt(info.substring(info.indexOf(':') + 1));
|
||||
cardToAttachId.put(c, id);
|
||||
} else if (info.startsWith("EnchantingPlayer:")) {
|
||||
// TODO: improve this for game states with more than two players
|
||||
String tgt = info.substring(info.indexOf(':') + 1);
|
||||
cardToEnchantPlayerId.put(c, tgt.equalsIgnoreCase("AI") ? TARGET_AI : TARGET_HUMAN);
|
||||
} else if (info.startsWith("Ability:")) {
|
||||
String abString = info.substring(info.indexOf(':') + 1).toLowerCase();
|
||||
c.addSpellAbility(AbilityFactory.getAbility(abilityString.get(abString), c));
|
||||
|
||||
@@ -168,12 +168,13 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title) {
|
||||
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title,
|
||||
Map<String, Object> params) {
|
||||
ApiType api = sa.getApi();
|
||||
if (null == api) {
|
||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||
}
|
||||
return SpellApiToAi.Converter.get(api).chooseSingleSpellAbility(player, sa, spells);
|
||||
return SpellApiToAi.Converter.get(api).chooseSingleSpellAbility(player, sa, spells, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -291,9 +292,38 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return ImmutablePair.of(toTop, toBottom);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#arrangeForSurveil(forge.game.card.CardCollection)
|
||||
*/
|
||||
@Override
|
||||
public ImmutablePair<CardCollection, CardCollection> arrangeForSurveil(CardCollection topN) {
|
||||
CardCollection toGraveyard = new CardCollection();
|
||||
CardCollection toTop = new CardCollection();
|
||||
|
||||
// TODO: Currently this logic uses the same routine as Scry. Possibly differentiate this and implement
|
||||
// a specific logic for Surveil (e.g. maybe to interact better with Reanimator strategies etc.).
|
||||
if (getPlayer().getCardsIn(ZoneType.Hand).size() <= getAi().getIntProperty(AiProps.SURVEIL_NUM_CARDS_IN_LIBRARY_TO_BAIL)) {
|
||||
toTop.addAll(topN);
|
||||
} else {
|
||||
for (Card c : topN) {
|
||||
if (ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c)) {
|
||||
toGraveyard.add(c);
|
||||
} else {
|
||||
toTop.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.shuffle(toTop, MyRandom.getRandom());
|
||||
return ImmutablePair.of(toTop, toGraveyard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willPutCardOnTop(Card c) {
|
||||
return true; // AI does not know what will happen next (another clash or that would become his topdeck)
|
||||
// This is used for Clash. Currently uses Scry logic to determine whether the card should be put on top.
|
||||
// Note that the AI does not know what will happen next (another clash or that would become his topdeck)
|
||||
|
||||
return !ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -324,7 +324,7 @@ public abstract class SpellAbilityAi {
|
||||
return null;
|
||||
}
|
||||
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells, Map<String, Object> params) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return spells.get(0);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
||||
.put(ApiType.Attach, AttachAi.class)
|
||||
.put(ApiType.Ascend, AlwaysPlayAi.class)
|
||||
.put(ApiType.AssignGroup, AssignGroupAi.class)
|
||||
.put(ApiType.Balance, BalanceAi.class)
|
||||
.put(ApiType.BecomeMonarch, AlwaysPlayAi.class)
|
||||
.put(ApiType.BecomesBlocked, BecomesBlockedAi.class)
|
||||
@@ -144,6 +145,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.SkipTurn, SkipTurnAi.class)
|
||||
.put(ApiType.StoreMap, StoreMapAi.class)
|
||||
.put(ApiType.StoreSVar, StoreSVarAi.class)
|
||||
.put(ApiType.Surveil, SurveilAi.class)
|
||||
.put(ApiType.Tap, TapAi.class)
|
||||
.put(ApiType.TapAll, TapAllAi.class)
|
||||
.put(ApiType.TapOrUntap, TapOrUntapAi.class)
|
||||
|
||||
@@ -12,6 +12,7 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
|
||||
@@ -93,7 +94,8 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||
Map<String, Object> params) {
|
||||
return spells.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ import forge.game.staticability.StaticAbilityLayer;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.ability.effects.AnimateEffectBase;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -363,11 +363,11 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
card.setSickness(hasOriginalCardSickness);
|
||||
|
||||
// AF specific sa
|
||||
int power = -1;
|
||||
Integer power = null;
|
||||
if (sa.hasParam("Power")) {
|
||||
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||
}
|
||||
int toughness = -1;
|
||||
Integer toughness = null;
|
||||
if (sa.hasParam("Toughness")) {
|
||||
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
|
||||
}
|
||||
@@ -453,65 +453,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
sVars.addAll(Arrays.asList(sa.getParam("sVars").split(",")));
|
||||
}
|
||||
|
||||
// duplicating AnimateEffectBase.doAnimate
|
||||
boolean removeSuperTypes = false;
|
||||
boolean removeCardTypes = false;
|
||||
boolean removeSubTypes = false;
|
||||
boolean removeCreatureTypes = false;
|
||||
boolean removeArtifactTypes = false;
|
||||
|
||||
if (sa.hasParam("OverwriteTypes")) {
|
||||
removeSuperTypes = true;
|
||||
removeCardTypes = true;
|
||||
removeSubTypes = true;
|
||||
removeCreatureTypes = true;
|
||||
removeArtifactTypes = true;
|
||||
}
|
||||
|
||||
if (sa.hasParam("KeepSupertypes")) {
|
||||
removeSuperTypes = false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("KeepCardTypes")) {
|
||||
removeCardTypes = false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveSuperTypes")) {
|
||||
removeSuperTypes = true;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveCardTypes")) {
|
||||
removeCardTypes = true;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveSubTypes")) {
|
||||
removeSubTypes = true;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveCreatureTypes")) {
|
||||
removeCreatureTypes = true;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveArtifactTypes")) {
|
||||
removeArtifactTypes = true;
|
||||
}
|
||||
|
||||
if ((power != -1) || (toughness != -1)) {
|
||||
card.addNewPT(power, toughness, timestamp);
|
||||
}
|
||||
|
||||
if (!types.isEmpty() || !removeTypes.isEmpty() || removeCreatureTypes) {
|
||||
card.addChangedCardTypes(types, removeTypes, removeSuperTypes, removeCardTypes, removeSubTypes,
|
||||
removeCreatureTypes, removeArtifactTypes, timestamp);
|
||||
}
|
||||
|
||||
card.addChangedCardKeywords(keywords, removeKeywords, sa.hasParam("RemoveAllAbilities"), timestamp);
|
||||
|
||||
for (final String k : hiddenKeywords) {
|
||||
card.addHiddenExtrinsicKeyword(k);
|
||||
}
|
||||
|
||||
card.addColor(finalDesc, !sa.hasParam("OverwriteColors"), timestamp);
|
||||
AnimateEffectBase.doAnimate(card, sa, power, toughness, types, removeTypes, finalDesc, keywords, removeKeywords, hiddenKeywords, timestamp);
|
||||
|
||||
// back to duplicating AnimateEffect.resolve
|
||||
// TODO will all these abilities/triggers/replacements/etc. lead to
|
||||
@@ -521,10 +463,14 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
boolean clearAbilities = sa.hasParam("OverwriteAbilities");
|
||||
boolean clearSpells = sa.hasParam("OverwriteSpells");
|
||||
boolean removeAll = sa.hasParam("RemoveAllAbilities");
|
||||
boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
|
||||
|
||||
if (clearAbilities || clearSpells || removeAll) {
|
||||
for (final SpellAbility ab : card.getSpellAbilities()) {
|
||||
if (removeAll || (ab.isAbility() && clearAbilities) || (ab.isSpell() && clearSpells)) {
|
||||
if (removeAll
|
||||
|| (ab.isIntrinsic() && removeIntrinsic && !ab.isBasicLandAbility())
|
||||
|| (ab.isAbility() && clearAbilities)
|
||||
|| (ab.isSpell() && clearSpells)) {
|
||||
card.removeSpellAbility(ab);
|
||||
removedAbilities.add(ab);
|
||||
}
|
||||
@@ -565,9 +511,11 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
// suppress triggers from the animated card
|
||||
final List<Trigger> removedTriggers = Lists.newArrayList();
|
||||
if (sa.hasParam("OverwriteTriggers") || removeAll) {
|
||||
final FCollectionView<Trigger> triggersToRemove = card.getTriggers();
|
||||
for (final Trigger trigger : triggersToRemove) {
|
||||
if (sa.hasParam("OverwriteTriggers") || removeAll || removeIntrinsic) {
|
||||
for (final Trigger trigger : card.getTriggers()) {
|
||||
if (removeIntrinsic && !trigger.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
trigger.setSuppressed(true);
|
||||
removedTriggers.add(trigger);
|
||||
}
|
||||
@@ -603,9 +551,11 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
// suppress static abilities from the animated card
|
||||
final List<StaticAbility> removedStatics = Lists.newArrayList();
|
||||
if (sa.hasParam("OverwriteStatics") || removeAll) {
|
||||
final FCollectionView<StaticAbility> staticsToRemove = card.getStaticAbilities();
|
||||
for (final StaticAbility stAb : staticsToRemove) {
|
||||
if (sa.hasParam("OverwriteStatics") || removeAll || removeIntrinsic) {
|
||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||
if (removeIntrinsic && !stAb.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
stAb.setTemporarilySuppressed(true);
|
||||
removedStatics.add(stAb);
|
||||
}
|
||||
@@ -613,8 +563,11 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
// suppress static abilities from the animated card
|
||||
final List<ReplacementEffect> removedReplacements = Lists.newArrayList();
|
||||
if (sa.hasParam("OverwriteReplacements") || removeAll) {
|
||||
if (sa.hasParam("OverwriteReplacements") || removeAll || removeIntrinsic) {
|
||||
for (final ReplacementEffect re : card.getReplacementEffects()) {
|
||||
if (removeIntrinsic && !re.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
re.setTemporarilySuppressed(true);
|
||||
removedReplacements.add(re);
|
||||
}
|
||||
|
||||
33
forge-ai/src/main/java/forge/ai/ability/AssignGroupAi.java
Normal file
33
forge-ai/src/main/java/forge/ai/ability/AssignGroupAi.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class AssignGroupAi extends SpellAbilityAi {
|
||||
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// TODO: Currently this AI relies on the card-specific limiting hints (NeedsToPlay / NeedsToPlayVar),
|
||||
// otherwise the AI considers the card playable.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells, Map<String, Object> params) {
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (logic.equals("FriendOrFoe")) {
|
||||
if (params.containsKey("Affected") && spells.size() >= 2) {
|
||||
Player t = (Player) params.get("Affected");
|
||||
return spells.get(player.isOpponentOf(t) ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
return Iterables.getFirst(spells, null);
|
||||
}
|
||||
}
|
||||
@@ -510,7 +510,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
// Prefer "tap to deal damage"
|
||||
// TODO : Skip this one if triggers on combat damage only?
|
||||
for (SpellAbility sa2 : card.getSpellAbilities()) {
|
||||
if ((sa2.getApi().equals(ApiType.DealDamage))
|
||||
if (ApiType.DealDamage.equals(sa2.getApi())
|
||||
&& (sa2.getTargetRestrictions().canTgtPlayer())) {
|
||||
cardPriority += 300;
|
||||
}
|
||||
@@ -971,8 +971,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
continue;
|
||||
}
|
||||
if ((affected.contains(stCheck) || affected.contains("AttachedBy"))) {
|
||||
totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), sa);
|
||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), sa);
|
||||
totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), stAbility);
|
||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), stAbility);
|
||||
|
||||
grantingAbilities |= stabMap.containsKey("AddAbility");
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
||||
|
||||
@@ -37,7 +38,8 @@ public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
||||
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||
Map<String, Object> params) {
|
||||
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
|
||||
return spells.get(0);
|
||||
}
|
||||
|
||||
@@ -152,7 +152,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return doReturnCommanderLogic(sa, aiPlayer);
|
||||
}
|
||||
|
||||
if ("IfNotBuffed".equals(sa.getParam("AILogic"))) {
|
||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||
return true;
|
||||
} else if ("IfNotBuffed".equals(sa.getParam("AILogic"))) {
|
||||
if (ComputerUtilCard.isUselessCreature(aiPlayer, sa.getHostCard())) {
|
||||
return true; // debuffed by opponent's auras to the level that it becomes useless
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -79,7 +80,8 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||
Map<String, Object> params) {
|
||||
Card host = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
final Game game = host.getGame();
|
||||
|
||||
@@ -7,6 +7,7 @@ import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
|
||||
@@ -36,7 +37,8 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||
Map<String, Object> params) {
|
||||
return spells.get(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
CardCollection list;
|
||||
Card choice = null;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
@@ -220,13 +220,9 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
|
||||
if ("Never".equals(logic)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("PayEnergy".equals(logic)) {
|
||||
} else if ("PayEnergy".equals(logic)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("PayEnergyConservatively".equals(logic)) {
|
||||
} else if ("PayEnergyConservatively".equals(logic)) {
|
||||
boolean onlyInCombat = ai.getController().isAI()
|
||||
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT);
|
||||
boolean onlyDefensive = ai.getController().isAI()
|
||||
@@ -266,9 +262,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logic.equals("MarkOppCreature")) {
|
||||
} else if (logic.equals("MarkOppCreature")) {
|
||||
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
@@ -283,6 +277,11 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
sa.getTargets().add(bestCreat);
|
||||
return true;
|
||||
}
|
||||
} else if (logic.equals("CheckDFC")) {
|
||||
// for cards like Ludevic's Test Subject
|
||||
if (!source.canTransform()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
|
||||
@@ -581,7 +580,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
@@ -661,7 +660,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
boolean preferred = true;
|
||||
CardCollection list;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
int left = amount;
|
||||
@@ -804,7 +803,8 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (mode == PlayerActionConfirmMode.Tribute) {
|
||||
// add counter if that opponent has a giant creature
|
||||
final List<Card> creats = player.getCreaturesInPlay();
|
||||
final int tributeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final int tributeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
final boolean isHaste = source.hasKeyword(Keyword.HASTE);
|
||||
List<Card> threatening = CardLists.filter(creats, new Predicate<Card>() {
|
||||
@@ -863,7 +863,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
final boolean isCurse = sa.isCurse();
|
||||
|
||||
@@ -481,10 +481,11 @@ public class DamageDealAi extends DamageAiBase {
|
||||
final PhaseHandler phase = game.getPhaseHandler();
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
|
||||
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
|
||||
if ("PowerDmg".equals(logic)) {
|
||||
// check if it is better to target the player instead, the original target is already set in PumpAi.pumpTgtAI()
|
||||
if (tgt.canTgtCreatureAndPlayer() && this.shouldTgtP(ai, sa, dmg, noPrevention)){
|
||||
sa.resetTargets();
|
||||
@@ -504,11 +505,11 @@ public class DamageDealAi extends DamageAiBase {
|
||||
TargetChoices tcs = sa.getTargets();
|
||||
|
||||
// Do not use if would kill self
|
||||
if (("SelfDamage".equals(sa.getParam("AILogic"))) && (ai.getLife() <= Integer.parseInt(source.getSVar("SelfDamageAmount")))) {
|
||||
if (("SelfDamage".equals(logic)) && (ai.getLife() <= Integer.parseInt(source.getSVar("SelfDamageAmount")))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("ChoiceBurn".equals(sa.getParam("AILogic"))) {
|
||||
if ("ChoiceBurn".equals(logic)) {
|
||||
// do not waste burns on player if other choices are present
|
||||
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||
tcs.add(enemy);
|
||||
@@ -517,7 +518,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ("Polukranos".equals(sa.getParam("AILogic"))) {
|
||||
if ("Polukranos".equals(logic)) {
|
||||
int dmgTaken = 0;
|
||||
CardCollection humCreatures = enemy.getCreaturesInPlay();
|
||||
Card lastTgt = null;
|
||||
@@ -681,7 +682,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else if ("OppAtTenLife".equals(sa.getParam("AILogic"))) {
|
||||
} else if ("OppAtTenLife".equals(logic)) {
|
||||
for (final Player p : ai.getOpponents()) {
|
||||
if (sa.canTarget(p) && p.getLife() == 10 && tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
tcs.add(p);
|
||||
@@ -690,9 +691,10 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
// TODO: Improve Damage, we shouldn't just target the player just
|
||||
// because we can
|
||||
else if (sa.canTarget(enemy)) {
|
||||
if (sa.canTarget(enemy) && tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|
||||
|| sa.getPayCosts() == null || immediately
|
||||
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) &&
|
||||
(!avoidTargetP(ai, sa))) {
|
||||
|
||||
@@ -26,10 +26,7 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.PaymentDecision;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -252,19 +249,34 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (num != null && num.equals("ChosenX")) {
|
||||
// Necrologia, Pay X Life : Draw X Cards
|
||||
if (sa.getSVar("X").equals("XChoice")) {
|
||||
// Draw up to max hand size but leave at least 3 in library
|
||||
numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
||||
// But no more than what's "safe" and doesn't risk a near death experience
|
||||
// Maybe would be better to check for "serious danger" and take more risk?
|
||||
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
|
||||
numCards--;
|
||||
|
||||
if (sa.getPayCosts() != null) {
|
||||
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
||||
// [Necrologia, Pay X Life : Draw X Cards]
|
||||
// Don't draw more than what's "safe" and don't risk a near death experience
|
||||
// Maybe would be better to check for "serious danger" and take more risk?
|
||||
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
|
||||
numCards--;
|
||||
}
|
||||
} else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||
// [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
|
||||
// TODO: Add special logic to limit/otherwise modify the ChosenX value here
|
||||
|
||||
// Skip this ability if nothing is to be chosen for sacrifice
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sa.setSVar("ChosenX", Integer.toString(numCards));
|
||||
source.setSVar("ChosenX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
// Logic for cards that require special handling
|
||||
if ("YawgmothsBargain".equals(logic)) {
|
||||
return SpecialCardAi.YawgmothsBargain.consider(ai, sa);
|
||||
|
||||
@@ -33,6 +33,12 @@ public class FightAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
// everything is defined or targeted above, can't do anything there?
|
||||
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
||||
// TODO extend Logic for cards like Arena or Grothama
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get creature lists
|
||||
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||
|
||||
@@ -9,6 +9,7 @@ import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.MagicStack;
|
||||
|
||||
public class LifeExchangeVariantAi extends SpellAbilityAi {
|
||||
|
||||
@@ -83,7 +84,25 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
||||
return shouldDo;
|
||||
}
|
||||
else if ("Evra, Halcyon Witness".equals(sourceName)) {
|
||||
// TODO add logic
|
||||
if (!ai.canGainLife())
|
||||
return false;
|
||||
|
||||
int aiLife = ai.getLife();
|
||||
|
||||
if (source.getNetPower() > aiLife) {
|
||||
if (ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check the top of stack
|
||||
MagicStack stack = ai.getGame().getStack();
|
||||
if (!stack.isEmpty()) {
|
||||
SpellAbility saTop = stack.peekAbility();
|
||||
if (ComputerUtil.predictDamageFromSpell(saTop, ai) >= aiLife) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
@@ -22,9 +21,8 @@ public class ScryAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt != null) { // It doesn't appear that Scry ever targets
|
||||
if (sa.usesTargeting()) { // It doesn't appear that Scry ever targets
|
||||
// ability is targeted
|
||||
sa.resetTargets();
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package forge.ai.ability;
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardSplitType;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
@@ -65,8 +64,6 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player aiPlayer, final SpellAbility sa, final String aiLogic) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
return super.checkAiLogic(aiPlayer, sa, aiLogic);
|
||||
}
|
||||
|
||||
@@ -87,7 +84,7 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
if("Transform".equals(mode)) {
|
||||
if (!sa.usesTargeting()) {
|
||||
// no Transform with Defined which is not Self
|
||||
if (source.hasKeyword("CARDNAME can't transform")) {
|
||||
if (!source.canTransform()) {
|
||||
return false;
|
||||
}
|
||||
return shouldTransformCard(source, ai, ph) || "Always".equals(logic);
|
||||
@@ -96,15 +93,13 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list = CardLists.getValidCards(CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES), tgt.getValidTgts(), ai, source, sa);
|
||||
// select only cards with Transform as SplitType
|
||||
// select only the ones that can transform
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card c) {
|
||||
return c.hasAlternateState() && c.getRules().getSplitType() == CardSplitType.Transform;
|
||||
return c.canTransform();
|
||||
}
|
||||
});
|
||||
// select only the ones that can transform
|
||||
list = CardLists.getNotKeyword(list, "CARDNAME can't transform");
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
|
||||
104
forge-ai/src/main/java/forge/ai/ability/SurveilAi.java
Normal file
104
forge-ai/src/main/java/forge/ai/ability/SurveilAi.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
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;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class SurveilAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player, forge.game.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) { // TODO: It doesn't appear that Surveil ever targets, is this necessary?
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.ai.SpellAbilityAi#chkAIDrawback(forge.game.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return doTriggerAINoCost(ai, sa, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
// if the Surveil ability requires tapping and has a mana cost, it's best done at the end of opponent's turn
|
||||
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
||||
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
||||
// even if there's no mana cost.
|
||||
if (sa.getPayCosts() != null) {
|
||||
if (sa.getPayCosts().hasTapCost()
|
||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
}
|
||||
|
||||
// in the player's turn Surveil should only be done in Main1 or in Upkeep if able
|
||||
if (ph.isPlayerTurn(ai)) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return ph.is(PhaseType.MAIN1) || sa.hasParam("Planeswalker");
|
||||
} else {
|
||||
return ph.is(PhaseType.UPKEEP);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if ("Never".equals(aiLogic)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: add card-specific Surveil AI logic here when/if necessary
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||
// Makes no sense to do Surveil when there's nothing in the library
|
||||
if (ai.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double chance = .4; // 40 percent chance for instant speed
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||
}
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.TokenEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.combat.Combat;
|
||||
@@ -42,14 +43,11 @@ import java.util.List;
|
||||
* @version $Id: AbilityFactoryToken.java 17656 2012-10-22 19:32:56Z Max mtg $
|
||||
*/
|
||||
public class TokenAi extends SpellAbilityAi {
|
||||
|
||||
|
||||
private String tokenAmount;
|
||||
private String tokenName;
|
||||
private String[] tokenTypes;
|
||||
private String[] tokenKeywords;
|
||||
private String tokenPower;
|
||||
private String tokenToughness;
|
||||
|
||||
private Card actualToken;
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for AbilityFactory_Token.
|
||||
@@ -58,23 +56,28 @@ public class TokenAi extends SpellAbilityAi {
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*/
|
||||
private void readParameters(final SpellAbility mapParams) {
|
||||
String[] keywords;
|
||||
|
||||
if (mapParams.hasParam("TokenKeywords")) {
|
||||
// TODO: Change this Split to a semicolon or something else
|
||||
keywords = mapParams.getParam("TokenKeywords").split("<>");
|
||||
} else {
|
||||
keywords = new String[0];
|
||||
}
|
||||
|
||||
|
||||
this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
|
||||
this.tokenPower = mapParams.getParam("TokenPower");
|
||||
this.tokenToughness = mapParams.getParam("TokenToughness");
|
||||
this.tokenName = mapParams.getParam("TokenName");
|
||||
this.tokenTypes = mapParams.getParam("TokenTypes").split(",");
|
||||
this.tokenKeywords = keywords;
|
||||
|
||||
TokenEffect effect = new TokenEffect();
|
||||
|
||||
this.actualToken = effect.loadTokenPrototype(mapParams);
|
||||
|
||||
if (actualToken == null) {
|
||||
String[] keywords;
|
||||
|
||||
if (mapParams.hasParam("TokenKeywords")) {
|
||||
// TODO: Change this Split to a semicolon or something else
|
||||
keywords = mapParams.getParam("TokenKeywords").split("<>");
|
||||
} else {
|
||||
keywords = new String[0];
|
||||
}
|
||||
|
||||
this.tokenPower = mapParams.getParam("TokenPower");
|
||||
this.tokenToughness = mapParams.getParam("TokenToughness");
|
||||
} else {
|
||||
this.tokenPower = actualToken.getBasePowerString();
|
||||
this.tokenToughness = actualToken.getBaseToughnessString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,8 +106,11 @@ public class TokenAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
final Card token = spawnToken(ai, sa);
|
||||
if (token == null) {
|
||||
if (actualToken == null) {
|
||||
actualToken = spawnToken(ai, sa);
|
||||
}
|
||||
|
||||
if (actualToken == null) {
|
||||
final AbilitySub sub = sa.getSubAbility();
|
||||
if (pwPlus || (sub != null && SpellApiToAi.Converter.get(sub.getApi()).chkAIDrawback(sub, ai))) {
|
||||
return true; // planeswalker plus ability or sub-ability is
|
||||
@@ -130,24 +136,21 @@ public class TokenAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (canInterruptSacrifice(ai, sa, token)) {
|
||||
if (canInterruptSacrifice(ai, sa, actualToken)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean haste = false;
|
||||
boolean haste = this.actualToken.hasKeyword(Keyword.HASTE);
|
||||
boolean oneShot = sa.getSubAbility() != null
|
||||
&& sa.getSubAbility().getApi() == ApiType.DelayedTrigger;
|
||||
for (final String kw : this.tokenKeywords) {
|
||||
if (kw.equals("Haste")) {
|
||||
haste = true;
|
||||
}
|
||||
}
|
||||
boolean isCreature = this.actualToken.getType().isCreature();
|
||||
|
||||
// Don't generate tokens without haste before main 2 if possible
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && ph.isPlayerTurn(ai) && !haste && !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
boolean buff = false;
|
||||
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
|
||||
if ("Creature".equals(c.getSVar("BuffedBy"))) {
|
||||
if (isCreature && "Creature".equals(c.getSVar("BuffedBy"))) {
|
||||
buff = true;
|
||||
}
|
||||
}
|
||||
@@ -180,12 +183,9 @@ public class TokenAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Don't kill AIs Legendary tokens
|
||||
for (final String type : this.tokenTypes) {
|
||||
if (type.equals("Legendary")) {
|
||||
if (ai.isCardInPlay(this.tokenName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.actualToken.getType().isLegendary() && ai.isCardInPlay(this.actualToken.getName())) {
|
||||
// TODO Check if Token is useless due to an aura or counters?
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
@@ -311,6 +311,18 @@ public class TokenAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (mandatory) {
|
||||
// Necessary because the AI goes into this method twice, first to set up targets (with mandatory=true)
|
||||
// and then the second time to confirm the trigger (where mandatory may be set to false).
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("OnlyOnAlliedAttack".equals(sa.getParam("AILogic"))) {
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
return combat != null && combat.getAttackingPlayer() != null
|
||||
&& !combat.getAttackingPlayer().isOpponentOf(ai);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
@@ -388,6 +400,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
* @param notNull if the token would not survive, still return it
|
||||
* @return token creature created by ability
|
||||
*/
|
||||
// TODO Is this just completely copied from TokenEffect? Let's just call that thing
|
||||
public static Card spawnToken(Player ai, SpellAbility sa, boolean notNull) {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
@@ -511,7 +524,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
// Apply static abilities and prune dead tokens
|
||||
final Game game = ai.getGame();
|
||||
ComputerUtilCard.applyStaticContPT(game, token, null);
|
||||
if (!notNull && token.getNetToughness() < 1) {
|
||||
if (!notNull && token.isCreature() && token.getNetToughness() < 1) {
|
||||
return null;
|
||||
} else {
|
||||
return token;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.11-SNAPSHOT</version>
|
||||
<version>1.6.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
@@ -16,7 +16,7 @@
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>24.1-jre</version>
|
||||
<version>24.1-android</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
@@ -24,4 +24,31 @@
|
||||
<version>3.7</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>checkstyle-validation</id>
|
||||
<phase>validate</phase>
|
||||
<configuration>
|
||||
<configLocation>../checkstyle.xml</configLocation>
|
||||
<includeTestSourceDirectory>true</includeTestSourceDirectory>
|
||||
<encoding>UTF-8</encoding>
|
||||
<consoleOutput>true</consoleOutput>
|
||||
<failsOnError>true</failsOnError>
|
||||
<failOnViolation>true</failOnViolation>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -413,6 +413,9 @@ public class CardStorageReader {
|
||||
return reader.readCard(lines, Files.getNameWithoutExtension(file.getName()));
|
||||
} catch (final FileNotFoundException ex) {
|
||||
throw new RuntimeException("CardReader : run error -- file not found: " + file.getPath(), ex);
|
||||
} catch (final Exception ex) {
|
||||
System.out.println("Error loading cardscript " + file.getName() + ". Please close Forge and resolve this.");
|
||||
throw ex;
|
||||
} finally {
|
||||
try {
|
||||
assert fileInputStream != null;
|
||||
|
||||
@@ -37,6 +37,8 @@ public class StaticData {
|
||||
private Predicate<PaperCard> brawlPredicate;
|
||||
private Predicate<PaperCard> modernPredicate;
|
||||
|
||||
private boolean filteredHandsEnabled = false;
|
||||
|
||||
// Loaded lazily:
|
||||
private IStorage<SealedProduct.Template> boosters;
|
||||
private IStorage<SealedProduct.Template> specialBoosters;
|
||||
@@ -209,6 +211,14 @@ public class StaticData {
|
||||
return brawlPredicate;
|
||||
}
|
||||
|
||||
public void setFilteredHandsEnabled(boolean filteredHandsEnabled){
|
||||
this.filteredHandsEnabled = filteredHandsEnabled;
|
||||
}
|
||||
|
||||
public boolean getFilteredHandsEnabled(){
|
||||
return filteredHandsEnabled;
|
||||
}
|
||||
|
||||
public PaperCard getCardByEditionDate(PaperCard card, Date editionDate) {
|
||||
|
||||
PaperCard c = this.getCommonCards().getCardFromEdition(card.getName(), editionDate, CardDb.SetPreference.LatestCoreExp, card.getArtIndex());
|
||||
|
||||
@@ -32,19 +32,24 @@ public class CardChangedType {
|
||||
private final boolean removeSuperTypes;
|
||||
private final boolean removeCardTypes;
|
||||
private final boolean removeSubTypes;
|
||||
private final boolean removeLandTypes;
|
||||
private final boolean removeCreatureTypes;
|
||||
private final boolean removeArtifactTypes;
|
||||
private final boolean removeEnchantmentTypes;
|
||||
|
||||
public CardChangedType(final CardType addType0, final CardType removeType0, final boolean removeSuperType0,
|
||||
final boolean removeCardType0, final boolean removeSubType0, final boolean removeCreatureType0,
|
||||
final boolean removeArtifactType0) {
|
||||
final boolean removeCardType0, final boolean removeSubType0, final boolean removeLandType0,
|
||||
final boolean removeCreatureType0, final boolean removeArtifactType0,
|
||||
final boolean removeEnchantmentTypes0) {
|
||||
addType = addType0;
|
||||
removeType = removeType0;
|
||||
removeSuperTypes = removeSuperType0;
|
||||
removeCardTypes = removeCardType0;
|
||||
removeSubTypes = removeSubType0;
|
||||
removeLandTypes = removeLandType0;
|
||||
removeCreatureTypes = removeCreatureType0;
|
||||
removeArtifactTypes = removeArtifactType0;
|
||||
removeEnchantmentTypes = removeEnchantmentTypes0;
|
||||
}
|
||||
|
||||
public final CardType getAddType() {
|
||||
@@ -67,6 +72,10 @@ public class CardChangedType {
|
||||
return removeSubTypes;
|
||||
}
|
||||
|
||||
public final boolean isRemoveLandTypes() {
|
||||
return removeLandTypes;
|
||||
}
|
||||
|
||||
public final boolean isRemoveCreatureTypes() {
|
||||
return removeCreatureTypes;
|
||||
}
|
||||
@@ -74,4 +83,8 @@ public class CardChangedType {
|
||||
public final boolean isRemoveArtifactTypes() {
|
||||
return removeArtifactTypes;
|
||||
}
|
||||
|
||||
public final boolean isRemoveEnchantmentTypes() {
|
||||
return removeEnchantmentTypes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,12 +123,19 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
private boolean smallSetOverride = false;
|
||||
private String boosterMustContain = "";
|
||||
private final CardInSet[] cards;
|
||||
private final String[] tokenNormalized;
|
||||
|
||||
private int boosterArts = 1;
|
||||
private SealedProduct.Template boosterTpl = null;
|
||||
|
||||
private CardEdition(CardInSet[] cards) {
|
||||
this.cards = cards;
|
||||
tokenNormalized = null;
|
||||
}
|
||||
|
||||
private CardEdition(CardInSet[] cards, String[] tokens) {
|
||||
this.cards = cards;
|
||||
this.tokenNormalized = tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,6 +261,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
protected CardEdition read(File file) {
|
||||
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
|
||||
|
||||
List<String> tokenNormalized = new ArrayList<>();
|
||||
List<CardEdition.CardInSet> processedCards = new ArrayList<>();
|
||||
if (contents.containsKey("cards")) {
|
||||
for(String line : contents.get("cards")) {
|
||||
@@ -277,7 +285,19 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
}
|
||||
}
|
||||
|
||||
CardEdition res = new CardEdition(processedCards.toArray(new CardInSet[processedCards.size()]));
|
||||
if (contents.containsKey("tokens")) {
|
||||
for(String line : contents.get("tokens")) {
|
||||
if (StringUtils.isBlank(line))
|
||||
continue;
|
||||
|
||||
tokenNormalized.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
CardEdition res = new CardEdition(
|
||||
processedCards.toArray(new CardInSet[processedCards.size()]),
|
||||
tokenNormalized.toArray(new String[tokenNormalized.size()])
|
||||
);
|
||||
|
||||
FileSection section = FileSection.parse(contents.get("metadata"), "=");
|
||||
res.name = section.get("name");
|
||||
|
||||
@@ -87,21 +87,27 @@ final class CardFace implements ICardFace {
|
||||
void setInitialLoyalty(int value) { this.initialLoyalty = value; }
|
||||
|
||||
void setPtText(String value) {
|
||||
final int slashPos = value.indexOf('/');
|
||||
if (slashPos == -1) {
|
||||
final String k[] = value.split("/");
|
||||
|
||||
if (k.length != 2) {
|
||||
throw new RuntimeException("Creature '" + this.getName() + "' has bad p/t stats");
|
||||
}
|
||||
boolean negPower = value.charAt(0) == '-';
|
||||
boolean negToughness = value.charAt(slashPos + 1) == '-';
|
||||
|
||||
this.power = negPower ? value.substring(1, slashPos) : value.substring(0, slashPos);
|
||||
this.toughness = negToughness ? value.substring(slashPos + 2) : value.substring(slashPos + 1);
|
||||
this.power = k[0];
|
||||
this.toughness = k[1];
|
||||
|
||||
this.iPower = StringUtils.isNumeric(this.power) ? Integer.parseInt(this.power) : 0;
|
||||
this.iToughness = StringUtils.isNumeric(this.toughness) ? Integer.parseInt(this.toughness) : 0;
|
||||
this.iPower = parsePT(k[0]);
|
||||
this.iToughness = parsePT(k[1]);
|
||||
}
|
||||
|
||||
if (negPower) { this.iPower *= -1; }
|
||||
if (negToughness) { this.iToughness *= -1; }
|
||||
static int parsePT(String val) {
|
||||
// normalize PT value
|
||||
if (val.contains("*")) {
|
||||
val = val.replace("+*", "");
|
||||
val = val.replace("-*", "");
|
||||
val = val.replace("*", "0");
|
||||
}
|
||||
return Integer.parseInt(val);
|
||||
}
|
||||
|
||||
// Raw fields used for Card creation
|
||||
|
||||
@@ -76,6 +76,51 @@ public final class CardFacePredicates {
|
||||
};
|
||||
}
|
||||
|
||||
static class ValidPredicate implements Predicate<ICardFace> {
|
||||
private String valid;
|
||||
|
||||
public ValidPredicate(final String valid) {
|
||||
this.valid = valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(ICardFace input) {
|
||||
String k[] = valid.split("\\.", 2);
|
||||
|
||||
if ("Card".equals(k[0])) {
|
||||
// okay
|
||||
} else if ("Permanent".equals(k[0])) {
|
||||
if (input.getType().isInstant() || input.getType().isSorcery()) {
|
||||
return false;
|
||||
}
|
||||
} else if (!input.getType().hasStringType(k[0])) {
|
||||
return false;
|
||||
}
|
||||
if (k.length > 1) {
|
||||
for (final String m : k[1].split("\\+")) {
|
||||
if (!hasProperty(input, m)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static protected boolean hasProperty(ICardFace input, final String v) {
|
||||
if (v.startsWith("non")) {
|
||||
return !hasProperty(input, v.substring(3));
|
||||
} else if (!input.getType().hasStringType(v)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Predicate<ICardFace> valid(final String val) {
|
||||
return new ValidPredicate(val);
|
||||
}
|
||||
|
||||
public static class Presets {
|
||||
/** The Constant isBasicLand. */
|
||||
public static final Predicate<ICardFace> IS_BASIC_LAND = new Predicate<ICardFace>() {
|
||||
|
||||
@@ -41,6 +41,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
private CardAiHints aiHints;
|
||||
private ColorSet colorIdentity;
|
||||
private String meldWith;
|
||||
private String partnerWith;
|
||||
|
||||
private CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) {
|
||||
splitType = altMode;
|
||||
@@ -48,6 +49,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
otherPart = faces[1];
|
||||
aiHints = cah;
|
||||
meldWith = "";
|
||||
partnerWith = "";
|
||||
|
||||
//calculate color identity
|
||||
byte colMask = calculateColorIdentity(mainPart);
|
||||
@@ -68,6 +70,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
aiHints = newRules.aiHints;
|
||||
colorIdentity = newRules.colorIdentity;
|
||||
meldWith = newRules.meldWith;
|
||||
partnerWith = newRules.partnerWith;
|
||||
}
|
||||
|
||||
private static byte calculateColorIdentity(final ICardFace face) {
|
||||
@@ -204,7 +207,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public boolean canBePartnerCommander() {
|
||||
return canBeCommander() && Iterables.contains(mainPart.getKeywords(), "Partner");
|
||||
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty());
|
||||
}
|
||||
|
||||
public boolean canBeBrawlCommander() {
|
||||
@@ -216,6 +219,10 @@ public final class CardRules implements ICardCharacteristics {
|
||||
return meldWith;
|
||||
}
|
||||
|
||||
public String getParterWith() {
|
||||
return partnerWith;
|
||||
}
|
||||
|
||||
// vanguard card fields, they don't use sides.
|
||||
private int deltaHand;
|
||||
private int deltaLife;
|
||||
@@ -262,6 +269,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
private int curFace = 0;
|
||||
private CardSplitType altMode = CardSplitType.None;
|
||||
private String meldWith = "";
|
||||
private String partnerWith = "";
|
||||
private String handLife = null;
|
||||
private String normalizedName = "";
|
||||
|
||||
@@ -291,6 +299,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
this.hints = null;
|
||||
this.has = null;
|
||||
this.meldWith = "";
|
||||
this.partnerWith = "";
|
||||
this.normalizedName = "";
|
||||
}
|
||||
|
||||
@@ -307,6 +316,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
|
||||
result.setNormalizedName(this.normalizedName);
|
||||
result.meldWith = this.meldWith;
|
||||
result.partnerWith = this.partnerWith;
|
||||
result.setDlUrls(pictureUrl);
|
||||
if (StringUtils.isNotBlank(handLife))
|
||||
result.setVanguardProperties(handLife);
|
||||
@@ -382,6 +392,9 @@ public final class CardRules implements ICardCharacteristics {
|
||||
case 'K':
|
||||
if ("K".equals(key)) {
|
||||
this.faces[this.curFace].addKeyword(value);
|
||||
if (value.startsWith("Partner:")) {
|
||||
this.partnerWith = value.split(":")[1];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -533,4 +546,8 @@ public final class CardRules implements ICardCharacteristics {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean hasKeyword(final String k) {
|
||||
return Iterables.contains(mainPart.getKeywords(), k);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.util.ComparableOp;
|
||||
import forge.util.PredicateString;
|
||||
import forge.util.PredicateString.StringOp;
|
||||
|
||||
/**
|
||||
* Filtering conditions specific for CardRules class, defined here along with
|
||||
@@ -558,6 +557,19 @@ public final class CardRulesPredicates {
|
||||
}
|
||||
};
|
||||
|
||||
public static final Predicate<CardRules> CAN_BE_COMMANDER = new Predicate<CardRules>() {
|
||||
@Override
|
||||
public boolean apply(final CardRules subject) {
|
||||
return subject.canBeCommander();
|
||||
}
|
||||
};
|
||||
public static final Predicate<CardRules> CAN_BE_PARTNER_COMMANDER = new Predicate<CardRules>() {
|
||||
@Override
|
||||
public boolean apply(final CardRules subject) {
|
||||
return subject.canBePartnerCommander();
|
||||
}
|
||||
};
|
||||
|
||||
public static final Predicate<CardRules> IS_PLANESWALKER = CardRulesPredicates.coreType(true, CardType.CoreType.Planeswalker);
|
||||
public static final Predicate<CardRules> IS_INSTANT = CardRulesPredicates.coreType(true, CardType.CoreType.Instant);
|
||||
public static final Predicate<CardRules> IS_SORCERY = CardRulesPredicates.coreType(true, CardType.CoreType.Sorcery);
|
||||
@@ -570,13 +582,10 @@ public final class CardRulesPredicates {
|
||||
public static final Predicate<CardRules> IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
|
||||
public static final Predicate<CardRules> IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
|
||||
public static final Predicate<CardRules> IS_NON_CREATURE_SPELL = Predicates.not(Predicates.or(Presets.IS_CREATURE, Presets.IS_LAND));
|
||||
public static final Predicate<CardRules> CAN_BE_COMMANDER = Predicates.or(CardRulesPredicates.rules(StringOp.CONTAINS_IC, "can be your commander"),
|
||||
Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
|
||||
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER,
|
||||
Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
|
||||
|
||||
/** The Constant IS_NONCREATURE_SPELL_FOR_GENERATOR. **/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Predicate<CardRules> IS_NONCREATURE_SPELL_FOR_GENERATOR = com.google.common.base.Predicates
|
||||
.or(Presets.IS_SORCERY, Presets.IS_INSTANT, Presets.IS_PLANESWALKER, Presets.IS_ENCHANTMENT,
|
||||
Predicates.and(Presets.IS_ARTIFACT, Predicates.not(Presets.IS_CREATURE)));
|
||||
|
||||
@@ -190,7 +190,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
|
||||
public boolean setCreatureTypes(Collection<String> ctypes) {
|
||||
// if it isn't a creature then this has no effect
|
||||
if (!coreTypes.contains(CoreType.Creature)) {
|
||||
if (!isCreature() && !isTribal()) {
|
||||
return false;
|
||||
}
|
||||
boolean changed = Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
|
||||
@@ -236,7 +236,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
final Set<String> landTypes = Sets.newHashSet();
|
||||
if (isLand()) {
|
||||
for (final String t : subtypes) {
|
||||
if (isALandType(t) || isABasicLandType(t)) {
|
||||
if (isALandType(t)) {
|
||||
landTypes.add(t);
|
||||
}
|
||||
}
|
||||
@@ -435,6 +435,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
@Override
|
||||
public CardTypeView getTypeWithChanges(final Iterable<CardChangedType> changedCardTypes) {
|
||||
CardType newType = null;
|
||||
if (Iterables.isEmpty(changedCardTypes)) {
|
||||
return this;
|
||||
}
|
||||
// we assume that changes are already correctly ordered (taken from TreeMap.values())
|
||||
for (final CardChangedType ct : changedCardTypes) {
|
||||
if(null == newType)
|
||||
@@ -449,7 +452,10 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
if (ct.isRemoveSubTypes()) {
|
||||
newType.subtypes.clear();
|
||||
}
|
||||
else {
|
||||
else if (!newType.subtypes.isEmpty()) {
|
||||
if (ct.isRemoveLandTypes()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_LAND_TYPE);
|
||||
}
|
||||
if (ct.isRemoveCreatureTypes()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
|
||||
// need to remove AllCreatureTypes too when removing creature Types
|
||||
@@ -458,6 +464,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
if (ct.isRemoveArtifactTypes()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
|
||||
}
|
||||
if (ct.isRemoveEnchantmentTypes()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_ENCHANTMENT_TYPE);
|
||||
}
|
||||
}
|
||||
if (ct.getRemoveType() != null) {
|
||||
newType.removeAll(ct.getRemoveType());
|
||||
@@ -466,6 +475,28 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
newType.addAll(ct.getAddType());
|
||||
}
|
||||
}
|
||||
// sanisfy subtypes
|
||||
if (newType != null && !newType.subtypes.isEmpty()) {
|
||||
if (!newType.isCreature() && !newType.isTribal()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
|
||||
newType.subtypes.remove("AllCreatureTypes");
|
||||
}
|
||||
if (!newType.isLand()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_LAND_TYPE);
|
||||
}
|
||||
if (!newType.isArtifact()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
|
||||
}
|
||||
if (!newType.isEnchantment()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_ENCHANTMENT_TYPE);
|
||||
}
|
||||
if (!newType.isInstant() && !newType.isSorcery()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_SPELL_TYPE);
|
||||
}
|
||||
if (!newType.isPlaneswalker() && !newType.isEmblem()) {
|
||||
Iterables.removeIf(newType.subtypes, Predicates.IS_WALKER_TYPE);
|
||||
}
|
||||
}
|
||||
return newType == null ? this : newType;
|
||||
}
|
||||
|
||||
@@ -574,6 +605,13 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
public static final BiMap<String,String> singularTypes = pluralTypes.inverse();
|
||||
}
|
||||
public static class Predicates {
|
||||
public static Predicate<String> IS_LAND_TYPE = new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(String input) {
|
||||
return CardType.isALandType(input);
|
||||
}
|
||||
};
|
||||
|
||||
public static Predicate<String> IS_ARTIFACT_TYPE = new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(String input) {
|
||||
@@ -587,6 +625,27 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
return CardType.isACreatureType(input);
|
||||
}
|
||||
};
|
||||
|
||||
public static Predicate<String> IS_ENCHANTMENT_TYPE = new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(String input) {
|
||||
return CardType.isAnEnchantmentType(input);
|
||||
}
|
||||
};
|
||||
|
||||
public static Predicate<String> IS_SPELL_TYPE = new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(String input) {
|
||||
return CardType.isASpellType(input);
|
||||
}
|
||||
};
|
||||
|
||||
public static Predicate<String> IS_WALKER_TYPE = new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(String input) {
|
||||
return CardType.isAPlaneswalkerType(input);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -656,7 +715,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
}
|
||||
|
||||
public static boolean isALandType(final String cardType) {
|
||||
return (Constant.LAND_TYPES.contains(cardType));
|
||||
return Constant.LAND_TYPES.contains(cardType) || isABasicLandType(cardType);
|
||||
}
|
||||
|
||||
public static boolean isAPlaneswalkerType(final String cardType) {
|
||||
@@ -667,6 +726,13 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
return (Constant.BASIC_TYPES.contains(cardType));
|
||||
}
|
||||
|
||||
public static boolean isAnEnchantmentType(final String cardType) {
|
||||
return (Constant.ENCHANTMENT_TYPES.contains(cardType));
|
||||
}
|
||||
|
||||
public static boolean isASpellType(final String cardType) {
|
||||
return (Constant.SPELL_TYPES.contains(cardType));
|
||||
}
|
||||
|
||||
/**
|
||||
* If the input is a plural type, return the corresponding singular form.
|
||||
|
||||
@@ -291,17 +291,13 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importDeck(Deck deck) {
|
||||
deck.loadDeferredSections();
|
||||
|
||||
for (DeckSection section: deck.parts.keySet()) {
|
||||
this.putSection(section, deck.get(section));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImageKey(boolean altState) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Deck getHumanDeck() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,20 @@
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.deck;
|
||||
|
||||
import forge.item.InventoryItem;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
public abstract class DeckBase implements Serializable, Comparable<DeckBase>, InventoryItem {
|
||||
private static final long serialVersionUID = -7538150536939660052L;
|
||||
// gameType is from Constant.GameType, like GameType.Regular
|
||||
@@ -59,7 +57,7 @@ public abstract class DeckBase implements Serializable, Comparable<DeckBase>, In
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
@@ -74,6 +72,7 @@ public abstract class DeckBase implements Serializable, Comparable<DeckBase>, In
|
||||
public String getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(String directory0) {
|
||||
directory = directory0;
|
||||
}
|
||||
@@ -101,7 +100,7 @@ public abstract class DeckBase implements Serializable, Comparable<DeckBase>, In
|
||||
* <p>
|
||||
* getComment.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String getComment() {
|
||||
@@ -149,5 +148,5 @@ public abstract class DeckBase implements Serializable, Comparable<DeckBase>, In
|
||||
|
||||
public abstract boolean isEmpty();
|
||||
|
||||
public abstract void importDeck(Deck deck);
|
||||
public abstract Deck getHumanDeck();
|
||||
}
|
||||
|
||||
@@ -248,41 +248,33 @@ public enum DeckFormat {
|
||||
return "too many commanders";
|
||||
}
|
||||
|
||||
// Bring values up to 100
|
||||
min++;
|
||||
max++;
|
||||
|
||||
byte cmdCI = 0;
|
||||
Boolean hasPartner = null;
|
||||
for (PaperCard pc : commanders) {
|
||||
// For each commander decrement size by 1 (99 for 1, 98 for 2)
|
||||
min--;
|
||||
max--;
|
||||
|
||||
if (!isLegalCommander(pc.getRules())) {
|
||||
return "has an illegal commander";
|
||||
}
|
||||
|
||||
if (hasPartner != null && !hasPartner) {
|
||||
return "has an illegal commander partnership";
|
||||
}
|
||||
|
||||
boolean isPartner = false;
|
||||
for(String s : pc.getRules().getMainPart().getKeywords()) {
|
||||
if (s.equals("Partner")) {
|
||||
isPartner = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasPartner == null) {
|
||||
hasPartner = isPartner;
|
||||
} else if (!isPartner) {
|
||||
return "has an illegal commander partnership";
|
||||
}
|
||||
|
||||
cmdCI |= pc.getRules().getColorIdentity().getColor();
|
||||
}
|
||||
|
||||
// special check for Partner
|
||||
if (commanders.size() == 2) {
|
||||
// two commander = 98 cards
|
||||
min--;
|
||||
max--;
|
||||
|
||||
PaperCard a = commanders.get(0);
|
||||
PaperCard b = commanders.get(1);
|
||||
|
||||
if (a.getRules().hasKeyword("Partner") && b.getRules().hasKeyword("Partner")) {
|
||||
// normal partner commander
|
||||
} else if (a.getName().equals(b.getRules().getParterWith())
|
||||
&& b.getName().equals(a.getRules().getParterWith())) {
|
||||
// paired partner commander
|
||||
} else {
|
||||
return "has an illegal commander partnership";
|
||||
}
|
||||
}
|
||||
|
||||
final List<PaperCard> erroneousCI = new ArrayList<PaperCard>();
|
||||
|
||||
Set<String> basicLandNames = new HashSet<>();
|
||||
@@ -521,6 +513,8 @@ public enum DeckFormat {
|
||||
for (final PaperCard p : commanders) {
|
||||
cmdCI |= p.getRules().getColorIdentity().getColor();
|
||||
}
|
||||
return Predicates.compose(Predicates.or(CardRulesPredicates.hasColorIdentity(cmdCI), CardRulesPredicates.hasKeyword("Partner")), PaperCard.FN_GET_RULES);
|
||||
// TODO : check commander what kind of Partner it needs
|
||||
return Predicates.compose(Predicates.or(CardRulesPredicates.hasColorIdentity(cmdCI),
|
||||
CardRulesPredicates.Presets.CAN_BE_PARTNER_COMMANDER), PaperCard.FN_GET_RULES);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
package forge.deck;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import forge.StaticData;
|
||||
import forge.item.PaperCard;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -47,7 +44,8 @@ public class DeckGroup extends DeckBase {
|
||||
*
|
||||
* @return the human deck
|
||||
*/
|
||||
public final Deck getHumanDeck() {
|
||||
@Override
|
||||
public Deck getHumanDeck() {
|
||||
return humanDeck;
|
||||
}
|
||||
|
||||
@@ -160,100 +158,4 @@ public class DeckGroup extends DeckBase {
|
||||
public String getImageKey(boolean altState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void importDeck(Deck deck) {
|
||||
CardPool draftedCards = this.getHumanDeck().getAllCardsInASinglePool(false);
|
||||
|
||||
this.getHumanDeck().putSection(DeckSection.Main, new CardPool());
|
||||
this.getHumanDeck().putSection(DeckSection.Sideboard, new CardPool());
|
||||
|
||||
HashMap<String, Integer> countByName = getCountByName(deck);
|
||||
|
||||
addFromDraftedCardPool(countByName, draftedCards);
|
||||
addBasicLands(deck, countByName, draftedCards);
|
||||
}
|
||||
|
||||
private HashMap<String, Integer> getCountByName(Deck deck) {
|
||||
HashMap<String, Integer> result = new HashMap<String, Integer>();
|
||||
|
||||
for (Map.Entry<PaperCard, Integer> entry: deck.getMain()) {
|
||||
PaperCard importedCard = entry.getKey();
|
||||
|
||||
Integer previousCount = result.getOrDefault(importedCard.getName(), 0);
|
||||
int countToAdd = entry.getValue();
|
||||
|
||||
result.put(importedCard.getName(), countToAdd + previousCount);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addFromDraftedCardPool(HashMap<String, Integer> countByName, CardPool availableCards) {
|
||||
for (Map.Entry<PaperCard, Integer> entry: availableCards) {
|
||||
|
||||
PaperCard availableCard = entry.getKey();
|
||||
Integer availableCount = entry.getValue();
|
||||
int countToAdd = countByName.getOrDefault(availableCard.getName(), 0);
|
||||
|
||||
if (availableCard.getRules().getType().isBasicLand()) {
|
||||
// basic lands are added regardless from drafted cards
|
||||
continue;
|
||||
}
|
||||
|
||||
int countMain = Math.min(availableCount, countToAdd);
|
||||
|
||||
if (countMain > 0) {
|
||||
this.getHumanDeck().getMain().add(availableCard, countMain);
|
||||
countByName.put(availableCard.getName(), countToAdd - countMain);
|
||||
}
|
||||
|
||||
int countSideboard = availableCount - countMain;
|
||||
|
||||
if (countSideboard > 0) {
|
||||
CardPool sideboard = this.getHumanDeck().getOrCreate(DeckSection.Sideboard);
|
||||
sideboard.add(availableCard, countSideboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addBasicLands(Deck deck, HashMap<String, Integer> countByName, CardPool availableCards) {
|
||||
HashMap<String, PaperCard> basicLandsByName = getBasicLandsByName(deck, countByName);
|
||||
|
||||
Date dateWithAllCards = StaticData.instance().getEditions().getEarliestDateWithAllCards(availableCards);
|
||||
for (String cardName: countByName.keySet()) {
|
||||
|
||||
PaperCard card = basicLandsByName.getOrDefault(cardName, null);
|
||||
|
||||
if (card == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int countToAdd = countByName.get(cardName);
|
||||
|
||||
card = StaticData.instance().getCardByEditionDate(card, dateWithAllCards);
|
||||
this.getHumanDeck().getMain().add(card.getName(), card.getEdition(), countToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, PaperCard> getBasicLandsByName(Deck deck, HashMap<String, Integer> countByName) {
|
||||
HashMap<String, PaperCard> result = new HashMap<String, PaperCard>();
|
||||
|
||||
for (Map.Entry<PaperCard, Integer> entry: deck.getMain()) {
|
||||
PaperCard card = entry.getKey();
|
||||
|
||||
if (!card.getRules().getType().isBasicLand()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.containsKey(card.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.put(card.getName(), card);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ public enum DeckSection {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String valToCompate = value.trim();
|
||||
final String valToCompare = value.trim();
|
||||
for (final DeckSection v : DeckSection.values()) {
|
||||
if (v.name().compareToIgnoreCase(valToCompate) == 0) {
|
||||
if (v.name().compareToIgnoreCase(valToCompare) == 0) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,6 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
|
||||
@Override
|
||||
public String getImageKey(boolean altState) {
|
||||
return ImageKeys.TOKEN_PREFIX + imageFileName;
|
||||
return ImageKeys.TOKEN_PREFIX + imageFileName.replace(" ", "_");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,19 @@ public class TokenDb implements ITokenDatabase {
|
||||
|
||||
@Override
|
||||
public PaperToken getToken(String tokenName, String edition) {
|
||||
try {
|
||||
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition));
|
||||
// TODO Cache the token after it's referenced
|
||||
return pt;
|
||||
} catch(Exception e) {
|
||||
return null;
|
||||
String fullName = String.format("%s_%s", tokenName, edition.toLowerCase());
|
||||
|
||||
if (!tokensByName.containsKey(fullName)) {
|
||||
try {
|
||||
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition));
|
||||
tokensByName.put(fullName, pt);
|
||||
return pt;
|
||||
} catch(Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return tokensByName.get(fullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.11-SNAPSHOT</version>
|
||||
<version>1.6.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
@@ -30,5 +30,37 @@
|
||||
<scope>test</scope>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.sentry</groupId>
|
||||
<artifactId>sentry-log4j</artifactId>
|
||||
<version>1.7.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>checkstyle-validation</id>
|
||||
<phase>validate</phase>
|
||||
<configuration>
|
||||
<configLocation>../checkstyle.xml</configLocation>
|
||||
<includeTestSourceDirectory>true</includeTestSourceDirectory>
|
||||
<encoding>UTF-8</encoding>
|
||||
<consoleOutput>true</consoleOutput>
|
||||
<failsOnError>true</failsOnError>
|
||||
<failOnViolation>true</failOnViolation>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -314,7 +314,11 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
list.addAll(p.getCardsIn(presentZone));
|
||||
}
|
||||
}
|
||||
|
||||
if (presentPlayer.equals("Any")) {
|
||||
for (final Player p : this.getHostCard().getController().getAllies()) {
|
||||
list.addAll(p.getCardsIn(presentZone));
|
||||
}
|
||||
}
|
||||
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), null);
|
||||
|
||||
int right = 1;
|
||||
|
||||
@@ -168,6 +168,10 @@ public class ForgeScript {
|
||||
if (!sa.isFlashBackAbility()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("Jumpstart")) {
|
||||
if (!sa.isJumpstart()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("Kicked")) {
|
||||
if (!sa.isKicked()) {
|
||||
return false;
|
||||
@@ -204,6 +208,12 @@ public class ForgeScript {
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("YouCtrl")) {
|
||||
return sa.getActivatingPlayer().equals(sourceController);
|
||||
} else if (sa.getHostCard() != null) {
|
||||
if (!sa.getHostCard().hasProperty(property, sourceController, source, spellAbility)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -20,6 +20,7 @@ package forge.game;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.*;
|
||||
import forge.GameCommand;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -150,8 +151,9 @@ public class GameAction {
|
||||
// Cards returned from exile face-down must be reset to their original state, otherwise
|
||||
// all sort of funky shenanigans may happen later (e.g. their ETB replacement effects are set
|
||||
// up on the wrong card state etc.).
|
||||
if (zoneTo.is(ZoneType.Hand) && zoneFrom.is(ZoneType.Exile) && c.isFaceDown()) {
|
||||
if (c.isFaceDown() && (fromBattlefield || (toHand && zoneFrom.is(ZoneType.Exile)))) {
|
||||
c.setState(CardStateName.Original, true);
|
||||
c.runFaceupCommands();
|
||||
}
|
||||
|
||||
// Clean up the temporary Dash SVar when the Dashed card leaves the battlefield
|
||||
@@ -190,7 +192,7 @@ public class GameAction {
|
||||
|
||||
if (!c.isToken()) {
|
||||
if (c.isCloned()) {
|
||||
c.switchStates(CardStateName.Cloner, CardStateName.Original, false);
|
||||
c.switchStates(CardStateName.Original, CardStateName.Cloner, false);
|
||||
c.setState(CardStateName.Original, false);
|
||||
c.clearStates(CardStateName.Cloner, false);
|
||||
if (c.isFlipCard()) {
|
||||
@@ -250,11 +252,24 @@ public class GameAction {
|
||||
Card noLandLKI = CardUtil.getLKICopy(c);
|
||||
// this check needs to check if this card would be on the battlefield
|
||||
noLandLKI.setLastKnownZone(zoneTo);
|
||||
|
||||
|
||||
CardCollection preList = new CardCollection(noLandLKI);
|
||||
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
|
||||
|
||||
// fake etb counters thing, then if something changed,
|
||||
// need to apply checkStaticAbilities again
|
||||
if(!noLandLKI.isLand()) {
|
||||
if (noLandLKI.putEtbCounters()) {
|
||||
// counters are added need to check again
|
||||
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
|
||||
}
|
||||
}
|
||||
|
||||
if(noLandLKI.isLand()) {
|
||||
// if it isn't on the Stack, it stays in that Zone
|
||||
if (!c.getZone().is(ZoneType.Stack)) {
|
||||
return c;
|
||||
}
|
||||
// if something would only be a land when entering the battlefield and not before
|
||||
// put it into the graveyard instead
|
||||
zoneTo = c.getOwner().getZone(ZoneType.Graveyard);
|
||||
@@ -262,6 +277,9 @@ public class GameAction {
|
||||
copied.setState(CardStateName.Original, false);
|
||||
copied.setManifested(false);
|
||||
copied.updateStateForView();
|
||||
|
||||
// not to battlefield anymore!
|
||||
toBattlefield = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +375,10 @@ public class GameAction {
|
||||
// do ETB counters after StaticAbilities check
|
||||
if (!suppress) {
|
||||
if (toBattlefield) {
|
||||
copied.putEtbCounters();
|
||||
if (copied.putEtbCounters()) {
|
||||
// if counter where put of card, call checkStaticAbilities again
|
||||
checkStaticAbilities();
|
||||
}
|
||||
}
|
||||
copied.clearEtbCounters();
|
||||
}
|
||||
@@ -367,6 +388,7 @@ public class GameAction {
|
||||
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Card", lastKnownInfo);
|
||||
runParams.put("Cause", cause);
|
||||
runParams.put("Origin", zoneFrom != null ? zoneFrom.getZoneType().name() : null);
|
||||
runParams.put("Destination", zoneTo.getZoneType().name());
|
||||
runParams.put("SpellAbilityStackInstance", game.stack.peek());
|
||||
@@ -873,9 +895,13 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
if (runEvents) {
|
||||
// preList means that this is run by a pre Check with LKI objects
|
||||
// in that case Always trigger should not Run
|
||||
if (preList.isEmpty()) {
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Always, runParams, false);
|
||||
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Immediate, runParams, false);
|
||||
}
|
||||
|
||||
// Update P/T and type in the view only once after all the cards have been processed, to avoid flickering
|
||||
@@ -1562,6 +1588,53 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
private void drawStartingHand(Player p1){
|
||||
|
||||
//check initial hand
|
||||
List<Card> lib1 = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
|
||||
List<Card> hand1 = lib1.subList(0,p1.getMaxHandSize());
|
||||
System.out.println(hand1.toString());
|
||||
|
||||
//shuffle
|
||||
List<Card> shuffledCards = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
|
||||
Collections.shuffle(shuffledCards);
|
||||
|
||||
//check a second hand
|
||||
List<Card> hand2 = shuffledCards.subList(0,p1.getMaxHandSize());
|
||||
System.out.println(hand2.toString());
|
||||
|
||||
//choose better hand according to land count
|
||||
float averageLandRatio = getLandRatio(lib1);
|
||||
if(getHandScore(hand1, averageLandRatio)>getHandScore(hand2, averageLandRatio)){
|
||||
p1.getZone(ZoneType.Library).setCards(shuffledCards);
|
||||
}
|
||||
p1.drawCards(p1.getMaxHandSize());
|
||||
}
|
||||
|
||||
private float getLandRatio(List<Card> deck){
|
||||
int landCount = 0;
|
||||
for(Card c:deck){
|
||||
if(c.isLand()){
|
||||
landCount++;
|
||||
}
|
||||
}
|
||||
if (landCount == 0 ){
|
||||
return 0;
|
||||
}
|
||||
return Float.valueOf(landCount)/Float.valueOf(deck.size());
|
||||
}
|
||||
|
||||
private float getHandScore(List<Card> hand, float landRatio){
|
||||
int landCount = 0;
|
||||
for(Card c:hand){
|
||||
if(c.isLand()){
|
||||
landCount++;
|
||||
}
|
||||
}
|
||||
float averageCount = landRatio * hand.size();
|
||||
return Math.abs(averageCount-landCount);
|
||||
}
|
||||
|
||||
public void startGame(GameOutcome lastGameOutcome) {
|
||||
startGame(lastGameOutcome, null);
|
||||
}
|
||||
@@ -1582,7 +1655,11 @@ public class GameAction {
|
||||
|
||||
game.setAge(GameStage.Mulligan);
|
||||
for (final Player p1 : game.getPlayers()) {
|
||||
p1.drawCards(p1.getMaxHandSize());
|
||||
if (StaticData.instance().getFilteredHandsEnabled() ) {
|
||||
drawStartingHand(p1);
|
||||
} else {
|
||||
p1.drawCards(p1.getStartingHandSize());
|
||||
}
|
||||
|
||||
// If pl has Backup Plan as a Conspiracy draw that many extra hands
|
||||
|
||||
@@ -1702,6 +1779,11 @@ public class GameAction {
|
||||
boolean isMultiPlayer = game.getPlayers().size() > 2;
|
||||
int mulliganDelta = isMultiPlayer ? 0 : 1;
|
||||
|
||||
// https://magic.wizards.com/en/articles/archive/feature/checking-brawl-2018-07-09
|
||||
if (game.getRules().hasAppliedVariant(GameType.Brawl) && !isMultiPlayer){
|
||||
mulliganDelta = 0;
|
||||
}
|
||||
|
||||
boolean allKept;
|
||||
do {
|
||||
allKept = true;
|
||||
@@ -1766,7 +1848,7 @@ public class GameAction {
|
||||
//Vancouver Mulligan
|
||||
for(Player p : whoCanMulligan) {
|
||||
if (p.getStartingHandSize() > p.getZone(ZoneType.Hand).size()) {
|
||||
p.scry(1);
|
||||
p.scry(1, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public final class GameActionUtil {
|
||||
final SpellAbility newSA = sa.copy(activator);
|
||||
final SpellAbilityRestriction sar = newSA.getRestrictions();
|
||||
if (o.isWithFlash()) {
|
||||
sar.setInstantSpeed(true);
|
||||
sar.setInstantSpeed(true);
|
||||
}
|
||||
sar.setZone(null);
|
||||
newSA.setMayPlay(o.getAbility());
|
||||
@@ -174,7 +174,8 @@ public final class GameActionUtil {
|
||||
}
|
||||
|
||||
if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
|
||||
final SpellAbility newSA = sa.copyWithNoManaCost();
|
||||
// set the cost to this directly to buypass non mana cost
|
||||
final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
|
||||
newSA.setBasicSpell(false);
|
||||
newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0"));
|
||||
// makes new SpellDescription
|
||||
@@ -186,6 +187,15 @@ public final class GameActionUtil {
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
|
||||
if (sa.hasParam("Equip") && activator.hasKeyword("EquipInstantSpeed")) {
|
||||
final SpellAbility newSA = sa.copy(activator);
|
||||
SpellAbilityRestriction sar = newSA.getRestrictions();
|
||||
sar.setSorcerySpeed(false);
|
||||
sar.setInstantSpeed(true);
|
||||
newSA.setDescription(sa.getDescription() + " (you may activate any time you could cast an instant )");
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
|
||||
for (final KeywordInterface inst : source.getKeywords()) {
|
||||
final String keyword = inst.getOriginal();
|
||||
if (sa.isSpell() && keyword.startsWith("Flashback")) {
|
||||
@@ -201,20 +211,12 @@ public final class GameActionUtil {
|
||||
flashback.getRestrictions().setZone(ZoneType.Graveyard);
|
||||
|
||||
// there is a flashback cost (and not the cards cost)
|
||||
if (!keyword.equals("Flashback")) {
|
||||
flashback.setPayCosts(new Cost(keyword.substring(10), false));
|
||||
if (keyword.contains(":")) {
|
||||
final String k[] = keyword.split(":");
|
||||
flashback.setPayCosts(new Cost(k[1], false));
|
||||
}
|
||||
alternatives.add(flashback);
|
||||
}
|
||||
|
||||
if (sa.hasParam("Equip") && sa instanceof AbilityActivated && keyword.equals("EquipInstantSpeed")) {
|
||||
final SpellAbility newSA = sa.copy(activator);
|
||||
SpellAbilityRestriction sar = newSA.getRestrictions();
|
||||
sar.setSorcerySpeed(false);
|
||||
sar.setInstantSpeed(true);
|
||||
newSA.setDescription(sa.getDescription() + " (you may activate any time you could cast an instant )");
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
}
|
||||
return alternatives;
|
||||
}
|
||||
@@ -259,6 +261,11 @@ public final class GameActionUtil {
|
||||
final Cost cost = new Cost("Discard<1/Land>", false);
|
||||
costs.add(new OptionalCostValue(OptionalCost.Retrace, cost));
|
||||
}
|
||||
} else if (keyword.equals("Jump-start")) {
|
||||
if (source.getZone().is(ZoneType.Graveyard)) {
|
||||
final Cost cost = new Cost("Discard<1/Card>", false);
|
||||
costs.add(new OptionalCostValue(OptionalCost.Jumpstart, cost));
|
||||
}
|
||||
} else if (keyword.startsWith("MayFlashCost")) {
|
||||
String[] k = keyword.split(":");
|
||||
final Cost cost = new Cost(k[1], false);
|
||||
@@ -286,6 +293,7 @@ public final class GameActionUtil {
|
||||
result.addConspireInstance();
|
||||
break;
|
||||
case Retrace:
|
||||
case Jumpstart:
|
||||
result.getRestrictions().setZone(ZoneType.Graveyard);
|
||||
break;
|
||||
case Flash:
|
||||
@@ -389,8 +397,8 @@ public final class GameActionUtil {
|
||||
}
|
||||
}
|
||||
} else if (keyword.startsWith("Kicker")) {
|
||||
String[] sCosts = TextUtil.split(keyword.substring(6), ':');
|
||||
boolean generic = "Generic".equals(sCosts[sCosts.length - 1]);
|
||||
String[] sCosts = TextUtil.split(keyword.substring(6), ':');
|
||||
boolean generic = "Generic".equals(sCosts[sCosts.length - 1]);
|
||||
// If this is a "generic kicker" (Undergrowth), ignore value for kicker creations
|
||||
int numKickers = sCosts.length - (generic ? 1 : 0);
|
||||
for (int i = 0; i < abilities.size(); i++) {
|
||||
|
||||
@@ -25,7 +25,9 @@ import forge.game.card.CounterType;
|
||||
import forge.game.event.GameEventCardAttachment;
|
||||
import forge.game.event.GameEventCardAttachment.AttachMethod;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
@@ -337,6 +339,17 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canBeEnchantedBy(final Card aura) {
|
||||
SpellAbility sa = aura.getFirstAttachSpell();
|
||||
TargetRestrictions tgt = null;
|
||||
if (sa != null) {
|
||||
tgt = sa.getTargetRestrictions();
|
||||
}
|
||||
|
||||
return !(hasProtectionFrom(aura)
|
||||
|| ((tgt != null) && !isValid(tgt.getValidTgts(), aura.getController(), aura, sa)));
|
||||
}
|
||||
|
||||
public abstract boolean hasProtectionFrom(final Card source);
|
||||
|
||||
// Counters!
|
||||
@@ -365,7 +378,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
|
||||
|
||||
abstract public boolean canReceiveCounters(final CounterType type);
|
||||
abstract public void addCounter(final CounterType counterType, final int n, final Card source, final boolean applyMultiplier, final boolean fireEvents);
|
||||
abstract public int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents);
|
||||
abstract public void subtractCounter(final CounterType counterName, final int n);
|
||||
abstract public void clearCounters();
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import forge.game.event.GameEventPlayerPoisoned;
|
||||
import forge.game.event.GameEventScry;
|
||||
import forge.game.event.GameEventSpellAbilityCast;
|
||||
import forge.game.event.GameEventSpellResolved;
|
||||
import forge.game.event.GameEventSurveil;
|
||||
import forge.game.event.GameEventTurnBegan;
|
||||
import forge.game.event.GameEventTurnPhase;
|
||||
import forge.game.event.IGameEventVisitor;
|
||||
@@ -65,7 +66,24 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
|
||||
|
||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, scryOutcome);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventSurveil ev) {
|
||||
String surveilOutcome = "";
|
||||
String toLibrary = Lang.nounWithAmount(ev.toLibrary, "card") + " to the top of the library";
|
||||
String toGraveyard = Lang.nounWithAmount(ev.toGraveyard, "card") + " to the graveyard";
|
||||
|
||||
if (ev.toLibrary > 0 && ev.toGraveyard > 0) {
|
||||
surveilOutcome = ev.player.toString() + " surveiled " + toLibrary + " and " + toGraveyard;
|
||||
} else if (ev.toGraveyard == 0) {
|
||||
surveilOutcome = ev.player.toString() + " surveiled " + toLibrary;
|
||||
} else {
|
||||
surveilOutcome = ev.player.toString() + " surveiled " + toGraveyard;
|
||||
}
|
||||
|
||||
return new GameLogEntry(GameLogEntryType.STACK_RESOLVE, surveilOutcome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameLogEntry visit(GameEventSpellResolved ev) {
|
||||
String messageForLog = ev.hasFizzled ? ev.spell.getHostCard().getName() + " ability fizzles." : ev.spell.getStackDescription();
|
||||
|
||||
@@ -58,9 +58,6 @@ public class StaticEffect {
|
||||
private String chosenType;
|
||||
private Map<String, String> mapParams = Maps.newTreeMap();
|
||||
|
||||
// for P/T
|
||||
private final Map<Card, String> originalPT = Maps.newTreeMap();
|
||||
|
||||
// for types
|
||||
private boolean overwriteTypes = false;
|
||||
private boolean keepSupertype = false;
|
||||
@@ -101,7 +98,6 @@ public class StaticEffect {
|
||||
copy.xValueMap = this.xValueMap;
|
||||
copy.chosenType = this.chosenType;
|
||||
copy.mapParams = this.mapParams;
|
||||
map.fillKeyedMap(copy.originalPT, this.originalPT);
|
||||
copy.overwriteTypes = this.overwriteTypes;
|
||||
copy.keepSupertype = this.keepSupertype;
|
||||
copy.removeSubTypes = this.removeSubTypes;
|
||||
@@ -345,68 +341,6 @@ public class StaticEffect {
|
||||
this.originalKeywords.clear();
|
||||
}
|
||||
|
||||
// original power/toughness
|
||||
/**
|
||||
* <p>
|
||||
* addOriginalPT.
|
||||
* </p>
|
||||
*
|
||||
* @param c
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param power
|
||||
* a int.
|
||||
* @param toughness
|
||||
* a int.
|
||||
*/
|
||||
public final void addOriginalPT(final Card c, final int power, final int toughness) {
|
||||
final String pt = power + "/" + toughness;
|
||||
if (!this.originalPT.containsKey(c)) {
|
||||
this.originalPT.put(c, pt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getOriginalPower.
|
||||
* </p>
|
||||
*
|
||||
* @param c
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public final int getOriginalPower(final Card c) {
|
||||
int power = -1;
|
||||
if (this.originalPT.containsKey(c)) {
|
||||
power = Integer.parseInt(this.originalPT.get(c).split("/")[0]);
|
||||
}
|
||||
return power;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getOriginalToughness.
|
||||
* </p>
|
||||
*
|
||||
* @param c
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public final int getOriginalToughness(final Card c) {
|
||||
int tough = -1;
|
||||
if (this.originalPT.containsKey(c)) {
|
||||
tough = Integer.parseInt(this.originalPT.get(c).split("/")[1]);
|
||||
}
|
||||
return tough;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* clearAllOriginalPTs.
|
||||
* </p>
|
||||
*/
|
||||
public final void clearAllOriginalPTs() {
|
||||
this.originalPT.clear();
|
||||
}
|
||||
|
||||
// should we overwrite types?
|
||||
/**
|
||||
@@ -995,7 +929,7 @@ public class StaticEffect {
|
||||
}
|
||||
|
||||
// remove set P/T
|
||||
if (!params.containsKey("CharacteristicDefining") && setPT) {
|
||||
if (setPT) {
|
||||
affectedCard.removeNewPT(getTimestamp());
|
||||
}
|
||||
|
||||
@@ -1036,7 +970,7 @@ public class StaticEffect {
|
||||
}
|
||||
|
||||
// remove abilities
|
||||
if (params.containsKey("RemoveAllAbilities")) {
|
||||
if (params.containsKey("RemoveAllAbilities") || params.containsKey("RemoveIntrinsicAbilities")) {
|
||||
affectedCard.unSuppressCardTraits();
|
||||
}
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ public final class AbilityFactory {
|
||||
}
|
||||
}
|
||||
|
||||
if (api == ApiType.Charm || api == ApiType.GenericChoice) {
|
||||
if (api == ApiType.Charm || api == ApiType.GenericChoice || api == ApiType.AssignGroup) {
|
||||
final String key = "Choices";
|
||||
if (mapParams.containsKey(key)) {
|
||||
List<String> names = Lists.newArrayList(mapParams.get(key).split(","));
|
||||
|
||||
@@ -381,7 +381,7 @@ public class AbilityUtils {
|
||||
svarval = ability.getSVar(amount);
|
||||
}
|
||||
if (StringUtils.isBlank(svarval)) {
|
||||
if ((ability != null) && (ability instanceof SpellAbility) && !(ability instanceof SpellPermanent)) {
|
||||
if ((ability != null) && (ability instanceof SpellAbility) && !(ability instanceof SpellPermanent) && !amount.equals("ChosenX")) {
|
||||
System.err.printf("SVar '%s' not found in ability, fallback to Card (%s). Ability is (%s)%n", amount, card.getName(), ability);
|
||||
}
|
||||
svarval = card.getSVar(amount);
|
||||
@@ -439,6 +439,10 @@ public class AbilityUtils {
|
||||
players.addAll(game.getPlayers());
|
||||
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
|
||||
}
|
||||
else if (hType.equals("YourTeam")) {
|
||||
players.addAll(player.getYourTeam());
|
||||
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
|
||||
}
|
||||
else if (hType.equals("Opponents")) {
|
||||
players.addAll(player.getOpponents());
|
||||
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
|
||||
@@ -891,7 +895,11 @@ public class AbilityUtils {
|
||||
|
||||
final Player player = sa == null ? card.getController() : sa.getActivatingPlayer();
|
||||
|
||||
if (defined.equals("Targeted") || defined.equals("TargetedPlayer")) {
|
||||
if (defined.equals("TargetedOrController")) {
|
||||
players.addAll(getDefinedPlayers(card, "Targeted", sa));
|
||||
players.addAll(getDefinedPlayers(card, "TargetedController", sa));
|
||||
}
|
||||
else if (defined.equals("Targeted") || defined.equals("TargetedPlayer")) {
|
||||
final SpellAbility saTargeting = sa.getSATargetingPlayer();
|
||||
if (saTargeting != null) {
|
||||
players.addAll(saTargeting.getTargets().getTargetPlayers());
|
||||
@@ -1672,7 +1680,10 @@ public class AbilityUtils {
|
||||
res.setZone(null);
|
||||
newSA.setRestrictions(res);
|
||||
// timing restrictions still apply
|
||||
if (res.checkTimingRestrictions(tgtCard, newSA) && newSA.checkOtherRestrictions()) {
|
||||
if (res.checkTimingRestrictions(tgtCard, newSA)
|
||||
// still need to check the other restrictions like Aftermath
|
||||
&& res.checkOtherRestrictions(tgtCard, newSA, controller)
|
||||
&& newSA.checkOtherRestrictions()) {
|
||||
sas.add(newSA);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public enum ApiType {
|
||||
AnimateAll (AnimateAllEffect.class),
|
||||
Attach (AttachEffect.class),
|
||||
Ascend (AscendEffect.class),
|
||||
AssignGroup (AssignGroupEffect.class),
|
||||
Balance (BalanceEffect.class),
|
||||
BecomeMonarch (BecomeMonarchEffect.class),
|
||||
BecomesBlocked (BecomesBlockedEffect.class),
|
||||
@@ -81,6 +82,7 @@ public enum ApiType {
|
||||
GenericChoice (ChooseGenericEffect.class),
|
||||
Goad (GoadEffect.class),
|
||||
Haunt (HauntEffect.class),
|
||||
ImmediateTrigger (ImmediateTriggerEffect.class),
|
||||
LookAt (LookAtEffect.class),
|
||||
LoseLife (LifeLoseEffect.class),
|
||||
LosesGame (GameLossEffect.class),
|
||||
@@ -142,6 +144,7 @@ public enum ApiType {
|
||||
SkipTurn (SkipTurnEffect.class),
|
||||
StoreSVar (StoreSVarEffect.class),
|
||||
StoreMap (StoreMapEffect.class),
|
||||
Surveil (SurveilEffect.class),
|
||||
Tap (TapEffect.class),
|
||||
TapAll (TapAllEffect.class),
|
||||
TapOrUntap (TapOrUntapEffect.class),
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.card.CardType;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
@@ -385,4 +386,83 @@ public abstract class SpellAbilityEffect {
|
||||
|
||||
return eff;
|
||||
}
|
||||
|
||||
protected static void replaceDying(final SpellAbility sa) {
|
||||
if (sa.hasParam("ReplaceDyingDefined") || sa.hasParam("ReplaceDyingValid")) {
|
||||
|
||||
if (sa.hasParam("ReplaceDyingCondition")) {
|
||||
// currently there is only one with Kicker
|
||||
final String condition = sa.getParam("ReplaceDyingCondition");
|
||||
if ("Kicked".equals(condition)) {
|
||||
if (!sa.isKicked()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final Player controller = sa.getActivatingPlayer();
|
||||
final Game game = host.getGame();
|
||||
String zone = sa.getParamOrDefault("ReplaceDyingZone", "Exile");
|
||||
|
||||
CardCollection cards = null;
|
||||
|
||||
if (sa.hasParam("ReplaceDyingDefined")) {
|
||||
cards = AbilityUtils.getDefinedCards(host, sa.getParam("ReplaceDyingDefined"), sa);
|
||||
// no cards, no need for Effect
|
||||
if (cards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// build an Effect with that infomation
|
||||
String name = host.getName() + "'s Effect";
|
||||
|
||||
final Card eff = createEffect(host, controller, name, host.getImageKey());
|
||||
if (cards != null) {
|
||||
eff.addRemembered(cards);
|
||||
}
|
||||
|
||||
String valid = sa.getParamOrDefault("ReplaceDyingValid", "Card.IsRemembered");
|
||||
|
||||
String repeffstr = "Event$ Moved | ValidCard$ " + valid +
|
||||
"| Origin$ Battlefield | Destination$ Graveyard " +
|
||||
"| Description$ If the creature would die this turn, exile it instead.";
|
||||
String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone;
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
|
||||
re.setLayer(ReplacementLayer.Other);
|
||||
|
||||
re.setOverridingAbility(AbilityFactory.getAbility(effect, eff));
|
||||
eff.addReplacementEffect(re);
|
||||
|
||||
if (cards != null) {
|
||||
// Add forgot trigger
|
||||
addForgetOnMovedTrigger(eff, "Battlefield");
|
||||
}
|
||||
|
||||
// Copy text changes
|
||||
if (sa.isIntrinsic()) {
|
||||
eff.copyChangedTextFrom(host);
|
||||
}
|
||||
|
||||
final GameCommand endEffect = new GameCommand() {
|
||||
private static final long serialVersionUID = -5861759814760561373L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
game.getAction().exile(eff, null);
|
||||
}
|
||||
};
|
||||
|
||||
game.getEndOfTurn().addUntil(endEffect);
|
||||
|
||||
eff.updateStateForView();
|
||||
|
||||
// TODO: Add targeting to the effect so it knows who it's dealing with
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
game.getAction().moveTo(ZoneType.Command, eff, sa);
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import forge.util.Lang;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
@@ -49,7 +50,8 @@ public class ActivateAbilityEffect extends SpellAbilityEffect {
|
||||
if (possibleAb.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
SpellAbility manaAb = p.getController().chooseSingleSpellForEffect(possibleAb, sa, "Choose a mana ability:");
|
||||
SpellAbility manaAb = p.getController().chooseSingleSpellForEffect(
|
||||
possibleAb, sa, "Choose a mana ability:", ImmutableMap.of());
|
||||
p.getController().playChosenSpellAbility(manaAb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class AnimateAllEffect extends AnimateEffectBase {
|
||||
|
||||
@Override
|
||||
@@ -144,6 +146,9 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
|
||||
list = CardLists.getValidCards(list, valid.split(","), host.getController(), host, sa);
|
||||
|
||||
boolean removeAll = sa.hasParam("RemoveAllAbilities");
|
||||
boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
|
||||
|
||||
for (final Card c : list) {
|
||||
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
|
||||
keywords, removeKeywords, hiddenKeywords, timestamp);
|
||||
@@ -161,11 +166,14 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
|
||||
// remove abilities
|
||||
final List<SpellAbility> removedAbilities = new ArrayList<SpellAbility>();
|
||||
if (sa.hasParam("OverwriteAbilities") || sa.hasParam("RemoveAllAbilities")) {
|
||||
if (sa.hasParam("OverwriteAbilities") || removeAll || removeIntrinsic) {
|
||||
for (final SpellAbility ab : c.getSpellAbilities()) {
|
||||
if (ab.isAbility()) {
|
||||
c.removeSpellAbility(ab);
|
||||
removedAbilities.add(ab);
|
||||
if (removeAll
|
||||
|| (ab.isIntrinsic() && removeIntrinsic && !ab.isBasicLandAbility())) {
|
||||
ab.setTemporarilySuppressed(true);
|
||||
removedAbilities.add(ab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,19 +198,24 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
|
||||
// suppress triggers from the animated card
|
||||
final List<Trigger> removedTriggers = new ArrayList<Trigger>();
|
||||
if (sa.hasParam("OverwriteTriggers") || sa.hasParam("RemoveAllAbilities")) {
|
||||
if (sa.hasParam("OverwriteTriggers") || removeAll || removeIntrinsic) {
|
||||
final FCollectionView<Trigger> triggersToRemove = c.getTriggers();
|
||||
for (final Trigger trigger : triggersToRemove) {
|
||||
trigger.setSuppressed(true);
|
||||
if (removeIntrinsic && !trigger.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
trigger.setSuppressed(true); // why this not TemporarilySuppressed?
|
||||
removedTriggers.add(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
// suppress static abilities from the animated card
|
||||
final List<StaticAbility> removedStatics = new ArrayList<StaticAbility>();
|
||||
if (sa.hasParam("OverwriteStatics") || sa.hasParam("RemoveAllAbilities")) {
|
||||
final FCollectionView<StaticAbility> staticsToRemove = c.getStaticAbilities();
|
||||
for (final StaticAbility stAb : staticsToRemove) {
|
||||
if (sa.hasParam("OverwriteStatics") || removeAll || removeIntrinsic) {
|
||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||
if (removeIntrinsic && !stAb.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
stAb.setTemporarilySuppressed(true);
|
||||
removedStatics.add(stAb);
|
||||
}
|
||||
@@ -210,9 +223,11 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
|
||||
// suppress static abilities from the animated card
|
||||
final List<ReplacementEffect> removedReplacements = new ArrayList<ReplacementEffect>();
|
||||
if (sa.hasParam("OverwriteReplacements") || sa.hasParam("RemoveAllAbilities")) {
|
||||
final FCollectionView<ReplacementEffect> replacementsToRemove = c.getReplacementEffects();
|
||||
for (final ReplacementEffect re : replacementsToRemove) {
|
||||
if (sa.hasParam("OverwriteReplacements") || removeAll || removeIntrinsic) {
|
||||
for (final ReplacementEffect re : c.getReplacementEffects()) {
|
||||
if (removeIntrinsic && !re.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
re.setTemporarilySuppressed(true);
|
||||
removedReplacements.add(re);
|
||||
}
|
||||
@@ -234,8 +249,11 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
public void run() {
|
||||
doUnanimate(c, sa, finalDesc, hiddenKeywords,
|
||||
addedAbilities, addedTriggers, addedReplacements,
|
||||
false, removedAbilities, timestamp);
|
||||
ImmutableList.of(), timestamp);
|
||||
|
||||
for (final SpellAbility sa : removedAbilities) {
|
||||
sa.setTemporarilySuppressed(false);
|
||||
}
|
||||
// give back suppressed triggers
|
||||
for (final Trigger t : removedTriggers) {
|
||||
t.setSuppressed(false);
|
||||
|
||||
@@ -18,7 +18,6 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -162,21 +161,20 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
boolean clearAbilities = sa.hasParam("OverwriteAbilities");
|
||||
boolean clearSpells = sa.hasParam("OverwriteSpells");
|
||||
boolean removeAll = sa.hasParam("RemoveAllAbilities");
|
||||
boolean removeIntrinsic = sa.hasParam("RemoveIntrinsicAbilities");
|
||||
|
||||
if (clearAbilities || clearSpells || removeAll) {
|
||||
for (final SpellAbility ab : c.getSpellAbilities()) {
|
||||
if (removeAll || (ab.isAbility() && clearAbilities)
|
||||
if (removeAll
|
||||
|| (ab.isIntrinsic() && removeIntrinsic && !ab.isBasicLandAbility())
|
||||
|| (ab.isAbility() && clearAbilities)
|
||||
|| (ab.isSpell() && clearSpells)) {
|
||||
ab.setTemporarilySuppressed(true);
|
||||
removedAbilities.add(ab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Can't rmeove SAs in foreach loop that finds them
|
||||
for (final SpellAbility ab : removedAbilities) {
|
||||
c.removeSpellAbility(ab);
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveThisAbility") && !removedAbilities.contains(sa)) {
|
||||
c.removeSpellAbility(sa);
|
||||
removedAbilities.add(sa);
|
||||
@@ -215,20 +213,23 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
|
||||
// suppress triggers from the animated card
|
||||
final List<Trigger> removedTriggers = Lists.newArrayList();
|
||||
if (sa.hasParam("OverwriteTriggers") || removeAll) {
|
||||
final FCollectionView<Trigger> triggersToRemove = c.getTriggers();
|
||||
for (final Trigger trigger : triggersToRemove) {
|
||||
trigger.setSuppressed(true);
|
||||
if (sa.hasParam("OverwriteTriggers") || removeAll || removeIntrinsic) {
|
||||
for (final Trigger trigger : c.getTriggers()) {
|
||||
if (removeIntrinsic && !trigger.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
trigger.setSuppressed(true); // why this not TemporarilySuppressed?
|
||||
removedTriggers.add(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
// give static abilities (should only be used by cards to give
|
||||
// itself a static ability)
|
||||
final List<StaticAbility> addedStaticAbilities = Lists.newArrayList();
|
||||
if (stAbs.size() > 0) {
|
||||
for (final String s : stAbs) {
|
||||
final String actualAbility = source.getSVar(s);
|
||||
c.addStaticAbility(actualAbility);
|
||||
addedStaticAbilities.add(c.addStaticAbility(actualAbility));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,9 +249,11 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
|
||||
// suppress static abilities from the animated card
|
||||
final List<StaticAbility> removedStatics = Lists.newArrayList();
|
||||
if (sa.hasParam("OverwriteStatics") || removeAll) {
|
||||
final FCollectionView<StaticAbility> staticsToRemove = c.getStaticAbilities();
|
||||
for (final StaticAbility stAb : staticsToRemove) {
|
||||
if (sa.hasParam("OverwriteStatics") || removeAll || removeIntrinsic) {
|
||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||
if (removeIntrinsic && !stAb.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
stAb.setTemporarilySuppressed(true);
|
||||
removedStatics.add(stAb);
|
||||
}
|
||||
@@ -258,8 +261,11 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
|
||||
// suppress static abilities from the animated card
|
||||
final List<ReplacementEffect> removedReplacements = Lists.newArrayList();
|
||||
if (sa.hasParam("OverwriteReplacements") || removeAll) {
|
||||
if (sa.hasParam("OverwriteReplacements") || removeAll || removeIntrinsic) {
|
||||
for (final ReplacementEffect re : c.getReplacementEffects()) {
|
||||
if (removeIntrinsic && !re.isIntrinsic()) {
|
||||
continue;
|
||||
}
|
||||
re.setTemporarilySuppressed(true);
|
||||
removedReplacements.add(re);
|
||||
}
|
||||
@@ -272,8 +278,6 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
}
|
||||
}
|
||||
|
||||
final boolean givesStAbs = (stAbs.size() > 0);
|
||||
|
||||
final GameCommand unanimate = new GameCommand() {
|
||||
private static final long serialVersionUID = -5861759814760561373L;
|
||||
|
||||
@@ -281,9 +285,13 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
public void run() {
|
||||
doUnanimate(c, sa, finalDesc, hiddenKeywords,
|
||||
addedAbilities, addedTriggers, addedReplacements,
|
||||
givesStAbs, removedAbilities, timestamp);
|
||||
addedStaticAbilities, timestamp);
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c));
|
||||
|
||||
for (final SpellAbility sa : removedAbilities) {
|
||||
sa.setTemporarilySuppressed(false);
|
||||
}
|
||||
// give back suppressed triggers
|
||||
for (final Trigger t : removedTriggers) {
|
||||
t.setSuppressed(false);
|
||||
@@ -458,6 +466,8 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
sb.append(" until ").append(host).append(" leaves the battlefield.");
|
||||
} else if (sa.hasParam("UntilYourNextUpkeep")) {
|
||||
sb.append(" until your next upkeep.");
|
||||
} else if (sa.hasParam("UntilYourNextTurn")) {
|
||||
sb.append(" until your next turn.");
|
||||
} else if (sa.hasParam("UntilControllerNextUntap")) {
|
||||
sb.append(" until its controller's next untap step.");
|
||||
} else {
|
||||
|
||||
@@ -24,11 +24,10 @@ import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
void doAnimate(final Card c, final SpellAbility sa, final Integer power, final Integer toughness,
|
||||
public static void doAnimate(final Card c, final SpellAbility sa, final Integer power, final Integer toughness,
|
||||
final CardType addType, final CardType removeType, final String colors,
|
||||
final List<String> keywords, final List<String> removeKeywords,
|
||||
final List<String> hiddenKeywords, final long timestamp) {
|
||||
@@ -36,15 +35,19 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
boolean removeSuperTypes = false;
|
||||
boolean removeCardTypes = false;
|
||||
boolean removeSubTypes = false;
|
||||
boolean removeLandTypes = false;
|
||||
boolean removeCreatureTypes = false;
|
||||
boolean removeArtifactTypes = false;
|
||||
boolean removeEnchantmentTypes = false;
|
||||
|
||||
if (sa.hasParam("OverwriteTypes")) {
|
||||
removeSuperTypes = true;
|
||||
removeCardTypes = true;
|
||||
removeSubTypes = true;
|
||||
removeLandTypes = true;
|
||||
removeCreatureTypes = true;
|
||||
removeArtifactTypes = true;
|
||||
removeEnchantmentTypes = true;
|
||||
}
|
||||
|
||||
if (sa.hasParam("KeepSupertypes")) {
|
||||
@@ -57,6 +60,10 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
|
||||
if (sa.hasParam("KeepSubtypes")) {
|
||||
removeSubTypes = false;
|
||||
removeLandTypes = false;
|
||||
removeCreatureTypes = false;
|
||||
removeArtifactTypes = false;
|
||||
removeEnchantmentTypes = false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveSuperTypes")) {
|
||||
@@ -71,23 +78,30 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
removeSubTypes = true;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveLandTypes")) {
|
||||
removeCreatureTypes = true;
|
||||
}
|
||||
if (sa.hasParam("RemoveCreatureTypes")) {
|
||||
removeCreatureTypes = true;
|
||||
}
|
||||
|
||||
if (sa.hasParam("RemoveArtifactTypes")) {
|
||||
removeArtifactTypes = true;
|
||||
}
|
||||
if (sa.hasParam("RemoveEnchantmentTypes")) {
|
||||
removeEnchantmentTypes = true;
|
||||
}
|
||||
|
||||
if ((power != null) || (toughness != null)) {
|
||||
c.addNewPT(power, toughness, timestamp);
|
||||
}
|
||||
|
||||
if (!addType.isEmpty() || !removeType.isEmpty() || removeCreatureTypes) {
|
||||
c.addChangedCardTypes(addType, removeType, removeSuperTypes, removeCardTypes, removeSubTypes,
|
||||
removeCreatureTypes, removeArtifactTypes, timestamp);
|
||||
removeLandTypes, removeCreatureTypes, removeArtifactTypes, removeEnchantmentTypes, timestamp);
|
||||
}
|
||||
|
||||
c.addChangedCardKeywords(keywords, removeKeywords, sa.hasParam("RemoveAllAbilities"), timestamp);
|
||||
c.addChangedCardKeywords(keywords, removeKeywords,
|
||||
sa.hasParam("RemoveAllAbilities"), sa.hasParam("RemoveIntrinsicAbilities"), timestamp);
|
||||
|
||||
for (final String k : hiddenKeywords) {
|
||||
c.addHiddenExtrinsicKeyword(k);
|
||||
@@ -114,10 +128,10 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
* @param timestamp
|
||||
* a long.
|
||||
*/
|
||||
void doUnanimate(final Card c, SpellAbility sa, final String colorDesc,
|
||||
static void doUnanimate(final Card c, SpellAbility sa, final String colorDesc,
|
||||
final List<String> hiddenKeywords, final List<SpellAbility> addedAbilities,
|
||||
final List<Trigger> addedTriggers, final List<ReplacementEffect> addedReplacements,
|
||||
final boolean givesStAbs, final List<SpellAbility> removedAbilities, final long timestamp) {
|
||||
final List<StaticAbility> addedStaticAbilities, final long timestamp) {
|
||||
|
||||
if (sa.hasParam("LastsIndefinitely")) {
|
||||
return;
|
||||
@@ -127,16 +141,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
|
||||
c.removeChangedCardKeywords(timestamp);
|
||||
|
||||
// remove all static abilities
|
||||
if (givesStAbs) {
|
||||
c.setStaticAbilities(new ArrayList<StaticAbility>());
|
||||
}
|
||||
|
||||
if (sa.hasParam("Types") || sa.hasParam("RemoveTypes")
|
||||
|| sa.hasParam("RemoveCreatureTypes") || sa.hasParam("RemoveArtifactTypes")) {
|
||||
c.removeChangedCardTypes(timestamp);
|
||||
}
|
||||
|
||||
c.removeChangedCardTypes(timestamp);
|
||||
c.removeColor(timestamp);
|
||||
|
||||
for (final String k : hiddenKeywords) {
|
||||
@@ -147,10 +152,6 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
c.removeSpellAbility(saAdd);
|
||||
}
|
||||
|
||||
for (final SpellAbility saRem : removedAbilities) {
|
||||
c.addSpellAbility(saRem);
|
||||
}
|
||||
|
||||
for (final Trigger t : addedTriggers) {
|
||||
c.removeTrigger(t);
|
||||
}
|
||||
@@ -159,6 +160,10 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
||||
c.removeReplacementEffect(rep);
|
||||
}
|
||||
|
||||
for (final StaticAbility stAb : addedStaticAbilities) {
|
||||
c.removeStaticAbility(stAb);
|
||||
}
|
||||
|
||||
// any other unanimate cleanup
|
||||
if (!c.isCreature()) {
|
||||
c.unEquipAllCards();
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class AssignGroupEffect extends SpellAbilityEffect {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.ability.SpellAbilityEffect#getStackDescription(forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
return sa.getDescription();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.game.ability.SpellAbilityEffect#resolve(forge.game.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
|
||||
List<GameObject> defined = getDefinedOrTargeted(sa, "Defined");
|
||||
|
||||
final List<SpellAbility> abilities = Lists.<SpellAbility>newArrayList(sa.getAdditionalAbilityList("Choices"));
|
||||
|
||||
Player chooser = sa.getActivatingPlayer();
|
||||
if (sa.hasParam("Chooser")) {
|
||||
final String choose = sa.getParam("Chooser");
|
||||
chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0);
|
||||
}
|
||||
|
||||
Multimap<SpellAbility, GameObject> result = ArrayListMultimap.create();
|
||||
|
||||
for (GameObject g : defined) {
|
||||
final String title = "Choose ability for " + g.toString();
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Affected", g);
|
||||
|
||||
result.put(chooser.getController().chooseSingleSpellForEffect(abilities, sa, title, params), g);
|
||||
}
|
||||
|
||||
// in order of choice list
|
||||
for (SpellAbility s : abilities) {
|
||||
// is that in Player order?
|
||||
Collection<GameObject> l = result.get(s);
|
||||
|
||||
host.addRemembered(l);
|
||||
AbilityUtils.resolve(s);
|
||||
host.removeRemembered(l);
|
||||
|
||||
// this will refresh continuous abilities for players and permanents.
|
||||
game.getAction().checkStaticAbilities();
|
||||
game.getTriggerHandler().resetActiveTriggers(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -156,7 +156,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
// Auras without Candidates stay in their current location
|
||||
if (c.isAura()) {
|
||||
final SpellAbility saAura = c.getFirstAttachSpell();
|
||||
if (!saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
|
||||
if (saAura != null && !saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardType;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
@@ -32,6 +34,7 @@ import forge.util.collect.*;
|
||||
import forge.util.Lang;
|
||||
import forge.util.MessageUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -484,7 +487,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (sa.hasParam("WithCounters")) {
|
||||
String[] parse = sa.getParam("WithCounters").split("_");
|
||||
tgtC.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), hostCard);
|
||||
tgtC.addEtbCounter(CounterType.getType(parse[0]), Integer.parseInt(parse[1]), player);
|
||||
}
|
||||
if (sa.hasParam("GainControl")) {
|
||||
if (sa.hasParam("NewController")) {
|
||||
@@ -552,9 +555,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// location
|
||||
if (tgtC.isAura()) {
|
||||
final SpellAbility saAura = tgtC.getFirstAttachSpell();
|
||||
saAura.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
if (!saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
|
||||
continue;
|
||||
if (saAura != null) {
|
||||
saAura.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
if (!saAura.getTargetRestrictions().hasCandidates(saAura, false)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -865,7 +870,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
final boolean champion = sa.hasParam("Champion");
|
||||
final boolean forget = sa.hasParam("ForgetChanged");
|
||||
final boolean imprint = sa.hasParam("Imprint");
|
||||
final String selectPrompt = sa.hasParam("SelectPrompt") ? sa.getParam("SelectPrompt") : MessageUtil.formatMessage("Select a card from {player's} " + Lang.joinHomogenous(origin).toLowerCase(), decider, player);
|
||||
String selectPrompt = sa.hasParam("SelectPrompt") ? sa.getParam("SelectPrompt") : MessageUtil.formatMessage("Select a card from {player's} " + Lang.joinHomogenous(origin).toLowerCase(), decider, player);
|
||||
final String totalcmc = sa.getParam("WithTotalCMC");
|
||||
int totcmc = AbilityUtils.calculateAmount(source, totalcmc, sa);
|
||||
|
||||
@@ -874,7 +879,20 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
CardCollection chosenCards = new CardCollection();
|
||||
// only multi-select if player can select more than one
|
||||
if (changeNum > 1 && allowMultiSelect(decider, sa)) {
|
||||
for (Card card : decider.getController().chooseCardsForZoneChange(destination, origin, sa, fetchList, delayedReveal, selectPrompt, decider)) {
|
||||
List<Card> selectedCards;
|
||||
if (! sa.hasParam("SelectPrompt")) {
|
||||
// new default messaging for multi select
|
||||
if (fetchList.size() > changeNum) {
|
||||
selectPrompt = MessageUtil.formatMessage("Select up to " + changeNum + " cards from {player's} " + Lang.joinHomogenous(origin).toLowerCase(), decider, player);
|
||||
} else {
|
||||
selectPrompt = MessageUtil.formatMessage("Select cards from {player's} " + Lang.joinHomogenous(origin).toLowerCase(), decider, player);
|
||||
}
|
||||
}
|
||||
// ensure that selection is within maximum allowed changeNum
|
||||
do {
|
||||
selectedCards = decider.getController().chooseCardsForZoneChange(destination, origin, sa, fetchList, delayedReveal, selectPrompt, decider);
|
||||
} while (selectedCards != null && selectedCards.size() > changeNum);
|
||||
for (Card card : selectedCards) {
|
||||
chosenCards.add(card);
|
||||
};
|
||||
// maybe prompt the user if they selected fewer than the maximum possible?
|
||||
@@ -1101,11 +1119,49 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
|
||||
if (sa.hasParam("FaceDown") && ZoneType.Battlefield.equals(destination)) {
|
||||
c.setState(CardStateName.FaceDown, true);
|
||||
|
||||
// set New Pt doesn't work because this values need to be copyable for clone effects
|
||||
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")) {
|
||||
if (sa.hasParam("FaceDownPower")) {
|
||||
c.setBasePower(AbilityUtils.calculateAmount(
|
||||
source, sa.getParam("FaceDownPower"), sa));
|
||||
}
|
||||
if (sa.hasParam("FaceDownToughness")) {
|
||||
c.setBaseToughness(AbilityUtils.calculateAmount(
|
||||
source, sa.getParam("FaceDownToughness"), sa));
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("FaceDownAddType")) {
|
||||
CardType t = new CardType(c.getCurrentState().getType());
|
||||
t.addAll(Arrays.asList(sa.getParam("FaceDownAddType").split(",")));
|
||||
c.getCurrentState().setType(t);
|
||||
}
|
||||
|
||||
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
|
||||
|| sa.hasParam("FaceDownAddType")) {
|
||||
final GameCommand unanimate = new GameCommand() {
|
||||
private static final long serialVersionUID = 8853789549297846163L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
c.clearStates(CardStateName.FaceDown, true);
|
||||
}
|
||||
};
|
||||
|
||||
c.addFaceupCommand(unanimate);
|
||||
}
|
||||
}
|
||||
movedCard = game.getAction().moveTo(c.getController().getZone(destination), c, sa, null);
|
||||
if (sa.hasParam("Tapped")) {
|
||||
movedCard.setTapped(true);
|
||||
}
|
||||
if (sa.hasParam("FaceDown")) {
|
||||
|
||||
// need to do that again?
|
||||
if (sa.hasParam("FaceDown") && !ZoneType.Battlefield.equals(destination)) {
|
||||
movedCard.setState(CardStateName.FaceDown, true);
|
||||
}
|
||||
movedCard.setTimestamp(ts);
|
||||
@@ -1181,7 +1237,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
&& !sa.hasParam("DifferentNames")
|
||||
&& !sa.hasParam("DifferentCMC")
|
||||
&& !sa.hasParam("AtRandom")
|
||||
&& !sa.hasParam("ChangeNum") // TODO: doesn't work with card number limits, e.g. Doomsday
|
||||
&& (!sa.hasParam("Defined") || sa.hasParam("ChooseFromDefined"))
|
||||
&& sa.getParam("WithTotalCMC") == null;
|
||||
}
|
||||
|
||||
@@ -105,17 +105,8 @@ public class ChooseCardNameEffect extends SpellAbilityEffect {
|
||||
final String message = validDesc.equals("card") ? "Name a card" : "Name a " + validDesc + " card.";
|
||||
|
||||
Predicate<ICardFace> cpp = Predicates.alwaysTrue();
|
||||
if ( StringUtils.containsIgnoreCase(valid, "nonland") ) {
|
||||
cpp = CardFacePredicates.Presets.IS_NON_LAND;
|
||||
}
|
||||
if ( StringUtils.containsIgnoreCase(valid, "nonbasic") ) {
|
||||
cpp = Predicates.not(CardFacePredicates.Presets.IS_BASIC_LAND);
|
||||
}
|
||||
|
||||
if ( StringUtils.containsIgnoreCase(valid, "noncreature") ) {
|
||||
cpp = Predicates.not(CardFacePredicates.Presets.IS_CREATURE);
|
||||
} else if ( StringUtils.containsIgnoreCase(valid, "creature") ) {
|
||||
cpp = CardFacePredicates.Presets.IS_CREATURE;
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
cpp = CardFacePredicates.valid(valid);
|
||||
}
|
||||
|
||||
chosen = p.getController().chooseCardName(sa, cpp, valid, message);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
@@ -65,7 +66,8 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
int idxChosen = MyRandom.getRandom().nextInt(abilities.size());
|
||||
chosenSA = abilities.get(idxChosen);
|
||||
} else {
|
||||
chosenSA = p.getController().chooseSingleSpellForEffect(abilities, sa, "Choose one");
|
||||
chosenSA = p.getController().chooseSingleSpellForEffect(abilities, sa, "Choose one",
|
||||
ImmutableMap.of());
|
||||
}
|
||||
|
||||
if (chosenSA != null) {
|
||||
|
||||
@@ -64,11 +64,15 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
Card cardToCopy = null;
|
||||
|
||||
if (sa.hasParam("Choices")) {
|
||||
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
||||
ZoneType choiceZone = ZoneType.Battlefield;
|
||||
if (sa.hasParam("ChoiceZone")) {
|
||||
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
||||
}
|
||||
CardCollectionView choices = game.getCardsIn(choiceZone);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host);
|
||||
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : "Choose a card ";
|
||||
cardToCopy = activator.getController().chooseSingleEntityForEffect(choices, sa, title, true);
|
||||
cardToCopy = activator.getController().chooseSingleEntityForEffect(choices, sa, title, false);
|
||||
} else if (sa.hasParam("Defined")) {
|
||||
List<Card> cloneSources = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||
if (!cloneSources.isEmpty()) {
|
||||
@@ -213,6 +217,9 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
else if (duration.equals("UntilYourNextTurn")) {
|
||||
game.getCleanup().addUntil(host.getController(), unclone);
|
||||
}
|
||||
else if (duration.equals("UntilUnattached")) {
|
||||
sa.getHostCard().addUnattachCommand(unclone);
|
||||
}
|
||||
}
|
||||
game.fireEvent(new GameEventCardStatsChanged(tgtCard));
|
||||
} // cloneResolve
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
@@ -22,6 +21,7 @@ import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.player.Player;
|
||||
@@ -37,10 +37,10 @@ import forge.util.collect.FCollectionView;
|
||||
import forge.util.PredicateString.StringOp;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -167,12 +167,18 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
} else if (sa.hasParam("Choices")) {
|
||||
Player chooser = activator;
|
||||
if (sa.hasParam("Chooser")) {
|
||||
final String choose = sa.getParam("Chooser");
|
||||
chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0);
|
||||
}
|
||||
|
||||
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host);
|
||||
if (!choices.isEmpty()) {
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : "Choose a card ";
|
||||
|
||||
Card choosen = activator.getController().chooseSingleEntityForEffect(choices, sa, title, false);
|
||||
Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, false);
|
||||
|
||||
if (choosen != null) {
|
||||
tgtCards.add(choosen);
|
||||
@@ -186,29 +192,17 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
for (final Card c : tgtCards) {
|
||||
if (!sa.usesTargeting() || c.canBeTargetedBy(sa)) {
|
||||
|
||||
int multiplier = numCopies;
|
||||
|
||||
final Map<String, Object> repParams = Maps.newHashMap();
|
||||
repParams.put("Event", "CreateToken");
|
||||
repParams.put("Affected", controller);
|
||||
repParams.put("TokenNum", multiplier);
|
||||
repParams.put("EffectOnly", true);
|
||||
Pair<Player, Integer> result = TokenInfo.calculateMultiplier(
|
||||
game, controller, true, numCopies);
|
||||
|
||||
switch (game.getReplacementHandler().run(repParams)) {
|
||||
case NotReplaced:
|
||||
break;
|
||||
case Updated: {
|
||||
multiplier = (int) repParams.get("TokenNum");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return ;
|
||||
if (result.getRight() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Card> crds = Lists.newArrayListWithCapacity(multiplier);
|
||||
final List<Card> crds = Lists.newArrayListWithCapacity(result.getRight());
|
||||
|
||||
for (int i = 0; i < multiplier; i++) {
|
||||
final Card copy = CardFactory.copyCopiableCharacteristics(c, activator);
|
||||
for (int i = 0; i < result.getRight(); i++) {
|
||||
final Card copy = CardFactory.copyCopiableCharacteristics(c, result.getLeft());
|
||||
copy.setToken(true);
|
||||
copy.setCopiedPermanent(c);
|
||||
// add keywords from sa
|
||||
@@ -337,7 +331,7 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
// set the controller before move to play: Crafty Cutpurse
|
||||
copy.setController(controller, 0);
|
||||
copy.setController(result.getLeft(), 0);
|
||||
copy.updateStateForView();
|
||||
|
||||
// Temporarily register triggers of an object created with CopyPermanent
|
||||
@@ -350,7 +344,7 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
copyInPlay.setCloneOrigin(host);
|
||||
sa.getHostCard().addClone(copyInPlay);
|
||||
if (!pumpKeywords.isEmpty()) {
|
||||
copyInPlay.addChangedCardKeywords(pumpKeywords, Lists.<String>newArrayList(), false, timestamp);
|
||||
copyInPlay.addChangedCardKeywords(pumpKeywords, Lists.<String>newArrayList(), false, false, timestamp);
|
||||
}
|
||||
crds.add(copyInPlay);
|
||||
if (sa.hasParam("RememberCopied")) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@@ -83,8 +84,9 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
|
||||
for (int multi = 0; multi < spellCount && !tgtSpells.isEmpty(); multi++) {
|
||||
String prompt = "Select " + Lang.getOrdinal(multi + 1) + " spell to copy to stack";
|
||||
SpellAbility chosen = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, prompt);
|
||||
SpellAbility copiedSpell = CardFactory.copySpellAbilityAndSrcCard(card, chosen.getHostCard(), chosen, true);
|
||||
SpellAbility chosen = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, prompt,
|
||||
ImmutableMap.of());
|
||||
SpellAbility copiedSpell = CardFactory.copySpellAbilityAndPossiblyHost(card, chosen.getHostCard(), chosen, true);
|
||||
copiedSpell.getHostCard().setController(card.getController(), card.getGame().getNextTimestamp());
|
||||
copiedSpell.setActivatingPlayer(controller);
|
||||
copies.add(copiedSpell);
|
||||
@@ -92,7 +94,8 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
else if (sa.hasParam("CopyForEachCanTarget")) {
|
||||
SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, "Select a spell to copy");
|
||||
SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa,
|
||||
"Select a spell to copy", ImmutableMap.of());
|
||||
chosenSA.setActivatingPlayer(controller);
|
||||
// Find subability or rootability that has targets
|
||||
SpellAbility targetedSA = chosenSA;
|
||||
@@ -114,7 +117,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
|
||||
mayChooseNewTargets = false;
|
||||
for (GameEntity o : candidates) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndSrcCard(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
resetFirstTargetOnCopy(copy, o, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
@@ -140,22 +143,23 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
valid.remove(originalTarget);
|
||||
mayChooseNewTargets = false;
|
||||
for (final Card c : valid) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndSrcCard(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
resetFirstTargetOnCopy(copy, c, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
for (final Player p : players) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndSrcCard(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true);
|
||||
resetFirstTargetOnCopy(copy, p, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, "Select a spell to copy");
|
||||
SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa,
|
||||
"Select a spell to copy", ImmutableMap.of());
|
||||
chosenSA.setActivatingPlayer(controller);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
copies.add(CardFactory.copySpellAbilityAndSrcCard(card, chosenSA.getHostCard(), chosenSA, true));
|
||||
copies.add(CardFactory.copySpellAbilityAndPossiblyHost(card, chosenSA.getHostCard(), chosenSA, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
// Test to see if the card we're trying to add is in the expected state
|
||||
return;
|
||||
}
|
||||
dest = cur;
|
||||
|
||||
int csum = 0;
|
||||
|
||||
@@ -145,7 +146,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (csum > 0) {
|
||||
dest.addCounter(cType, csum, host, true);
|
||||
dest.addCounter(cType, csum, player, true);
|
||||
game.updateLastStateForCard(dest);
|
||||
}
|
||||
return;
|
||||
@@ -194,15 +195,15 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", cType);
|
||||
params.put("Source", source);
|
||||
params.put("Target", dest);
|
||||
params.put("Target", cur);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Put how many ").append(cType.getName()).append(" counters on ").append(dest).append("?");
|
||||
sb.append("Put how many ").append(cType.getName()).append(" counters on ").append(cur).append("?");
|
||||
int cnum = player.getController().chooseNumber(sa, sb.toString(), 0, source.getCounters(cType), params);
|
||||
|
||||
if (cnum > 0) {
|
||||
source.subtractCounter(cType, cnum);
|
||||
dest.addCounter(cType, cnum, host, true);
|
||||
game.updateLastStateForCard(dest);
|
||||
cur.addCounter(cType, cnum, player, true);
|
||||
game.updateLastStateForCard(cur);
|
||||
updateSource = true;
|
||||
}
|
||||
}
|
||||
@@ -245,7 +246,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (!"Any".matches(counterName)) {
|
||||
if (!dest.canReceiveCounters(cType)) {
|
||||
if (!cur.canReceiveCounters(cType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -253,7 +254,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", cType);
|
||||
params.put("Source", source);
|
||||
params.put("Target", dest);
|
||||
params.put("Target", cur);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Take how many ").append(cType.getName());
|
||||
sb.append(" counters from ").append(source).append("?");
|
||||
@@ -262,8 +263,8 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
|
||||
if (source.getCounters(cType) >= cntToMove) {
|
||||
source.subtractCounter(cType, cntToMove);
|
||||
dest.addCounter(cType, cntToMove, host, true);
|
||||
game.updateLastStateForCard(dest);
|
||||
cur.addCounter(cType, cntToMove, player, true);
|
||||
game.updateLastStateForCard(cur);
|
||||
}
|
||||
} else {
|
||||
// any counterType currently only Leech Bonder
|
||||
@@ -296,7 +297,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
sa, sb.toString(), 0, Math.min(tgtCounters.get(chosenType), cntToMove), params);
|
||||
|
||||
if (chosenAmount > 0) {
|
||||
dest.addCounter(chosenType, chosenAmount, host, true);
|
||||
dest.addCounter(chosenType, chosenAmount, player, true);
|
||||
source.subtractCounter(chosenType, chosenAmount);
|
||||
game.updateLastStateForCard(dest);
|
||||
cntToMove -= chosenAmount;
|
||||
|
||||
@@ -7,6 +7,7 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Lang;
|
||||
|
||||
@@ -37,6 +38,7 @@ public class CountersMultiplyEffect extends SpellAbilityEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
|
||||
final CounterType counterType = getCounterType(sa);
|
||||
final int n = Integer.valueOf(sa.getParamOrDefault("Multiplier", "2")) - 1;
|
||||
@@ -50,10 +52,10 @@ public class CountersMultiplyEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
if (counterType != null) {
|
||||
gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, host, true);
|
||||
gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, player, true);
|
||||
} else {
|
||||
for (Map.Entry<CounterType, Integer> e : gameCard.getCounters().entrySet()) {
|
||||
gameCard.addCounter(e.getKey(), e.getValue() * n, host, true);
|
||||
gameCard.addCounter(e.getKey(), e.getValue() * n, player, true);
|
||||
}
|
||||
}
|
||||
game.updateLastStateForCard(gameCard);
|
||||
|
||||
@@ -6,6 +6,7 @@ import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class CountersNoteEffect extends SpellAbilityEffect {
|
||||
@@ -26,7 +27,7 @@ public class CountersNoteEffect extends SpellAbilityEffect {
|
||||
if (mode.equals(MODE_STORE)) {
|
||||
noteCounters(c, source);
|
||||
} else if (mode.equals(MODE_LOAD)) {
|
||||
loadCounters(c, source);
|
||||
loadCounters(c, source, sa.getActivatingPlayer());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,11 +40,11 @@ public class CountersNoteEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCounters(Card notee, Card source) {
|
||||
private void loadCounters(Card notee, Card source, final Player p) {
|
||||
for(Entry<String, String> svar : source.getSVars().entrySet()) {
|
||||
String key = svar.getKey();
|
||||
if (key.startsWith(NOTE_COUNTERS)) {
|
||||
notee.addCounter(CounterType.getType(key.substring(NOTE_COUNTERS.length())), Integer.parseInt(svar.getValue()), source, false);
|
||||
notee.addCounter(CounterType.getType(key.substring(NOTE_COUNTERS.length())), Integer.parseInt(svar.getValue()), p, false);
|
||||
}
|
||||
// TODO Probably should "remove" the svars that were temporarily used
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ public class CountersProliferateEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Player p = sa.getActivatingPlayer();
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
Player controller = host.getController();
|
||||
@@ -32,10 +33,10 @@ public class CountersProliferateEffect extends SpellAbilityEffect {
|
||||
return;
|
||||
for(Entry<GameEntity, CounterType> ge: proliferateChoice.entrySet()) {
|
||||
if( ge.getKey() instanceof Player )
|
||||
((Player) ge.getKey()).addCounter(ge.getValue(), 1, host, true);
|
||||
((Player) ge.getKey()).addCounter(ge.getValue(), 1, p, true);
|
||||
else if( ge.getKey() instanceof Card) {
|
||||
Card c = (Card) ge.getKey();
|
||||
c.addCounter(ge.getValue(), 1, host, true);
|
||||
c.addCounter(ge.getValue(), 1, p, true);
|
||||
game.updateLastStateForCard(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,30 +38,37 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final int counterAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
|
||||
final String valid = sa.getParam("ValidCards");
|
||||
final ZoneType zone = sa.hasParam("ValidZone") ? ZoneType.smartValueOf(sa.getParam("ValidZone")) : ZoneType.Battlefield;
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
final Game game = activator.getGame();
|
||||
|
||||
if (counterAmount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CardCollectionView cards = game.getCardsIn(zone);
|
||||
cards = CardLists.getValidCards(cards, valid, sa.getHostCard().getController(), sa.getHostCard());
|
||||
cards = CardLists.getValidCards(cards, valid, host.getController(), sa.getHostCard());
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
final Player pl = sa.getTargets().getFirstTargetedPlayer();
|
||||
cards = CardLists.filterControlledBy(cards, pl);
|
||||
}
|
||||
|
||||
Player placer = activator;
|
||||
if (sa.hasParam("Placer")) {
|
||||
final String pstr = sa.getParam("Placer");
|
||||
placer = AbilityUtils.getDefinedPlayers(host, pstr, sa).get(0);
|
||||
}
|
||||
|
||||
for (final Card tgtCard : cards) {
|
||||
if (game.getZoneOf(tgtCard).is(ZoneType.Battlefield)) {
|
||||
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, host, true);
|
||||
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, true);
|
||||
} else {
|
||||
// adding counters to something like re-suspend cards
|
||||
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, host, false);
|
||||
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, false);
|
||||
}
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
final boolean dividedAsYouChoose = sa.hasParam("DividedAsYouChoose");
|
||||
|
||||
|
||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParam("CounterNum"), sa);
|
||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("CounterNum", "1"), sa);
|
||||
if (sa.hasParam("Bolster")) {
|
||||
sb.append("Bolster ").append(amount);
|
||||
return sb.toString();
|
||||
@@ -110,6 +110,12 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
Player placer = activator;
|
||||
if (sa.hasParam("Placer")) {
|
||||
final String pstr = sa.getParam("Placer");
|
||||
placer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), pstr, sa).get(0);
|
||||
}
|
||||
|
||||
final boolean etbcounter = sa.hasParam("ETB");
|
||||
final boolean remember = sa.hasParam("RememberCounters");
|
||||
final boolean rememberCards = sa.hasParam("RememberCards");
|
||||
@@ -129,9 +135,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
|
||||
for (final GameObject obj : tgtObjects) {
|
||||
// check if the object is still in game or if it was moved
|
||||
Card gameCard = null;
|
||||
if (obj instanceof Card) {
|
||||
Card tgtCard = (Card) obj;
|
||||
Card gameCard = game.getCardState(tgtCard, null);
|
||||
gameCard = game.getCardState(tgtCard, null);
|
||||
// gameCard is LKI in that case, the card is not in game anymore
|
||||
// or the timestamp did change
|
||||
// this should check Self too
|
||||
@@ -155,10 +162,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
if (eachExistingCounter) {
|
||||
for(CounterType ct : choices) {
|
||||
if (obj instanceof Player) {
|
||||
((Player) obj).addCounter(ct, counterAmount, card, true);
|
||||
((Player) obj).addCounter(ct, counterAmount, placer, true);
|
||||
}
|
||||
if (obj instanceof Card) {
|
||||
((Card) obj).addCounter(ct, counterAmount, card, true);
|
||||
gameCard.addCounter(ct, counterAmount, placer, true);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
@@ -179,7 +186,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (obj instanceof Card) {
|
||||
Card tgtCard = (Card) obj;
|
||||
Card tgtCard = gameCard;
|
||||
counterAmount = sa.usesTargeting() && sa.hasParam("DividedAsYouChoose") ? sa.getTargetRestrictions().getDividedValue(tgtCard) : counterAmount;
|
||||
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
|
||||
if (max != -1) {
|
||||
@@ -232,9 +239,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
final Zone zone = tgtCard.getGame().getZoneOf(tgtCard);
|
||||
if (zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack)) {
|
||||
if (etbcounter) {
|
||||
tgtCard.addEtbCounter(counterType, counterAmount, card);
|
||||
tgtCard.addEtbCounter(counterType, counterAmount, placer);
|
||||
} else {
|
||||
tgtCard.addCounter(counterType, counterAmount, card, true);
|
||||
tgtCard.addCounter(counterType, counterAmount, placer, true);
|
||||
}
|
||||
if (remember) {
|
||||
final int value = tgtCard.getTotalCountersToAdd();
|
||||
@@ -263,9 +270,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
// adding counters to something like re-suspend cards
|
||||
// etbcounter should apply multiplier
|
||||
if (etbcounter) {
|
||||
tgtCard.addEtbCounter(counterType, counterAmount, card);
|
||||
tgtCard.addEtbCounter(counterType, counterAmount, placer);
|
||||
} else {
|
||||
tgtCard.addCounter(counterType, counterAmount, card, false);
|
||||
tgtCard.addCounter(counterType, counterAmount, placer, false);
|
||||
}
|
||||
}
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
@@ -273,7 +280,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
} else if (obj instanceof Player) {
|
||||
// Add Counters to players!
|
||||
Player pl = (Player) obj;
|
||||
pl.addCounter(counterType, counterAmount, card, true);
|
||||
pl.addCounter(counterType, counterAmount, placer, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -64,16 +65,16 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
||||
if (gameCard == null || !tgtCard.equalsWithTimestamp(gameCard)) {
|
||||
continue;
|
||||
}
|
||||
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
|
||||
if (tgtCard.hasCounters()) {
|
||||
if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) {
|
||||
if (gameCard.hasCounters()) {
|
||||
if (sa.hasParam("EachExistingCounter")) {
|
||||
for (CounterType listType : Lists.newArrayList(tgtCard.getCounters().keySet())) {
|
||||
addOrRemoveCounter(sa, tgtCard, listType, counterAmount);
|
||||
for (CounterType listType : Lists.newArrayList(gameCard.getCounters().keySet())) {
|
||||
addOrRemoveCounter(sa, gameCard, listType, counterAmount);
|
||||
}
|
||||
} else {
|
||||
addOrRemoveCounter(sa, tgtCard, ctype, counterAmount);
|
||||
addOrRemoveCounter(sa, gameCard, ctype, counterAmount);
|
||||
}
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
game.updateLastStateForCard(gameCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,8 +82,8 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
private void addOrRemoveCounter(final SpellAbility sa, final Card tgtCard, CounterType ctype,
|
||||
final int counterAmount) {
|
||||
PlayerController pc = sa.getActivatingPlayer().getController();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player pl = sa.getActivatingPlayer();
|
||||
final PlayerController pc = pl.getController();
|
||||
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", tgtCard);
|
||||
@@ -105,7 +106,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
boolean apply = zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack);
|
||||
|
||||
tgtCard.addCounter(chosenType, counterAmount, source, apply);
|
||||
tgtCard.addCounter(chosenType, counterAmount, pl, apply);
|
||||
} else {
|
||||
tgtCard.subtractCounter(chosenType, counterAmount);
|
||||
}
|
||||
|
||||
@@ -115,27 +115,27 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
if (gameCard == null || !tgtCard.equalsWithTimestamp(gameCard)) {
|
||||
continue;
|
||||
}
|
||||
if (!sa.usesTargeting() || tgtCard.canBeTargetedBy(sa)) {
|
||||
if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) {
|
||||
final Zone zone = game.getZoneOf(gameCard);
|
||||
if (type.equals("All")) {
|
||||
for (Map.Entry<CounterType, Integer> e : tgtCard.getCounters().entrySet()) {
|
||||
tgtCard.subtractCounter(e.getKey(), e.getValue());
|
||||
for (Map.Entry<CounterType, Integer> e : gameCard.getCounters().entrySet()) {
|
||||
gameCard.subtractCounter(e.getKey(), e.getValue());
|
||||
}
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
game.updateLastStateForCard(gameCard);
|
||||
continue;
|
||||
} else if (num.equals("All")) {
|
||||
cntToRemove = tgtCard.getCounters(counterType);
|
||||
cntToRemove = gameCard.getCounters(counterType);
|
||||
} else if (sa.getParam("CounterNum").equals("Remembered")) {
|
||||
cntToRemove = tgtCard.getCountersAddedBy(card, counterType);
|
||||
cntToRemove = gameCard.getCountersAddedBy(card, counterType);
|
||||
}
|
||||
|
||||
PlayerController pc = sa.getActivatingPlayer().getController();
|
||||
|
||||
if (type.equals("Any")) {
|
||||
while (cntToRemove > 0 && tgtCard.hasCounters()) {
|
||||
final Map<CounterType, Integer> tgtCounters = tgtCard.getCounters();
|
||||
while (cntToRemove > 0 && gameCard.hasCounters()) {
|
||||
final Map<CounterType, Integer> tgtCounters = gameCard.getCounters();
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", tgtCard);
|
||||
params.put("Target", gameCard);
|
||||
|
||||
String prompt = "Select type of counters to remove";
|
||||
CounterType chosenType = pc.chooseCounterType(
|
||||
@@ -143,13 +143,13 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
prompt = "Select the number of " + chosenType.getName() + " counters to remove";
|
||||
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
|
||||
params = Maps.newHashMap();
|
||||
params.put("Target", tgtCard);
|
||||
params.put("Target", gameCard);
|
||||
params.put("CounterType", chosenType);
|
||||
int chosenAmount = pc.chooseNumber(sa, prompt, 1, max, params);
|
||||
|
||||
if (chosenAmount > 0) {
|
||||
tgtCard.subtractCounter(chosenType, chosenAmount);
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
gameCard.subtractCounter(chosenType, chosenAmount);
|
||||
game.updateLastStateForCard(gameCard);
|
||||
if (rememberRemoved) {
|
||||
for (int i = 0; i < chosenAmount; i++) {
|
||||
card.addRemembered(Pair.of(chosenType, i));
|
||||
@@ -159,12 +159,12 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cntToRemove = Math.min(cntToRemove, tgtCard.getCounters(counterType));
|
||||
cntToRemove = Math.min(cntToRemove, gameCard.getCounters(counterType));
|
||||
|
||||
if (zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Exile)) {
|
||||
if (sa.hasParam("UpTo")) {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", tgtCard);
|
||||
params.put("Target", gameCard);
|
||||
params.put("CounterType", type);
|
||||
String title = "Select the number of " + type + " counters to remove";
|
||||
cntToRemove = pc.chooseNumber(sa, title, 0, cntToRemove, params);
|
||||
@@ -172,13 +172,13 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
}
|
||||
if (cntToRemove > 0) {
|
||||
tgtCard.subtractCounter(counterType, cntToRemove);
|
||||
gameCard.subtractCounter(counterType, cntToRemove);
|
||||
if (rememberRemoved) {
|
||||
for (int i = 0; i < cntToRemove; i++) {
|
||||
card.addRemembered(Pair.of(counterType, i));
|
||||
}
|
||||
}
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
game.updateLastStateForCard(gameCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,7 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.replacement.ReplacementLayer;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
abstract public class DamageBaseEffect extends SpellAbilityEffect {
|
||||
|
||||
static void replaceDying(final SpellAbility sa) {
|
||||
if (sa.hasParam("ReplaceDyingDefined")) {
|
||||
|
||||
if (sa.hasParam("ReplaceDyingCondition")) {
|
||||
// currently there is only one with Kicker
|
||||
final String condition = sa.getParam("ReplaceDyingCondition");
|
||||
if ("Kicked".equals(condition)) {
|
||||
if (!sa.isKicked()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final Player controller = sa.getActivatingPlayer();
|
||||
final Game game = host.getGame();
|
||||
String zone = sa.getParamOrDefault("ReplaceDyingZone", "Exile");
|
||||
CardCollection cards = AbilityUtils.getDefinedCards(host, sa.getParam("ReplaceDyingDefined"), sa);
|
||||
// no cards, no need for Effect
|
||||
if (cards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// build an Effect with that infomation
|
||||
String name = host.getName() + "'s Effect";
|
||||
|
||||
final Card eff = createEffect(host, controller, name, host.getImageKey());
|
||||
eff.addRemembered(cards);
|
||||
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered " +
|
||||
"| Origin$ Battlefield | Destination$ Graveyard " +
|
||||
"| Description$ If the creature would die this turn, exile it instead.";
|
||||
String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone;
|
||||
|
||||
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
|
||||
re.setLayer(ReplacementLayer.Other);
|
||||
|
||||
re.setOverridingAbility(AbilityFactory.getAbility(effect, eff));
|
||||
eff.addReplacementEffect(re);
|
||||
|
||||
// Add forgot trigger
|
||||
addForgetOnMovedTrigger(eff, "Battlefield");
|
||||
|
||||
// Copy text changes
|
||||
if (sa.isIntrinsic()) {
|
||||
eff.copyChangedTextFrom(host);
|
||||
}
|
||||
|
||||
final GameCommand endEffect = new GameCommand() {
|
||||
private static final long serialVersionUID = -5861759814760561373L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
game.getAction().exile(eff, null);
|
||||
}
|
||||
};
|
||||
|
||||
game.getEndOfTurn().addUntil(endEffect);
|
||||
|
||||
eff.updateStateForView();
|
||||
|
||||
// TODO: Add targeting to the effect so it knows who it's dealing with
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
game.getAction().moveTo(ZoneType.Command, eff, sa);
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -68,6 +70,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Game game = hostCard.getGame();
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(hostCard, damage, sa);
|
||||
@@ -176,7 +179,12 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
dmg = (sa.usesTargeting() && sa.hasParam("DividedAsYouChoose")) ? sa.getTargetRestrictions().getDividedValue(o) : dmg;
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) {
|
||||
final Card gc = game.getCardState(c, null);
|
||||
if (gc == null || !c.equalsWithTimestamp(gc) || !gc.isInPlay()) {
|
||||
// timestamp different or not in play
|
||||
continue;
|
||||
}
|
||||
if (!targeted || c.canBeTargetedBy(sa)) {
|
||||
if (removeDamage) {
|
||||
c.setDamage(0);
|
||||
c.setHasBeenDealtDeathtouchDamage(false);
|
||||
|
||||
@@ -137,7 +137,7 @@ public class DebuffEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
removedKW.addAll(kws);
|
||||
tgtC.addChangedCardKeywords(addedKW, removedKW, false, timestamp);
|
||||
tgtC.addChangedCardKeywords(addedKW, removedKW, false, false, timestamp);
|
||||
}
|
||||
if (!sa.hasParam("Permanent")) {
|
||||
game.getEndOfTurn().addUntil(new GameCommand() {
|
||||
|
||||
@@ -35,7 +35,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
sb.append(Lang.nounWithAmount(numToDig, "card")).append(" of ");
|
||||
|
||||
if (tgtPlayers.contains(host.getController())) {
|
||||
sb.append("his or her ");
|
||||
sb.append("their ");
|
||||
}
|
||||
else {
|
||||
for (final Player p : tgtPlayers) {
|
||||
@@ -377,7 +377,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
}
|
||||
} else if (destZone2 == ZoneType.Exile) {
|
||||
if (sa.hasParam("ExileWithCounter")) {
|
||||
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, effectHost, true);
|
||||
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, true);
|
||||
}
|
||||
c.setExiledWith(effectHost);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class DigUntilEffect extends SpellAbilityEffect {
|
||||
sb.append(pl).append(" ");
|
||||
}
|
||||
|
||||
sb.append("reveals cards from his or her library until revealing ");
|
||||
sb.append("reveals cards from their library until revealing ");
|
||||
sb.append(untilAmount).append(" ").append(desc).append(" card");
|
||||
if (untilAmount != 1) {
|
||||
sb.append("s");
|
||||
@@ -56,18 +56,18 @@ public class DigUntilEffect extends SpellAbilityEffect {
|
||||
sb.append(" ");
|
||||
|
||||
if (found.equals(ZoneType.Hand)) {
|
||||
sb.append("into his or her hand ");
|
||||
sb.append("into their hand ");
|
||||
}
|
||||
|
||||
if (revealed.equals(ZoneType.Graveyard)) {
|
||||
sb.append("and all other cards into his or her graveyard.");
|
||||
sb.append("and all other cards into their graveyard.");
|
||||
}
|
||||
if (revealed.equals(ZoneType.Exile)) {
|
||||
sb.append("and exile all other cards revealed this way.");
|
||||
}
|
||||
} else {
|
||||
if (revealed.equals(ZoneType.Hand)) {
|
||||
sb.append("all cards revealed this way into his or her hand");
|
||||
sb.append("all cards revealed this way into their hand");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
@@ -35,9 +35,9 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (mode.equals("RevealYouChoose")) {
|
||||
sb.append("reveals his or her hand.").append(" You choose (");
|
||||
sb.append("reveals their hand.").append(" You choose (");
|
||||
} else if (mode.equals("RevealDiscardAll")) {
|
||||
sb.append("reveals his or her hand. Discard (");
|
||||
sb.append("reveals their hand. Discard (");
|
||||
} else {
|
||||
sb.append("discards (");
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (mode.equals("Hand")) {
|
||||
sb.append("his or her hand");
|
||||
sb.append("their hand");
|
||||
} else if (mode.equals("RevealDiscardAll")) {
|
||||
sb.append("All");
|
||||
} else if (sa.hasParam("AnyNumber")) {
|
||||
|
||||
@@ -44,7 +44,6 @@ public class ExploreEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
// check if only the activating player counts
|
||||
final Card card = sa.getHostCard();
|
||||
final Player pl = sa.getActivatingPlayer();
|
||||
final PlayerController pc = pl.getController();
|
||||
final Game game = pl.getGame();
|
||||
@@ -78,7 +77,7 @@ public class ExploreEffect extends SpellAbilityEffect {
|
||||
// if the card is not more in the game anymore
|
||||
// this might still return true but its no problem
|
||||
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.getTimestamp() == c.getTimestamp()) {
|
||||
c.addCounter(CounterType.P1P1, 1, card, true);
|
||||
c.addCounter(CounterType.P1P1, 1, pl, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,10 +41,10 @@ public class FightEffect extends DamageBaseEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
List<Card> fighters = getFighters(sa);
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
final Game game = host.getGame();
|
||||
|
||||
if (fighters.size() < 2 || !fighters.get(0).isInPlay()
|
||||
|| !fighters.get(1).isInPlay()) {
|
||||
// check is done in getFighters
|
||||
if (fighters.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,21 +55,7 @@ public class FightEffect extends DamageBaseEffect {
|
||||
}
|
||||
}
|
||||
|
||||
boolean fightToughness = sa.hasParam("FightWithToughness");
|
||||
CardDamageMap damageMap = new CardDamageMap();
|
||||
CardDamageMap preventMap = new CardDamageMap();
|
||||
|
||||
// Damage is dealt simultaneously, so we calculate the damage from source to target before it is applied
|
||||
final int dmg1 = fightToughness ? fighters.get(0).getNetToughness() : fighters.get(0).getNetPower();
|
||||
final int dmg2 = fightToughness ? fighters.get(1).getNetToughness() : fighters.get(1).getNetPower();
|
||||
|
||||
dealDamage(fighters.get(0), fighters.get(1), dmg1, damageMap, preventMap, sa);
|
||||
dealDamage(fighters.get(1), fighters.get(0), dmg2, damageMap, preventMap, sa);
|
||||
|
||||
preventMap.triggerPreventDamage(false);
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
replaceDying(sa);
|
||||
dealDamage(sa, fighters.get(0), fighters.get(1));
|
||||
|
||||
for (Card c : fighters) {
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
@@ -83,6 +69,8 @@ public class FightEffect extends DamageBaseEffect {
|
||||
|
||||
Card fighter1 = null;
|
||||
Card fighter2 = null;
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
|
||||
List<Card> tgts = null;
|
||||
if (sa.usesTargeting()) {
|
||||
@@ -92,12 +80,27 @@ public class FightEffect extends DamageBaseEffect {
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("Defined")) {
|
||||
List<Card> defined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
List<Card> defined = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||
// Allow both fighters to come from defined list if first fighter not already found
|
||||
if (sa.hasParam("ExtraDefined")) {
|
||||
defined.addAll(AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("ExtraDefined"), sa));
|
||||
defined.addAll(AbilityUtils.getDefinedCards(host, sa.getParam("ExtraDefined"), sa));
|
||||
}
|
||||
|
||||
List<Card> newDefined = Lists.newArrayList();
|
||||
for (final Card d : defined) {
|
||||
final Card g = game.getCardState(d, null);
|
||||
// 701.12b If a creature instructed to fight is no longer on the battlefield or is no longer a creature,
|
||||
// no damage is dealt. If a creature is an illegal target
|
||||
// for a resolving spell or ability that instructs it to fight, no damage is dealt.
|
||||
if (g == null || !g.equalsWithTimestamp(d) || !d.isInPlay() || !d.isCreature()) {
|
||||
// Test to see if the card we're trying to add is in the expected state
|
||||
continue;
|
||||
}
|
||||
newDefined.add(g);
|
||||
}
|
||||
// replace with new List using CardState
|
||||
defined = newDefined;
|
||||
|
||||
if (!defined.isEmpty()) {
|
||||
if (defined.size() > 1 && fighter1 == null) {
|
||||
fighter1 = defined.get(0);
|
||||
@@ -120,9 +123,38 @@ public class FightEffect extends DamageBaseEffect {
|
||||
|
||||
return fighterList;
|
||||
}
|
||||
|
||||
private void dealDamage(Card source, Card target, int damage, CardDamageMap damageMap, CardDamageMap preventMap, final SpellAbility sa) {
|
||||
target.addDamage(damage, source, damageMap, preventMap, sa);
|
||||
}
|
||||
|
||||
private void dealDamage(final SpellAbility sa, Card fighterA, Card fighterB) {
|
||||
boolean fightToughness = sa.hasParam("FightWithToughness");
|
||||
|
||||
boolean usedDamageMap = true;
|
||||
CardDamageMap damageMap = sa.getDamageMap();
|
||||
CardDamageMap preventMap = sa.getPreventMap();
|
||||
|
||||
if (damageMap == null) {
|
||||
// make a new damage map
|
||||
damageMap = new CardDamageMap();
|
||||
preventMap = new CardDamageMap();
|
||||
usedDamageMap = false;
|
||||
}
|
||||
|
||||
// 701.12c If a creature fights itself, it deals damage to itself equal to twice its power.
|
||||
|
||||
final int dmg1 = fightToughness ? fighterA.getNetToughness() : fighterA.getNetPower();
|
||||
if (fighterA.equals(fighterB)) {
|
||||
fighterA.addDamage(dmg1 * 2, fighterA, damageMap, preventMap, sa);
|
||||
} else {
|
||||
final int dmg2 = fightToughness ? fighterB.getNetToughness() : fighterB.getNetPower();
|
||||
|
||||
fighterB.addDamage(dmg1, fighterA, damageMap, preventMap, sa);
|
||||
fighterA.addDamage(dmg2, fighterB, damageMap, preventMap, sa);
|
||||
}
|
||||
|
||||
if (!usedDamageMap) {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
}
|
||||
|
||||
replaceDying(sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,9 +118,9 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
if (sa.getParam("RememberWinner") != null) {
|
||||
host.addRemembered(host);
|
||||
}
|
||||
AbilitySub sub = sa.getAdditionalAbility("WinSubAbility");
|
||||
if (sub != null) {
|
||||
AbilityUtils.resolve(sub);
|
||||
|
||||
if (sa.hasAdditionalAbility("WinSubAbility")) {
|
||||
AbilityUtils.resolve(sa.getAdditionalAbility("WinSubAbility"));
|
||||
}
|
||||
// runParams.put("Won","True");
|
||||
} else {
|
||||
@@ -128,9 +128,8 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
host.addRemembered(host);
|
||||
}
|
||||
|
||||
AbilitySub sub = sa.getAdditionalAbility("LoseSubAbility");
|
||||
if (sub != null) {
|
||||
AbilityUtils.resolve(sub);
|
||||
if (sa.hasAdditionalAbility("LoseSubAbility")) {
|
||||
AbilityUtils.resolve(sa.getAdditionalAbility("LoseSubAbility"));
|
||||
}
|
||||
// runParams.put("Won","False");
|
||||
}
|
||||
@@ -167,7 +166,7 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
flipper.getGame().getAction().nofityOfValue(sa, flipper, result ? "heads" : "tails", null);
|
||||
} while (sa.hasParam("FlipUntilYouLose") && result != false);
|
||||
|
||||
if (sa.hasParam("FlipUntilYouLose")) {
|
||||
if (sa.hasParam("FlipUntilYouLose") && sa.hasAdditionalAbility("LoseSubAbility")) {
|
||||
sa.getAdditionalAbility("LoseSubAbility").setSVar(varName, "Number$" + numSuccesses);
|
||||
}
|
||||
|
||||
@@ -216,7 +215,7 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
caller.getGame().getTriggerHandler().runTrigger(TriggerType.FlippedCoin, runParams, false);
|
||||
} while (sa.hasParam("FlipUntilYouLose") && wonFlip);
|
||||
|
||||
if (sa.hasParam("FlipUntilYouLose")) {
|
||||
if (sa.hasParam("FlipUntilYouLose") && sa.hasAdditionalAbility("LoseSubAbility")) {
|
||||
sa.getAdditionalAbility("LoseSubAbility").setSVar(varName, "Number$" + numSuccesses);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -8,10 +9,14 @@ public class HauntEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
if (sa.usesTargeting() && !card.isToken()) {
|
||||
Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
card = game.getCardState(card, null);
|
||||
if (card == null) {
|
||||
return;
|
||||
} else if (sa.usesTargeting() && !card.isToken()) {
|
||||
// haunt target but only if card is no token
|
||||
final Card copy = card.getGame().getAction().exile(card, sa);
|
||||
final Card copy = game.getAction().exile(card, sa);
|
||||
sa.getTargets().getFirstTargetedCard().addHauntedBy(copy);
|
||||
} else if (!sa.usesTargeting() && card.getHaunting() != null) {
|
||||
// unhaunt
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ImmediateTriggerEffect extends SpellAbilityEffect {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
if (sa.hasParam("TriggerDescription")) {
|
||||
return sa.getParam("TriggerDescription");
|
||||
}
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
Map<String, String> mapParams = Maps.newHashMap(sa.getMapParams());
|
||||
|
||||
if (mapParams.containsKey("Cost")) {
|
||||
mapParams.remove("Cost");
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("SpellDescription")) {
|
||||
mapParams.put("TriggerDescription", mapParams.get("SpellDescription"));
|
||||
mapParams.remove("SpellDescription");
|
||||
}
|
||||
|
||||
String triggerRemembered = null;
|
||||
|
||||
// Set Remembered
|
||||
if (sa.hasParam("RememberObjects")) {
|
||||
triggerRemembered = sa.getParam("RememberObjects");
|
||||
}
|
||||
|
||||
mapParams.put("Mode", TriggerType.Immediate.name());
|
||||
|
||||
final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, sa.getHostCard(), true);
|
||||
|
||||
if (sa.hasParam("CopyTriggeringObjects")) {
|
||||
immediateTrig.setStoredTriggeredObjects(sa.getTriggeringObjects());
|
||||
}
|
||||
|
||||
// Need to copy paid costs
|
||||
|
||||
if (triggerRemembered != null) {
|
||||
for (final String rem : triggerRemembered.split(",")) {
|
||||
for (final Object o : AbilityUtils.getDefinedObjects(sa.getHostCard(), rem, sa)) {
|
||||
if (o instanceof SpellAbility) {
|
||||
// "RememberObjects$ Remembered" don't remember spellability
|
||||
continue;
|
||||
}
|
||||
immediateTrig.addRemembered(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("Execute") || sa.hasAdditionalAbility("Execute")) {
|
||||
SpellAbility overridingSA = sa.getAdditionalAbility("Execute");
|
||||
overridingSA.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
immediateTrig.setOverridingAbility(overridingSA);
|
||||
}
|
||||
final TriggerHandler trigHandler = sa.getActivatingPlayer().getGame().getTriggerHandler();
|
||||
|
||||
// Instead of registering this, add to the delayed triggers as an immediate trigger type? Which means it'll fire as soon as possible
|
||||
trigHandler.registerDelayedTrigger(immediateTrig);
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public class MillEffect extends SpellAbilityEffect {
|
||||
sb.append("s");
|
||||
}
|
||||
final String millPosition = sa.hasParam("FromBottom") ? "bottom" : "top";
|
||||
sb.append(" from the " + millPosition + " of his or her library.");
|
||||
sb.append(" from the " + millPosition + " of their library.");
|
||||
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -36,7 +36,7 @@ public class MustAttackEffect extends SpellAbilityEffect {
|
||||
|
||||
for (final Player player : tgtPlayers) {
|
||||
sb.append("Creatures ").append(player).append(" controls attack ");
|
||||
sb.append(defender).append(" during his or her next turn.");
|
||||
sb.append(defender).append(" during their next turn.");
|
||||
}
|
||||
for (final Card c : getTargetCards(sa)) {
|
||||
sb.append(c).append(" must attack ");
|
||||
|
||||
@@ -90,7 +90,7 @@ public class ProtectAllEffect extends SpellAbilityEffect {
|
||||
|
||||
for (final Card tgtC : list) {
|
||||
if (tgtC.isInPlay()) {
|
||||
tgtC.addChangedCardKeywords(gainsKWList, ImmutableList.<String>of(), false, timestamp, true);
|
||||
tgtC.addChangedCardKeywords(gainsKWList, null, false, false, timestamp, true);
|
||||
|
||||
if (!sa.hasParam("Permanent")) {
|
||||
// If not Permanent, remove protection at EOT
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.GameCommand;
|
||||
import forge.card.MagicColor;
|
||||
@@ -153,7 +152,7 @@ public class ProtectEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
tgtC.addChangedCardKeywords(gainsKWList, ImmutableList.<String>of(), false, timestamp, true);
|
||||
tgtC.addChangedCardKeywords(gainsKWList, null, false, false, timestamp, true);
|
||||
|
||||
if (!sa.hasParam("Permanent")) {
|
||||
// If not Permanent, remove protection at EOT
|
||||
@@ -181,7 +180,7 @@ public class ProtectEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
unTgtC.addChangedCardKeywords(gainsKWList, ImmutableList.<String>of(), false, timestamp, true);
|
||||
unTgtC.addChangedCardKeywords(gainsKWList, null, false, false, timestamp, true);
|
||||
|
||||
if (!sa.hasParam("Permanent")) {
|
||||
// If not Permanent, remove protection at EOT
|
||||
|
||||
@@ -13,10 +13,11 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
public class PumpAllEffect extends SpellAbilityEffect {
|
||||
private static void applyPumpAll(final SpellAbility sa,
|
||||
final List<Card> list, final int a, final int d,
|
||||
@@ -24,23 +25,18 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
final long timestamp = game.getNextTimestamp();
|
||||
final List<String> kws = new ArrayList<String>();
|
||||
final List<String> hiddenkws = new ArrayList<String>();
|
||||
boolean suspend = false;
|
||||
final List<String> kws = Lists.newArrayList();
|
||||
final List<String> hiddenkws = Lists.newArrayList();
|
||||
|
||||
for (String kw : keywords) {
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
hiddenkws.add(kw);
|
||||
} else {
|
||||
kws.add(kw);
|
||||
if (kw.equals("Suspend")) {
|
||||
suspend = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final Card tgtC : list) {
|
||||
|
||||
// only pump things in the affected zones.
|
||||
boolean found = false;
|
||||
for (final ZoneType z : affectedZones) {
|
||||
@@ -55,7 +51,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
|
||||
tgtC.addTempPowerBoost(a);
|
||||
tgtC.addTempToughnessBoost(d);
|
||||
tgtC.addChangedCardKeywords(kws, new ArrayList<String>(), false, timestamp);
|
||||
tgtC.addChangedCardKeywords(kws, null, false, false, timestamp);
|
||||
|
||||
for (String kw : hiddenkws) {
|
||||
tgtC.addHiddenExtrinsicKeyword(kw);
|
||||
@@ -118,13 +114,11 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(final SpellAbility sa) {
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
final List<ZoneType> affectedZones = new ArrayList<ZoneType>();
|
||||
final List<ZoneType> affectedZones = Lists.newArrayList();
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
|
||||
if (sa.hasParam("PumpZone")) {
|
||||
for (final String zone : sa.getParam("PumpZone").split(",")) {
|
||||
affectedZones.add(ZoneType.valueOf(zone));
|
||||
}
|
||||
affectedZones.addAll(ZoneType.listValueOf(sa.getParam("PumpZone")));
|
||||
} else {
|
||||
affectedZones.add(ZoneType.Battlefield);
|
||||
}
|
||||
@@ -149,7 +143,10 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
|
||||
list = (CardCollection)AbilityUtils.filterListByType(list, valid, sa);
|
||||
|
||||
List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<String>();
|
||||
List<String> keywords = Lists.newArrayList();
|
||||
if (sa.hasParam("KW")) {
|
||||
keywords.addAll(Arrays.asList(sa.getParam("KW").split(" & ")));
|
||||
}
|
||||
final int a = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa, true);
|
||||
final int d = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa, true);
|
||||
|
||||
@@ -161,6 +158,8 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard());
|
||||
}
|
||||
applyPumpAll(sa, list, a, d, keywords, affectedZones);
|
||||
|
||||
replaceDying(sa);
|
||||
} // pumpAllResolve()
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import forge.game.event.GameEventCardStatsChanged;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Lang;
|
||||
@@ -31,32 +30,40 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
final int a, final int d, final List<String> keywords,
|
||||
final long timestamp) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
//if host is not on the battlefield don't apply
|
||||
// Suspend should does Affect the Stack
|
||||
if (sa.hasParam("UntilLoseControlOfHost")
|
||||
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
|
||||
// do Game Check there in case of LKI
|
||||
final Card gameCard = game.getCardState(applyTo, null);
|
||||
if (gameCard == null || !applyTo.equalsWithTimestamp(gameCard)) {
|
||||
return;
|
||||
}
|
||||
final List<String> kws = Lists.newArrayList();
|
||||
|
||||
boolean redrawPT = false;
|
||||
for (String kw : keywords) {
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
applyTo.addHiddenExtrinsicKeyword(kw);
|
||||
gameCard.addHiddenExtrinsicKeyword(kw);
|
||||
redrawPT |= kw.contains("CARDNAME's power and toughness are switched");
|
||||
} else {
|
||||
kws.add(kw);
|
||||
}
|
||||
}
|
||||
|
||||
applyTo.addTempPowerBoost(a);
|
||||
applyTo.addTempToughnessBoost(d);
|
||||
applyTo.addChangedCardKeywords(kws, Lists.<String>newArrayList(), false, timestamp);
|
||||
if (redrawPT) { applyTo.updatePowerToughnessForView(); }
|
||||
gameCard.addTempPowerBoost(a);
|
||||
gameCard.addTempToughnessBoost(d);
|
||||
gameCard.addChangedCardKeywords(kws, Lists.<String>newArrayList(), false, false, timestamp);
|
||||
if (redrawPT) {
|
||||
gameCard.updatePowerToughnessForView();
|
||||
}
|
||||
|
||||
if (sa.hasParam("LeaveBattlefield")) {
|
||||
addLeaveBattlefieldReplacement(applyTo, sa, sa.getParam("LeaveBattlefield"));
|
||||
addLeaveBattlefieldReplacement(gameCard, sa, sa.getParam("LeaveBattlefield"));
|
||||
}
|
||||
|
||||
if (!sa.hasParam("Permanent")) {
|
||||
@@ -66,8 +73,8 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
applyTo.addTempPowerBoost(-1 * a);
|
||||
applyTo.addTempToughnessBoost(-1 * d);
|
||||
gameCard.addTempPowerBoost(-1 * a);
|
||||
gameCard.addTempToughnessBoost(-1 * d);
|
||||
|
||||
if (keywords.size() > 0) {
|
||||
boolean redrawPT = false;
|
||||
@@ -75,16 +82,16 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
for (String kw : keywords) {
|
||||
redrawPT |= kw.contains("CARDNAME's power and toughness are switched");
|
||||
if (kw.startsWith("HIDDEN")) {
|
||||
applyTo.removeHiddenExtrinsicKeyword(kw);
|
||||
gameCard.removeHiddenExtrinsicKeyword(kw);
|
||||
if (redrawPT) {
|
||||
applyTo.updatePowerToughnessForView();
|
||||
gameCard.updatePowerToughnessForView();
|
||||
}
|
||||
}
|
||||
}
|
||||
applyTo.removeChangedCardKeywords(timestamp);
|
||||
gameCard.removeChangedCardKeywords(timestamp);
|
||||
}
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(applyTo));
|
||||
game.fireEvent(new GameEventCardStatsChanged(gameCard));
|
||||
}
|
||||
};
|
||||
if (sa.hasParam("UntilEndOfCombat")) {
|
||||
@@ -107,12 +114,19 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
game.getEndOfTurn().addUntil(untilEOT);
|
||||
}
|
||||
}
|
||||
game.fireEvent(new GameEventCardStatsChanged(applyTo));
|
||||
game.fireEvent(new GameEventCardStatsChanged(gameCard));
|
||||
}
|
||||
|
||||
private static void applyPump(final SpellAbility sa, final Player p,
|
||||
final List<String> keywords, final long timestamp) {
|
||||
final Game game = p.getGame();
|
||||
final Card host = sa.getHostCard();
|
||||
//if host is not on the battlefield don't apply
|
||||
// Suspend should does Affect the Stack
|
||||
if (sa.hasParam("UntilLoseControlOfHost")
|
||||
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
p.addChangedKeywords(keywords, ImmutableList.<String>of(), timestamp);
|
||||
|
||||
if (!sa.hasParam("Permanent")) {
|
||||
@@ -134,6 +148,9 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
game.getEndOfCombat().addUntil(untilEOT);
|
||||
} else if (sa.hasParam("UntilYourNextUpkeep")) {
|
||||
game.getUpkeep().addUntil(sa.getActivatingPlayer(), untilEOT);
|
||||
} else if (sa.hasParam("UntilLoseControlOfHost")) {
|
||||
sa.getHostCard().addLeavesPlayCommand(untilEOT);
|
||||
sa.getHostCard().addChangeControllerCommand(untilEOT);
|
||||
} else {
|
||||
game.getEndOfTurn().addUntil(untilEOT);
|
||||
}
|
||||
@@ -208,7 +225,6 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
public void resolve(final SpellAbility sa) {
|
||||
|
||||
final List<Card> untargetedCards = Lists.newArrayList();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
final Card host = sa.getHostCard();
|
||||
final long timestamp = game.getNextTimestamp();
|
||||
@@ -251,7 +267,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
final String landtype = sa.getParam("DefinedLandwalk");
|
||||
final Card c = AbilityUtils.getDefinedCards(host, landtype, sa).get(0);
|
||||
for (String type : c.getType()) {
|
||||
if (CardType.isALandType(type) || CardType.isABasicLandType(type)) {
|
||||
if (CardType.isALandType(type)) {
|
||||
keywords.add(type + "walk");
|
||||
}
|
||||
}
|
||||
@@ -340,7 +356,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
// if pump is a target, make sure we can still target now
|
||||
if ((tgt != null) && !tgtC.canBeTargetedBy(sa)) {
|
||||
if (sa.usesTargeting() && !tgtC.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -367,5 +383,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
|
||||
applyPump(sa, p, keywords, timestamp);
|
||||
}
|
||||
|
||||
replaceDying(sa);
|
||||
} // pumpResolve()
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public class RearrangeTopOfLibraryEffect extends SpellAbilityEffect {
|
||||
ret.append("that");
|
||||
}
|
||||
|
||||
ret.append(" player shuffle his or her library.");
|
||||
ret.append(" player shuffle their library.");
|
||||
}
|
||||
|
||||
return ret.toString();
|
||||
|
||||
@@ -48,6 +48,10 @@ public class ReplaceEffect extends SpellAbilityEffect {
|
||||
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
|
||||
}
|
||||
|
||||
if (params.containsKey("EffectOnly")) {
|
||||
params.put("EffectOnly", true);
|
||||
}
|
||||
|
||||
//try to call replacementHandler with new Params
|
||||
ReplacementResult result = game.getReplacementHandler().run(params);
|
||||
switch (result) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user