From 4dd34902c145e1148fc48d1fa022a8e8fed2b0af Mon Sep 17 00:00:00 2001 From: Christiaan Date: Thu, 29 Mar 2018 14:14:36 +0200 Subject: [PATCH 01/21] Fixed card count in readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 131eda1e52b..afb6bf7edec 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ [![Join the chat at https://gitter.im/magefree/mage](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magefree/mage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/magefree/mage.svg?branch=master)](https://travis-ci.org/magefree/mage) -XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **16950** unique cards (over 328000 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented. +XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **16950** unique cards (over 32800 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented. There are public servers where you can play XMage against other players. You can also host your own server to play against the AI and/or your friends. From fc065bbe3cafc9a851c4dd862f8271772f85ec9c Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 29 Mar 2018 17:55:22 +0400 Subject: [PATCH 02/21] Little fixes --- Mage.Sets/src/mage/cards/n/NaturesWill.java | 1 + Mage.Sets/src/mage/sets/MastersEdition.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/n/NaturesWill.java b/Mage.Sets/src/mage/cards/n/NaturesWill.java index c6a2e8bbc06..491f83febf4 100644 --- a/Mage.Sets/src/mage/cards/n/NaturesWill.java +++ b/Mage.Sets/src/mage/cards/n/NaturesWill.java @@ -93,6 +93,7 @@ class NaturesWillEffect extends OneShotEffect { land.untap(game); } } + return true; } return false; } diff --git a/Mage.Sets/src/mage/sets/MastersEdition.java b/Mage.Sets/src/mage/sets/MastersEdition.java index cdf94aba0e5..8eafe3bc3c6 100644 --- a/Mage.Sets/src/mage/sets/MastersEdition.java +++ b/Mage.Sets/src/mage/sets/MastersEdition.java @@ -206,7 +206,7 @@ public class MastersEdition extends ExpansionSet { cards.add(new SetCardInfo("Rabid Wombat", 126, Rarity.UNCOMMON, mage.cards.r.RabidWombat.class)); cards.add(new SetCardInfo("Rainbow Vale", 179, Rarity.RARE, mage.cards.r.RainbowVale.class)); cards.add(new SetCardInfo("Righteous Avengers", 25, Rarity.COMMON, mage.cards.r.RighteousAvengers.class)); - cards.add(new SetCardInfo("Ring of Ma'rûf", 163, Rarity.RARE, mage.cards.r.RingOfMaruf.class)); + cards.add(new SetCardInfo("Ring of Ma'ruf", 163, Rarity.RARE, mage.cards.r.RingOfMaruf.class)); cards.add(new SetCardInfo("River Merfolk", 47, Rarity.COMMON, mage.cards.r.RiverMerfolk.class)); cards.add(new SetCardInfo("Roots", 127, Rarity.COMMON, mage.cards.r.Roots.class)); cards.add(new SetCardInfo("Scryb Sprites", 128, Rarity.COMMON, mage.cards.s.ScrybSprites.class)); From 6c98f4802eedca585176b3330bd925e5e1630717 Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 31 Mar 2018 00:06:44 +0000 Subject: [PATCH 03/21] Fix for Cabal Slaver Cabal Slaver's ability caused the damaging goblin's controller to discard --- .../DealsDamageToAPlayerAllTriggeredAbility.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java index 4dcca733cda..aa57b85dc1c 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToAPlayerAllTriggeredAbility.java @@ -47,12 +47,18 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp private final FilterPermanent filter; private final SetTargetPointer setTargetPointer; private final boolean onlyCombat; + private final boolean affectsDefendingPlayer; public DealsDamageToAPlayerAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyCombat) { + this(effect, filter, optional, setTargetPointer, onlyCombat, false); + } + + public DealsDamageToAPlayerAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyCombat, boolean affectsDefendingPlayer) { super(Zone.BATTLEFIELD, effect, optional); this.setTargetPointer = setTargetPointer; this.filter = filter; this.onlyCombat = onlyCombat; + this.affectsDefendingPlayer = affectsDefendingPlayer; } public DealsDamageToAPlayerAllTriggeredAbility(final DealsDamageToAPlayerAllTriggeredAbility ability) { @@ -60,6 +66,7 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp this.setTargetPointer = ability.setTargetPointer; this.filter = ability.filter; this.onlyCombat = ability.onlyCombat; + this.affectsDefendingPlayer = ability.affectsDefendingPlayer; } @Override @@ -81,6 +88,10 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp for (Effect effect : this.getEffects()) { effect.setValue("damage", event.getAmount()); effect.setValue("sourceId", event.getSourceId()); + if (affectsDefendingPlayer) { + effect.setTargetPointer(new FixedTarget(event.getTargetId())); + continue; + } switch (setTargetPointer) { case PLAYER: effect.setTargetPointer(new FixedTarget(permanent.getControllerId())); From 1dff6d9c486e1b31e9a80385f5d0ef2346f0805d Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 31 Mar 2018 00:07:36 +0000 Subject: [PATCH 04/21] Fix for Cabal Slaver Cabal Slaver's ability caused the damaging goblin's controller to discard --- Mage.Sets/src/mage/cards/c/CabalSlaver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/c/CabalSlaver.java b/Mage.Sets/src/mage/cards/c/CabalSlaver.java index 3cbe12b1e67..8958f5e40cb 100644 --- a/Mage.Sets/src/mage/cards/c/CabalSlaver.java +++ b/Mage.Sets/src/mage/cards/c/CabalSlaver.java @@ -59,7 +59,7 @@ public class CabalSlaver extends CardImpl { this.toughness = new MageInt(1); // Whenever a Goblin deals combat damage to a player, that player discards a card. - this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility(new DiscardTargetEffect(1), filter, false, SetTargetPointer.PLAYER, true)); + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility(new DiscardTargetEffect(1), filter, false, SetTargetPointer.NONE, true, true)); } public CabalSlaver(final CabalSlaver card) { From b69f2c4fd820ddd9a2c61123b674944ecd385ec9 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sat, 31 Mar 2018 17:55:55 +1100 Subject: [PATCH 05/21] Add 'Choose Matching' to deck editor --- .../java/mage/client/cards/DragCardGrid.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java index c2435410ac9..258892ee88f 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -1083,6 +1083,22 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } repaint(); } + + private void chooseMatching() { + Collection toMatch = dragCardList(); + + for (DragCardGridListener l : listeners) { + for (CardView card : allCards) { + for (CardView aMatch : toMatch) { + if (card.getName().equals(aMatch.getName())) { + card.setSelected(true); + cardViews.get(card.getId()).update(card); + } + } + } + } + repaint(); + } private void showAll() { for (DragCardGridListener l : listeners) { @@ -1704,6 +1720,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg JMenuItem invertSelection = new JMenuItem("Invert Selection"); invertSelection.addActionListener(e2 -> invertSelection()); menu.add(invertSelection); + + JMenuItem chooseMatching = new JMenuItem("Choose Matching"); + chooseMatching.addActionListener(e2 -> chooseMatching()); + menu.add(chooseMatching); // Show 'Duplicate Selection' for FREE_BUILDING if (this.mode == Constants.DeckEditorMode.FREE_BUILDING) { From 7bbfe9f2ae6a91a2874114eb6d3e7375218bb919 Mon Sep 17 00:00:00 2001 From: L_J Date: Sat, 31 Mar 2018 13:41:38 +0000 Subject: [PATCH 06/21] Growth Spurt text fix --- Mage.Sets/src/mage/cards/g/GrowthSpurt.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/g/GrowthSpurt.java b/Mage.Sets/src/mage/cards/g/GrowthSpurt.java index 1e27dd000e6..1e092f5e751 100644 --- a/Mage.Sets/src/mage/cards/g/GrowthSpurt.java +++ b/Mage.Sets/src/mage/cards/g/GrowthSpurt.java @@ -71,7 +71,7 @@ public class GrowthSpurt extends CardImpl { class GrowthSpurtEffect extends OneShotEffect { GrowthSpurtEffect() { super(Outcome.BoostCreature); - this.staticText = "todo"; //TODO + this.staticText = "Roll a six-sided die. Target creature gets +X/+X until end of turn, where X is the result"; } GrowthSpurtEffect(final GrowthSpurtEffect effect) { From 936bab33030ee11405fd74bca08f44d6aaa3a6c6 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Sun, 1 Apr 2018 00:43:45 -0400 Subject: [PATCH 07/21] Add missing mountain to Commander 2016 Entropic Uprising deck Fixes issue #4694 --- .../Commander/Commander 2016/Entropic Uprising (UBRG).dck | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Client/release/sample-decks/Commander/Commander 2016/Entropic Uprising (UBRG).dck b/Mage.Client/release/sample-decks/Commander/Commander 2016/Entropic Uprising (UBRG).dck index e2ebfff4137..fb309083023 100644 --- a/Mage.Client/release/sample-decks/Commander/Commander 2016/Entropic Uprising (UBRG).dck +++ b/Mage.Client/release/sample-decks/Commander/Commander 2016/Entropic Uprising (UBRG).dck @@ -80,7 +80,7 @@ 1 [C16:104] Windfall 1 [C16:148] Far Wanderings 1 [C16:46] Thrasios, Triton Hero -1 [C16:346] Mountain +2 [C16:346] Mountain 2 [C16:347] Mountain 1 [C16:348] Mountain 1 [C16:105] Army of the Damned From 20f03cfcb136dde89db4330e8dcfb344ab31f379 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 1 Apr 2018 12:33:59 +0200 Subject: [PATCH 08/21] Some minor changes to Momir code. --- .../sample-decks/Momir Basic/Momir Basic.dck | 31 +++++-------------- .../src/mage/game/MomirDuel.java | 17 ---------- .../src/mage/game/MomirFreeForAllType.java | 4 +-- 3 files changed, 10 insertions(+), 42 deletions(-) diff --git a/Mage.Client/release/sample-decks/Momir Basic/Momir Basic.dck b/Mage.Client/release/sample-decks/Momir Basic/Momir Basic.dck index 1295622aa28..7720b6c3a94 100644 --- a/Mage.Client/release/sample-decks/Momir Basic/Momir Basic.dck +++ b/Mage.Client/release/sample-decks/Momir Basic/Momir Basic.dck @@ -1,23 +1,8 @@ -NAME:Mormir Basic -3 [BFZ:259] Island -3 [BFZ:261] Swamp -3 [BFZ:250] Plains -3 [BFZ:272] Forest -3 [BFZ:260] Swamp -3 [BFZ:271] Forest -3 [BFZ:270] Forest -3 [BFZ:265] Mountain -2 [BFZ:254] Plains -3 [BFZ:264] Swamp -3 [BFZ:274] Forest -1 [BFZ:252] Plains -3 [BFZ:262] Swamp -3 [BFZ:251] Plains -2 [BFZ:273] Forest -3 [BFZ:258] Island -2 [BFZ:269] Mountain -3 [BFZ:268] Mountain -3 [BFZ:257] Island -3 [BFZ:267] Mountain -3 [BFZ:266] Mountain -2 [BFZ:255] Island +NAME:Mormir Basic +12 [BFZ:250a] Plains +12 [BFZ:260a] Swamp +12 [BFZ:270a] Forest +12 [BFZ:265a] Mountain +12 [BFZ:255a] Island +LAYOUT MAIN:(1,5)(COLOR_IDENTITY,true,5)|([BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a],[BFZ:270a])([BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a],[BFZ:265a])([BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a],[BFZ:260a])([BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a],[BFZ:255a])([BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a],[BFZ:250a]) +LAYOUT SIDEBOARD:(0,0)(COLOR,true,5)| diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java index a5f1ce81cf9..37ed63aa184 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/src/mage/game/MomirDuel.java @@ -28,38 +28,21 @@ package mage.game; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.common.DiscardCardCost; -import mage.abilities.costs.mana.VariableManaCost; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.InfoEffect; -import mage.cards.Card; -import mage.cards.ExpansionSet; -import mage.cards.Sets; -import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; -import mage.constants.CardType; import mage.constants.MultiplayerAttackOption; -import mage.constants.Outcome; import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; -import mage.constants.SetType; -import mage.constants.TimingRule; import mage.constants.Zone; -import mage.game.command.Emblem; import mage.game.command.emblems.MomirEmblem; import mage.game.match.MatchType; -import mage.game.permanent.token.EmptyToken; import mage.game.turn.TurnMod; import mage.players.Player; -import mage.util.CardUtil; -import mage.util.RandomUtil; /** * diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllType.java b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllType.java index 5eb6c301702..21f01c65f9d 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllType.java +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/src/mage/game/MomirFreeForAllType.java @@ -38,14 +38,14 @@ public class MomirFreeForAllType extends MatchType { public MomirFreeForAllType() { this.name = "Momir Basic Free For All"; this.maxPlayers = 10; - this.minPlayers = 2; + this.minPlayers = 3; this.numTeams = 0; this.useAttackOption = true; this.useRange = true; this.sideboardingAllowed = false; } - protected MomirFreeForAllType(final MomirFreeForAllType matchType){ + protected MomirFreeForAllType(final MomirFreeForAllType matchType) { super(matchType); } From 2ff3d2ca872648e62bdecb9fd591007bcba5b582 Mon Sep 17 00:00:00 2001 From: spjspj Date: Sun, 1 Apr 2018 21:40:16 +1000 Subject: [PATCH 09/21] Add an on-hover highlight effect. --- .../mage/client/components/HoverButton.java | 95 ++++++++++++++++++- .../java/mage/client/game/PlayerPanelExt.java | 72 +------------- 2 files changed, 96 insertions(+), 71 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/HoverButton.java b/Mage.Client/src/main/java/mage/client/components/HoverButton.java index 863cb49109c..6930e9a9643 100644 --- a/Mage.Client/src/main/java/mage/client/components/HoverButton.java +++ b/Mage.Client/src/main/java/mage/client/components/HoverButton.java @@ -8,10 +8,13 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.font.FontRenderContext; import javax.swing.JPanel; +import javax.swing.Timer; import mage.client.util.Command; /** @@ -43,6 +46,7 @@ public class HoverButton extends JPanel implements MouseListener { private Image topTextImageRight; private String centerText; + private boolean wasHovered = false; private boolean isHovered = false; private boolean isSelected = false; private boolean drawSet = false; @@ -52,7 +56,8 @@ public class HoverButton extends JPanel implements MouseListener { private Command onHover = null; private Color textColor = Color.white; private final Rectangle centerTextArea = new Rectangle(5, 18, 75, 40); - private Color centerTextColor = new Color(200, 190, 0, 180); + private Color centerTextColor = new Color(200, 210, 0, 180); + private Color origCenterTextColor = new Color(200, 210, 0, 180); private final Color textBGColor = Color.black; static final Font textFont = new Font("Arial", Font.PLAIN, 12); @@ -64,6 +69,13 @@ public class HoverButton extends JPanel implements MouseListener { private boolean alignTextLeft = false; + Timer faderGainLife = null; + Timer faderLoseLife = null; + private int loseX = 0; + private int gainX = 0; + private boolean doLoseFade = true; + private boolean doGainFade = true; + public HoverButton(String text, Image image, Rectangle size) { this(text, image, image, null, image, size); if (image == null) { @@ -95,6 +107,10 @@ public class HoverButton extends JPanel implements MouseListener { Graphics2D g2d = (Graphics2D) g; if (isEnabled()) { if (isHovered || textAlwaysVisible) { + if (isHovered) { + wasHovered = true; + setCenterColor(Color.YELLOW); + } g.drawImage(hoverImage, 0, 0, imageSize.width, imageSize.height, this); if (text != null) { if (textColor != null) { @@ -109,6 +125,10 @@ public class HoverButton extends JPanel implements MouseListener { g2d.drawString(text, textOffsetX, textOffsetY); } } else { + if (wasHovered) { + wasHovered = false; + setCenterColor(origCenterTextColor); + } g.drawImage(image, 0, 0, imageSize.width, imageSize.height, this); } if (isSelected) { @@ -174,7 +194,7 @@ public class HoverButton extends JPanel implements MouseListener { g2d.drawString(set, 0, 0); } } - + public void setCenterColor(Color c) { centerTextColor = c; } @@ -361,4 +381,75 @@ public class HoverButton extends JPanel implements MouseListener { // Draw the String g.drawString(text, x, y); } + + public void gainLifeDisplay() { + if (faderGainLife == null && doGainFade) { + doGainFade = false; + faderGainLife = new Timer(50, new ActionListener() { + public void actionPerformed(ActionEvent ae) { + gainX++; + int alpha = Math.max(250 - gainX, 180); + setCenterColor(new Color(2 * gainX, 210, 255, alpha)); + repaint(); + if (gainX >= 100) { + setCenterColor(new Color(200, 210, 0, 180)); + gainX = 100; + + if (faderGainLife != null) { + faderGainLife.stop(); + faderGainLife.setRepeats(false); + faderGainLife.setDelay(50000); + } + } + } + }); + gainX = 0; + faderGainLife.setInitialDelay(25); + faderGainLife.setRepeats(true); + faderGainLife.start(); + } + } + + public void loseLifeDisplay() { + if (faderLoseLife == null && doLoseFade) { + doLoseFade = false; + faderLoseLife = new Timer(50, new ActionListener() { + public void actionPerformed(ActionEvent ae) { + loseX++; + int alpha = Math.max(250 - loseX, 180); + setCenterColor(new Color(250 - loseX / 2, 130 + loseX, 0, alpha)); + repaint(); + if (loseX >= 100) { + setCenterColor(new Color(200, 210, 0, 180)); + loseX = 100; + stopLifeDisplay(); + + if (faderLoseLife != null) { + faderLoseLife.stop(); + faderLoseLife.setRepeats(false); + faderLoseLife.setDelay(50000); + } + } + } + }); + loseX = 0; + faderLoseLife.setInitialDelay(25); + faderLoseLife.setRepeats(true); + faderLoseLife.start(); + } + } + + public void stopLifeDisplay() { + + if (faderGainLife != null && gainX >= 100) { + faderGainLife.stop(); + faderGainLife = null; + } + doGainFade = true; + if (faderLoseLife != null && loseX >= 100) { + faderLoseLife.stop(); + faderLoseLife = null; + } + doLoseFade = true; + } } diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java index ed4b0d8a978..bc21430a31a 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java @@ -38,8 +38,6 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Image; import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.LinkedHashSet; @@ -55,7 +53,6 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.LayoutStyle.ComponentPlacement; import javax.swing.SwingConstants; -import javax.swing.Timer; import javax.swing.border.Border; import javax.swing.border.LineBorder; import mage.cards.decks.importer.DckDeckImporter; @@ -117,12 +114,6 @@ public class PlayerPanelExt extends javax.swing.JPanel { private String flagName; private String basicTooltipText; private static final Map playerLives = new HashMap<>(); - private int loseX; - private boolean doLoseFade = true; - private int gainX; - private boolean doGainFade = true; - Timer faderGainLife = null; - Timer faderLoseLife = null; private PriorityTimer timer; @@ -200,69 +191,12 @@ public class PlayerPanelExt extends javax.swing.JPanel { if (displayLife) { if (playerLife != pastLife) { if (playerLife > pastLife) { - if (faderGainLife == null && doGainFade) { - doGainFade = false; - faderGainLife = new Timer(50, new ActionListener() { - public void actionPerformed(ActionEvent ae) { - gainX++; - int alpha = Math.max(250 - gainX, 180); - avatar.setCenterColor(new Color(2 * gainX, 190, 255, alpha)); - avatar.repaint(); - if (gainX >= 100) { - avatar.setCenterColor(new Color(200, 190, 0, 180)); - gainX = 100; - - if (faderGainLife != null) { - faderGainLife.stop(); - faderGainLife.setRepeats(false); - faderGainLife.setDelay(50000); - } - } - } - }); - gainX = 0; - faderGainLife.setInitialDelay(25); - faderGainLife.setRepeats(true); - faderGainLife.start(); - } + avatar.gainLifeDisplay(); } else if (playerLife < pastLife) { - if (faderLoseLife == null && doLoseFade) { - doLoseFade = false; - faderLoseLife = new Timer(50, new ActionListener() { - public void actionPerformed(ActionEvent ae) { - loseX++; - int alpha = Math.max(250 - loseX, 180); - avatar.setCenterColor(new Color(250 - loseX / 2, 140 + loseX / 2, 0, alpha)); - avatar.repaint(); - if (loseX >= 100) { - avatar.setCenterColor(new Color(200, 190, 0, 180)); - loseX = 100; - - if (faderLoseLife != null) { - faderLoseLife.stop(); - faderLoseLife.setRepeats(false); - faderLoseLife.setDelay(50000); - } - } - } - }); - loseX = 0; - faderLoseLife.setInitialDelay(25); - faderLoseLife.setRepeats(true); - faderLoseLife.start(); - } + avatar.loseLifeDisplay(); } } else if (playerLife == pastLife) { - if (faderGainLife != null && gainX >= 100) { - faderGainLife.stop(); - faderGainLife = null; - } - doGainFade = true; - if (faderLoseLife != null && loseX >= 100) { - faderLoseLife.stop(); - faderLoseLife = null; - } - doLoseFade = true; + avatar.stopLifeDisplay(); } } From 7698856d296dffcb63a7c70776d19c8df56eb645 Mon Sep 17 00:00:00 2001 From: spjspj Date: Mon, 2 Apr 2018 01:01:32 +1000 Subject: [PATCH 10/21] Add an on-hover highlight effect. --- .../mage/client/components/HoverButton.java | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/HoverButton.java b/Mage.Client/src/main/java/mage/client/components/HoverButton.java index 6930e9a9643..535a22d5cd1 100644 --- a/Mage.Client/src/main/java/mage/client/components/HoverButton.java +++ b/Mage.Client/src/main/java/mage/client/components/HoverButton.java @@ -8,11 +8,13 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; import javax.swing.JPanel; import javax.swing.Timer; import mage.client.util.Command; @@ -56,8 +58,8 @@ public class HoverButton extends JPanel implements MouseListener { private Command onHover = null; private Color textColor = Color.white; private final Rectangle centerTextArea = new Rectangle(5, 18, 75, 40); - private Color centerTextColor = new Color(200, 210, 0, 180); - private Color origCenterTextColor = new Color(200, 210, 0, 180); + private Color centerTextColor = new Color(200, 210, 0, 200); + private Color origCenterTextColor = new Color(200, 210, 0, 200); private final Color textBGColor = Color.black; static final Font textFont = new Font("Arial", Font.PLAIN, 12); @@ -171,7 +173,7 @@ public class HoverButton extends JPanel implements MouseListener { } else if (val > 99) { fontSize = 34; } - drawCenteredString(g2d, centerText, centerTextArea, new Font("Arial", Font.BOLD, fontSize)); + drawCenteredStringWOutline(g2d, centerText, centerTextArea, new Font("Arial", Font.BOLD, fontSize)); } g2d.setColor(textColor); if (overlayImage != null) { @@ -369,7 +371,7 @@ public class HoverButton extends JPanel implements MouseListener { * @param rect The Rectangle to center the text in. * @param font */ - public void drawCenteredString(Graphics g, String text, Rectangle rect, Font font) { + public void drawCenteredStringWOutline(Graphics2D g, String text, Rectangle rect, Font font) { // Get the FontMetrics FontMetrics metrics = g.getFontMetrics(font); // Determine the X coordinate for the text @@ -378,8 +380,21 @@ public class HoverButton extends JPanel implements MouseListener { int y = rect.y + ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent(); // Set the font g.setFont(font); - // Draw the String - g.drawString(text, x, y); + + GlyphVector gv = font.createGlyphVector(g.getFontRenderContext(), text); + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + g.drawGlyphVector(gv, x, y); + + g.translate(x - 1, y - 1); + for (int i = 0; i < text.length(); i++) { + g.setColor(Color.BLACK); + g.draw(gv.getGlyphOutline(i)); + } + g.translate(-x + 1, -y + 1); + } public void gainLifeDisplay() { @@ -388,11 +403,11 @@ public class HoverButton extends JPanel implements MouseListener { faderGainLife = new Timer(50, new ActionListener() { public void actionPerformed(ActionEvent ae) { gainX++; - int alpha = Math.max(250 - gainX, 180); + int alpha = Math.max(250 - gainX, 200); setCenterColor(new Color(2 * gainX, 210, 255, alpha)); repaint(); if (gainX >= 100) { - setCenterColor(new Color(200, 210, 0, 180)); + setCenterColor(new Color(200, 210, 0, 200)); gainX = 100; if (faderGainLife != null) { @@ -416,11 +431,11 @@ public class HoverButton extends JPanel implements MouseListener { faderLoseLife = new Timer(50, new ActionListener() { public void actionPerformed(ActionEvent ae) { loseX++; - int alpha = Math.max(250 - loseX, 180); + int alpha = Math.max(250 - loseX, 200); setCenterColor(new Color(250 - loseX / 2, 130 + loseX, 0, alpha)); repaint(); if (loseX >= 100) { - setCenterColor(new Color(200, 210, 0, 180)); + setCenterColor(new Color(200, 210, 0, 200)); loseX = 100; stopLifeDisplay(); From ef44c57a32d81741e2fbdfc24312499181f29863 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Sun, 1 Apr 2018 11:08:39 -0400 Subject: [PATCH 11/21] 2 additional Planeswalker decks --- .../Huatli, Dinosaur Knight.dck | 28 +++++++++++++++++++ .../Planeswalk Decks/Nissa, Genesis Mage.dck | 23 +++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Huatli, Dinosaur Knight.dck create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Genesis Mage.dck diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Huatli, Dinosaur Knight.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Huatli, Dinosaur Knight.dck new file mode 100644 index 00000000000..539358b8f62 --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Huatli, Dinosaur Knight.dck @@ -0,0 +1,28 @@ +2 [XLN:31] Raptor Companion +1 [XLN:135] Burning Sun's Avatar +2 [XLN:38] Slash of Talons +2 [XLN:36] Shining Aerosaur +1 [XLN:13] Goring Ceratops +3 [XLN:274] Mountain +3 [XLN:273] Mountain +2 [XLN:133] Bonded Horncrest +2 [XLN:275] Mountain +2 [XLN:30] Rallying Roar +3 [XLN:272] Mountain +2 [XLN:28] Pterodon Knight +2 [XLN:149] Lightning Strike +2 [XLN:146] Frenzied Raptor +4 [XLN:289] Stone Quarry +2 [XLN:288] Sun-Blessed Mount +2 [XLN:169] Tilonalli's Knight +1 [XLN:285] Huatli, Dinosaur Knight +2 [XLN:263] Plains +3 [XLN:262] Plains +3 [XLN:287] Huatli's Spurring +4 [XLN:286] Huatli's Snubhorn +2 [XLN:41] Territorial Hammerskull +3 [XLN:261] Plains +3 [XLN:260] Plains +2 [XLN:18] Kinjalli's Caller +LAYOUT MAIN:(1,4)(CARD_TYPE,false,50)|([XLN:13],[XLN:288],[XLN:288],[XLN:135],[XLN:18],[XLN:18],[XLN:286],[XLN:286],[XLN:286],[XLN:286],[XLN:31],[XLN:31],[XLN:169],[XLN:169],[XLN:146],[XLN:146],[XLN:41],[XLN:41],[XLN:133],[XLN:133],[XLN:28],[XLN:28],[XLN:36],[XLN:36])([XLN:287],[XLN:287],[XLN:287],[XLN:38],[XLN:38],[XLN:149],[XLN:149],[XLN:30],[XLN:30])([XLN:289],[XLN:289],[XLN:289],[XLN:289],[XLN:260],[XLN:260],[XLN:260],[XLN:261],[XLN:261],[XLN:261],[XLN:262],[XLN:262],[XLN:262],[XLN:263],[XLN:263],[XLN:272],[XLN:272],[XLN:272],[XLN:273],[XLN:273],[XLN:273],[XLN:274],[XLN:274],[XLN:274],[XLN:275],[XLN:275])([XLN:285]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Genesis Mage.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Genesis Mage.dck new file mode 100644 index 00000000000..c3ccd369b92 --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Genesis Mage.dck @@ -0,0 +1,23 @@ +4 [HOU:193] Island +4 [HOU:192] Island +2 [AKH:41] Angler Drake +2 [HOU:29] Aerial Guide +2 [AKH:209] Weaver of Currents +2 [HOU:30] Aven Reedstalker +2 [AKH:219] Spring // Mind +4 [HOU:204] Woodland Stream +3 [HOU:54] Unsummon +3 [HOU:201] Avid Reclaimer +3 [AKH:179] Pouncing Cheetah +1 [HOU:200] Nissa, Genesis Mage +2 [HOU:203] Nissa's Encouragement +2 [HOU:115] Feral Prowler +4 [HOU:202] Brambleweft Behemoth +1 [AKH:196] Bounty of the Luxa +7 [HOU:199] Forest +1 [HOU:154] Reason // Believe +2 [HOU:143] River Hoopoe +2 [HOU:110] Ambuscade +7 [HOU:198] Forest +LAYOUT MAIN:(1,7)(CARD_TYPE,false,50)|([HOU:115],[HOU:115],[HOU:143],[HOU:143],[HOU:201],[HOU:201],[HOU:201],[AKH:179],[AKH:179],[AKH:179],[HOU:29],[HOU:29],[AKH:209],[AKH:209],[HOU:30],[HOU:30],[HOU:202],[HOU:202],[HOU:202],[HOU:202],[AKH:41],[AKH:41])([AKH:196])([AKH:219],[AKH:219])([HOU:54],[HOU:54],[HOU:54],[HOU:110],[HOU:110])([HOU:204],[HOU:204],[HOU:204],[HOU:204],[HOU:192],[HOU:192],[HOU:192],[HOU:192],[HOU:193],[HOU:193],[HOU:193],[HOU:193],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:198],[HOU:199],[HOU:199],[HOU:199],[HOU:199],[HOU:199],[HOU:199],[HOU:199])([HOU:200])([HOU:154],[HOU:203],[HOU:203]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| From 56b124c5246753b79c6273201d4a6ca271591654 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 01:09:22 +0000 Subject: [PATCH 12/21] Implemented Mine, Mine, Mine! For some reason, making MineMineMineDontLoseEffect replace EventType.EMPTY_DRAW still resulted in a game loss --- Mage.Sets/src/mage/cards/m/MineMineMine.java | 180 +++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MineMineMine.java diff --git a/Mage.Sets/src/mage/cards/m/MineMineMine.java b/Mage.Sets/src/mage/cards/m/MineMineMine.java new file mode 100644 index 00000000000..e5b7648e21b --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MineMineMine.java @@ -0,0 +1,180 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.m; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.continuous.CantCastMoreThanOneSpellEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect.HandSizeModification; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class MineMineMine extends CardImpl { + + public MineMineMine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{4}{G}{G}"); + + // When Mine, Mine, Mine enters the battlefield, each player puts his or her library into his or her hand. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MineMineMineDrawEffect())); + + // Players have no maximum hand size and don't lose the game for drawing from an empty library. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new MaximumHandSizeControllerEffect(Integer.MAX_VALUE, Duration.WhileOnBattlefield, HandSizeModification.SET, TargetController.ANY) + .setText("Players have no maximum hand size and don't lose the game for drawing from an empty library"))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new MineMineMineDontLoseEffect())); + + // Each player can't cast more than one spell each turn. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantCastMoreThanOneSpellEffect(TargetController.ANY))); + + // When Mine, Mine, Mine leaves the battlefield, each player shuffles his or her hand and graveyard into his or her library. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new MineMineMineShuffleEffect(), false)); + } + + public MineMineMine(final MineMineMine card) { + super(card); + } + + @Override + public MineMineMine copy() { + return new MineMineMine(this); + } +} + +class MineMineMineDrawEffect extends OneShotEffect { + + MineMineMineDrawEffect() { + super(Outcome.DrawCard); + this.staticText = "each player puts his or her library into his or her hand"; + } + + MineMineMineDrawEffect(final MineMineMineDrawEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + CardsImpl libraryCards = new CardsImpl(); + libraryCards.addAll(player.getLibrary().getCards(game)); + player.moveCards(libraryCards, Zone.HAND, source, game); + } + } + return true; + } + + @Override + public MineMineMineDrawEffect copy() { + return new MineMineMineDrawEffect(this); + } +} + +class MineMineMineDontLoseEffect extends ReplacementEffectImpl { + + MineMineMineDontLoseEffect() { + super(Duration.WhileOnBattlefield, Outcome.Neutral); + } + + MineMineMineDontLoseEffect(final MineMineMineDontLoseEffect effect) { + super(effect); + } + + @Override + public MineMineMineDontLoseEffect copy() { + return new MineMineMineDontLoseEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DRAW_CARD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Player player = game.getPlayer(event.getPlayerId()); + if (player != null && player.getLibrary().getCards(game).isEmpty()) { + return true; + } + return false; + } +} + +class MineMineMineShuffleEffect extends OneShotEffect { + + public MineMineMineShuffleEffect() { + super(Outcome.Neutral); + staticText = "each player shuffles his or her hand and graveyard into his or her library"; + } + + public MineMineMineShuffleEffect(final MineMineMineShuffleEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.moveCards(player.getHand(), Zone.LIBRARY, source, game); + player.moveCards(player.getGraveyard(), Zone.LIBRARY, source, game); + player.shuffleLibrary(source, game); + } + } + return true; + } + + @Override + public MineMineMineShuffleEffect copy() { + return new MineMineMineShuffleEffect(this); + } +} From ee2c475c874eb7eef2247d5b6f0971321fc33ccd Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 01:09:56 +0000 Subject: [PATCH 13/21] Implemented Mine, Mine, Mine! --- Mage.Sets/src/mage/sets/Unglued.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Unglued.java b/Mage.Sets/src/mage/sets/Unglued.java index f4de694b14f..4518e0b8e78 100644 --- a/Mage.Sets/src/mage/sets/Unglued.java +++ b/Mage.Sets/src/mage/sets/Unglued.java @@ -33,6 +33,7 @@ public class Unglued extends ExpansionSet { cards.add(new SetCardInfo("Jack-in-the-Mox", 75, Rarity.RARE, mage.cards.j.JackInTheMox.class)); cards.add(new SetCardInfo("Jumbo Imp", 34, Rarity.UNCOMMON, mage.cards.j.JumboImp.class)); cards.add(new SetCardInfo("Krazy Kow", 48, Rarity.COMMON, mage.cards.k.KrazyKow.class)); + cards.add(new SetCardInfo("Mine, Mine, Mine!", 65, Rarity.RARE, mage.cards.m.MineMineMine.class)); cards.add(new SetCardInfo("Mountain", 87, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Paper Tiger", 78, Rarity.COMMON, mage.cards.p.PaperTiger.class)); cards.add(new SetCardInfo("Plains", 84, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); From 97d0f328892cfe2baf22d978520bab9b4112eb1d Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Sun, 1 Apr 2018 22:27:33 -0400 Subject: [PATCH 14/21] Update Feline Ferocity to include missing card Added missing "Stalking Leonin" that was implemented after deck was built. --- .../sample-decks/Commander/Commander 2017/FelineFerocity.dck | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Client/release/sample-decks/Commander/Commander 2017/FelineFerocity.dck b/Mage.Client/release/sample-decks/Commander/Commander 2017/FelineFerocity.dck index 989a2561e45..14b8ff2111d 100644 --- a/Mage.Client/release/sample-decks/Commander/Commander 2017/FelineFerocity.dck +++ b/Mage.Client/release/sample-decks/Commander/Commander 2017/FelineFerocity.dck @@ -66,6 +66,7 @@ 1 [C17:158] Soul's Majesty 1 [C17:73] Spirit of the Hearth 1 [C17:224] Staff of Nin +1 [C17:7] Stalking Leonin 1 [C17:281] Stirring Wildwood 1 [C17:75] Sunspear Shikari 1 [C17:226] Swiftfoot Boots From 471d49892f67f82947073e06617b4e907cd9f9b4 Mon Sep 17 00:00:00 2001 From: spjspj Date: Mon, 2 Apr 2018 19:31:05 +1000 Subject: [PATCH 15/21] Attempt to allow players to fix the game if the active/choosing/person with priority has left or has run down the clock --- .../main/java/mage/server/ChatManager.java | 21 +++++ .../java/mage/server/game/GameController.java | 82 ++++++++++++++++++- 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/ChatManager.java b/Mage.Server/src/main/java/mage/server/ChatManager.java index ed017b14229..d25448c3cfe 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManager.java +++ b/Mage.Server/src/main/java/mage/server/ChatManager.java @@ -244,6 +244,27 @@ public enum ChatManager { } return true; } + if (command.startsWith("FIX")) { + message += "
" + GameManager.instance.getChatId(chatId); + ChatSession session = chatSessions.get(chatId); + if (session != null && session.getInfo() != null) { + String gameId = session.getInfo(); + if (gameId.startsWith("Game ")) { + UUID id = java.util.UUID.fromString(gameId.substring(5, gameId.length())); + for (Entry entry : GameManager.instance.getGameController().entrySet()) { + if (entry.getKey().equals(id)) { + GameController controller = entry.getValue(); + if (controller != null) { + message += controller.attemptToFixGame(); + chatSessions.get(chatId).broadcastInfoToUser(user, message); + } + } + } + + } + } + return true; + } if (command.startsWith("CARD ")) { Matcher matchPattern = getCardTextPattern.matcher(message.toLowerCase(Locale.ENGLISH)); if (matchPattern.find()) { diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 8bce119961f..3b18789ecf9 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -37,6 +37,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.zip.GZIPOutputStream; import mage.MageException; import mage.abilities.Ability; +import mage.abilities.common.PassAbility; import mage.cards.Card; import mage.cards.Cards; import mage.cards.decks.Deck; @@ -57,6 +58,7 @@ import mage.game.events.PlayerQueryEvent; import mage.game.events.TableEvent; import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; +import mage.game.turn.Phase; import mage.interfaces.Action; import mage.players.Player; import mage.server.*; @@ -1150,13 +1152,13 @@ public class GameController implements GameCallback { sb.append(state.getPlayerList()); sb.append("
getPlayers: "); sb.append(state.getPlayers()); - sb.append("
Player with Priority is: "); + sb.append("
Player with Priority is: "); if (state.getPriorityPlayerId() != null) { sb.append(game.getPlayer(state.getPriorityPlayerId()).getName()); } else { sb.append("noone!"); } - sb.append("
getRevealed: "); + sb.append("

getRevealed: "); sb.append(state.getRevealed()); sb.append("
getSpecialActions: "); sb.append(state.getSpecialActions()); @@ -1187,4 +1189,80 @@ public class GameController implements GameCallback { return sb.toString(); } + public String attemptToFixGame() { + if (game == null) { + return ""; + } + GameState state = game.getState(); + if (state == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append("
Game State:
"); + sb.append(state); + boolean fixedAlready = false; + + sb.append("
Active player is: "); + sb.append(game.getPlayer(state.getActivePlayerId()).getName()); + PassAbility pass = new PassAbility(); + if (game.getPlayer(state.getActivePlayerId()).hasLeft()) { + Phase currentPhase = game.getPhase(); + if (currentPhase != null) { + currentPhase.getStep().skipStep(game, state.getActivePlayerId()); + sb.append("
Forcibly passing the phase!"); + fixedAlready = true; + } else { + sb.append("
Current phase null"); + } + sb.append("
Active player has left"); + } + + sb.append("
getChoosingPlayerId: "); + if (state.getChoosingPlayerId() != null) { + if (game.getPlayer(state.getChoosingPlayerId()).hasLeft()) { + Phase currentPhase = game.getPhase(); + if (currentPhase != null && !fixedAlready) { + currentPhase.getStep().endStep(game, state.getActivePlayerId()); + fixedAlready = true; + sb.append("
Forcibly passing the phase!"); + } else if (currentPhase == null) { + sb.append("
Current phase null"); + } + sb.append("
Choosing player has left"); + } + } + + sb.append("
Player with Priority is: "); + if (state.getPriorityPlayerId() != null) { + if (game.getPlayer(state.getPriorityPlayerId()).hasLeft()) { + Phase currentPhase = game.getPhase(); + if (currentPhase != null && !fixedAlready) { + currentPhase.getStep().skipStep(game, state.getActivePlayerId()); + fixedAlready = true; + sb.append("
Forcibly passing the phase!"); + } + } + sb.append(game.getPlayer(state.getPriorityPlayerId()).getName()); + sb.append("
"); + } + + sb.append("
Future Timeout:"); + if (futureTimeout != null) { + sb.append("Cancelled?="); + sb.append(futureTimeout.isCancelled()); + sb.append(",,,Done?="); + sb.append(futureTimeout.isDone()); + sb.append(",,,GetDelay?="); + sb.append((int) futureTimeout.getDelay(TimeUnit.SECONDS)); + if ((int) futureTimeout.getDelay(TimeUnit.SECONDS) < 25) { + game.endTurn(pass); + sb.append("
Forcibly passing the turn!"); + } + } else { + sb.append("Not using future Timeout!"); + } + sb.append("
"); + return sb.toString(); + } + } From 1e2e53073c570c9613f967622d464da908284971 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Mon, 2 Apr 2018 15:30:57 +0200 Subject: [PATCH 16/21] * Deep Analysis - Fixed that its flashback costs did not work with mana casting cost modification effects (fixes #4677). --- Mage.Sets/src/mage/cards/d/DeepAnalysis.java | 13 +++----- .../src/mage/cards/m/MizzixOfTheIzmagnus.java | 12 +++---- .../abilities/keywords/FlashbackTest.java | 32 +++++++++++++++++++ .../modification/MizzixOfTheIzmagnusTest.java | 32 +++++++++++++++++++ .../main/java/mage/filter/StaticFilters.java | 8 ++++- 5 files changed, 81 insertions(+), 16 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DeepAnalysis.java b/Mage.Sets/src/mage/cards/d/DeepAnalysis.java index 7d186198370..2ad4b705318 100644 --- a/Mage.Sets/src/mage/cards/d/DeepAnalysis.java +++ b/Mage.Sets/src/mage/cards/d/DeepAnalysis.java @@ -28,9 +28,6 @@ package mage.cards.d; import java.util.UUID; -import mage.abilities.costs.Cost; -import mage.abilities.costs.Costs; -import mage.abilities.costs.CostsImpl; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DrawCardTargetEffect; @@ -48,18 +45,16 @@ import mage.target.TargetPlayer; public class DeepAnalysis extends CardImpl { public DeepAnalysis(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{3}{U}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}"); // Target player draws two cards. this.getSpellAbility().addEffect(new DrawCardTargetEffect(2)); this.getSpellAbility().addTarget(new TargetPlayer()); // Flashback-{1}{U}, Pay 3 life. - Costs costs = new CostsImpl<>(); - costs.add(new ManaCostsImpl("{1}{U}")); - costs.add(new PayLifeCost(3)); - this.addAbility(new FlashbackAbility(costs, TimingRule.SORCERY)); + FlashbackAbility ability = new FlashbackAbility(new ManaCostsImpl("{1}{U}"), TimingRule.SORCERY); + ability.addCost(new PayLifeCost(3)); + this.addAbility(ability); } public DeepAnalysis(final DeepAnalysis card) { diff --git a/Mage.Sets/src/mage/cards/m/MizzixOfTheIzmagnus.java b/Mage.Sets/src/mage/cards/m/MizzixOfTheIzmagnus.java index d1e431b69cf..ce9a178b8d1 100644 --- a/Mage.Sets/src/mage/cards/m/MizzixOfTheIzmagnus.java +++ b/Mage.Sets/src/mage/cards/m/MizzixOfTheIzmagnus.java @@ -41,7 +41,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; -import mage.filter.common.FilterInstantOrSorceryCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterInstantOrSorcerySpell; import mage.filter.predicate.Predicate; import mage.game.Game; @@ -62,7 +62,7 @@ public class MizzixOfTheIzmagnus extends CardImpl { } public MizzixOfTheIzmagnus(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{U}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{R}"); addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.GOBLIN); this.subtype.add(SubType.WIZARD); @@ -136,11 +136,11 @@ class MizzixOfTheIzmagnusCostReductionEffect extends CostModificationEffectImpl if (abilityToModify instanceof SpellAbility && abilityToModify.getControllerId().equals(source.getControllerId())) { Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId()); if (spell != null) { - return new FilterInstantOrSorceryCard().match(spell, source.getSourceId(), source.getControllerId(), game); - } else { - // used at least for flashback ability because Flashback ability doesn't use stack or for getPlayables where spell is not cast yet + return StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(spell, source.getSourceId(), source.getControllerId(), game); + } else if (((SpellAbility) abilityToModify).isCheckPlayableMode()) { + // Spell is not on the stack yet, but possible playable spells are determined Card sourceCard = game.getCard(abilityToModify.getSourceId()); - return sourceCard != null && new FilterInstantOrSorceryCard().match(sourceCard, source.getSourceId(), source.getControllerId(), game); + return sourceCard != null && StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY.match(sourceCard, source.getSourceId(), source.getControllerId(), game); } } return false; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java index 02bf64326b3..fd574695830 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/FlashbackTest.java @@ -30,6 +30,7 @@ package org.mage.test.cards.abilities.keywords; import mage.abilities.keyword.TrampleAbility; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -580,4 +581,35 @@ public class FlashbackTest extends CardTestPlayerBase { assertLife(playerA, 20); } + + /** + * Test cost reduction with mixed flashback costs + */ + @Test + public void testReduceMixedFlashbackCosts() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + // Whenever you cast an instant or sorcery spell with converted mana cost greater than the number of experience counters you have, you get an experience counter. + // Instant and sorcery spells you cast cost {1} less to cast for each experience counter you have. + addCard(Zone.BATTLEFIELD, playerA, "Mizzix of the Izmagnus");// 2/2 + + // Target player draws two cards. + // Flashback-{1}{U}, Pay 3 life. + addCard(Zone.HAND, playerA, "Deep Analysis"); // {3}{U} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deep Analysis"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Deep Analysis", 0); + assertExileCount(playerA, "Deep Analysis", 1); + assertHandCount(playerA, 4); + + assertCounterCount(playerA, CounterType.EXPERIENCE, 2); + + assertLife(playerA, 17); + + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/MizzixOfTheIzmagnusTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/MizzixOfTheIzmagnusTest.java index 3d5c58d10b8..4aa99bbe7f6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/MizzixOfTheIzmagnusTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/MizzixOfTheIzmagnusTest.java @@ -120,4 +120,36 @@ public class MizzixOfTheIzmagnusTest extends CardTestPlayerBase { assertLife(playerB, 17); } + + /** + * Test to reduce Flashback costs + */ + @Test + public void testReduceFlashbackCosts() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + + // Whenever you cast an instant or sorcery spell with converted mana cost greater than the number of experience counters you have, you get an experience counter. + // Instant and sorcery spells you cast cost {1} less to cast for each experience counter you have. + addCard(Zone.BATTLEFIELD, playerA, "Mizzix of the Izmagnus");// 2/2 + + // Engulfing Flames deals 1 damage to target creature. It can't be regenerated this turn. + // Flashback {3}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.) + addCard(Zone.HAND, playerA, "Engulfing Flames"); // {R} + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");// 2/2 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Engulfing Flames", "Silvercoat Lion"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback", "Silvercoat Lion"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Engulfing Flames", 0); + assertExileCount(playerA, "Engulfing Flames", 1); + + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + + assertCounterCount(playerA, CounterType.EXPERIENCE, 1); + + } } diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 27a367e88cb..97546761d2e 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -74,6 +74,12 @@ public final class StaticFilters { FILTER_CARD_A_NON_LAND.setLockedFilter(true); } + public static final FilterInstantOrSorceryCard FILTER_CARD_INSTANT_OR_SORCERY = new FilterInstantOrSorceryCard(); + + static { + FILTER_CARD_INSTANT_OR_SORCERY.setLockedFilter(true); + } + public static final FilterPermanent FILTER_PERMANENT = new FilterPermanent(); static { @@ -236,7 +242,7 @@ public final class StaticFilters { static { FILTER_BASIC_LAND_CARD.setLockedFilter(true); } - + // Used for sacrifice targets that don't need the "you control" text public static final FilterControlledLandPermanent FILTER_CONTROLLED_LAND_SHORT_TEXT = new FilterControlledLandPermanent("a land"); From e88a6a1fadd136f56fee2e69442239dac6f85c18 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 19:17:41 +0000 Subject: [PATCH 17/21] Implemented Brutal Suppression --- .../src/mage/cards/b/BrutalSuppression.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BrutalSuppression.java diff --git a/Mage.Sets/src/mage/cards/b/BrutalSuppression.java b/Mage.Sets/src/mage/cards/b/BrutalSuppression.java new file mode 100644 index 00000000000..5ed3b44b9f9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrutalSuppression.java @@ -0,0 +1,122 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.b; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.AbilityType; +import mage.constants.CostModificationType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author L_J + */ +public class BrutalSuppression extends CardImpl { + + public BrutalSuppression(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}"); + + // Activated abilities of nontoken Rebels cost an additional "Sacrifice a land" to activate. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BrutalSuppressionAdditionalCostEffect())); + } + + public BrutalSuppression(final BrutalSuppression card) { + super(card); + } + + @Override + public BrutalSuppression copy() { + return new BrutalSuppression(this); + } +} + +class BrutalSuppressionAdditionalCostEffect extends CostModificationEffectImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("a land"); + static{ + filter.add(new CardTypePredicate(CardType.LAND)); + } + + private static final FilterPermanent filter2 = new FilterPermanent("nontoken Rebels"); + static{ + filter2.add(new SubtypePredicate(SubType.REBEL)); + filter.add(Predicates.not(new TokenPredicate())); + } + + BrutalSuppressionAdditionalCostEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); + this.staticText = "Activated abilities of nontoken Rebels cost an additional \"Sacrifice a land\" to activate"; + } + + BrutalSuppressionAdditionalCostEffect(BrutalSuppressionAdditionalCostEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, true); + target.setRequired(false); + abilityToModify.addCost(new SacrificeTargetCost(target)); + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify.getAbilityType() == AbilityType.ACTIVATED || abilityToModify.getAbilityType() == AbilityType.MANA) { + Permanent rebelPermanent = game.getPermanent(abilityToModify.getSourceId()); + if (rebelPermanent != null) { + return filter2.match(rebelPermanent, game); + } + } + return false; + } + + @Override + public BrutalSuppressionAdditionalCostEffect copy() { + return new BrutalSuppressionAdditionalCostEffect(this); + } +} From b2d4db1d2718238b1d6c4781c04df5c61fb0b391 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 19:18:06 +0000 Subject: [PATCH 18/21] Implemented Brutal Suppression --- Mage.Sets/src/mage/sets/Prophecy.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Prophecy.java b/Mage.Sets/src/mage/sets/Prophecy.java index 25bd32e66ce..6708779181c 100644 --- a/Mage.Sets/src/mage/sets/Prophecy.java +++ b/Mage.Sets/src/mage/sets/Prophecy.java @@ -69,6 +69,7 @@ public class Prophecy extends ExpansionSet { cards.add(new SetCardInfo("Bog Elemental", 57, Rarity.RARE, mage.cards.b.BogElemental.class)); cards.add(new SetCardInfo("Bog Glider", 58, Rarity.COMMON, mage.cards.b.BogGlider.class)); cards.add(new SetCardInfo("Branded Brawlers", 84, Rarity.COMMON, mage.cards.b.BrandedBrawlers.class)); + cards.add(new SetCardInfo("Brutal Suppression", 85, Rarity.UNCOMMON, mage.cards.b.BrutalSuppression.class)); cards.add(new SetCardInfo("Calming Verse", 110, Rarity.COMMON, mage.cards.c.CalmingVerse.class)); cards.add(new SetCardInfo("Celestial Convergence", 5, Rarity.RARE, mage.cards.c.CelestialConvergence.class)); cards.add(new SetCardInfo("Chilling Apparition", 59, Rarity.UNCOMMON, mage.cards.c.ChillingApparition.class)); From 20a805e739ab88ad0816f63eba039f35f67691d8 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 21:23:02 +0000 Subject: [PATCH 19/21] Implemented Elvish Impersonators --- .../src/mage/cards/e/ElvishImpersonators.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/ElvishImpersonators.java diff --git a/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java b/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java new file mode 100644 index 00000000000..6fa6244ad58 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElvishImpersonators.java @@ -0,0 +1,99 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.e; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class ElvishImpersonators extends CardImpl { + + public ElvishImpersonators(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}"); + this.subtype.add(SubType.ELF); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // As Elvish Impersonators enters the battlefield, roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result. + this.addAbility(new AsEntersBattlefieldAbility(new ElvishImpersonatorsEffect())); + } + + public ElvishImpersonators(final ElvishImpersonators card) { + super(card); + } + + @Override + public ElvishImpersonators copy() { + return new ElvishImpersonators(this); + } +} + +class ElvishImpersonatorsEffect extends OneShotEffect { + + public ElvishImpersonatorsEffect() { + super(Outcome.Neutral); + staticText = "roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result"; + } + + public ElvishImpersonatorsEffect(final ElvishImpersonatorsEffect effect) { + super(effect); + } + + @Override + public ElvishImpersonatorsEffect copy() { + return new ElvishImpersonatorsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int firstRoll = controller.rollDice(game, 6); + int secondRoll = controller.rollDice(game, 6); + game.addEffect(new SetPowerToughnessSourceEffect(firstRoll, secondRoll, Duration.WhileOnBattlefield, SubLayer.SetPT_7b), source); + return true; + } + return false; + } +} From aecfc347bbb7839a80b0f87ed7ba7243ac1e40a0 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 21:26:56 +0000 Subject: [PATCH 20/21] Implemented Once More With Feeling --- .../src/mage/cards/o/OnceMoreWithFeeling.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/o/OnceMoreWithFeeling.java diff --git a/Mage.Sets/src/mage/cards/o/OnceMoreWithFeeling.java b/Mage.Sets/src/mage/cards/o/OnceMoreWithFeeling.java new file mode 100644 index 00000000000..915d5840885 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OnceMoreWithFeeling.java @@ -0,0 +1,116 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.o; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardAllEffect; +import mage.abilities.effects.common.ExileSpellEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.SetPlayerLifeAllEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class OnceMoreWithFeeling extends CardImpl { + + public OnceMoreWithFeeling(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{W}{W}{W}{W}"); + + // Exile all permanents and all cards from all graveyards. Each player shuffles his or her hand into his or her library, then draws seven cards. Each player's life total becomes 10. Exile Once More with Feeling. + this.getSpellAbility().addEffect(new OnceMoreWithFeelingEffect()); + Effect effect = new DrawCardAllEffect(7); + effect.setText(", then draws seven cards"); + this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addEffect(new SetPlayerLifeAllEffect(10)); + this.getSpellAbility().addEffect(ExileSpellEffect.getInstance()); + + // DCI ruling — A deck can have only one card named Once More with Feeling. + // (according to rule 112.6m, this shouldn't do anything) + this.getSpellAbility().addEffect(new InfoEffect("
DCI ruling — A deck can have only one card named {this}")); + } + + public OnceMoreWithFeeling(final OnceMoreWithFeeling card) { + super(card); + } + + @Override + public OnceMoreWithFeeling copy() { + return new OnceMoreWithFeeling(this); + } +} + +class OnceMoreWithFeelingEffect extends OneShotEffect { + + public OnceMoreWithFeelingEffect() { + super(Outcome.Detriment); + staticText = "Exile all permanents and all cards from all graveyards. Each player shuffles his or her hand into his or her library"; + } + + public OnceMoreWithFeelingEffect(final OnceMoreWithFeelingEffect effect) { + super(effect); + } + + @Override + public OnceMoreWithFeelingEffect copy() { + return new OnceMoreWithFeelingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { + permanent.moveToExile(null, "", source.getSourceId(), game); + } + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + for (UUID cid : player.getGraveyard().copy()) { + Card c = game.getCard(cid); + if (c != null) { + c.moveToExile(null, null, source.getSourceId(), game); + } + } + player.moveCards(player.getHand(), Zone.LIBRARY, source, game); + player.shuffleLibrary(source, game); + } + } + return true; + } +} From 8a432934262f699ccfdb1050cb31ee6aa12c9e07 Mon Sep 17 00:00:00 2001 From: L_J Date: Mon, 2 Apr 2018 21:27:50 +0000 Subject: [PATCH 21/21] Implemented Elvish Impersonators & Once More With Feeling --- Mage.Sets/src/mage/sets/Unglued.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mage.Sets/src/mage/sets/Unglued.java b/Mage.Sets/src/mage/sets/Unglued.java index 4518e0b8e78..48aa475c3b4 100644 --- a/Mage.Sets/src/mage/sets/Unglued.java +++ b/Mage.Sets/src/mage/sets/Unglued.java @@ -23,6 +23,7 @@ public class Unglued extends ExpansionSet { cards.add(new SetCardInfo("Chicken Egg", 41, Rarity.COMMON, mage.cards.c.ChickenEgg.class)); cards.add(new SetCardInfo("Chicken a la King", 17, Rarity.RARE, mage.cards.c.ChickenALaKing.class)); + cards.add(new SetCardInfo("Elvish Impersonators", 56, Rarity.COMMON, mage.cards.e.ElvishImpersonators.class)); cards.add(new SetCardInfo("Forest", 88, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Fowl Play", 24, Rarity.COMMON, mage.cards.f.FowlPlay.class)); cards.add(new SetCardInfo("Goblin Tutor", 45, Rarity.UNCOMMON, mage.cards.g.GoblinTutor.class)); @@ -35,6 +36,7 @@ public class Unglued extends ExpansionSet { cards.add(new SetCardInfo("Krazy Kow", 48, Rarity.COMMON, mage.cards.k.KrazyKow.class)); cards.add(new SetCardInfo("Mine, Mine, Mine!", 65, Rarity.RARE, mage.cards.m.MineMineMine.class)); cards.add(new SetCardInfo("Mountain", 87, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); + cards.add(new SetCardInfo("Once More With Feeling", 11, Rarity.RARE, mage.cards.o.OnceMoreWithFeeling.class)); cards.add(new SetCardInfo("Paper Tiger", 78, Rarity.COMMON, mage.cards.p.PaperTiger.class)); cards.add(new SetCardInfo("Plains", 84, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Poultrygeist", 37, Rarity.COMMON, mage.cards.p.Poultrygeist.class));