diff --git a/Mage.Sets/src/mage/cards/c/CallToArms.java b/Mage.Sets/src/mage/cards/c/CallToArms.java index a2c4ccb20e9..2b0bd85b998 100644 --- a/Mage.Sets/src/mage/cards/c/CallToArms.java +++ b/Mage.Sets/src/mage/cards/c/CallToArms.java @@ -1,6 +1,5 @@ package mage.cards.c; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.StateTriggeredAbility; @@ -8,8 +7,7 @@ import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.common.MostCommonColorCondition; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.Effect; +import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.ChooseColorEffect; import mage.abilities.effects.common.ChooseOpponentEffect; import mage.abilities.effects.common.SacrificeSourceEffect; @@ -18,40 +16,45 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Layer; import mage.constants.Outcome; -import mage.constants.SubLayer; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.filter.predicate.permanent.TokenPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class CallToArms extends CardImpl { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(new ColorPredicate(ObjectColor.WHITE)); + } + public CallToArms(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); // As Call to Arms enters the battlefield, choose a color and an opponent. - Ability ability = new AsEntersBattlefieldAbility( - new ChooseColorEffect(Outcome.Detriment) - ); - ability.addEffect(new ChooseOpponentEffect( - Outcome.Benefit - ).setText("and an opponent")); + Ability ability = new AsEntersBattlefieldAbility(new ChooseColorEffect(Outcome.Detriment)); + ability.addEffect(new ChooseOpponentEffect(Outcome.Benefit).setText("and an opponent")); this.addAbility(ability); // White creatures get +1/+1 as long as the chosen color is the most common color among nontoken permanents the chosen player controls but isn't tied for most common. - this.addAbility(new SimpleStaticAbility( - new CallToArmsEffect() - )); + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostAllEffect(1, 1, Duration.WhileOnBattlefield, filter, false), + CallToArmsCondition.instance, "white creatures get +1/+1 as long as the chosen color " + + "is the most common color among nontoken permanents the chosen player controls but isn't tied for most common" + ))); // When the chosen color isn't the most common color among nontoken permanents the chosen player controls or is tied for most common, sacrifice Call to Arms. this.addAbility(new CallToArmsStateTriggeredAbility()); @@ -67,46 +70,17 @@ public final class CallToArms extends CardImpl { } } -class CallToArmsEffect extends ContinuousEffectImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("White creatures"); - - static { - filter.add(new ColorPredicate(ObjectColor.WHITE)); - } - - public CallToArmsEffect() { - super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.ModifyPT_7c, Outcome.Benefit); - staticText = "White creatures get +1/+1 as long as the chosen color " - + "is the most common color among nontoken permanents " - + "the chosen player controls but isn't tied for most common."; - } - - private CallToArmsEffect(final CallToArmsEffect effect) { - super(effect); - } - - @Override - public CallToArmsEffect copy() { - return new CallToArmsEffect(this); - } +enum CallToArmsCondition implements Condition { + instance; @Override public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(source.getSourceId()); - UUID playerId = (UUID) game.getState().getValue(source.getSourceId() + ChooseOpponentEffect.VALUE_KEY); - if (permanent != null) { - Player opponent = game.getPlayer(playerId); - if (opponent != null) { - ObjectColor color = (ObjectColor) game.getState().getValue(permanent.getId() + "_color"); - Condition condition = new MostCommonColorCondition(color, true, new ControllerIdPredicate(playerId)); - if (condition.apply(game, source)) { - Effect effect = new BoostAllEffect(1, 1, Duration.WhileOnBattlefield, filter, false); - return effect.apply(game, source); - } - } - } - return false; + Player opponent = game.getPlayer((UUID) game.getState().getValue(source.getSourceId() + ChooseOpponentEffect.VALUE_KEY)); + return permanent != null && opponent != null && new MostCommonColorCondition( + (ObjectColor) game.getState().getValue(permanent.getId() + "_color"), true, + Predicates.and(TokenPredicate.FALSE, new ControllerIdPredicate(opponent.getId())) + ).apply(game, source); } } @@ -129,17 +103,6 @@ class CallToArmsStateTriggeredAbility extends StateTriggeredAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { - Permanent permanent = game.getPermanent(getSourceId()); - UUID playerId = (UUID) game.getState().getValue(getSourceId() + ChooseOpponentEffect.VALUE_KEY); - if (permanent != null) { - Player opponent = game.getPlayer(playerId); - if (opponent != null) { - ObjectColor color = (ObjectColor) game.getState().getValue(permanent.getId() + "_color"); - Condition condition = new MostCommonColorCondition(color, true, new ControllerIdPredicate(playerId)); - return !condition.apply(game, this); - } - } - return false; + return !CallToArmsCondition.instance.apply(game, this); } - } diff --git a/Mage.Sets/src/mage/cards/s/SwordOfDungeonsAndDragons.java b/Mage.Sets/src/mage/cards/s/SwordOfDungeonsAndDragons.java index 52ceb304a95..076482c618c 100644 --- a/Mage.Sets/src/mage/cards/s/SwordOfDungeonsAndDragons.java +++ b/Mage.Sets/src/mage/cards/s/SwordOfDungeonsAndDragons.java @@ -1,15 +1,9 @@ - - package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealsDamageToAPlayerAttachedTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.keyword.EquipAbility; @@ -18,50 +12,51 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.AttachmentType; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.permanent.Permanent; import mage.game.permanent.token.DragonTokenGold; import mage.players.Player; -import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** + * This creates a "gold" token which is represented as a creature with all colors + * as the color gold is not supported in the black-bordered game rules * * @author Saga */ public final class SwordOfDungeonsAndDragons extends CardImpl { - + private static final FilterCard filter = new FilterCard("Rogues and from Clerics"); - static {filter.add(Predicates.or( - SubType.ROGUE.getPredicate(), - SubType.CLERIC.getPredicate() - )); + + static { + filter.add(Predicates.or( + SubType.ROGUE.getPredicate(), + SubType.CLERIC.getPredicate() + )); } public SwordOfDungeonsAndDragons(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); this.subtype.add(SubType.EQUIPMENT); // Equipped creature gets +2/+2 and has protection from Rogues and from Clerics. Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2)); - Effect effect = new GainAbilityAttachedEffect(new ProtectionAbility(filter), AttachmentType.EQUIPMENT); - effect.setText("and has protection from Rogues and from Clerics"); - ability.addEffect(effect); + ability.addEffect(new GainAbilityAttachedEffect( + new ProtectionAbility(filter), AttachmentType.EQUIPMENT + ).setText("and has protection from Rogues and from Clerics")); this.addAbility(ability); // Whenever equipped creature deals combat damage to a player, you create a 4/4 gold Dragon creature token with flying and roll a d20. If you roll a 20, repeat this process. - this.addAbility(new SwordOfDungeonsAndDragonsAbility()); + this.addAbility(new DealsDamageToAPlayerAttachedTriggeredAbility( + new SwordOfDungeonsAndDragonsEffect(), "equipped creature", false + )); // Equip {2} - this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(2), new TargetControlledCreaturePermanent(), false)); + this.addAbility(new EquipAbility(2)); } private SwordOfDungeonsAndDragons(final SwordOfDungeonsAndDragons card) { @@ -74,49 +69,12 @@ public final class SwordOfDungeonsAndDragons extends CardImpl { } } -class SwordOfDungeonsAndDragonsAbility extends TriggeredAbilityImpl { - - public SwordOfDungeonsAndDragonsAbility() { - super(Zone.BATTLEFIELD, new SwordOfDungeonsAndDragonsEffect(),false); - } - - private SwordOfDungeonsAndDragonsAbility(final SwordOfDungeonsAndDragonsAbility ability) { - super(ability); - } - - @Override - public SwordOfDungeonsAndDragonsAbility copy() { - return new SwordOfDungeonsAndDragonsAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - DamagedPlayerEvent damageEvent = (DamagedPlayerEvent)event; - Permanent p = game.getPermanent(event.getSourceId()); - if (damageEvent.isCombatDamage() && p != null && p.getAttachments().contains(this.getSourceId())) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getPlayerId())); - } - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever equipped creature deals combat damage to a player, you create a 4/4 gold Dragon creature token with flying and roll a d20. If you roll a 20, repeat this process."; - } -} - class SwordOfDungeonsAndDragonsEffect extends OneShotEffect { - - public SwordOfDungeonsAndDragonsEffect() { + + SwordOfDungeonsAndDragonsEffect() { super(Outcome.Benefit); + staticText = "you create a 4/4 gold Dragon creature token with flying " + + "and roll a d20. If you roll a 20, repeat this process"; } private SwordOfDungeonsAndDragonsEffect(final SwordOfDungeonsAndDragonsEffect effect) { @@ -131,16 +89,15 @@ class SwordOfDungeonsAndDragonsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int count = 1; - int amount = controller.rollDice(outcome, source, game, 20); - - while (amount == 20) { - count += 1; - amount = controller.rollDice(outcome, source, game, 20); - } - return new CreateTokenEffect(new DragonTokenGold(), count).apply(game, source); + if (controller == null) { + return false; } - return false; + for (int i = 0; i < 1000; i++) { // just in case loop goes too long + new DragonTokenGold().putOntoBattlefield(1, game, source); + if (controller.rollDice(outcome, source, game, 20) != 20) { + break; + } + } + return true; } } diff --git a/Mage/src/main/java/mage/ObjectColor.java b/Mage/src/main/java/mage/ObjectColor.java index e85e739ca24..b4caa2d6a13 100644 --- a/Mage/src/main/java/mage/ObjectColor.java +++ b/Mage/src/main/java/mage/ObjectColor.java @@ -5,10 +5,7 @@ import mage.constants.ColoredManaSymbol; import mage.util.Copyable; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.util.*; public class ObjectColor implements Serializable, Copyable, Comparable { @@ -19,17 +16,13 @@ public class ObjectColor implements Serializable, Copyable, Compara public static final ObjectColor GREEN = new ObjectColor("G"); public static final ObjectColor COLORLESS = new ObjectColor(); - - public static final ObjectColor GOLD = new ObjectColor("O"); // Not multicolored - Sword of Dungeons & Dragons TODO: remove this, it shouldn't have been added - +private static final ListallColors= Arrays.asList(WHITE,BLUE,BLACK,RED,GREEN); private boolean white; private boolean blue; private boolean black; private boolean red; private boolean green; - private boolean gold; - public ObjectColor() { } @@ -51,10 +44,6 @@ public class ObjectColor implements Serializable, Copyable, Compara case 'G': green = true; break; - - case 'O': - gold = true; - break; } } } @@ -65,8 +54,6 @@ public class ObjectColor implements Serializable, Copyable, Compara black = color.black; red = color.red; green = color.green; - - gold = color.gold; } /** @@ -83,8 +70,6 @@ public class ObjectColor implements Serializable, Copyable, Compara newColor.black = black || other.black; newColor.red = red || other.red; newColor.green = green || other.green; - - newColor.gold = gold || other.gold; return newColor; } @@ -102,8 +87,6 @@ public class ObjectColor implements Serializable, Copyable, Compara newColor.black = black && other.black; newColor.red = red && other.red; newColor.green = green && other.green; - - newColor.gold = gold && other.gold; return newColor; } @@ -124,10 +107,6 @@ public class ObjectColor implements Serializable, Copyable, Compara if (red) { count++; } - - if (gold) { - count++; - } return count; } @@ -176,10 +155,6 @@ public class ObjectColor implements Serializable, Copyable, Compara if (colors.size() >= 2 && secondColor - firstColor >= 3) { Collections.swap(colors, 0, 1); } - - if (this.isGold()) { - colors.add(ObjectColor.GOLD); - } return colors; } @@ -189,8 +164,6 @@ public class ObjectColor implements Serializable, Copyable, Compara this.setGreen(color != null && color.isGreen()); this.setRed(color != null && color.isRed()); this.setWhite(color != null && color.isWhite()); - - this.setGold(color != null && color.isGold()); } public void addColor(ObjectColor color) { @@ -209,10 +182,6 @@ public class ObjectColor implements Serializable, Copyable, Compara if (color.isGreen()) { setGreen(true); } - - if (color.isGold()) { - setGold(true); - } } public boolean isColorless() { @@ -220,32 +189,26 @@ public class ObjectColor implements Serializable, Copyable, Compara } public boolean hasColor() { - return white || blue || black || red || green - || gold; + return white || blue || black || red || green; } public boolean isMulticolored() { if (isColorless()) { return false; } - if (white && (blue || black || red || green - || gold)) { - return true; + if (white) { + return blue || black || red || green; } - if (blue && (black || red || green - || gold)) { - return true; + if (blue) { + return black || red || green; } - if (black && (red || green - || gold)) { - return true; + if (black) { + return red || green; } - if (red && (green - || gold)) { - return true; + if (red) { + return green; } - return green - && gold; + return false; } public boolean isWhite() { @@ -288,14 +251,6 @@ public class ObjectColor implements Serializable, Copyable, Compara this.green = green; } - public boolean isGold() { - return gold; - } - - public void setGold(boolean gold) { - this.gold = gold; - } - @Override public String toString() { StringBuilder sb = new StringBuilder(5); @@ -314,36 +269,27 @@ public class ObjectColor implements Serializable, Copyable, Compara if (green) { sb.append('G'); } - - if (gold) { - sb.append('O'); - } return sb.toString(); } public String getDescription() { - if (getColorCount() > 1) { + if (isMulticolored()) { return "multicolored"; - } else { - if (white) { - return "white"; - } - if (blue) { - return "blue"; - } - if (black) { - return "black"; - } - if (red) { - return "red"; - } - if (green) { - return "green"; - } - - if (gold) { - return "gold"; - } + } + if (white) { + return "white"; + } + if (blue) { + return "blue"; + } + if (black) { + return "black"; + } + if (red) { + return "red"; + } + if (green) { + return "green"; } return "colorless"; } @@ -372,10 +318,6 @@ public class ObjectColor implements Serializable, Copyable, Compara if (test.green != this.green) { return false; } - if (test.gold != this.gold) { - return false; - } - return true; } @@ -387,8 +329,6 @@ public class ObjectColor implements Serializable, Copyable, Compara hash = 23 * hash + (this.black ? 1 : 0); hash = 23 * hash + (this.red ? 1 : 0); hash = 23 * hash + (this.green ? 1 : 0); - - hash = 23 * hash + (this.gold ? 1 : 0); return hash; } @@ -411,10 +351,6 @@ public class ObjectColor implements Serializable, Copyable, Compara if (color.green && this.green) { return true; } - - if (color.gold && this.gold) { - return true; - } return false; } @@ -422,8 +358,7 @@ public class ObjectColor implements Serializable, Copyable, Compara // 105.4. [...] “Multicolored” is not a color. Neither is “colorless.” return !color.isColorless() && (color.white && white || color.blue && blue || color.black && black - || color.red && red || color.green && green - || color.gold && gold); + || color.red && red || color.green && green); } @Override @@ -450,8 +385,6 @@ public class ObjectColor implements Serializable, Copyable, Compara o1 = 4; } else if (this.isWhite()) { o1 = 5; - } else if (this.isGold()) { - o1 = 6; } if (o.isMulticolored()) { @@ -468,8 +401,6 @@ public class ObjectColor implements Serializable, Copyable, Compara o2 = 4; } else if (o.isWhite()) { o2 = 5; - } else if (o.isGold()) { - o2 = 6; } return o1 - o2; @@ -482,7 +413,6 @@ public class ObjectColor implements Serializable, Copyable, Compara * @return null or */ public ColoredManaSymbol getOneColoredManaSymbol() { - if (isMulticolored()) { throw new IllegalStateException("Found multicolor object, but was waiting for simple color."); } @@ -503,19 +433,10 @@ public class ObjectColor implements Serializable, Copyable, Compara return ColoredManaSymbol.W; } - if (isGold()) { - return ColoredManaSymbol.O; - } return null; } public static List getAllColors() { - List colors = new ArrayList<>(); - colors.add(ObjectColor.WHITE); - colors.add(ObjectColor.BLUE); - colors.add(ObjectColor.BLACK); - colors.add(ObjectColor.RED); - colors.add(ObjectColor.GREEN); - return colors; + return allColors; } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/MostCommonColorCondition.java b/Mage/src/main/java/mage/abilities/condition/common/MostCommonColorCondition.java index 3992edf70d1..98eb08a6c12 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/MostCommonColorCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/MostCommonColorCondition.java @@ -4,72 +4,65 @@ import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.condition.Condition; import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicate; -import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; /** - * * @author TheElk801 */ public class MostCommonColorCondition implements Condition { protected final ObjectColor compareColor; protected final boolean isMono; - protected final Predicate predicate; + protected final FilterPermanent filter; public MostCommonColorCondition(ObjectColor color) { this(color, false, null); } - //Use this one if you don't want a tie for most common and want to restrict to a player (literally only Call to Arms) + // Use this one if you don't want a tie for most common and want to restrict to a player (literally only Call to Arms) public MostCommonColorCondition(ObjectColor color, boolean isMono, Predicate predicate) { this.compareColor = color; this.isMono = isMono; - this.predicate = predicate; + if (predicate == null) { + this.filter = StaticFilters.FILTER_PERMANENT; + } else { + this.filter = new FilterPermanent(); + this.filter.add(predicate); + } } @Override public boolean apply(Game game, Ability source) { - FilterPermanent[] colorFilters = new FilterPermanent[6]; - int i = 0; - for (ObjectColor color : ObjectColor.getAllColors()) { - colorFilters[i] = new FilterPermanent(); - colorFilters[i].add(new ColorPredicate(color)); - if (predicate != null) { - colorFilters[i].add(predicate); - } - i++; - } - int[] colorCounts = new int[6]; - i = 0; - for (ObjectColor color : ObjectColor.getAllColors()) { - colorFilters[i].add(new ColorPredicate(color)); - colorCounts[i] = game.getBattlefield().count(colorFilters[i], source.getControllerId(), source, game); - i++; - } - int max = 0; - for (i = 0; i < 5; i++) { - if (colorCounts[i] > max) { - max = colorCounts[i] * 1; + Map colorMap = new HashMap<>(); + for (Permanent permanent : game + .getBattlefield() + .getActivePermanents(filter, source.getControllerId(), source, game)) { + for (char c : permanent.getColor(game).toString().toCharArray()) { + colorMap.compute("" + c, CardUtil::setOrIncrementValue); } } - i = 0; - ObjectColor commonest = new ObjectColor(); - for (ObjectColor color : ObjectColor.getAllColors()) { - if (colorCounts[i] == max) { - commonest.addColor(color); - } - i++; - } - if (compareColor.shares(commonest)) { - if (isMono) { - return !commonest.isMulticolored(); - } else { - return true; - } - } - return false; + int most = colorMap + .values() + .stream() + .mapToInt(x -> x) + .max() + .orElse(0); + ObjectColor common = new ObjectColor( + colorMap.entrySet() + .stream() + .filter(e -> e.getValue() == most) + .map(Map.Entry::getKey) + .collect(Collectors.joining()) + ); + return common.shares(compareColor) && (!isMono || common.getColorCount() == 1); } @Override diff --git a/Mage/src/main/java/mage/game/permanent/token/DragonTokenGold.java b/Mage/src/main/java/mage/game/permanent/token/DragonTokenGold.java index dce23cdf7d5..fbc243bebb6 100644 --- a/Mage/src/main/java/mage/game/permanent/token/DragonTokenGold.java +++ b/Mage/src/main/java/mage/game/permanent/token/DragonTokenGold.java @@ -6,6 +6,8 @@ import mage.constants.CardType; import mage.constants.SubType; /** + * This token is supposed to be "gold" but the game rules don't support gold as a color in black border + * * @author Saga */ public final class DragonTokenGold extends TokenImpl { @@ -13,7 +15,11 @@ public final class DragonTokenGold extends TokenImpl { public DragonTokenGold() { super("Dragon Token", "4/4 gold Dragon creature token with flying"); cardType.add(CardType.CREATURE); - color.setGold(true); + color.setWhite(true); + color.setBlue(true); + color.setBlack(true); + color.setRed(true); + color.setGreen(true); subtype.add(SubType.DRAGON); power = new MageInt(4); toughness = new MageInt(4); @@ -27,4 +33,4 @@ public final class DragonTokenGold extends TokenImpl { public DragonTokenGold copy() { return new DragonTokenGold(this); } -} \ No newline at end of file +}