mirror of
https://github.com/correl/mage.git
synced 2024-11-15 03:00:16 +00:00
* Kicker abilities - fixed that some cards don't see kicked status of multikicker spells (example: Hallar, the Firefletcher, #4895);
This commit is contained in:
parent
4d362d7edc
commit
0c2c33e940
6 changed files with 99 additions and 17 deletions
|
@ -278,6 +278,39 @@ public class KickerTest extends CardTestPlayerBase {
|
||||||
assertGraveyardCount(playerA, "Sunscape Battlemage", 1);
|
assertGraveyardCount(playerA, "Sunscape Battlemage", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_Conditional_TriggeredAbilityMustSeeMultikickedStatus() {
|
||||||
|
// bug:
|
||||||
|
// Hallar Not Procing Right: When I kick Thornscape Battlemage it doesn't proc Hallar effect for some reason.
|
||||||
|
// I tried this 3 times and it never triggered properly.
|
||||||
|
|
||||||
|
// Kicker {R} and/or {W} (You may pay an additional {R} and/or {W} as you cast this spell.)
|
||||||
|
// When Thornscape Battlemage enters the battlefield, if it was kicked with its {R} kicker, it deals 2 damage to any target.
|
||||||
|
// When Thornscape Battlemage enters the battlefield, if it was kicked with its {W} kicker, destroy target artifact.
|
||||||
|
addCard(Zone.HAND, playerA, "Thornscape Battlemage", 1); // {2}{G}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
|
//
|
||||||
|
// Whenever you cast a spell, if that spell was kicked, put a +1/+1 counter on Hallar, the Firefletcher,
|
||||||
|
// then Hallar deals damage equal to the number of +1/+1 counters on it to each opponent.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Hallar, the Firefletcher", 1);
|
||||||
|
|
||||||
|
// cast kicked spell
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thornscape Battlemage");
|
||||||
|
setChoice(playerA, "Yes"); // use kicker {R} - 2 damage to any target
|
||||||
|
setChoice(playerA, "No"); // not use kicker {W} - destroy target
|
||||||
|
addTarget(playerA, playerB); // target for 2 damage
|
||||||
|
setChoice(playerA, "Yes"); // put counter on hallar
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertCounterCount(playerA, "Hallar, the Firefletcher", CounterType.P1P1, 1);
|
||||||
|
assertLife(playerB, 20 - 2 - 1); // 2 damage from kicked spell, 1 damage from hallar's trigger
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_ZCC_ReturnedPermanentMustNotBeKicked() {
|
public void test_ZCC_ReturnedPermanentMustNotBeKicked() {
|
||||||
// bug:
|
// bug:
|
||||||
|
|
|
@ -1869,6 +1869,20 @@ public class TestPlayer implements Player {
|
||||||
printAbilities(game, computerPlayer.getPlayable(game, true));
|
printAbilities(game, computerPlayer.getPlayable(game, true));
|
||||||
printEnd();
|
printEnd();
|
||||||
}
|
}
|
||||||
|
if (choiceType.equals("choice")) {
|
||||||
|
printStart("Unused choices");
|
||||||
|
if (!choices.isEmpty()) {
|
||||||
|
System.out.println(String.join("\n", choices));
|
||||||
|
}
|
||||||
|
printEnd();
|
||||||
|
}
|
||||||
|
if (choiceType.equals("target")) {
|
||||||
|
printStart("Unused targets");
|
||||||
|
if (targets.isEmpty()) {
|
||||||
|
System.out.println(String.join("\n", targets));
|
||||||
|
}
|
||||||
|
printEnd();
|
||||||
|
}
|
||||||
Assert.fail("Missing " + choiceType.toUpperCase(Locale.ENGLISH) + " def for"
|
Assert.fail("Missing " + choiceType.toUpperCase(Locale.ENGLISH) + " def for"
|
||||||
+ " turn " + game.getTurnNum()
|
+ " turn " + game.getTurnNum()
|
||||||
+ ", step " + (game.getStep() != null ? game.getStep().getType().name() : "not started")
|
+ ", step " + (game.getStep() != null ? game.getStep().getType().name() : "not started")
|
||||||
|
|
|
@ -27,7 +27,7 @@ public enum KickedCondition implements Condition {
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
for (Ability ability : card.getAbilities()) {
|
for (Ability ability : card.getAbilities()) {
|
||||||
if (ability instanceof KickerAbility) {
|
if (ability instanceof KickerAbility) {
|
||||||
if (((KickerAbility) ability).isKicked(game, source, "")) {
|
if (((KickerAbility) ability).isKicked(game, source)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ public enum GetKickerXValue implements DynamicValue {
|
||||||
.stream()
|
.stream()
|
||||||
.anyMatch(varCost -> !((OptionalAdditionalCostImpl) varCost).getVariableCosts().isEmpty());
|
.anyMatch(varCost -> !((OptionalAdditionalCostImpl) varCost).getVariableCosts().isEmpty());
|
||||||
|
|
||||||
|
|
||||||
if (haveVarCost) {
|
if (haveVarCost) {
|
||||||
int kickedCount = ((KickerAbility) ability).getKickedCounter(game, sourceAbility);
|
int kickedCount = ((KickerAbility) ability).getKickedCounter(game, sourceAbility);
|
||||||
if (kickedCount > 0) {
|
if (kickedCount > 0) {
|
||||||
|
|
|
@ -122,27 +122,63 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getKickedCounter(Game game, Ability source) {
|
private int getKickedCounterStrict(Game game, Ability source, String needKickerCost) {
|
||||||
String key = getActivationKey(source, "", game);
|
String key;
|
||||||
return activations.getOrDefault(key, 0);
|
if (needKickerCost.isEmpty()) {
|
||||||
|
// need all kickers
|
||||||
|
key = getActivationKey(source, "", game);
|
||||||
|
} else {
|
||||||
|
// need only cost related kickers
|
||||||
|
key = getActivationKey(source, needKickerCost, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isKicked(Game game, Ability source, String costText) {
|
int totalActivations = 0;
|
||||||
String key = getActivationKey(source, costText, game);
|
|
||||||
if (kickerCosts.size() > 1) {
|
if (kickerCosts.size() > 1) {
|
||||||
for (String activationKey : activations.keySet()) {
|
for (String activationKey : activations.keySet()) {
|
||||||
if (activationKey.startsWith(key)
|
if (activationKey.startsWith(key) && activations.get(activationKey) > 0) {
|
||||||
&& activations.get(activationKey) > 0) {
|
totalActivations += activations.get(activationKey);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (activations.containsKey(key)) {
|
if (activations.containsKey(key) && activations.get(key) > 0) {
|
||||||
return activations.get(key) > 0;
|
totalActivations += activations.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalActivations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return total kicker activations (kicker + multikicker)
|
||||||
|
*
|
||||||
|
* @param game
|
||||||
|
* @param source
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getKickedCounter(Game game, Ability source) {
|
||||||
|
return getKickedCounterStrict(game, source, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If spell was kicked
|
||||||
|
*
|
||||||
|
* @param game
|
||||||
|
* @param source
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isKicked(Game game, Ability source) {
|
||||||
|
return isKicked(game, source, "");
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
/**
|
||||||
|
* If spell was kicked by specific kicker cost
|
||||||
|
*
|
||||||
|
* @param game
|
||||||
|
* @param source
|
||||||
|
* @param needKickerCost use cost.getText(true)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isKicked(Game game, Ability source, String needKickerCost) {
|
||||||
|
return getKickedCounterStrict(game, source, needKickerCost) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OptionalAdditionalCost> getKickerCosts() {
|
public List<OptionalAdditionalCost> getKickerCosts() {
|
||||||
|
@ -196,7 +232,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
||||||
while (player.canRespond() && again) {
|
while (player.canRespond() && again) {
|
||||||
String times = "";
|
String times = "";
|
||||||
if (kickerCost.isRepeatable()) {
|
if (kickerCost.isRepeatable()) {
|
||||||
int activatedCount = getKickedCounter(game, ability);
|
int activatedCount = getKickedCounterStrict(game, ability, kickerCost.getText(true));
|
||||||
times = (activatedCount + 1) + (activatedCount == 0 ? " time " : " times ");
|
times = (activatedCount + 1) + (activatedCount == 0 ? " time " : " times ");
|
||||||
}
|
}
|
||||||
// TODO: add AI support to find max number of possible activations (from available mana)
|
// TODO: add AI support to find max number of possible activations (from available mana)
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class KickerWithAnyNumberModesAbility extends KickerAbility implements Op
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changeModes(Ability ability, Game game) {
|
public void changeModes(Ability ability, Game game) {
|
||||||
if (!isKicked(game, ability, "")) {
|
if (!isKicked(game, ability)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue