Merge branch 'master' into master

This commit is contained in:
kubikrubikvkube 2017-02-06 14:29:59 +03:00 committed by GitHub
commit 6a114ac902
99 changed files with 3416 additions and 901 deletions

View file

@ -30,7 +30,6 @@ package mage.abilities;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.Mana;
@ -39,6 +38,7 @@ import mage.abilities.costs.AlternativeSourceCosts;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.common.TapSourceCost;
@ -50,8 +50,8 @@ import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.BasicManaEffect;
import mage.abilities.effects.common.DynamicManaEffect;
import mage.abilities.effects.common.ManaEffect;
import mage.abilities.keyword.FlashbackAbility;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.Card;
@ -284,6 +284,9 @@ public abstract class AbilityImpl implements Ability {
this.getManaCostsToPay().clear();
}
}
if (modes.getAdditionalCost() != null) {
((OptionalAdditionalModeSourceCosts) modes.getAdditionalCost()).addOptionalAdditionalModeCosts(this, game);
}
// 20130201 - 601.2b
// If the spell has alternative or additional costs that will be paid as it's being cast such
// as buyback, kicker, or convoke costs (see rules 117.8 and 117.9), the player announces his
@ -415,8 +418,8 @@ public abstract class AbilityImpl implements Ability {
Effect effect = getEffects().get(0);
if (effect instanceof DynamicManaEffect) {
mana = ((DynamicManaEffect) effect).getMana(game, this);
} else if (effect instanceof BasicManaEffect) {
mana = ((BasicManaEffect) effect).getMana(game, this);
} else if (effect instanceof ManaEffect) {
mana = ((ManaEffect) effect).getMana(game, this);
}
if (mana != null && mana.getAny() == 0) { // if mana == null or Any > 0 the event has to be fired in the mana effect to know which mana was produced
ManaEvent event = new ManaEvent(GameEvent.EventType.TAPPED_FOR_MANA, sourceId, sourceId, controllerId, mana);

View file

@ -56,6 +56,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
private boolean eachModeMoreThanOnce; // each mode can be selected multiple times during one choice
private boolean eachModeOnlyOnce; // state if each mode can be chosen only once as long as the source object exists
private final LinkedHashMap<UUID, Mode> duplicateModes = new LinkedHashMap<>();
private OptionalAdditionalModeSourceCosts optionalAdditionalModeSourceCosts = null; // only set if costs have to be paid
public Modes() {
this.currentMode = new Mode();
@ -87,6 +88,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
this.modeChooser = modes.modeChooser;
this.eachModeOnlyOnce = modes.eachModeOnlyOnce;
this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce;
this.optionalAdditionalModeSourceCosts = modes.optionalAdditionalModeSourceCosts;
}
public Modes copy() {
@ -186,7 +188,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
if (card != null) {
for (Ability modeModifyingAbility : card.getAbilities()) {
if (modeModifyingAbility instanceof OptionalAdditionalModeSourceCosts) {
((OptionalAdditionalModeSourceCosts) modeModifyingAbility).addOptionalAdditionalModeCosts(source, game);
((OptionalAdditionalModeSourceCosts) modeModifyingAbility).changeModes(source, game);
}
}
}
@ -385,4 +387,12 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
this.eachModeMoreThanOnce = eachModeMoreThanOnce;
}
public OptionalAdditionalModeSourceCosts getAdditionalCost() {
return optionalAdditionalModeSourceCosts;
}
public void setAdditionalCost(OptionalAdditionalModeSourceCosts optionalAdditionalModeSourceCosts) {
this.optionalAdditionalModeSourceCosts = optionalAdditionalModeSourceCosts;
}
}

View file

@ -27,11 +27,11 @@
*/
package mage.abilities.common;
import mage.constants.Zone;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
@ -48,7 +48,7 @@ public class BlocksOrBecomesBlockedTriggeredAbility extends TriggeredAbilityImpl
protected boolean setTargetPointer;
public BlocksOrBecomesBlockedTriggeredAbility(Effect effect, boolean optional) {
this(effect, new FilterCreaturePermanent(), optional, null, false);
this(effect, StaticFilters.FILTER_PERMANENT_CREATURE, optional, null, false);
}
public BlocksOrBecomesBlockedTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional) {

View file

@ -0,0 +1,130 @@
/*
* 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.abilities.common;
import mage.abilities.Ability;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
/**
*
* @author emerald000
*/
public class CantHaveMoreThanAmountCountersSourceAbility extends SimpleStaticAbility {
private final CounterType counterType;
private final int amount;
public CantHaveMoreThanAmountCountersSourceAbility(CounterType counterType, int amount) {
super(Zone.BATTLEFIELD, new CantHaveMoreThanAmountCountersSourceEffect(counterType, amount));
this.counterType = counterType;
this.amount = amount;
}
private CantHaveMoreThanAmountCountersSourceAbility(CantHaveMoreThanAmountCountersSourceAbility ability) {
super(ability);
this.counterType = ability.counterType;
this.amount = ability.amount;
}
@Override
public String getRule() {
return "Rasputin can't have more than " + CardUtil.numberToText(this.amount) + " " + this.counterType.getName() + " counters on it.";
}
@Override
public CantHaveMoreThanAmountCountersSourceAbility copy() {
return new CantHaveMoreThanAmountCountersSourceAbility(this);
}
public CounterType getCounterType() {
return this.counterType;
}
public int getAmount() {
return this.amount;
}
}
class CantHaveMoreThanAmountCountersSourceEffect extends ReplacementEffectImpl {
private final CounterType counterType;
private final int amount;
CantHaveMoreThanAmountCountersSourceEffect(CounterType counterType, int amount) {
super(Duration.WhileOnBattlefield, Outcome.Detriment, false);
this.counterType = counterType;
this.amount = amount;
}
CantHaveMoreThanAmountCountersSourceEffect(final CantHaveMoreThanAmountCountersSourceEffect effect) {
super(effect);
this.counterType = effect.counterType;
this.amount = effect.amount;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
return true;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == EventType.ADD_COUNTER;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent == null) {
permanent = game.getPermanentEntering(event.getTargetId());
}
return permanent != null
&& permanent.getId().equals(source.getSourceId())
&& event.getData().equals(this.counterType.getName())
&& permanent.getCounters(game).getCount(this.counterType) == this.amount;
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public CantHaveMoreThanAmountCountersSourceEffect copy() {
return new CantHaveMoreThanAmountCountersSourceEffect(this);
}
}

View file

@ -34,9 +34,11 @@ import mage.game.Game;
*
* @author LevelX2
*/
public interface OptionalAdditionalModeSourceCosts {
void addOptionalAdditionalModeCosts(Ability ability, Game game);
void changeModes(Ability ability, Game game);
String getCastMessageSuffix();
}

View file

@ -95,11 +95,11 @@ public class ExileFromGraveCost extends CostImpl {
}
exiledCards.add(card);
}
Cards cardsToExile = new CardsImpl();
cardsToExile.addAll(exiledCards);
controller.moveCards(cardsToExile, Zone.EXILED, ability, game);
paid = true;
}
Cards cardsToExile = new CardsImpl();
cardsToExile.addAll(exiledCards);
controller.moveCards(cardsToExile, Zone.EXILED, ability, game);
paid = true;
}
return paid;

