mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
- Use the original implementation for orderAndPlaySimultaneousSa for the AI for the time being until the AI side of the implementation can be improved.
This commit is contained in:
committed by
Michael Kamensky
parent
06e0ed9a79
commit
9d0433f812
@@ -494,7 +494,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TargetChoices chooseNewTargetsFor(SpellAbility ability) {
|
public TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate<GameObject> filter, boolean optional) {
|
||||||
// AI currently can't do this. But when it can it will need to be based on Ability API
|
// AI currently can't do this. But when it can it will need to be based on Ability API
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -978,9 +978,13 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
player.getGame().getStackZone().add(sa.getHostCard());
|
player.getGame().getStackZone().add(sa.getHostCard());
|
||||||
}
|
}
|
||||||
// TODO check if static abilities needs to be run for things affecting the copy?
|
|
||||||
|
/* FIXME: the new implementation (below) requires implementing setupNewTargets in the AI controller, among other possible changes, otherwise breaks AI
|
||||||
|
if (sa.isMayChooseNewTargets()) {
|
||||||
|
sa.setupNewTargets(player);
|
||||||
|
}
|
||||||
|
*/
|
||||||
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
||||||
// if targets can't be done, remove copy from existence
|
|
||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
sa.getHostCard().ceaseToExist();
|
sa.getHostCard().ceaseToExist();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
Card choice = null;
|
Card choice = null;
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
|
|
||||||
@@ -398,16 +398,15 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
|
||||||
// only evaluates case where all tokens are placed on a single target
|
// only evaluates case where all tokens are placed on a single target
|
||||||
if (sa.usesTargeting() && abTgt.getMinTargets(source, sa) < 2) {
|
if (sa.usesTargeting() && sa.getMinTargets() < 2) {
|
||||||
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
|
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
|
||||||
Card c = sa.getTargets().getFirstTargetedCard();
|
Card c = sa.getTargets().getFirstTargetedCard();
|
||||||
if (sa.getTargets().size() > 1) {
|
if (sa.getTargets().size() > 1) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
abTgt.addDividedAllocation(sa.getTargetCard(), amount);
|
sa.addDividedAllocation(sa.getTargetCard(), amount);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@@ -462,7 +461,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Abzan Charm")) {
|
if (sourceName.equals("Abzan Charm")) {
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
|
||||||
// specific AI for instant with distribute two +1/+1 counters
|
// specific AI for instant with distribute two +1/+1 counters
|
||||||
ComputerUtilCard.sortByEvaluateCreature(list);
|
ComputerUtilCard.sortByEvaluateCreature(list);
|
||||||
// maximise the number of targets
|
// maximise the number of targets
|
||||||
@@ -472,11 +470,11 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i,
|
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i,
|
||||||
Lists.newArrayList())) {
|
Lists.newArrayList())) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
abTgt.addDividedAllocation(c, i);
|
sa.addDividedAllocation(c, i);
|
||||||
left -= i;
|
left -= i;
|
||||||
}
|
}
|
||||||
if (left < i || sa.getTargets().size() == abTgt.getMaxTargets(source, sa)) {
|
if (left < i || sa.getTargets().size() == sa.getMaxTargets()) {
|
||||||
abTgt.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i);
|
sa.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i);
|
||||||
left = 0;
|
left = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -542,7 +540,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
sa.getTargetRestrictions().addDividedAllocation(choice, amount);
|
sa.addDividedAllocation(choice, amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
choice = null;
|
choice = null;
|
||||||
@@ -609,7 +607,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|
||||||
final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
|
final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
|
||||||
@@ -672,7 +670,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
sa.getTargetRestrictions().addDividedAllocation(choice, amount);
|
sa.addDividedAllocation(choice, amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,7 +688,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
CardCollection list;
|
CardCollection list;
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
int left = amount;
|
int left = amount;
|
||||||
|
|
||||||
@@ -818,12 +816,11 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (choice != null && divided) {
|
if (choice != null && divided) {
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
|
||||||
int alloc = Math.max(amount / totalTargets, 1);
|
int alloc = Math.max(amount / totalTargets, 1);
|
||||||
if (sa.getTargets().size() == Math.min(totalTargets, abTgt.getMaxTargets(sa.getHostCard(), sa)) - 1) {
|
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||||
abTgt.addDividedAllocation(choice, left);
|
sa.addDividedAllocation(choice, left);
|
||||||
} else {
|
} else {
|
||||||
abTgt.addDividedAllocation(choice, alloc);
|
sa.addDividedAllocation(choice, alloc);
|
||||||
left -= alloc;
|
left -= alloc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) ||
|
if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) ||
|
||||||
sourceName.equals("Crater's Claws")){
|
sourceName.equals("Crater's Claws")){
|
||||||
// If I can kill my target by paying less mana, do it
|
// If I can kill my target by paying less mana, do it
|
||||||
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
|
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.isDividedAsYouChoose()) {
|
||||||
int actualPay = dmg;
|
int actualPay = dmg;
|
||||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||||
@@ -547,7 +547,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
final PhaseHandler phase = game.getPhaseHandler();
|
final PhaseHandler phase = game.getPhaseHandler();
|
||||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
|
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
@@ -613,7 +613,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (assignedDamage <= dmg
|
if (assignedDamage <= dmg
|
||||||
&& humanCreature.getShieldCount() == 0 && !ComputerUtil.canRegenerate(humanCreature.getController(), humanCreature)) {
|
&& humanCreature.getShieldCount() == 0 && !ComputerUtil.canRegenerate(humanCreature.getController(), humanCreature)) {
|
||||||
tcs.add(humanCreature);
|
tcs.add(humanCreature);
|
||||||
tgt.addDividedAllocation(humanCreature, assignedDamage);
|
sa.addDividedAllocation(humanCreature, assignedDamage);
|
||||||
lastTgt = humanCreature;
|
lastTgt = humanCreature;
|
||||||
dmg -= assignedDamage;
|
dmg -= assignedDamage;
|
||||||
}
|
}
|
||||||
@@ -625,7 +625,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dmg > 0 && lastTgt != null) {
|
if (dmg > 0 && lastTgt != null) {
|
||||||
tgt.addDividedAllocation(lastTgt, tgt.getDividedValue(lastTgt) + dmg);
|
sa.addDividedAllocation(lastTgt, sa.getDividedValue(lastTgt) + dmg);
|
||||||
dmg = 0;
|
dmg = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -635,14 +635,14 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tcs.add(humanCreature);
|
tcs.add(humanCreature);
|
||||||
tgt.addDividedAllocation(humanCreature, dmg);
|
sa.addDividedAllocation(humanCreature, dmg);
|
||||||
dmg = 0;
|
dmg = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int totalTargetedSoFar = -1;
|
int totalTargetedSoFar = -1;
|
||||||
while (tcs.size() < tgt.getMaxTargets(source, sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (totalTargetedSoFar == tcs.size()) {
|
if (totalTargetedSoFar == tcs.size()) {
|
||||||
// Avoid looping endlessly when choosing targets for cards with variable target number and type
|
// Avoid looping endlessly when choosing targets for cards with variable target number and type
|
||||||
// like Jaya's Immolating Inferno
|
// like Jaya's Immolating Inferno
|
||||||
@@ -664,7 +664,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (divided) {
|
if (divided) {
|
||||||
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||||
assignedDamage = Math.min(dmg, assignedDamage);
|
assignedDamage = Math.min(dmg, assignedDamage);
|
||||||
tgt.addDividedAllocation(c, assignedDamage);
|
sa.addDividedAllocation(c, assignedDamage);
|
||||||
dmg = dmg - assignedDamage;
|
dmg = dmg - assignedDamage;
|
||||||
if (dmg <= 0) {
|
if (dmg <= 0) {
|
||||||
break;
|
break;
|
||||||
@@ -680,7 +680,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
|
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||||
tcs.add(enemy);
|
tcs.add(enemy);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgt.addDividedAllocation(enemy, dmg);
|
sa.addDividedAllocation(enemy, dmg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -702,7 +702,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (divided) {
|
if (divided) {
|
||||||
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||||
if (assignedDamage <= dmg) {
|
if (assignedDamage <= dmg) {
|
||||||
tgt.addDividedAllocation(c, assignedDamage);
|
sa.addDividedAllocation(c, assignedDamage);
|
||||||
}
|
}
|
||||||
dmg = dmg - assignedDamage;
|
dmg = dmg - assignedDamage;
|
||||||
if (dmg <= 0) {
|
if (dmg <= 0) {
|
||||||
@@ -739,7 +739,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (freePing && sa.canTarget(enemy) && (!avoidTargetP(ai, sa))) {
|
if (freePing && sa.canTarget(enemy) && (!avoidTargetP(ai, sa))) {
|
||||||
tcs.add(enemy);
|
tcs.add(enemy);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgt.addDividedAllocation(enemy, dmg);
|
sa.addDividedAllocation(enemy, dmg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -757,9 +757,9 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (divided) {
|
if (divided) {
|
||||||
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||||
if (assignedDamage <= dmg) {
|
if (assignedDamage <= dmg) {
|
||||||
tgt.addDividedAllocation(c, assignedDamage);
|
sa.addDividedAllocation(c, assignedDamage);
|
||||||
} else {
|
} else {
|
||||||
tgt.addDividedAllocation(c, dmg);
|
sa.addDividedAllocation(c, dmg);
|
||||||
}
|
}
|
||||||
dmg = dmg - assignedDamage;
|
dmg = dmg - assignedDamage;
|
||||||
if (dmg <= 0) {
|
if (dmg <= 0) {
|
||||||
@@ -785,7 +785,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
(!avoidTargetP(ai, sa))) {
|
(!avoidTargetP(ai, sa))) {
|
||||||
tcs.add(enemy);
|
tcs.add(enemy);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgt.addDividedAllocation(enemy, dmg);
|
sa.addDividedAllocation(enemy, dmg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -880,16 +880,16 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg) {
|
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg) {
|
||||||
// this is for Triggered targets that are mandatory
|
// this is for Triggered targets that are mandatory
|
||||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getWeakestOpponent();
|
||||||
|
|
||||||
while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (tgt.canTgtPlaneswalker()) {
|
if (tgt.canTgtPlaneswalker()) {
|
||||||
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, true);
|
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, true);
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgt.addDividedAllocation(c, dmg);
|
sa.addDividedAllocation(c, dmg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -902,7 +902,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (c != null) {
|
if (c != null) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgt.addDividedAllocation(c, dmg);
|
sa.addDividedAllocation(c, dmg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -912,7 +912,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
if (sa.getTargets().add(opp)) {
|
if (sa.getTargets().add(opp)) {
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgt.addDividedAllocation(opp, dmg);
|
sa.addDividedAllocation(opp, dmg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -927,7 +927,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
Card c = ComputerUtilCard.getWorstPermanentAI(indestructible, false, false, false, false);
|
Card c = ComputerUtilCard.getWorstPermanentAI(indestructible, false, false, false, false);
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgt.addDividedAllocation(c, dmg);
|
sa.addDividedAllocation(c, dmg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -938,7 +938,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (c != null) {
|
if (c != null) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgt.addDividedAllocation(c, dmg);
|
sa.addDividedAllocation(c, dmg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -948,7 +948,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (sa.canTarget(ai)) {
|
if (sa.canTarget(ai)) {
|
||||||
if (sa.getTargets().add(ai)) {
|
if (sa.getTargets().add(ai)) {
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgt.addDividedAllocation(ai, dmg);
|
sa.addDividedAllocation(ai, dmg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -988,7 +988,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.isDividedAsYouChoose()) {
|
||||||
// If I can kill my target by paying less mana, do it
|
// If I can kill my target by paying less mana, do it
|
||||||
int actualPay = 0;
|
int actualPay = 0;
|
||||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().isEmpty()) {
|
if (sa.usesTargeting() && sa.isDividedAsYouChoose() && !sa.getTargets().isEmpty()) {
|
||||||
tgt.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
sa.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
return chance;
|
||||||
@@ -179,12 +179,11 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
// filter AIs battlefield by what I can target
|
// filter AIs battlefield by what I can target
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
|
||||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||||
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
||||||
Card target = null;
|
Card target = null;
|
||||||
|
|
||||||
@@ -215,8 +214,8 @@ public class DamagePreventAi extends SpellAbilityAi {
|
|||||||
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
|
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(target);
|
sa.getTargets().add(target);
|
||||||
if (sa.hasParam("DividedAsYouChoose")) {
|
if (sa.isDividedAsYouChoose()) {
|
||||||
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
sa.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
|
|
||||||
public class GameSimulator {
|
public class GameSimulator {
|
||||||
public static boolean COPY_STACK = false;
|
public static boolean COPY_STACK = false;
|
||||||
@@ -164,14 +163,12 @@ public class GameSimulator {
|
|||||||
SpellAbility saOrSubSa = sa;
|
SpellAbility saOrSubSa = sa;
|
||||||
do {
|
do {
|
||||||
if (origSaOrSubSa.usesTargeting()) {
|
if (origSaOrSubSa.usesTargeting()) {
|
||||||
final boolean divided = origSaOrSubSa.hasParam("DividedAsYouChoose");
|
final boolean divided = origSaOrSubSa.isDividedAsYouChoose();
|
||||||
final TargetRestrictions origTgtRes = origSaOrSubSa.getTargetRestrictions();
|
|
||||||
final TargetRestrictions tgtRes = saOrSubSa.getTargetRestrictions();
|
|
||||||
for (final GameObject o : origSaOrSubSa.getTargets()) {
|
for (final GameObject o : origSaOrSubSa.getTargets()) {
|
||||||
final GameObject target = copier.find(o);
|
final GameObject target = copier.find(o);
|
||||||
saOrSubSa.getTargets().add(target);
|
saOrSubSa.getTargets().add(target);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
tgtRes.addDividedAllocation(target, origTgtRes.getDividedValue(o));
|
saOrSubSa.addDividedAllocation(target, origSaOrSubSa.getDividedValue(o));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,16 +173,15 @@ public class PossibleTargetSelector {
|
|||||||
|
|
||||||
// Divide up counters, since AI is expected to do this. For now,
|
// Divide up counters, since AI is expected to do this. For now,
|
||||||
// divided evenly with left-overs going to the first target.
|
// divided evenly with left-overs going to the first target.
|
||||||
if (targetingSa.hasParam("DividedAsYouChoose")) {
|
if (targetingSa.isDividedAsYouChoose()) {
|
||||||
final int targetCount = targetingSa.getTargets().getTargetCards().size();
|
final int targetCount = targetingSa.getTargets().getTargetCards().size();
|
||||||
if (targetCount > 0) {
|
if (targetCount > 0) {
|
||||||
final String amountStr = targetingSa.getParam("CounterNum");
|
final String amountStr = targetingSa.getParam("CounterNum");
|
||||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, targetingSa);
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, targetingSa);
|
||||||
final int amountPerCard = amount / targetCount;
|
final int amountPerCard = amount / targetCount;
|
||||||
int amountLeftOver = amount - (amountPerCard * targetCount);
|
int amountLeftOver = amount - (amountPerCard * targetCount);
|
||||||
final TargetRestrictions tgtRes = targetingSa.getTargetRestrictions();
|
|
||||||
for (GameObject target : targetingSa.getTargets()) {
|
for (GameObject target : targetingSa.getTargets()) {
|
||||||
tgtRes.addDividedAllocation(target, amountPerCard + amountLeftOver);
|
targetingSa.addDividedAllocation(target, amountPerCard + amountLeftOver);
|
||||||
amountLeftOver = 0;
|
amountLeftOver = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -364,10 +364,6 @@ public final class AbilityFactory {
|
|||||||
if (mapParams.containsKey("TargetsWithDifferentCMC")) {
|
if (mapParams.containsKey("TargetsWithDifferentCMC")) {
|
||||||
abTgt.setDifferentCMC(true);
|
abTgt.setDifferentCMC(true);
|
||||||
}
|
}
|
||||||
if (mapParams.containsKey("DividedAsYouChoose")) {
|
|
||||||
abTgt.calculateStillToDivide(mapParams.get("DividedAsYouChoose"), null, null);
|
|
||||||
abTgt.setDividedAsYouChoose(true);
|
|
||||||
}
|
|
||||||
if (mapParams.containsKey("TargetsAtRandom")) {
|
if (mapParams.containsKey("TargetsAtRandom")) {
|
||||||
abTgt.setRandomTarget(true);
|
abTgt.setRandomTarget(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package forge.game.ability.effects;
|
package forge.game.ability.effects;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
|
import forge.game.GameObjectPredicates;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -19,7 +21,7 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Write javadoc for this type.
|
* TODO: Write javadoc for this type.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -42,9 +44,6 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean changesOneTarget = sa.hasParam("ChangeSingleTarget"); // The only card known to replace targets with self is Spellskite
|
|
||||||
// There is also Muck Drubb but it replaces ALL occurences of a single target with itself (unlike Spellskite that has to be activated for each).
|
|
||||||
|
|
||||||
SpellAbilityStackInstance changingTgtSI = si;
|
SpellAbilityStackInstance changingTgtSI = si;
|
||||||
Player chooser = sa.getActivatingPlayer();
|
Player chooser = sa.getActivatingPlayer();
|
||||||
|
|
||||||
@@ -54,11 +53,11 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
if (isOptional && !chooser.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantChangeAbilityTargets", tgtSA.getHostCard().toString()))) {
|
if (isOptional && !chooser.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantChangeAbilityTargets", tgtSA.getHostCard().toString()))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (changesOneTarget) {
|
if (sa.hasParam("ChangeSingleTarget")) {
|
||||||
// 1. choose a target of target spell
|
// 1. choose a target of target spell
|
||||||
List<Pair<SpellAbilityStackInstance, GameObject>> allTargets = new ArrayList<>();
|
List<Pair<SpellAbilityStackInstance, GameObject>> allTargets = new ArrayList<>();
|
||||||
while(changingTgtSI != null) {
|
while(changingTgtSI != null) {
|
||||||
SpellAbility changedSa = changingTgtSI.getSpellAbility(true);
|
SpellAbility changedSa = changingTgtSI.getSpellAbility(true);
|
||||||
if (changedSa.usesTargeting()) {
|
if (changedSa.usesTargeting()) {
|
||||||
for(GameObject it : changedSa.getTargets())
|
for(GameObject it : changedSa.getTargets())
|
||||||
allTargets.add(ImmutablePair.of(changingTgtSI, it));
|
allTargets.add(ImmutablePair.of(changingTgtSI, it));
|
||||||
@@ -77,6 +76,8 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
GameObject oldTarget = chosenTarget.getValue();
|
GameObject oldTarget = chosenTarget.getValue();
|
||||||
TargetChoices oldTargetBlock = replaceIn.getTargetChoices();
|
TargetChoices oldTargetBlock = replaceIn.getTargetChoices();
|
||||||
TargetChoices newTargetBlock = oldTargetBlock.clone();
|
TargetChoices newTargetBlock = oldTargetBlock.clone();
|
||||||
|
// gets the divied value from old target
|
||||||
|
Integer div = oldTargetBlock.getDividedValue(oldTarget);
|
||||||
newTargetBlock.remove(oldTarget);
|
newTargetBlock.remove(oldTarget);
|
||||||
replaceIn.updateTarget(newTargetBlock);
|
replaceIn.updateTarget(newTargetBlock);
|
||||||
// 3. test if updated choices would be correct.
|
// 3. test if updated choices would be correct.
|
||||||
@@ -84,7 +85,10 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
if (replaceIn.getSpellAbility(true).canTarget(newTarget)) {
|
if (replaceIn.getSpellAbility(true).canTarget(newTarget)) {
|
||||||
newTargetBlock.add(newTarget);
|
newTargetBlock.add(newTarget);
|
||||||
replaceIn.updateTarget(newTargetBlock, oldTarget, newTarget);
|
if (div != null) {
|
||||||
|
newTargetBlock.addDividedAllocation(newTarget, div);
|
||||||
|
}
|
||||||
|
replaceIn.updateTarget(newTargetBlock);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
replaceIn.updateTarget(oldTargetBlock);
|
replaceIn.updateTarget(oldTargetBlock);
|
||||||
@@ -93,36 +97,38 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
else {
|
else {
|
||||||
while(changingTgtSI != null) {
|
while(changingTgtSI != null) {
|
||||||
SpellAbility changingTgtSA = changingTgtSI.getSpellAbility(true);
|
SpellAbility changingTgtSA = changingTgtSI.getSpellAbility(true);
|
||||||
if (sa.hasParam("RandomTarget")){
|
if (changingTgtSA.usesTargeting()) {
|
||||||
if (changingTgtSA.usesTargeting()) {
|
// random target and DefinedMagnet works on single targets
|
||||||
|
if (sa.hasParam("RandomTarget")){
|
||||||
|
int div = changingTgtSA.getTotalDividedValue();
|
||||||
changingTgtSA.resetTargets();
|
changingTgtSA.resetTargets();
|
||||||
List<GameEntity> candidates = changingTgtSA.getTargetRestrictions().getAllCandidates(changingTgtSA, true);
|
List<GameEntity> candidates = changingTgtSA.getTargetRestrictions().getAllCandidates(changingTgtSA, true);
|
||||||
GameEntity choice = Aggregates.random(candidates);
|
GameEntity choice = Aggregates.random(candidates);
|
||||||
changingTgtSA.getTargets().add(choice);
|
changingTgtSA.getTargets().add(choice);
|
||||||
changingTgtSI.updateTarget(changingTgtSA.getTargets(), null, choice);
|
if (changingTgtSA.isDividedAsYouChoose()) {
|
||||||
|
changingTgtSA.addDividedAllocation(choice, div);
|
||||||
|
}
|
||||||
|
|
||||||
|
changingTgtSI.updateTarget(changingTgtSA.getTargets());
|
||||||
}
|
}
|
||||||
}
|
else if (sa.hasParam("DefinedMagnet")){
|
||||||
else if (sa.hasParam("DefinedMagnet")){
|
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
|
||||||
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
|
if (newTarget != null && changingTgtSA.canTarget(newTarget)) {
|
||||||
if (newTarget != null && changingTgtSA.canTarget(newTarget)) {
|
int div = changingTgtSA.getTotalDividedValue();
|
||||||
changingTgtSA.resetTargets();
|
changingTgtSA.resetTargets();
|
||||||
changingTgtSA.getTargets().add(newTarget);
|
changingTgtSA.getTargets().add(newTarget);
|
||||||
changingTgtSI.updateTarget(changingTgtSA.getTargets(), null, newTarget);
|
changingTgtSI.updateTarget(changingTgtSA.getTargets());
|
||||||
}
|
if (changingTgtSA.isDividedAsYouChoose()) {
|
||||||
}
|
changingTgtSA.addDividedAllocation(newTarget, div);
|
||||||
else {
|
|
||||||
// Update targets, with a potential new target
|
|
||||||
TargetChoices newTarget = sa.getActivatingPlayer().getController().chooseNewTargetsFor(changingTgtSA);
|
|
||||||
if (null != newTarget) {
|
|
||||||
if (sa.hasParam("TargetRestriction")) {
|
|
||||||
if (newTarget.getFirstTargetedCard() != null && newTarget.getFirstTargetedCard().
|
|
||||||
isValid(sa.getParam("TargetRestriction").split(","), activator, sa.getHostCard(), sa)) {
|
|
||||||
changingTgtSI.updateTarget(newTarget);
|
|
||||||
} else if (newTarget.getFirstTargetedPlayer() != null && newTarget.getFirstTargetedPlayer().
|
|
||||||
isValid(sa.getParam("TargetRestriction").split(","), activator, sa.getHostCard(), sa)) {
|
|
||||||
changingTgtSI.updateTarget(newTarget);
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Update targets, with a potential new target
|
||||||
|
Predicate<GameObject> filter = sa.hasParam("TargetRestriction") ? GameObjectPredicates.restriction(sa.getParam("TargetRestriction").split(","), activator, sa.getHostCard(), sa) : null;
|
||||||
|
// TODO Creature.Other might not work yet as it should
|
||||||
|
TargetChoices newTarget = sa.getActivatingPlayer().getController().chooseNewTargetsFor(changingTgtSA, filter, false);
|
||||||
|
if (null != newTarget) {
|
||||||
changingTgtSI.updateTarget(newTarget);
|
changingTgtSI.updateTarget(newTarget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
|||||||
candidates.remove(p);
|
candidates.remove(p);
|
||||||
|
|
||||||
for (GameEntity o : candidates) {
|
for (GameEntity o : candidates) {
|
||||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||||
resetFirstTargetOnCopy(copy, o, targetedSA);
|
resetFirstTargetOnCopy(copy, o, targetedSA);
|
||||||
copies.add(copy);
|
copies.add(copy);
|
||||||
}
|
}
|
||||||
@@ -144,12 +144,12 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final Card c : valid) {
|
for (final Card c : valid) {
|
||||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||||
resetFirstTargetOnCopy(copy, c, targetedSA);
|
resetFirstTargetOnCopy(copy, c, targetedSA);
|
||||||
copies.add(copy);
|
copies.add(copy);
|
||||||
}
|
}
|
||||||
for (final Player p : players) {
|
for (final Player p : players) {
|
||||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||||
resetFirstTargetOnCopy(copy, p, targetedSA);
|
resetFirstTargetOnCopy(copy, p, targetedSA);
|
||||||
copies.add(copy);
|
copies.add(copy);
|
||||||
}
|
}
|
||||||
@@ -157,12 +157,9 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (int i = 0; i < amount; i++) {
|
for (int i = 0; i < amount; i++) {
|
||||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||||
if (sa.hasParam("MayChooseTarget")) {
|
if (sa.hasParam("MayChooseTarget")) {
|
||||||
copy.setMayChooseNewTargets(true);
|
copy.setMayChooseNewTargets(true);
|
||||||
if (copy.usesTargeting()) {
|
|
||||||
copy.getTargetRestrictions().setMandatory(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// extra case for Epic to remove the keyword and the last part of the SpellAbility
|
// extra case for Epic to remove the keyword and the last part of the SpellAbility
|
||||||
@@ -206,12 +203,9 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
int extraAmount = addAmount - copies.size();
|
int extraAmount = addAmount - copies.size();
|
||||||
for (int i = 0; i < extraAmount; i++) {
|
for (int i = 0; i < extraAmount; i++) {
|
||||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
|
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||||
// extra copies added with CopySpellReplacenment currently always has new choose targets
|
// extra copies added with CopySpellReplacenment currently always has new choose targets
|
||||||
copy.setMayChooseNewTargets(true);
|
copy.setMayChooseNewTargets(true);
|
||||||
if (copy.usesTargeting()) {
|
|
||||||
copy.getTargetRestrictions().setMandatory(true);
|
|
||||||
}
|
|
||||||
copies.add(copy);
|
copies.add(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,13 +27,12 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
final Card host = sa.getHostCard();
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
final List<Card> tgtCards = getDefinedCardsOrTargeted(sa);
|
final List<Card> tgtCards = getDefinedCardsOrTargeted(sa);
|
||||||
|
|
||||||
Card source = null;
|
Card source = null;
|
||||||
if (sa.usesTargeting() && sa.getTargetRestrictions().getMinTargets(host, sa) == 2) {
|
if (sa.usesTargeting() && sa.getMinTargets() == 2) {
|
||||||
if (tgtCards.size() < 2) {
|
if (tgtCards.size() < 2) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -241,7 +240,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
|||||||
Card source = null;
|
Card source = null;
|
||||||
List<Card> tgtCards = getDefinedCardsOrTargeted(sa);
|
List<Card> tgtCards = getDefinedCardsOrTargeted(sa);
|
||||||
// special logic for moving from Target to Target
|
// special logic for moving from Target to Target
|
||||||
if (sa.usesTargeting() && sa.getTargetRestrictions().getMinTargets(host, sa) == 2) {
|
if (sa.usesTargeting() && sa.getMinTargets() == 2) {
|
||||||
if (tgtCards.size() < 2) {
|
if (tgtCards.size() < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
protected String getStackDescription(SpellAbility spellAbility) {
|
protected String getStackDescription(SpellAbility spellAbility) {
|
||||||
final StringBuilder stringBuilder = new StringBuilder();
|
final StringBuilder stringBuilder = new StringBuilder();
|
||||||
final Card card = spellAbility.getHostCard();
|
final Card card = spellAbility.getHostCard();
|
||||||
final boolean dividedAsYouChoose = spellAbility.hasParam("DividedAsYouChoose");
|
|
||||||
|
|
||||||
final int amount = AbilityUtils.calculateAmount(card, spellAbility.getParamOrDefault("CounterNum", "1"), spellAbility);
|
final int amount = AbilityUtils.calculateAmount(card, spellAbility.getParamOrDefault("CounterNum", "1"), spellAbility);
|
||||||
//skip the StringBuilder if no targets are chosen ("up to" scenario)
|
//skip the StringBuilder if no targets are chosen ("up to" scenario)
|
||||||
@@ -60,7 +59,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
stringBuilder.append("Bolster ").append(amount);
|
stringBuilder.append("Bolster ").append(amount);
|
||||||
return stringBuilder.toString();
|
return stringBuilder.toString();
|
||||||
}
|
}
|
||||||
if (dividedAsYouChoose) {
|
if (spellAbility.isDividedAsYouChoose()) {
|
||||||
stringBuilder.append("Distribute ");
|
stringBuilder.append("Distribute ");
|
||||||
} else {
|
} else {
|
||||||
stringBuilder.append("Put ");
|
stringBuilder.append("Put ");
|
||||||
@@ -84,7 +83,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
stringBuilder.append("s");
|
stringBuilder.append("s");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dividedAsYouChoose) {
|
if (spellAbility.isDividedAsYouChoose()) {
|
||||||
stringBuilder.append(" among ");
|
stringBuilder.append(" among ");
|
||||||
} else {
|
} else {
|
||||||
stringBuilder.append(" on ");
|
stringBuilder.append(" on ");
|
||||||
@@ -96,8 +95,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
for(int i = 0; i < targetCards.size(); i++) {
|
for(int i = 0; i < targetCards.size(); i++) {
|
||||||
Card targetCard = targetCards.get(i);
|
Card targetCard = targetCards.get(i);
|
||||||
stringBuilder.append(targetCard);
|
stringBuilder.append(targetCard);
|
||||||
if (spellAbility.getTargetRestrictions().getDividedMap().get(targetCard) != null) // fix null counter stack description
|
Integer v = spellAbility.getDividedValue(targetCard);
|
||||||
stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" counter)");
|
if (v != null) // fix null counter stack description
|
||||||
|
stringBuilder.append(" (").append(v).append(" counter)");
|
||||||
|
|
||||||
if(i == targetCards.size() - 2) {
|
if(i == targetCards.size() - 2) {
|
||||||
stringBuilder.append(" and ");
|
stringBuilder.append(" and ");
|
||||||
@@ -259,7 +259,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
if (obj instanceof Card) {
|
if (obj instanceof Card) {
|
||||||
boolean counterAdded = false;
|
boolean counterAdded = false;
|
||||||
counterAmount = sa.usesTargeting() && sa.hasParam("DividedAsYouChoose") ? sa.getTargetRestrictions().getDividedValue(gameCard) : counterAmount;
|
counterAmount = sa.usesTargeting() && sa.isDividedAsYouChoose() ? sa.getDividedValue(gameCard) : counterAmount;
|
||||||
if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) {
|
if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) {
|
||||||
if (max != -1) {
|
if (max != -1) {
|
||||||
counterAmount = Math.max(Math.min(max - gameCard.getCounters(counterType), counterAmount), 0);
|
counterAmount = Math.max(Math.min(max - gameCard.getCounters(counterType), counterAmount), 0);
|
||||||
@@ -270,7 +270,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
params.put("CounterType", counterType);
|
params.put("CounterType", counterType);
|
||||||
counterAmount = pc.chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCounters"), 0, counterAmount, params);
|
counterAmount = pc.chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCounters"), 0, counterAmount, params);
|
||||||
}
|
}
|
||||||
if (sa.hasParam("DividedAsYouChoose") && !sa.usesTargeting()) {
|
if (sa.isDividedAsYouChoose() && !sa.usesTargeting()) {
|
||||||
Map<String, Object> params = Maps.newHashMap();
|
Map<String, Object> params = Maps.newHashMap();
|
||||||
params.put("Target", obj);
|
params.put("Target", obj);
|
||||||
params.put("CounterType", counterType);
|
params.put("CounterType", counterType);
|
||||||
@@ -378,7 +378,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
card.addRemembered(gameCard);
|
card.addRemembered(gameCard);
|
||||||
}
|
}
|
||||||
game.updateLastStateForCard(gameCard);
|
game.updateLastStateForCard(gameCard);
|
||||||
if (sa.hasParam("DividedAsYouChoose") && !sa.usesTargeting()) {
|
if (sa.isDividedAsYouChoose() && !sa.usesTargeting()) {
|
||||||
counterRemain = counterRemain - counterAmount;
|
counterRemain = counterRemain - counterAmount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
|||||||
if (spellAbility.usesTargeting()) {
|
if (spellAbility.usesTargeting()) {
|
||||||
if (spellAbility.hasParam("DivideEvenly")) {
|
if (spellAbility.hasParam("DivideEvenly")) {
|
||||||
stringBuilder.append("divided evenly (rounded down) to\n");
|
stringBuilder.append("divided evenly (rounded down) to\n");
|
||||||
} else if (spellAbility.hasParam("DividedAsYouChoose")) {
|
} else if (spellAbility.isDividedAsYouChoose()) {
|
||||||
stringBuilder.append("divided to\n");
|
stringBuilder.append("divided to\n");
|
||||||
} else
|
} else
|
||||||
stringBuilder.append("to ");
|
stringBuilder.append("to ");
|
||||||
@@ -75,8 +75,9 @@ public class DamageDealEffect extends DamageBaseEffect {
|
|||||||
for (int i = 0; i < targetCards.size(); i++) {
|
for (int i = 0; i < targetCards.size(); i++) {
|
||||||
Card targetCard = targetCards.get(i);
|
Card targetCard = targetCards.get(i);
|
||||||
stringBuilder.append(targetCard);
|
stringBuilder.append(targetCard);
|
||||||
if (spellAbility.getTargetRestrictions().getDividedMap().get(targetCard) != null) //fix null damage stack description
|
Integer v = spellAbility.getDividedValue(targetCard);
|
||||||
stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" damage)");
|
if (v != null) //fix null damage stack description
|
||||||
|
stringBuilder.append(" (").append(v).append(" damage)");
|
||||||
|
|
||||||
if (i == targetCount - 2) {
|
if (i == targetCount - 2) {
|
||||||
stringBuilder.append(" and ");
|
stringBuilder.append(" and ");
|
||||||
@@ -89,8 +90,9 @@ public class DamageDealEffect extends DamageBaseEffect {
|
|||||||
for (int i = 0; i < players.size(); i++) {
|
for (int i = 0; i < players.size(); i++) {
|
||||||
Player targetPlayer = players.get(i);
|
Player targetPlayer = players.get(i);
|
||||||
stringBuilder.append(targetPlayer);
|
stringBuilder.append(targetPlayer);
|
||||||
if (spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer) != null) //fix null damage stack description
|
Integer v = spellAbility.getDividedValue(targetPlayer);
|
||||||
stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer)).append(" damage)");
|
if (v != null) //fix null damage stack description
|
||||||
|
stringBuilder.append(" (").append(v).append(" damage)");
|
||||||
|
|
||||||
if (i == players.size() - 2) {
|
if (i == players.size() - 2) {
|
||||||
stringBuilder.append(" and ");
|
stringBuilder.append(" and ");
|
||||||
@@ -102,7 +104,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
|||||||
} else {
|
} else {
|
||||||
if (spellAbility.hasParam("DivideEvenly")) {
|
if (spellAbility.hasParam("DivideEvenly")) {
|
||||||
stringBuilder.append("divided evenly (rounded down) ");
|
stringBuilder.append("divided evenly (rounded down) ");
|
||||||
} else if (spellAbility.hasParam("DividedAsYouChoose")) {
|
} else if (spellAbility.isDividedAsYouChoose()) {
|
||||||
stringBuilder.append("divided as you choose ");
|
stringBuilder.append("divided as you choose ");
|
||||||
}
|
}
|
||||||
stringBuilder.append("to ").append(Lang.joinHomogenous(targets));
|
stringBuilder.append("to ").append(Lang.joinHomogenous(targets));
|
||||||
@@ -229,7 +231,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
|||||||
|
|
||||||
for (final GameObject o : tgts) {
|
for (final GameObject o : tgts) {
|
||||||
if (!removeDamage) {
|
if (!removeDamage) {
|
||||||
dmg = (sa.usesTargeting() && sa.hasParam("DividedAsYouChoose")) ? sa.getTargetRestrictions().getDividedValue(o) : dmg;
|
dmg = (sa.usesTargeting() && sa.isDividedAsYouChoose()) ? sa.getDividedValue(o) : dmg;
|
||||||
if (dmg <= 0) {
|
if (dmg <= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class DamagePreventEffect extends SpellAbilityEffect {
|
|||||||
sb.append("Prevent the next ");
|
sb.append("Prevent the next ");
|
||||||
sb.append(sa.getParam("Amount"));
|
sb.append(sa.getParam("Amount"));
|
||||||
sb.append(" damage that would be dealt ");
|
sb.append(" damage that would be dealt ");
|
||||||
if (sa.hasParam("DividedAsYouChoose")) {
|
if (sa.isDividedAsYouChoose()) {
|
||||||
sb.append("between ");
|
sb.append("between ");
|
||||||
} else {
|
} else {
|
||||||
sb.append("to ");
|
sb.append("to ");
|
||||||
@@ -75,8 +75,8 @@ public class DamagePreventEffect extends SpellAbilityEffect {
|
|||||||
final boolean targeted = (sa.usesTargeting());
|
final boolean targeted = (sa.usesTargeting());
|
||||||
final boolean preventionWithEffect = sa.hasParam("PreventionSubAbility");
|
final boolean preventionWithEffect = sa.hasParam("PreventionSubAbility");
|
||||||
|
|
||||||
for (final Object o : tgts) {
|
for (final GameObject o : tgts) {
|
||||||
numDam = (sa.usesTargeting() && sa.hasParam("DividedAsYouChoose")) ? sa.getTargetRestrictions().getDividedValue(o) : numDam;
|
numDam = (sa.usesTargeting() && sa.isDividedAsYouChoose()) ? sa.getDividedValue(o) : numDam;
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
final Card c = (Card) o;
|
final Card c = (Card) o;
|
||||||
if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) {
|
if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) {
|
||||||
|
|||||||
@@ -260,13 +260,8 @@ public class PlayEffect extends SpellAbilityEffect {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean noManaCost = sa.hasParam("WithoutManaCost");
|
if (sa.hasParam("WithoutManaCost")) {
|
||||||
if (noManaCost) {
|
|
||||||
tgtSA = tgtSA.copyWithNoManaCost();
|
tgtSA = tgtSA.copyWithNoManaCost();
|
||||||
// FIXME: a hack to get cards like Detonate only allow legal targets when cast without paying mana cost (with X=0).
|
|
||||||
if (tgtSA.hasParam("ValidTgtsWithoutManaCost")) {
|
|
||||||
tgtSA.getTargetRestrictions().changeValidTargets(tgtSA.getParam("ValidTgtsWithoutManaCost").split(","));
|
|
||||||
}
|
|
||||||
} else if (sa.hasParam("PlayCost")) {
|
} else if (sa.hasParam("PlayCost")) {
|
||||||
Cost abCost;
|
Cost abCost;
|
||||||
if ("ManaCost".equals(sa.getParam("PlayCost"))) {
|
if ("ManaCost".equals(sa.getParam("PlayCost"))) {
|
||||||
|
|||||||
@@ -117,10 +117,9 @@ public class CardFactory {
|
|||||||
* which wouldn't ordinarily get set during a simple Card.copy() call.
|
* which wouldn't ordinarily get set during a simple Card.copy() call.
|
||||||
* </p>
|
* </p>
|
||||||
* */
|
* */
|
||||||
private final static Card copySpellHost(final SpellAbility sourceSA, final SpellAbility targetSA){
|
private final static Card copySpellHost(final SpellAbility sourceSA, final SpellAbility targetSA, Player controller) {
|
||||||
final Card source = sourceSA.getHostCard();
|
final Card source = sourceSA.getHostCard();
|
||||||
final Card original = targetSA.getHostCard();
|
final Card original = targetSA.getHostCard();
|
||||||
Player controller = sourceSA.getActivatingPlayer();
|
|
||||||
final Card c = copyCard(original, true);
|
final Card c = copyCard(original, true);
|
||||||
|
|
||||||
// change the color of the copy (eg: Fork)
|
// change the color of the copy (eg: Fork)
|
||||||
@@ -168,17 +167,15 @@ public class CardFactory {
|
|||||||
* @param bCopyDetails
|
* @param bCopyDetails
|
||||||
* a boolean.
|
* a boolean.
|
||||||
*/
|
*/
|
||||||
public final static SpellAbility copySpellAbilityAndPossiblyHost(final SpellAbility sourceSA, final SpellAbility targetSA) {
|
public final static SpellAbility copySpellAbilityAndPossiblyHost(final SpellAbility sourceSA, final SpellAbility targetSA, final Player controller) {
|
||||||
Player controller = sourceSA.getActivatingPlayer();
|
|
||||||
|
|
||||||
//it is only necessary to copy the host card if the SpellAbility is a spell, not an ability
|
//it is only necessary to copy the host card if the SpellAbility is a spell, not an ability
|
||||||
final Card c = targetSA.isSpell() ? copySpellHost(sourceSA, targetSA) : targetSA.getHostCard();
|
final Card c = targetSA.isSpell() ? copySpellHost(sourceSA, targetSA, controller) : targetSA.getHostCard();
|
||||||
|
|
||||||
final SpellAbility copySA;
|
final SpellAbility copySA;
|
||||||
if (targetSA.isTrigger() && targetSA.isWrapper()) {
|
if (targetSA.isTrigger() && targetSA.isWrapper()) {
|
||||||
copySA = getCopiedTriggeredAbility((WrappedAbility)targetSA, c);
|
copySA = getCopiedTriggeredAbility((WrappedAbility)targetSA, c, controller);
|
||||||
} else {
|
} else {
|
||||||
copySA = targetSA.copy(c, false);
|
copySA = targetSA.copy(c, controller, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
copySA.setCopied(true);
|
copySA.setCopied(true);
|
||||||
@@ -555,12 +552,12 @@ public class CardFactory {
|
|||||||
*
|
*
|
||||||
* return a wrapped ability
|
* return a wrapped ability
|
||||||
*/
|
*/
|
||||||
public static SpellAbility getCopiedTriggeredAbility(final WrappedAbility sa, final Card newHost) {
|
public static SpellAbility getCopiedTriggeredAbility(final WrappedAbility sa, final Card newHost, final Player controller) {
|
||||||
if (!sa.isTrigger()) {
|
if (!sa.isTrigger()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, false), sa.getDecider());
|
return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, controller, false), controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) {
|
public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) {
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public abstract class PlayerController {
|
|||||||
public abstract Integer announceRequirements(SpellAbility ability, String announce);
|
public abstract Integer announceRequirements(SpellAbility ability, String announce);
|
||||||
public abstract CardCollectionView choosePermanentsToSacrifice(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);
|
public abstract CardCollectionView choosePermanentsToSacrifice(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);
|
||||||
public abstract CardCollectionView choosePermanentsToDestroy(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);
|
public abstract CardCollectionView choosePermanentsToDestroy(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);
|
||||||
public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability);
|
public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate<GameObject> filter, boolean optional);
|
||||||
public abstract boolean chooseTargetsFor(SpellAbility currentAbility); // this is bad a function for it assigns targets to sa inside its body
|
public abstract boolean chooseTargetsFor(SpellAbility currentAbility); // this is bad a function for it assigns targets to sa inside its body
|
||||||
|
|
||||||
// Specify a target of a spell (Spellskite)
|
// Specify a target of a spell (Spellskite)
|
||||||
|
|||||||
@@ -152,6 +152,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
private TargetRestrictions targetRestrictions = null;
|
private TargetRestrictions targetRestrictions = null;
|
||||||
private TargetChoices targetChosen = new TargetChoices();
|
private TargetChoices targetChosen = new TargetChoices();
|
||||||
|
|
||||||
|
private Integer dividedValue = null;
|
||||||
|
|
||||||
private SpellAbilityView view;
|
private SpellAbilityView view;
|
||||||
|
|
||||||
private StaticAbility mayPlay = null;
|
private StaticAbility mayPlay = null;
|
||||||
@@ -1101,7 +1103,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
|
|
||||||
if (hasParam("TargetingPlayerControls") && entity instanceof Card) {
|
if (hasParam("TargetingPlayerControls") && entity instanceof Card) {
|
||||||
final Card c = (Card) entity;
|
final Card c = (Card) entity;
|
||||||
if (!c.getController().equals(targetingPlayer)) {
|
if (!c.getController().equals(getTargetingPlayer())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1417,6 +1419,35 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
targetChosen = new TargetChoices();
|
targetChosen = new TargetChoices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a boolean dividedAsYouChoose
|
||||||
|
*/
|
||||||
|
public boolean isDividedAsYouChoose() {
|
||||||
|
return hasParam("DividedAsYouChoose");
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void addDividedAllocation(final GameObject tgt, final Integer portionAllocated) {
|
||||||
|
getTargets().addDividedAllocation(tgt, portionAllocated);
|
||||||
|
}
|
||||||
|
public Integer getDividedValue(GameObject c) {
|
||||||
|
return getTargets().getDividedValue(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalDividedValue() {
|
||||||
|
return getTargets().getTotalDividedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getDividedValue() {
|
||||||
|
return this.dividedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStillToDivide() {
|
||||||
|
if (!isDividedAsYouChoose() || dividedValue == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return dividedValue - getTotalDividedValue();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the first target.
|
* Reset the first target.
|
||||||
*
|
*
|
||||||
@@ -1424,16 +1455,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
public void resetFirstTarget(GameObject c, SpellAbility originalSA) {
|
public void resetFirstTarget(GameObject c, SpellAbility originalSA) {
|
||||||
SpellAbility sa = this;
|
SpellAbility sa = this;
|
||||||
while (sa != null) {
|
while (sa != null) {
|
||||||
if (sa.targetRestrictions != null) {
|
if (sa.usesTargeting()) {
|
||||||
sa.targetChosen = new TargetChoices();
|
sa.resetTargets();
|
||||||
sa.targetChosen.add(c);
|
sa.getTargets().add(c);
|
||||||
if (!originalSA.targetRestrictions.getDividedMap().isEmpty()) {
|
if (!originalSA.getTargets().getDividedValues().isEmpty()) {
|
||||||
sa.targetRestrictions.addDividedAllocation(c,
|
sa.addDividedAllocation(c, Iterables.getFirst(originalSA.getTargets().getDividedValues(), null));
|
||||||
Iterables.getFirst(originalSA.targetRestrictions.getDividedMap().values(), null));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sa = sa.subAbility;
|
sa = sa.getSubAbility();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1450,7 +1480,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isZeroTargets() {
|
public boolean isZeroTargets() {
|
||||||
return getTargetRestrictions().getMinTargets(hostCard, this) == 0 && getTargets().size() == 0;
|
return getMinTargets() == 0 && getTargets().size() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMinTargetChosen() {
|
public boolean isMinTargetChosen() {
|
||||||
@@ -1724,6 +1754,27 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
return p != null && p.isTargeting(o);
|
return p != null && p.isTargeting(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean setupNewTargets(Player forceTargetingPlayer) {
|
||||||
|
// Skip to paying if parent ability doesn't target and has no subAbilities.
|
||||||
|
// (or trigger case where its already targeted)
|
||||||
|
SpellAbility currentAbility = this;
|
||||||
|
do {
|
||||||
|
if (currentAbility.usesTargeting()) {
|
||||||
|
TargetChoices oldTargets = currentAbility.getTargets();
|
||||||
|
if (forceTargetingPlayer.getController().chooseNewTargetsFor(currentAbility, null, true) == null) {
|
||||||
|
currentAbility.setTargets(oldTargets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final AbilitySub subAbility = currentAbility.getSubAbility();
|
||||||
|
if (subAbility != null) {
|
||||||
|
// This is necessary for "TargetsWithDefinedController$ ParentTarget"
|
||||||
|
subAbility.setParent(currentAbility);
|
||||||
|
}
|
||||||
|
currentAbility = subAbility;
|
||||||
|
} while (currentAbility != null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean setupTargets() {
|
public boolean setupTargets() {
|
||||||
// Skip to paying if parent ability doesn't target and has no subAbilities.
|
// Skip to paying if parent ability doesn't target and has no subAbilities.
|
||||||
// (or trigger case where its already targeted)
|
// (or trigger case where its already targeted)
|
||||||
@@ -1741,6 +1792,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
} else {
|
} else {
|
||||||
targetingPlayer = getActivatingPlayer();
|
targetingPlayer = getActivatingPlayer();
|
||||||
}
|
}
|
||||||
|
// don't set targeting player when forceful target,
|
||||||
|
// "targeting player controls" should not be reset when the spell is copied
|
||||||
currentAbility.setTargetingPlayer(targetingPlayer);
|
currentAbility.setTargetingPlayer(targetingPlayer);
|
||||||
if (!targetingPlayer.getController().chooseTargetsFor(currentAbility)) {
|
if (!targetingPlayer.getController().chooseTargetsFor(currentAbility)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1756,11 +1809,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public final void clearTargets() {
|
public final void clearTargets() {
|
||||||
final TargetRestrictions tg = getTargetRestrictions();
|
if (usesTargeting()) {
|
||||||
if (tg != null) {
|
|
||||||
resetTargets();
|
resetTargets();
|
||||||
if (hasParam("DividedAsYouChoose")) {
|
if (isDividedAsYouChoose()) {
|
||||||
tg.calculateStillToDivide(getParam("DividedAsYouChoose"), getHostCard(), this);
|
this.dividedValue = AbilityUtils.calculateAmount(getHostCard(), this.getParam("DividedAsYouChoose"), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,10 @@ import forge.util.TextUtil;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -85,7 +84,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
|||||||
private Integer xManaPaid = null;
|
private Integer xManaPaid = null;
|
||||||
|
|
||||||
// Other Paid things
|
// Other Paid things
|
||||||
private final HashMap<String, CardCollection> paidHash;
|
private final Map<String, CardCollection> paidHash;
|
||||||
|
|
||||||
// Additional info
|
// Additional info
|
||||||
// is Kicked, is Buyback
|
// is Kicked, is Buyback
|
||||||
@@ -96,7 +95,6 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
|||||||
|
|
||||||
private final Map<String, String> storedSVars = Maps.newHashMap();
|
private final Map<String, String> storedSVars = Maps.newHashMap();
|
||||||
|
|
||||||
private final List<ZoneType> zonesToOpen;
|
|
||||||
private final Map<Player, Object> playersWithValidTargets;
|
private final Map<Player, Object> playersWithValidTargets;
|
||||||
|
|
||||||
private final StackItemView view;
|
private final StackItemView view;
|
||||||
@@ -109,7 +107,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
|||||||
activatingPlayer = sa.getActivatingPlayer();
|
activatingPlayer = sa.getActivatingPlayer();
|
||||||
|
|
||||||
// Payment info
|
// Payment info
|
||||||
paidHash = new HashMap<>(ability.getPaidHash());
|
paidHash = Maps.newHashMap(ability.getPaidHash());
|
||||||
ability.resetPaidHash();
|
ability.resetPaidHash();
|
||||||
splicedCards = sa.getSplicedCards();
|
splicedCards = sa.getSplicedCards();
|
||||||
|
|
||||||
@@ -149,18 +147,13 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
|||||||
|
|
||||||
//store zones to open and players to open them for at the time the SpellAbility first goes on the stack based on the selected targets
|
//store zones to open and players to open them for at the time the SpellAbility first goes on the stack based on the selected targets
|
||||||
if (tc == null) {
|
if (tc == null) {
|
||||||
zonesToOpen = null;
|
|
||||||
playersWithValidTargets = null;
|
playersWithValidTargets = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
zonesToOpen = new ArrayList<>();
|
playersWithValidTargets = Maps.newHashMap();
|
||||||
playersWithValidTargets = new HashMap<>();
|
|
||||||
for (Card card : tc.getTargetCards()) {
|
for (Card card : tc.getTargetCards()) {
|
||||||
ZoneType zoneType = card.getZone() != null ? card.getZone().getZoneType() : null;
|
ZoneType zoneType = card.getZone() != null ? card.getZone().getZoneType() : null;
|
||||||
if (zoneType != ZoneType.Battlefield) { //don't need to worry about targets on battlefield
|
if (zoneType != ZoneType.Battlefield) { //don't need to worry about targets on battlefield
|
||||||
if (zoneType != null && !zonesToOpen.contains(zoneType)) {
|
|
||||||
zonesToOpen.add(zoneType);
|
|
||||||
}
|
|
||||||
playersWithValidTargets.put(card.getController(), null);
|
playersWithValidTargets.put(card.getController(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,75 +246,32 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
|||||||
return tc;
|
return tc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final List<ZoneType> getZonesToOpen() {
|
|
||||||
return zonesToOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Map<Player, Object> getPlayersWithValidTargets() {
|
public final Map<Player, Object> getPlayersWithValidTargets() {
|
||||||
return playersWithValidTargets;
|
return playersWithValidTargets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateTarget(TargetChoices target) {
|
public void updateTarget(TargetChoices target) {
|
||||||
updateTarget(target, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateTarget(TargetChoices target, GameObject oldTarget, GameObject newTarget) {
|
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
|
TargetChoices oldTarget = tc;
|
||||||
tc = target;
|
tc = target;
|
||||||
ability.setTargets(tc);
|
ability.setTargets(tc);
|
||||||
stackDescription = ability.getStackDescription();
|
stackDescription = ability.getStackDescription();
|
||||||
view.updateTargetCards(this);
|
view.updateTargetCards(this);
|
||||||
view.updateTargetPlayers(this);
|
view.updateTargetPlayers(this);
|
||||||
view.updateText(this);
|
view.updateText(this);
|
||||||
|
|
||||||
if (ability.hasParam("DividedAsYouChoose")) {
|
|
||||||
// try to update DividedAsYouChoose after retargeting
|
|
||||||
Object toRemove = null;
|
|
||||||
Object toAdd = null;
|
|
||||||
HashMap<Object,Integer> map = ability.getTargetRestrictions().getDividedMap();
|
|
||||||
|
|
||||||
if (oldTarget != null) {
|
|
||||||
toRemove = oldTarget;
|
|
||||||
} else {
|
|
||||||
// try to deduce which target has been replaced
|
|
||||||
// (this may be imprecise, updateTarget should specify old target if possible)
|
|
||||||
for (Object obj : map.keySet()) {
|
|
||||||
if (!target.contains(obj)) {
|
|
||||||
toRemove = obj;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newTarget != null) {
|
|
||||||
toAdd = newTarget;
|
|
||||||
} else {
|
|
||||||
// try to deduce which target was added
|
|
||||||
// (this may be imprecise, updateTarget should specify new target if possible)
|
|
||||||
for (Object newTgts : target) {
|
|
||||||
if (!map.containsKey(newTgts)) {
|
|
||||||
toAdd = newTgts;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toRemove != null && toAdd != null) {
|
|
||||||
int div = map.get(toRemove);
|
|
||||||
map.remove(toRemove);
|
|
||||||
ability.getTargetRestrictions().addDividedAllocation(toAdd, div);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run BecomesTargetTrigger
|
// Run BecomesTargetTrigger
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||||
runParams.put(AbilityKey.SourceSA, ability);
|
runParams.put(AbilityKey.SourceSA, ability);
|
||||||
Set<Object> distinctObjects = new HashSet<>();
|
Set<GameObject> distinctObjects = Sets.newHashSet();
|
||||||
for (final Object tgt : target) {
|
for (final GameObject tgt : target) {
|
||||||
if (distinctObjects.contains(tgt)) {
|
if (oldTarget != null && oldTarget.contains(tgt)) {
|
||||||
|
// it was an old target, so don't trigger becomes target
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!distinctObjects.add(tgt)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
distinctObjects.add(tgt);
|
|
||||||
|
|
||||||
if (tgt instanceof Card && !((Card) tgt).hasBecomeTargetThisTurn()) {
|
if (tgt instanceof Card && !((Card) tgt).hasBecomeTargetThisTurn()) {
|
||||||
runParams.put(AbilityKey.FirstTime, null);
|
runParams.put(AbilityKey.FirstTime, null);
|
||||||
@@ -330,7 +280,8 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
|
|||||||
runParams.put(AbilityKey.Target, tgt);
|
runParams.put(AbilityKey.Target, tgt);
|
||||||
getSourceCard().getGame().getTriggerHandler().runTrigger(TriggerType.BecomesTarget, runParams, false);
|
getSourceCard().getGame().getTriggerHandler().runTrigger(TriggerType.BecomesTarget, runParams, false);
|
||||||
}
|
}
|
||||||
runParams.put(AbilityKey.Targets, target);
|
runParams = AbilityKey.newMap();
|
||||||
|
runParams.put(AbilityKey.Targets, distinctObjects);
|
||||||
getSourceCard().getGame().getTriggerHandler().runTrigger(TriggerType.BecomesTargetOnce, runParams, false);
|
getSourceCard().getGame().getTriggerHandler().runTrigger(TriggerType.BecomesTargetOnce, runParams, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
@@ -21,6 +21,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.ForwardingList;
|
import com.google.common.collect.ForwardingList;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
@@ -30,13 +31,15 @@ import forge.game.card.CardCollectionView;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Target_Choices class.
|
* Target_Choices class.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Forge
|
* @author Forge
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
@@ -44,6 +47,8 @@ public class TargetChoices extends ForwardingList<GameObject> implements Cloneab
|
|||||||
|
|
||||||
private final FCollection<GameObject> targets = new FCollection<GameObject>();
|
private final FCollection<GameObject> targets = new FCollection<GameObject>();
|
||||||
|
|
||||||
|
private final Map<GameObject, Integer> dividedMap = Maps.newHashMap();
|
||||||
|
|
||||||
public final int getTotalTargetedCMC() {
|
public final int getTotalTargetedCMC() {
|
||||||
int totalCMC = 0;
|
int totalCMC = 0;
|
||||||
for (Card c : Iterables.filter(targets, Card.class)) {
|
for (Card c : Iterables.filter(targets, Card.class)) {
|
||||||
@@ -52,6 +57,7 @@ public class TargetChoices extends ForwardingList<GameObject> implements Cloneab
|
|||||||
return totalCMC;
|
return totalCMC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public final boolean add(final GameObject o) {
|
public final boolean add(final GameObject o) {
|
||||||
if (o instanceof Player || o instanceof Card || o instanceof SpellAbility) {
|
if (o instanceof Player || o instanceof Card || o instanceof SpellAbility) {
|
||||||
return super.add(o);
|
return super.add(o);
|
||||||
@@ -59,6 +65,22 @@ public class TargetChoices extends ForwardingList<GameObject> implements Cloneab
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeAll(Collection<?> collection) {
|
||||||
|
boolean result = super.removeAll(collection);
|
||||||
|
for (Object e : collection) {
|
||||||
|
this.dividedMap.remove(e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object object) {
|
||||||
|
boolean result = super.remove(object);
|
||||||
|
dividedMap.remove(object);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public final CardCollectionView getTargetCards() {
|
public final CardCollectionView getTargetCards() {
|
||||||
return new CardCollection(Iterables.filter(targets, Card.class));
|
return new CardCollection(Iterables.filter(targets, Card.class));
|
||||||
}
|
}
|
||||||
@@ -103,10 +125,31 @@ public class TargetChoices extends ForwardingList<GameObject> implements Cloneab
|
|||||||
public TargetChoices clone() {
|
public TargetChoices clone() {
|
||||||
TargetChoices tc = new TargetChoices();
|
TargetChoices tc = new TargetChoices();
|
||||||
tc.targets.addAll(targets);
|
tc.targets.addAll(targets);
|
||||||
|
tc.dividedMap.putAll(dividedMap);
|
||||||
return tc;
|
return tc;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected List<GameObject> delegate() {
|
protected List<GameObject> delegate() {
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final void addDividedAllocation(final GameObject tgt, final Integer portionAllocated) {
|
||||||
|
this.dividedMap.put(tgt, portionAllocated);
|
||||||
|
}
|
||||||
|
public Integer getDividedValue(GameObject c) {
|
||||||
|
return dividedMap.get(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Integer> getDividedValues() {
|
||||||
|
return dividedMap.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalDividedValue() {
|
||||||
|
int result = 0;
|
||||||
|
for (Integer i : getDividedValues()) {
|
||||||
|
if (i != null)
|
||||||
|
result += i;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,9 @@
|
|||||||
package forge.game.spellability;
|
package forge.game.spellability;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
@@ -75,11 +73,6 @@ public class TargetRestrictions {
|
|||||||
|
|
||||||
// What's the max total CMC of targets?
|
// What's the max total CMC of targets?
|
||||||
private String maxTotalCMC;
|
private String maxTotalCMC;
|
||||||
|
|
||||||
// For "Divided" cards. Is this better in TargetChoices?
|
|
||||||
private boolean dividedAsYouChoose = false;
|
|
||||||
private HashMap<Object, Integer> dividedMap = new HashMap<>();
|
|
||||||
private int stillToDivide = 0;
|
|
||||||
|
|
||||||
// Not sure what's up with Mandatory? Why wouldn't targeting be mandatory?
|
// Not sure what's up with Mandatory? Why wouldn't targeting be mandatory?
|
||||||
private boolean bMandatory = false;
|
private boolean bMandatory = false;
|
||||||
@@ -101,7 +94,6 @@ public class TargetRestrictions {
|
|||||||
this.maxTotalCMC = target.getMaxTotalCMC();
|
this.maxTotalCMC = target.getMaxTotalCMC();
|
||||||
this.tgtZone = target.getZone();
|
this.tgtZone = target.getZone();
|
||||||
this.saValidTargeting = target.getSAValidTargeting();
|
this.saValidTargeting = target.getSAValidTargeting();
|
||||||
this.dividedAsYouChoose = target.isDividedAsYouChoose();
|
|
||||||
this.uniqueTargets = target.isUniqueTargets();
|
this.uniqueTargets = target.isUniqueTargets();
|
||||||
this.singleZone = target.isSingleZone();
|
this.singleZone = target.isSingleZone();
|
||||||
this.differentControllers = target.isDifferentControllers();
|
this.differentControllers = target.isDifferentControllers();
|
||||||
@@ -728,82 +720,9 @@ public class TargetRestrictions {
|
|||||||
this.singleTarget = singleTarget;
|
this.singleTarget = singleTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a boolean dividedAsYouChoose
|
|
||||||
*/
|
|
||||||
public boolean isDividedAsYouChoose() {
|
|
||||||
return this.dividedAsYouChoose;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param divided the boolean to set
|
|
||||||
*/
|
|
||||||
public void setDividedAsYouChoose(boolean divided) {
|
|
||||||
this.dividedAsYouChoose = divided;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the amount remaining to distribute.
|
|
||||||
* @return int stillToDivide
|
|
||||||
*/
|
|
||||||
public int getStillToDivide() {
|
|
||||||
return this.stillToDivide;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param remaining set the amount still to be divided
|
|
||||||
*/
|
|
||||||
public void setStillToDivide(final int remaining) {
|
|
||||||
this.stillToDivide = remaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void calculateStillToDivide(String toDistribute, Card source, SpellAbility sa) {
|
|
||||||
// Recalculate this value just in case it's variable
|
|
||||||
if (!this.dividedAsYouChoose) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StringUtils.isNumeric(toDistribute)) {
|
|
||||||
this.setStillToDivide(Integer.parseInt(toDistribute));
|
|
||||||
} else if ( source == null ) {
|
|
||||||
return; // such calls come from AbilityFactory.readTarget - at this moment we don't yet know X or any other variables
|
|
||||||
} else if (source.getSVar(toDistribute).equals("xPaid")) {
|
|
||||||
this.setStillToDivide(source.getXManaCostPaid());
|
|
||||||
} else {
|
|
||||||
this.setStillToDivide(AbilityUtils.calculateAmount(source, toDistribute, sa));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store divided amount relative to a specific card/player.
|
|
||||||
* @param tgt the targeted object
|
|
||||||
* @param portionAllocated the divided portion allocated
|
|
||||||
*/
|
|
||||||
public final void addDividedAllocation(final Object tgt, final Integer portionAllocated) {
|
|
||||||
this.dividedMap.remove(tgt);
|
|
||||||
this.dividedMap.put(tgt, portionAllocated);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the divided amount relative to a specific card/player.
|
|
||||||
* @param tgt the targeted object
|
|
||||||
* @return an int.
|
|
||||||
*/
|
|
||||||
public int getDividedValue(Object tgt) {
|
|
||||||
return this.dividedMap.get(tgt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashMap<Object, Integer> getDividedMap() {
|
|
||||||
return this.dividedMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void applyTargetTextChanges(final SpellAbility sa) {
|
public final void applyTargetTextChanges(final SpellAbility sa) {
|
||||||
for (int i = 0; i < validTgts.length; i++) {
|
for (int i = 0; i < validTgts.length; i++) {
|
||||||
validTgts[i] = AbilityUtils.applyAbilityTextChangeEffects(originalValidTgts[i], sa);
|
validTgts[i] = AbilityUtils.applyAbilityTextChangeEffects(originalValidTgts[i], sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void changeValidTargets(final String[] validTgts) {
|
|
||||||
this.originalValidTgts = validTgts;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import forge.game.card.Card;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,9 +63,8 @@ public class TriggerBecomesTargetOnce extends Trigger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasParam("ValidTarget")) {
|
if (hasParam("ValidTarget")) {
|
||||||
List<GameObject> targets = (List<GameObject>) runParams.get(AbilityKey.Targets);
|
|
||||||
boolean valid = false;
|
boolean valid = false;
|
||||||
for (GameObject b : targets) {
|
for (GameObject b : (Iterable<GameObject>) runParams.get(AbilityKey.Targets)) {
|
||||||
if (matchesValid(b, getParam("ValidTarget").split(","), this.getHostCard())) {
|
if (matchesValid(b, getParam("ValidTarget").split(","), this.getHostCard())) {
|
||||||
valid = true;
|
valid = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasLegalTargeting(sp, source)) {
|
if (!sp.isCopied() && !hasLegalTargeting(sp, source)) {
|
||||||
String str = source + " - [Couldn't add to stack, failed to target] - " + sp.getDescription();
|
String str = source + " - [Couldn't add to stack, failed to target] - " + sp.getDescription();
|
||||||
System.err.println(str + sp.getAllTargetChoices());
|
System.err.println(str + sp.getAllTargetChoices());
|
||||||
game.getGameLog().add(GameLogEntryType.STACK_ADD, str);
|
game.getGameLog().add(GameLogEntryType.STACK_ADD, str);
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ public class PlayerControllerForTests extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TargetChoices chooseNewTargetsFor(SpellAbility ability) {
|
public TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate<GameObject> filter, boolean optional) {
|
||||||
throw new IllegalStateException("Erring on the side of caution here...");
|
throw new IllegalStateException("Erring on the side of caution here...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
Name:Detonate
|
Name:Detonate
|
||||||
ManaCost:X R
|
ManaCost:X R
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ Destroy | Cost$ X R | ValidTgts$ Artifact | ValidTgtsWithoutManaCost$ Artifact.cmcEQ0 | TgtPrompt$ Select target artifact | NoRegen$ True | SubAbility$ DBDamage | References$ X | SpellDescription$ Destroy target artifact with converted mana cost X. It can't be regenerated. CARDNAME deals X damage to that artifact's controller.
|
A:SP$ Destroy | Cost$ X R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | NoRegen$ True | SubAbility$ DBDamage | References$ X | SpellDescription$ Destroy target artifact with converted mana cost X. It can't be regenerated. CARDNAME deals X damage to that artifact's controller.
|
||||||
SVar:DBDamage:DB$DealDamage | Defined$ TargetedController | NumDmg$ X | References$ X
|
SVar:DBDamage:DB$DealDamage | Defined$ TargetedController | NumDmg$ X | References$ X
|
||||||
SVar:X:Targeted$CardManaCost
|
SVar:X:Count$xPaid
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/detonate.jpg
|
|
||||||
Oracle:Destroy target artifact with converted mana cost X. It can't be regenerated. Detonate deals X damage to that artifact's controller.
|
Oracle:Destroy target artifact with converted mana cost X. It can't be regenerated. Detonate deals X damage to that artifact's controller.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package forge.match.input;
|
package forge.match.input;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.FThreads;
|
import forge.FThreads;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
@@ -22,6 +25,7 @@ import forge.util.ITriggerEvent;
|
|||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -33,21 +37,25 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
|||||||
private final Map<GameEntity, Integer> targetDepth = new HashMap<>();
|
private final Map<GameEntity, Integer> targetDepth = new HashMap<>();
|
||||||
private final TargetRestrictions tgt;
|
private final TargetRestrictions tgt;
|
||||||
private final SpellAbility sa;
|
private final SpellAbility sa;
|
||||||
|
private final Collection<Integer> divisionValues;
|
||||||
private Card lastTarget = null;
|
private Card lastTarget = null;
|
||||||
private boolean bCancel = false;
|
private boolean bCancel = false;
|
||||||
private boolean bOk = false;
|
private boolean bOk = false;
|
||||||
private final boolean mandatory;
|
private final boolean mandatory;
|
||||||
|
private Predicate<GameObject> filter;
|
||||||
private static final long serialVersionUID = -1091595663541356356L;
|
private static final long serialVersionUID = -1091595663541356356L;
|
||||||
|
|
||||||
public final boolean hasCancelled() { return bCancel; }
|
public final boolean hasCancelled() { return bCancel; }
|
||||||
public final boolean hasPressedOk() { return bOk; }
|
public final boolean hasPressedOk() { return bOk; }
|
||||||
|
|
||||||
public InputSelectTargets(final PlayerControllerHuman controller, final List<Card> choices, final SpellAbility sa, final boolean mandatory) {
|
public InputSelectTargets(final PlayerControllerHuman controller, final List<Card> choices, final SpellAbility sa, final boolean mandatory, Collection<Integer> divisionValues, Predicate<GameObject> filter) {
|
||||||
super(controller);
|
super(controller);
|
||||||
this.choices = choices;
|
this.choices = choices;
|
||||||
this.tgt = sa.getTargetRestrictions();
|
this.tgt = sa.getTargetRestrictions();
|
||||||
this.sa = sa;
|
this.sa = sa;
|
||||||
this.mandatory = mandatory;
|
this.mandatory = mandatory;
|
||||||
|
this.divisionValues = divisionValues;
|
||||||
|
this.filter = filter;
|
||||||
controller.getGui().setSelectables(CardView.getCollection(choices));
|
controller.getGui().setSelectables(CardView.getCollection(choices));
|
||||||
final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
|
final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
|
||||||
for (final Card c : choices) {
|
for (final Card c : choices) {
|
||||||
@@ -97,7 +105,7 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
|||||||
sb.append(sa.getUniqueTargets());
|
sb.append(sa.getUniqueTargets());
|
||||||
}
|
}
|
||||||
|
|
||||||
final int maxTargets = tgt.getMaxTargets(sa.getHostCard(), sa);
|
final int maxTargets = sa.getMaxTargets();
|
||||||
final int targeted = sa.getTargets().size();
|
final int targeted = sa.getTargets().size();
|
||||||
if(maxTargets > 1) {
|
if(maxTargets > 1) {
|
||||||
sb.append(TextUtil.concatNoSpace("\n(", String.valueOf(maxTargets - targeted), " more can be targeted)"));
|
sb.append(TextUtil.concatNoSpace("\n(", String.valueOf(maxTargets - targeted), " more can be targeted)"));
|
||||||
@@ -108,10 +116,10 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
|||||||
"(Targeting ERROR)", "");
|
"(Targeting ERROR)", "");
|
||||||
showMessage(message, sa.getView());
|
showMessage(message, sa.getView());
|
||||||
|
|
||||||
if (tgt.isDividedAsYouChoose() && tgt.getMinTargets(sa.getHostCard(), sa) == 0 && sa.getTargets().size() == 0) {
|
if (sa.isDividedAsYouChoose() && sa.getMinTargets() == 0 && sa.getTargets().size() == 0) {
|
||||||
// extra logic for Divided with min targets = 0, should only work if num targets are 0 too
|
// extra logic for Divided with min targets = 0, should only work if num targets are 0 too
|
||||||
getController().getGui().updateButtons(getOwner(), true, true, false);
|
getController().getGui().updateButtons(getOwner(), true, true, false);
|
||||||
} else if (!tgt.isMinTargetsChosen(sa.getHostCard(), sa) || tgt.isDividedAsYouChoose()) {
|
} else if (!sa.isMinTargetChosen() || sa.isDividedAsYouChoose()) {
|
||||||
// If reached Minimum targets, enable OK button
|
// If reached Minimum targets, enable OK button
|
||||||
if (mandatory && tgt.hasCandidates(sa, true)) {
|
if (mandatory && tgt.hasCandidates(sa, true)) {
|
||||||
// Player has to click on a target
|
// Player has to click on a target
|
||||||
@@ -239,40 +247,11 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt.isDividedAsYouChoose()) {
|
if (sa.isDividedAsYouChoose()) {
|
||||||
final int stillToDivide = tgt.getStillToDivide();
|
Boolean val = onDividedAsYouChoose(card);
|
||||||
int allocatedPortion = 0;
|
if (val != null) {
|
||||||
// allow allocation only if the max targets isn't reached and there are more candidates
|
return val;
|
||||||
if ((sa.getTargets().size() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa))
|
|
||||||
&& (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
|
|
||||||
final ImmutableList.Builder<Integer> choices = ImmutableList.builder();
|
|
||||||
for (int i = 1; i <= stillToDivide; i++) {
|
|
||||||
choices.add(Integer.valueOf(i));
|
|
||||||
}
|
|
||||||
String apiBasedMessage = "Distribute how much to ";
|
|
||||||
if (sa.getApi() == ApiType.DealDamage) {
|
|
||||||
apiBasedMessage = "Select how much damage to deal to ";
|
|
||||||
}
|
|
||||||
else if (sa.getApi() == ApiType.PreventDamage) {
|
|
||||||
apiBasedMessage = "Select how much damage to prevent to ";
|
|
||||||
}
|
|
||||||
else if (sa.getApi() == ApiType.PutCounter) {
|
|
||||||
apiBasedMessage = "Select how many counters to distribute to ";
|
|
||||||
}
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(apiBasedMessage);
|
|
||||||
sb.append(card.toString());
|
|
||||||
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), choices.build());
|
|
||||||
if (chosen == null) {
|
|
||||||
return true; //still return true since there was a valid choice
|
|
||||||
}
|
|
||||||
allocatedPortion = chosen;
|
|
||||||
}
|
}
|
||||||
else { // otherwise assign the rest of the damage/protection
|
|
||||||
allocatedPortion = stillToDivide;
|
|
||||||
}
|
|
||||||
tgt.setStillToDivide(stillToDivide - allocatedPortion);
|
|
||||||
tgt.addDividedAllocation(card, allocatedPortion);
|
|
||||||
}
|
}
|
||||||
addTarget(card);
|
addTarget(card);
|
||||||
return true;
|
return true;
|
||||||
@@ -305,12 +284,49 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
|||||||
showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?).");
|
showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?).");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (filter != null && !filter.apply(player)) {
|
||||||
|
showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (tgt.isDividedAsYouChoose()) {
|
if (sa.isDividedAsYouChoose()) {
|
||||||
final int stillToDivide = tgt.getStillToDivide();
|
Boolean val = onDividedAsYouChoose(player);
|
||||||
|
if (val != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addTarget(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Boolean onDividedAsYouChoose(GameObject go) {
|
||||||
|
if (divisionValues != null) {
|
||||||
|
if (divisionValues.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String apiBasedMessage = "Distribute how much to ";
|
||||||
|
if (sa.getApi() == ApiType.DealDamage) {
|
||||||
|
apiBasedMessage = "Select how much damage to deal to ";
|
||||||
|
}
|
||||||
|
else if (sa.getApi() == ApiType.PreventDamage) {
|
||||||
|
apiBasedMessage = "Select how much damage to prevent to ";
|
||||||
|
}
|
||||||
|
else if (sa.getApi() == ApiType.PutCounter) {
|
||||||
|
apiBasedMessage = "Select how many counters to distribute to ";
|
||||||
|
}
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(apiBasedMessage);
|
||||||
|
sb.append(go.toString());
|
||||||
|
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), Lists.newArrayList(divisionValues));
|
||||||
|
if (chosen == null) {
|
||||||
|
return true; //still return true since there was a valid choice
|
||||||
|
}
|
||||||
|
divisionValues.remove(chosen);
|
||||||
|
sa.addDividedAllocation(go, chosen);
|
||||||
|
} else {
|
||||||
|
final int stillToDivide = sa.getStillToDivide();
|
||||||
int allocatedPortion = 0;
|
int allocatedPortion = 0;
|
||||||
// allow allocation only if the max targets isn't reached and there are more candidates
|
// allow allocation only if the max targets isn't reached and there are more candidates
|
||||||
if ((sa.getTargets().size() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa)) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
|
if ((sa.getTargets().size() + 1 < sa.getMaxTargets()) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
|
||||||
final ImmutableList.Builder<Integer> choices = ImmutableList.builder();
|
final ImmutableList.Builder<Integer> choices = ImmutableList.builder();
|
||||||
for (int i = 1; i <= stillToDivide; i++) {
|
for (int i = 1; i <= stillToDivide; i++) {
|
||||||
choices.add(Integer.valueOf(i));
|
choices.add(Integer.valueOf(i));
|
||||||
@@ -323,19 +339,18 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
|||||||
}
|
}
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
sb.append(apiBasedMessage);
|
sb.append(apiBasedMessage);
|
||||||
sb.append(player.getName());
|
sb.append(go.toString());
|
||||||
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), choices.build());
|
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), choices.build());
|
||||||
if (null == chosen) {
|
if (null == chosen) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
allocatedPortion = chosen;
|
allocatedPortion = chosen;
|
||||||
} else { // otherwise assign the rest of the damage/protection
|
} else { // otherwise assign the rest of the damage/protection
|
||||||
allocatedPortion = stillToDivide;
|
allocatedPortion = stillToDivide;
|
||||||
}
|
}
|
||||||
tgt.setStillToDivide(stillToDivide - allocatedPortion);
|
sa.addDividedAllocation(go, allocatedPortion);
|
||||||
tgt.addDividedAllocation(player, allocatedPortion);
|
|
||||||
}
|
}
|
||||||
addTarget(player);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTarget(final GameEntity ge) {
|
private void addTarget(final GameEntity ge) {
|
||||||
@@ -366,7 +381,7 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasAllTargets() {
|
private boolean hasAllTargets() {
|
||||||
return tgt.isMaxTargetsChosen(sa.getHostCard(), sa) || ( tgt.getStillToDivide() == 0 && tgt.isDividedAsYouChoose());
|
return sa.isMaxTargetChosen() || (sa.isDividedAsYouChoose() && sa.getStillToDivide() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1061,19 +1061,20 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
* SpellAbility, forge.card.spellability.SpellAbilityStackInstance)
|
* SpellAbility, forge.card.spellability.SpellAbilityStackInstance)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public TargetChoices chooseNewTargetsFor(final SpellAbility ability) {
|
public TargetChoices chooseNewTargetsFor(final SpellAbility ability, Predicate<GameObject> filter, boolean optional) {
|
||||||
final SpellAbility sa = ability.isWrapper() ? ((WrappedAbility) ability).getWrappedAbility() : ability;
|
final SpellAbility sa = ability.isWrapper() ? ((WrappedAbility) ability).getWrappedAbility() : ability;
|
||||||
if (sa.getTargetRestrictions() == null) {
|
if (!sa.usesTargeting()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final TargetChoices oldTarget = sa.getTargets();
|
final TargetChoices oldTarget = sa.getTargets();
|
||||||
final TargetSelection select = new TargetSelection(this, sa);
|
final TargetSelection select = new TargetSelection(this, sa);
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (select.chooseTargets(oldTarget.size())) {
|
if (select.chooseTargets(oldTarget.size(), Lists.newArrayList(oldTarget.getDividedValues()), filter, optional)) {
|
||||||
return sa.getTargets();
|
return sa.getTargets();
|
||||||
} else {
|
} else {
|
||||||
|
sa.setTargets(oldTarget);
|
||||||
// Return old target, since we had to reset them above
|
// Return old target, since we had to reset them above
|
||||||
return oldTarget;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1795,11 +1796,8 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
player.getGame().getStackZone().add(next.getHostCard());
|
player.getGame().getStackZone().add(next.getHostCard());
|
||||||
}
|
}
|
||||||
// TODO check if static abilities needs to be run for things affecting the copy?
|
// TODO check if static abilities needs to be run for things affecting the copy?
|
||||||
if (next.isMayChooseNewTargets() && !next.setupTargets()) {
|
if (next.isMayChooseNewTargets()) {
|
||||||
// if targets can't be done, remove copy from existence
|
next.setupNewTargets(player);
|
||||||
if (next.isSpell()) {
|
|
||||||
next.getHostCard().ceaseToExist();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.getGame().getStack().add(next);
|
player.getGame().getStack().add(next);
|
||||||
@@ -1820,7 +1818,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
@Override
|
@Override
|
||||||
public boolean chooseTargetsFor(final SpellAbility currentAbility) {
|
public boolean chooseTargetsFor(final SpellAbility currentAbility) {
|
||||||
final TargetSelection select = new TargetSelection(this, currentAbility);
|
final TargetSelection select = new TargetSelection(this, currentAbility);
|
||||||
return select.chooseTargets(null);
|
return select.chooseTargets(null, null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
package forge.player;
|
package forge.player;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -25,6 +27,7 @@ import forge.game.GameEntityView;
|
|||||||
import forge.game.GameEntityViewMap;
|
import forge.game.GameEntityViewMap;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.card.CardView;
|
import forge.game.card.CardView;
|
||||||
import forge.game.player.PlayerView;
|
import forge.game.player.PlayerView;
|
||||||
@@ -38,6 +41,7 @@ import forge.match.input.InputSelectTargets;
|
|||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -70,15 +74,15 @@ public class TargetSelection {
|
|||||||
return ability.isTrigger() || getTgt().getMandatory();
|
return ability.isTrigger() || getTgt().getMandatory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean chooseTargets(Integer numTargets) {
|
public final boolean chooseTargets(Integer numTargets, Collection<Integer> divisionValues, Predicate<GameObject> filter, boolean optional) {
|
||||||
if (!ability.usesTargeting()) {
|
if (!ability.usesTargeting()) {
|
||||||
throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability);
|
throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability);
|
||||||
}
|
}
|
||||||
final TargetRestrictions tgt = getTgt();
|
final TargetRestrictions tgt = getTgt();
|
||||||
|
|
||||||
// Number of targets is explicitly set only if spell is being redirected (ex. Swerve or Redirect)
|
// Number of targets is explicitly set only if spell is being redirected (ex. Swerve or Redirect)
|
||||||
final int minTargets = numTargets != null ? numTargets.intValue() : tgt.getMinTargets(ability.getHostCard(), ability);
|
final int minTargets = numTargets != null ? numTargets.intValue() : ability.getMinTargets();
|
||||||
final int maxTargets = numTargets != null ? numTargets.intValue() : tgt.getMaxTargets(ability.getHostCard(), ability);
|
final int maxTargets = numTargets != null ? numTargets.intValue() : ability.getMaxTargets();
|
||||||
//final int maxTotalCMC = tgt.getMaxTotalCMC(ability.getHostCard(), ability);
|
//final int maxTotalCMC = tgt.getMaxTotalCMC(ability.getHostCard(), ability);
|
||||||
final int numTargeted = ability.getTargets().size();
|
final int numTargeted = ability.getTargets().size();
|
||||||
final boolean isSingleZone = ability.getTargetRestrictions().isSingleZone();
|
final boolean isSingleZone = ability.getTargetRestrictions().isSingleZone();
|
||||||
@@ -92,7 +96,7 @@ public class TargetSelection {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) {
|
if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || ability.isDividedAsYouChoose() && divisionValues == null && ability.getStillToDivide() == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,11 +111,10 @@ public class TargetSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<ZoneType> zones = tgt.getZone();
|
final List<ZoneType> zones = tgt.getZone();
|
||||||
final boolean mandatory = isMandatory() && hasCandidates;
|
final boolean mandatory = isMandatory() && hasCandidates && !optional;
|
||||||
|
|
||||||
final boolean choiceResult;
|
final boolean choiceResult;
|
||||||
final boolean random = tgt.isRandomTarget();
|
if (tgt.isRandomTarget() && numTargets == null) {
|
||||||
if (random) {
|
|
||||||
final List<GameEntity> candidates = tgt.getAllCandidates(this.ability, true);
|
final List<GameEntity> candidates = tgt.getAllCandidates(this.ability, true);
|
||||||
final GameObject choice = Aggregates.random(candidates);
|
final GameObject choice = Aggregates.random(candidates);
|
||||||
return ability.getTargets().add(choice);
|
return ability.getTargets().add(choice);
|
||||||
@@ -122,7 +125,11 @@ public class TargetSelection {
|
|||||||
return this.chooseCardFromStack(mandatory);
|
return this.chooseCardFromStack(mandatory);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final List<Card> validTargets = CardUtil.getValidCardsToTarget(tgt, ability);
|
List<Card> validTargets = CardUtil.getValidCardsToTarget(tgt, ability);
|
||||||
|
if (filter != null) {
|
||||||
|
validTargets = new CardCollection(Iterables.filter(validTargets, filter));
|
||||||
|
}
|
||||||
|
|
||||||
// single zone
|
// single zone
|
||||||
if (isSingleZone) {
|
if (isSingleZone) {
|
||||||
final List<Card> removeCandidates = new ArrayList<>();
|
final List<Card> removeCandidates = new ArrayList<>();
|
||||||
@@ -149,8 +156,8 @@ public class TargetSelection {
|
|||||||
//if only one valid target card for triggered ability, auto-target that card
|
//if only one valid target card for triggered ability, auto-target that card
|
||||||
//only do this for triggered abilities to prevent auto-targeting when user chooses
|
//only do this for triggered abilities to prevent auto-targeting when user chooses
|
||||||
//to play a spell or activat an ability
|
//to play a spell or activat an ability
|
||||||
if (tgt.isDividedAsYouChoose()) {
|
if (ability.isDividedAsYouChoose()) {
|
||||||
tgt.addDividedAllocation(validTargets.get(0), tgt.getStillToDivide());
|
ability.addDividedAllocation(validTargets.get(0), ability.getStillToDivide());
|
||||||
}
|
}
|
||||||
return ability.getTargets().add(validTargets.get(0));
|
return ability.getTargets().add(validTargets.get(0));
|
||||||
}
|
}
|
||||||
@@ -162,7 +169,7 @@ public class TargetSelection {
|
|||||||
PlayerView playerView = controller.getLocalPlayerView();
|
PlayerView playerView = controller.getLocalPlayerView();
|
||||||
PlayerZoneUpdates playerZoneUpdates = controller.getGui().openZones(playerView, zones, playersWithValidTargets);
|
PlayerZoneUpdates playerZoneUpdates = controller.getGui().openZones(playerView, zones, playersWithValidTargets);
|
||||||
if (!zones.contains(ZoneType.Stack)) {
|
if (!zones.contains(ZoneType.Stack)) {
|
||||||
InputSelectTargets inp = new InputSelectTargets(controller, validTargets, ability, mandatory);
|
InputSelectTargets inp = new InputSelectTargets(controller, validTargets, ability, mandatory, divisionValues, filter);
|
||||||
inp.showAndWait();
|
inp.showAndWait();
|
||||||
choiceResult = !inp.hasCancelled();
|
choiceResult = !inp.hasCancelled();
|
||||||
bTargetingDone = inp.hasPressedOk();
|
bTargetingDone = inp.hasPressedOk();
|
||||||
@@ -174,7 +181,7 @@ public class TargetSelection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// some inputs choose cards one-by-one and need to be called again
|
// some inputs choose cards one-by-one and need to be called again
|
||||||
return choiceResult && chooseTargets(numTargets);
|
return choiceResult && chooseTargets(numTargets, divisionValues, filter, optional);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final boolean chooseCardFromList(final List<Card> choices, final boolean targeted, final boolean mandatory) {
|
private final boolean chooseCardFromList(final List<Card> choices, final boolean targeted, final boolean mandatory) {
|
||||||
@@ -232,7 +239,7 @@ public class TargetSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String msgDone = "[FINISH TARGETING]";
|
final String msgDone = "[FINISH TARGETING]";
|
||||||
if (this.getTgt().isMinTargetsChosen(this.ability.getHostCard(), this.ability)) {
|
if (ability.isMinTargetChosen()) {
|
||||||
// is there a more elegant way of doing this?
|
// is there a more elegant way of doing this?
|
||||||
choicesFiltered.add(msgDone);
|
choicesFiltered.add(msgDone);
|
||||||
}
|
}
|
||||||
@@ -282,12 +289,12 @@ public class TargetSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while(!bTargetingDone) {
|
while(!bTargetingDone) {
|
||||||
if (tgt.isMaxTargetsChosen(this.ability.getHostCard(), this.ability)) {
|
if (ability.isMaxTargetChosen()) {
|
||||||
bTargetingDone = true;
|
bTargetingDone = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectOptions.contains("[FINISH TARGETING]") && tgt.isMinTargetsChosen(this.ability.getHostCard(), this.ability)) {
|
if (!selectOptions.contains("[FINISH TARGETING]") && ability.isMinTargetChosen()) {
|
||||||
selectOptions.add("[FINISH TARGETING]");
|
selectOptions.add("[FINISH TARGETING]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user