View file

@ -25,17 +25,18 @@
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.target.Target;
import mage.target.TargetSpell;
/**
* @author nantuko
@ -71,8 +72,15 @@ public class PreventDamageByTargetEffect extends PreventionEffectImpl {
public boolean applies(GameEvent event, Ability source, Game game) {
if (!this.used && super.applies(event, source, game)) {
MageObject mageObject = game.getObject(event.getSourceId());
if (mageObject instanceof Spell){
return this.getTargetPointer().getTargets(game, source).contains(mageObject.getId());
if (mageObject != null
&& (mageObject.getCardType().contains(CardType.INSTANT) || mageObject.getCardType().contains(CardType.SORCERY))) {
for (Target target : source.getTargets()) {
if (target instanceof TargetSpell) {
if (((TargetSpell) target).getSourceIds().contains(event.getSourceId())) {
return true;
}
}
}
}
return this.getTargetPointer().getTargets(game, source).contains(event.getSourceId());
}

View file

@ -70,7 +70,7 @@ public class BecomesSubtypeAllEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source,
Game game) {
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, game)) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) {
if (permanent != null) {
switch (layer) {
case TypeChangingEffects_4:
@ -86,10 +86,8 @@ public class BecomesSubtypeAllEffect extends ContinuousEffectImpl {
}
break;
}
} else {
if (duration.equals(Duration.Custom)) {
discard();
}
} else if (duration.equals(Duration.Custom)) {
discard();
}
}
return true;

View file

@ -45,7 +45,7 @@ import mage.target.common.TargetCardInLibrary;
*/
public class SearchLibraryWithLessCMCPutInPlayEffect extends OneShotEffect {
private FilterCard filter;
private final FilterCard filter;
public SearchLibraryWithLessCMCPutInPlayEffect() {
this(new FilterCard());
@ -66,8 +66,9 @@ public class SearchLibraryWithLessCMCPutInPlayEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
filter.add(new ConvertedManaCostPredicate(Filter.ComparisonType.LessThan, source.getManaCostsToPay().getX() + 1));
TargetCardInLibrary target = new TargetCardInLibrary(filter);
FilterCard advancedFilter = filter.copy(); // never change static objects so copy the object here before
advancedFilter.add(new ConvertedManaCostPredicate(Filter.ComparisonType.LessThan, source.getManaCostsToPay().getX() + 1));
TargetCardInLibrary target = new TargetCardInLibrary(advancedFilter);
if (controller.searchLibrary(target, game)) {
if (!target.getTargets().isEmpty()) {
Card card = controller.getLibrary().getCard(target.getFirstTarget(), game);

View file

@ -33,6 +33,7 @@ import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.SplitCard;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
@ -120,15 +121,13 @@ class CascadeEffect extends OneShotEffect {
break;
}
controller.moveCardsToExile(card, source, game, true, exile.getId(), exile.getName());
} while (controller.isInGame() && card.getCardType().contains(CardType.LAND) || card.getConvertedManaCost() >= sourceCost);
} while (controller.isInGame() && (card.getCardType().contains(CardType.LAND) || !cardThatCostsLess(sourceCost, card, game)));
controller.getLibrary().reset(); // set back empty draw state if that caused an empty draw
if (card != null) {
if (controller.chooseUse(outcome, "Use cascade effect on " + card.getLogName() + '?', source, game)) {
if (controller.cast(card.getSpellAbility(), game, true)) {
exile.remove(card.getId());
}
}
if (controller.chooseUse(outcome, "Use cascade effect on " + card.getLogName() + "?", source, game)) {
controller.cast(card.getSpellAbility(), game, true);
}
// Move the remaining cards to the buttom of the library in a random order
Cards cardsFromExile = new CardsImpl();
@ -148,4 +147,12 @@ class CascadeEffect extends OneShotEffect {
return new CascadeEffect(this);
}
private boolean cardThatCostsLess(int value, Card card, Game game) {
if (card instanceof SplitCard) {
return ((SplitCard) card).getLeftHalfCard().getConvertedManaCost() < value
|| ((SplitCard) card).getRightHalfCard().getConvertedManaCost() < value;
} else {
return card.getConvertedManaCost() < value;
}
}
}

View file

@ -105,22 +105,17 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
}
@Override
public void addOptionalAdditionalModeCosts(Ability ability, Game game) {
public void changeModes(Ability ability, Game game) {
if (ability instanceof SpellAbility) {
Player player = game.getPlayer(controllerId);
if (player != null) {
this.resetCosts();
if (additionalCost != null) {
if (player.chooseUse(Outcome.Benefit, "Pay " + additionalCost.getText(false) + " ?", ability, game)) {
if (additionalCost.canPay(ability, ability.getSourceId(), ability.getControllerId(), game)
&& player.chooseUse(Outcome.Benefit, "Pay " + additionalCost.getText(false) + " ?", ability, game)) {
additionalCost.activate();
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
}
}
ability.getModes().setAdditionalCost(this);
ability.getModes().setMinModes(2);
ability.getModes().setMaxModes(2);
}
@ -129,6 +124,20 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
}
}
@Override
public void addOptionalAdditionalModeCosts(Ability ability, Game game) {
if (additionalCost.isActivated()) {
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
}
}
}
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();

View file

@ -275,7 +275,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
@Override
public Abilities<Ability> getAbilities(Game game) {
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(objectId);
if (otherAbilities == null) {
if (otherAbilities == null || otherAbilities.isEmpty()) {
return abilities;
}
Abilities<Ability> all = new AbilitiesImpl<>();

View file

@ -30,11 +30,16 @@ package mage.cards.repository;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.PlanswalkerEntersWithLoyalityCountersAbility;
import mage.cards.Card;
import mage.cards.CardGraphicInfo;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.FrameStyle;
@ -45,11 +50,6 @@ import mage.constants.Rarity;
import mage.constants.SpellAbilityType;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
*
* @author North
@ -205,7 +205,7 @@ public class CardInfo {
}
public Card getCard() {
return CardImpl.createCard(className, new CardSetInfo(name, setCode, cardNumber, rarity));
return CardImpl.createCard(className, new CardSetInfo(name, setCode, cardNumber, rarity, new CardGraphicInfo(FrameStyle.valueOf(frameStyle), variousArt)));
}
public Card getMockCard() {
@ -216,7 +216,9 @@ public class CardInfo {
}
}
public boolean usesVariousArt() { return variousArt; }
public boolean usesVariousArt() {
return variousArt;
}
public ObjectColor getColor() {
ObjectColor color = new ObjectColor();

View file

@ -60,7 +60,7 @@ public enum CardRepository {
// raise this if db structure was changed
private static final long CARD_DB_VERSION = 50;
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 69;
private static final long CARD_CONTENT_VERSION = 70;
private final TreeSet<String> landTypes = new TreeSet();
private Dao<CardInfo, Object> cardDao;
private Set<String> classNames;

View file

@ -53,6 +53,7 @@ public enum CounterType {
DEVOTION("devotion"),
DIVINITY("divinity"),
DOOM("doom"),
DREAM("dream"),
ELIXIR("elixir"),
ENERGY("energy"),
EON("eon"),

View file

@ -32,6 +32,7 @@ public class StaticFilters {
public static final FilterNonlandCard FILTER_CARD_NON_LAND = new FilterNonlandCard();
public static final FilterCard FILTER_CARD_ARTIFACT_OR_CREATURE = new FilterCard("artifact or creature card");
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE = new FilterCreaturePermanent();
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_GOBLINS = new FilterCreaturePermanent("Goblin", "Goblin creatures");
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_SLIVERS = new FilterCreaturePermanent("Sliver", "Sliver creatures");

View file

@ -0,0 +1,179 @@
/*
* 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.game;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import mage.constants.MultiplayerAttackOption;
import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence;
import mage.game.turn.TurnMod;
import mage.players.Player;
public abstract class GameCanadianHighlanderImpl extends GameImpl {
protected boolean startingPlayerSkipsDraw = true;
protected Map<UUID, String> usedMulligans = new LinkedHashMap<>();
public GameCanadianHighlanderImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) {
super(attackOption, range, 0, startLife);
}
public GameCanadianHighlanderImpl(final GameCanadianHighlanderImpl game) {
super(game);
}
@Override
protected void init(UUID choosingPlayerId) {
super.init(choosingPlayerId);
state.getTurnMods().add(new TurnMod(startingPlayerId, PhaseStep.DRAW));
}
private String getNextMulligan(String mulligan) {
if (mulligan.equals("7")) {
return "6a";
} else if (mulligan.equals("6a")) {
return "6b";
} else if (mulligan.equals("6b")) {
return "5a";
} else if (mulligan.equals("5a")) {
return "5b";
} else if (mulligan.equals("5b")) {
return "4a";
} else if (mulligan.equals("4a")) {
return "4b";
} else if (mulligan.equals("4b")) {
return "3a";
} else if (mulligan.equals("3a")) {
return "3b";
} else if (mulligan.equals("3b")) {
return "2a";
} else if (mulligan.equals("2a")) {
return "2b";
} else if (mulligan.equals("2b")) {
return "1a";
} else if (mulligan.equals("1a")) {
return "1b";
}
return "0";
}
private int getNextMulliganNum(String mulligan) {
if (mulligan.equals("7")) {
return 6;
} else if (mulligan.equals("6a")) {
return 6;
} else if (mulligan.equals("6b")) {
return 5;
} else if (mulligan.equals("5a")) {
return 5;
} else if (mulligan.equals("5b")) {
return 4;
} else if (mulligan.equals("4a")) {
return 4;
} else if (mulligan.equals("4b")) {
return 3;
} else if (mulligan.equals("3a")) {
return 3;
} else if (mulligan.equals("3b")) {
return 2;
} else if (mulligan.equals("2a")) {
return 2;
} else if (mulligan.equals("2b")) {
return 1;
} else if (mulligan.equals("1a")) {
return 1;
}
return 0;
}
@Override
public int mulliganDownTo(UUID playerId) {
Player player = getPlayer(playerId);
int deduction = 1;
int numToMulliganTo = -1;
if (usedMulligans != null) {
String mulliganCode = "7";
if (usedMulligans.containsKey(player.getId())) {
mulliganCode = usedMulligans.get(player.getId());
}
numToMulliganTo = getNextMulliganNum(mulliganCode);
}
if (numToMulliganTo == -1) {
return player.getHand().size() - deduction;
}
return numToMulliganTo;
}
@Override
public void mulligan(UUID playerId) {
Player player = getPlayer(playerId);
int numCards = player.getHand().size();
int numToMulliganTo = numCards;
player.getLibrary().addAll(player.getHand().getCards(this), this);
player.getHand().clear();
player.shuffleLibrary(null, this);
if (usedMulligans != null) {
String mulliganCode = "7";
if (usedMulligans.containsKey(player.getId())) {
mulliganCode = usedMulligans.get(player.getId());
}
numToMulliganTo = getNextMulliganNum(mulliganCode);
usedMulligans.put(player.getId(), getNextMulligan(mulliganCode));
}
fireInformEvent(new StringBuilder(player.getLogName())
.append(" mulligans to ")
.append(Integer.toString(numToMulliganTo))
.append(numToMulliganTo == 1 ? " card" : " cards").toString());
player.drawCards(numToMulliganTo, this);
}
@Override
public void endMulligan(UUID playerId) {
super.endMulligan(playerId);
}
@Override
public Set<UUID> getOpponents(UUID playerId) {
Set<UUID> opponents = new HashSet<>();
for (UUID opponentId : getState().getPlayersInRange(playerId, this)) {
if (!opponentId.equals(playerId)) {
opponents.add(opponentId);
}
}
return opponents;
}
@Override
public boolean isOpponent(Player player, UUID playerToCheck) {
return !player.getId().equals(playerToCheck);
}
}

View file

@ -52,6 +52,7 @@ import mage.abilities.OpeningHandAction;
import mage.abilities.SpellAbility;
import mage.abilities.TriggeredAbility;
import mage.abilities.common.AttachableToRestrictedAbility;
import mage.abilities.common.CantHaveMoreThanAmountCountersSourceAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffects;
import mage.abilities.effects.Effect;
@ -1923,6 +1924,19 @@ public abstract class GameImpl implements Game, Serializable {
perm.getCounters(this).removeCounter(CounterType.M1M1, min);
}
// 20170120 - 704.5s
// If a permanent with an ability that says it can't have more than N counters of a certain kind on it
// has more than N counters of that kind on it, all but N of those counters are removed from it.
for (Ability ability : perm.getAbilities(this)) {
if (ability instanceof CantHaveMoreThanAmountCountersSourceAbility) {
CantHaveMoreThanAmountCountersSourceAbility counterAbility = (CantHaveMoreThanAmountCountersSourceAbility) ability;
int count = perm.getCounters(this).getCount(counterAbility.getCounterType());
if (count > counterAbility.getAmount()) {
perm.removeCounters(counterAbility.getCounterType().getName(), count - counterAbility.getAmount(), this);
somethingHappened = true;
}
}
}
}
//201300713 - 704.5j
// If a player controls two or more planeswalkers that share a planeswalker type, that player

View file

@ -399,7 +399,9 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
}
} else {
Player defender = game.getPlayer(defenderId);
defender.damage(amount, attacker.getId(), game, true, true);
if (defender.isInGame()) {
defender.damage(amount, attacker.getId(), game, true, true);
}
}
}

View file

@ -23,6 +23,7 @@ public class UserData implements Serializable {
protected boolean passPriorityActivation;
protected boolean autoOrderTrigger;
protected boolean useFirstManaAbility = false;
private String userIdStr;
protected String matchHistory;
protected int matchQuitRatio;
@ -36,7 +37,7 @@ public class UserData implements Serializable {
public UserData(UserGroup userGroup, int avatarId, boolean showAbilityPickerForced,
boolean allowRequestShowHandCards, boolean confirmEmptyManaPool, UserSkipPrioritySteps userSkipPrioritySteps,
String flagName, boolean askMoveToGraveOrder, boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted,
boolean passPriorityCast, boolean passPriorityActivation, boolean autoOrderTrigger, boolean useFirstManaAbility) {
boolean passPriorityCast, boolean passPriorityActivation, boolean autoOrderTrigger, boolean useFirstManaAbility, String userIdStr) {
this.groupId = userGroup.getGroupId();
this.avatarId = avatarId;
this.showAbilityPickerForced = showAbilityPickerForced;
@ -55,6 +56,7 @@ public class UserData implements Serializable {
this.matchQuitRatio = 0;
this.tourneyHistory = "";
this.tourneyQuitRatio = 0;
this.userIdStr = userIdStr;
}
public void update(UserData userData) {
@ -72,11 +74,12 @@ public class UserData implements Serializable {
this.passPriorityActivation = userData.passPriorityActivation;
this.autoOrderTrigger = userData.autoOrderTrigger;
this.useFirstManaAbility = userData.useFirstManaAbility;
this.userIdStr = userData.userIdStr;
// todo: why we don't copy user stats here?
}
public static UserData getDefaultUserDataView() {
return new UserData(UserGroup.DEFAULT, 0, false, false, true, null, getDefaultFlagName(), false, true, true, false, false, false, false);
return new UserData(UserGroup.DEFAULT, 0, false, false, true, null, getDefaultFlagName(), false, true, true, false, false, false, false, "");
}
public void setGroupId(int groupId) {

View file

@ -44,6 +44,7 @@ import mage.game.stack.StackObject;
public class TargetSpell extends TargetObject {
protected final FilterSpell filter;
private final Set<UUID> sourceIds = new HashSet<>();
public TargetSpell() {
this(1, 1, new FilterSpell());
@ -68,6 +69,7 @@ public class TargetSpell extends TargetObject {
public TargetSpell(final TargetSpell target) {
super(target);
this.filter = target.filter.copy();
this.sourceIds.addAll(target.sourceIds);
}
@Override
@ -134,4 +136,18 @@ public class TargetSpell extends TargetObject {
&& game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
&& filter.match(stackObject, sourceID, sourceControllerId, game);
}
@Override
public void addTarget(UUID id, Ability source, Game game, boolean skipEvent) {
Spell spell = game.getStack().getSpell(id);
if (spell != null) { // remember the original sourceID
sourceIds.add(spell.getSourceId());
}
super.addTarget(id, source, game, skipEvent);
}
public Set<UUID> getSourceIds() {
return sourceIds;
}
}