Merge branch 'master' into Zzooouhh-banding-final

This commit is contained in:
L_J 2018-02-15 00:49:08 +01:00 committed by GitHub
commit 3dbd5a72c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 175 additions and 110 deletions

View file

@ -1,7 +1,7 @@
XMage.de 1 (Europe/Germany) fast :xmage.de:17171
woogerworks (North America/USA) :xmage.woogerworks.com:17171
play.xmage.net (North America/Canada) :play.xmage.net:17171
XMageBr. (South America/Brazil) :magic.ncs3sistemas.com.br:17171
old xmage.de (Europe/Germany) :185.3.232.200:17171
xmage.us (North America/USA) :xmage.us:17171
XMage Players MTG:xmageplayersmtg.ddns.net:17171
XMage.tahiti :xmage.tahiti.one:443
Seedds Server (Asia) :115.29.203.80:17171
localhost -> connect to your local server (must be started):localhost:17171

View file

@ -134,12 +134,6 @@ public final class GuiDisplayUtil {
for (String rule : card.getRules()) {
textLines.basicTextLength += rule.length();
}
if (card.getMageObjectType() == MageObjectType.PERMANENT) {
if (card.getPairedCard() != null) {
textLines.lines.add("<span color='green'><i>Paired with another creature</i></span>");
textLines.basicTextLength += 30;
}
}
if (card.getMageObjectType().canHaveCounters()) {
ArrayList<CounterView> counters = new ArrayList<>();
if (card instanceof PermanentView) {

View file

@ -41,7 +41,7 @@ public class MageVersion implements Serializable, Comparable<MageVersion> {
public final static int MAGE_VERSION_MAJOR = 1;
public final static int MAGE_VERSION_MINOR = 4;
public final static int MAGE_VERSION_PATCH = 27;
public final static String MAGE_VERSION_MINOR_PATCH = "V3";
public final static String MAGE_VERSION_MINOR_PATCH = "V4";
public final static String MAGE_VERSION_INFO = "";
private final int major;

View file

@ -55,7 +55,7 @@ public class ContestedWarZone extends CardImpl {
private static final FilterAttackingCreature filter = new FilterAttackingCreature("Attacking creatures");
public ContestedWarZone(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.LAND},null);
super(ownerId, setInfo, new CardType[]{CardType.LAND}, null);
// Whenever a creature deals combat damage to you, that creature's controller gains control of Contested War Zone.
this.addAbility(new ContestedWarZoneAbility());
@ -102,7 +102,7 @@ class ContestedWarZoneAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
DamagedPlayerEvent damageEvent = (DamagedPlayerEvent)event;
DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event;
if (damageEvent.isCombatDamage()) {
Permanent permanent = game.getPermanent(event.getSourceId());
if (damageEvent.getPlayerId().equals(getControllerId()) && permanent != null && permanent.isCreature()) {
@ -137,7 +137,7 @@ class ContestedWarZoneEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = (Permanent) source.getSourceObjectIfItStillExists(game);
Permanent permanent = game.getPermanent(source.getSourceId());
UUID controllerId = (UUID) game.getState().getValue(source.getSourceId().toString());
if (permanent != null && controllerId != null) {
return permanent.changeControllerId(controllerId, game);

View file

@ -45,7 +45,7 @@ import mage.target.common.TargetCreatureOrPlayer;
/**
*
* @author KholdFuzion
*
*/
public class DrainLife extends CardImpl {
@ -55,10 +55,8 @@ public class DrainLife extends CardImpl {
filterBlack.setBlack(true);
}
public DrainLife(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{X}{1}{B}");
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{1}{B}");
// Spend only black mana on X.
// Drain Life deals X damage to target creature or player. You gain life equal to the damage dealt, but not more life than the player's life total before Drain Life dealt damage or the creature's toughness.
@ -84,7 +82,7 @@ class DrainLifeEffect extends OneShotEffect {
public DrainLifeEffect() {
super(Outcome.Damage);
staticText = "Drain Life deals X damage to target creature or player. You gain life equal to the damage dealt, but not more life than the player's life total before Drain Life dealt damage or the creature's toughness.";
staticText = "Spend only black mana on X.<br>{this} deals X damage to target creature or player. You gain life equal to the damage dealt, but not more life than the player's life total before Drain Life dealt damage or the creature's toughness";
}
public DrainLifeEffect(final DrainLifeEffect effect) {
@ -97,7 +95,7 @@ class DrainLifeEffect extends OneShotEffect {
int lifetogain = amount;
if (amount > 0) {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent != null ) {
if (permanent != null) {
if (permanent.getToughness().getValue() < amount) {
lifetogain = permanent.getToughness().getValue();
}

View file

@ -178,7 +178,7 @@ class EyeOfTheStormEffect1 extends OneShotEffect {
}
boolean continueCasting = true;
while (continueCasting) {
while (spellController.isInGame() && continueCasting) {
continueCasting = copiedCards.size() > 1 && spellController.chooseUse(outcome, "Cast one of the copied cards without paying its mana cost?", source, game);
Card cardToCopy;

View file

@ -2,10 +2,8 @@ package mage.cards.g;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.effects.common.ReturnToHandTargetEffect;
@ -16,7 +14,6 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
import mage.target.common.TargetCardInGraveyard;
public class GraveScrabbler extends CardImpl {
@ -35,7 +32,7 @@ public class GraveScrabbler extends CardImpl {
//you may return target creature card from a graveyard to its owner's hand.
TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect(), true);
ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card in a graveyard")));
this.addAbility(new ConditionalTriggeredAbility(ability, MadnessPaidCondition.instance,
this.addAbility(new ConditionalTriggeredAbility(ability, MadnessAbility.GetCondition(),
"When {this} enters the battlefield, if its madness cost was paid, you may return target creature card from a graveyard to its owner's hand."));
}
@ -49,21 +46,3 @@ public class GraveScrabbler extends CardImpl {
}
}
enum MadnessPaidCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof MadnessAbility) {
return ((MadnessAbility) ability).getCosts().isPaid();
}
}
}
return false;
}
}

View file

@ -44,14 +44,13 @@ import mage.target.targetpointer.SecondTargetPointer;
public class Lunge extends CardImpl {
public Lunge(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{R}");
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}");
// Lunge deals 2 damage to target creature and 2 damage to target player.
this.getSpellAbility().addEffect(new DamageTargetEffect(2));
this.getSpellAbility().addEffect(new DamageTargetEffect(2).setUseOnlyTargetPointer(true));
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
Effect effect = new DamageTargetEffect(2);
Effect effect = new DamageTargetEffect(2).setUseOnlyTargetPointer(true);
effect.setTargetPointer(new SecondTargetPointer());
effect.setText("and 2 damage to target player");
this.getSpellAbility().addEffect(effect);

View file

@ -29,6 +29,7 @@ package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@ -512,4 +513,45 @@ public class FlashbackTest extends CardTestPlayerBase {
assertExileCount(playerA, dReturn, 1);
assertPermanentCount(playerA, bSable, 1);
}
/**
* I can play Force of Will with flashback paying his alternative mana cost.
* The ruling say no to it, because we only can choose one alternative cost
* to a spell, and the flashback cost is already an alternative cost.
*/
@Test
@Ignore
public void testSnapcasterMageSpellWithAlternateCost() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
// When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn.
// The flashback cost is equal to its mana cost.
addCard(Zone.HAND, playerA, "Snapcaster Mage", 2); // Creature{1}{U}
// You may pay 1 life and exile a blue card from your hand rather than pay Force of Will's mana cost.
// Counter target spell.
addCard(Zone.GRAVEYARD, playerA, "Force of Will");
addCard(Zone.HAND, playerB, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage");
setChoice(playerA, "Force of Will");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Snapcaster Mage");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback", null, "Lightning Bolt");
addTarget(playerA, "Lightning Bolt");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Snapcaster Mage", 0);
assertGraveyardCount(playerA, "Snapcaster Mage", 1);
assertGraveyardCount(playerA, "Force of Will", 1);
assertGraveyardCount(playerB, "Lightning Bolt", 1);
assertLife(playerA, 20);
}
}

View file

@ -38,6 +38,7 @@ import mage.cards.Card;
import mage.cards.SplitCard;
import mage.constants.AbilityType;
import mage.constants.AsThoughEffectType;
import mage.constants.SpellAbilityCastMode;
import mage.constants.SpellAbilityType;
import mage.constants.TimingRule;
import mage.constants.Zone;
@ -51,6 +52,7 @@ import mage.players.Player;
public class SpellAbility extends ActivatedAbilityImpl {
protected SpellAbilityType spellAbilityType;
protected SpellAbilityCastMode spellAbilityCastMode;
protected String cardName;
public SpellAbility(ManaCost cost, String cardName) {
@ -62,23 +64,22 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
public SpellAbility(ManaCost cost, String cardName, Zone zone, SpellAbilityType spellAbilityType) {
this(cost, cardName, zone, spellAbilityType, SpellAbilityCastMode.NORMAL);
}
public SpellAbility(ManaCost cost, String cardName, Zone zone, SpellAbilityType spellAbilityType, SpellAbilityCastMode spellAbilityCastMode) {
super(AbilityType.SPELL, zone);
this.cardName = cardName;
this.spellAbilityType = spellAbilityType;
this.spellAbilityCastMode = spellAbilityCastMode;
this.addManaCost(cost);
switch (spellAbilityType) {
case SPLIT_FUSED:
this.name = "Cast fused " + cardName;
break;
default:
this.name = "Cast " + cardName;
}
setSpellName();
}
public SpellAbility(final SpellAbility ability) {
super(ability);
this.spellAbilityType = ability.spellAbilityType;
this.spellAbilityCastMode = ability.spellAbilityCastMode;
this.cardName = ability.cardName;
}
@ -209,4 +210,24 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
return amount * xMultiplier;
}
private void setSpellName() {
switch (spellAbilityType) {
case SPLIT_FUSED:
this.name = "Cast fused " + cardName;
break;
default:
this.name = "Cast " + cardName + (this.spellAbilityCastMode != SpellAbilityCastMode.NORMAL ? " by " + spellAbilityCastMode.toString() : "");
}
}
public SpellAbilityCastMode getSpellAbilityCastMode() {
return spellAbilityCastMode;
}
public void setSpellAbilityCastMode(SpellAbilityCastMode spellAbilityCastMode) {
this.spellAbilityCastMode = spellAbilityCastMode;
setSpellName();
}
}

View file

@ -72,6 +72,9 @@ public class AddManaOfAnyColorEffect extends BasicManaEffect {
String mes = String.format("Select color of %d mana to add it to your mana pool", this.amount);
ChoiceColor choice = new ChoiceColor(true, mes, game.getObject(source.getSourceId()));
if (controller.choose(outcome, choice, game)) {
if (choice.getColor() == null) {
return false;
}
Mana createdMana = choice.getMana(amount);
if (createdMana != null) {
checkToFirePossibleEvents(createdMana, game, source);

View file

@ -11,8 +11,6 @@ import mage.constants.SubLayer;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
public class PlayLandsFromGraveyardEffect extends ContinuousEffectImpl {
public PlayLandsFromGraveyardEffect() {
@ -33,11 +31,10 @@ public class PlayLandsFromGraveyardEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
for (UUID cardId: player.getGraveyard()) {
Card card = game.getCard(cardId);
if(card != null && card.isLand()){
for (Card card : player.getGraveyard().getCards(game)) {
if (card != null && card.isLand()) {
PlayLandFromGraveyardAbility ability = new PlayLandFromGraveyardAbility(card.getName());
ability.setSourceId(cardId);
ability.setSourceId(card.getId());
ability.setControllerId(card.getOwnerId());
game.getState().addOtherAbility(card, ability);
}

View file

@ -1,8 +1,9 @@
package mage.abilities.keyword;
import java.util.ArrayList;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.condition.Condition;
@ -13,10 +14,13 @@ import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SpellAbilityCastMode;
import mage.constants.SpellAbilityType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.stack.Spell;
import mage.players.Player;
/**
@ -133,8 +137,6 @@ class MadnessReplacementEffect extends ReplacementEffectImpl {
*/
class MadnessTriggeredAbility extends TriggeredAbilityImpl {
//This array holds the Id's of all of the cards that activated madness
private static ArrayList<UUID> activatedIds = new ArrayList<>();
private final UUID madnessOriginalId;
MadnessTriggeredAbility(ManaCosts<ManaCost> madnessCost, UUID madnessOriginalId) {
@ -176,25 +178,9 @@ class MadnessTriggeredAbility extends TriggeredAbilityImpl {
}
return false;
}
activatedIds.add(getSourceId());
return true;
}
@Override
public boolean isActivated() {
//Look through the list of activated Ids and see if the current source's Id is one of them
for (UUID currentId : activatedIds) {
if (currentId.equals(getSourceId())) {
//Remove the current source from the list, so if the card is somehow recast without
//paying the madness cost, this will return false
activatedIds.remove(currentId);
return true;
}
}
//If the current source's Id was not found, return false
return false;
}
@Override
public String getRule() {
return "When this card is exiled this way, " + super.getRule();
@ -225,17 +211,15 @@ class MadnessCastEffect extends OneShotEffect {
}
if (owner != null && card != null
&& owner.chooseUse(outcome, "Cast " + card.getLogName() + " by madness?", source, game)) {
ManaCosts<ManaCost> costRef = card.getSpellAbility().getManaCostsToPay();
// replace with the new cost
SpellAbility castByMadness = card.getSpellAbility().copy();
ManaCosts<ManaCost> costRef = castByMadness.getManaCostsToPay();
castByMadness.setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE);
castByMadness.setSpellAbilityCastMode(SpellAbilityCastMode.MADNESS);
costRef.clear();
costRef.add(madnessCost);
boolean result = owner.cast(card.getSpellAbility(), game, false);
// Reset the casting costs (in case the player cancels cast and plays the card later)
// TODO: Check if this is neccessary
costRef.clear();
for (ManaCost manaCost : card.getSpellAbility().getManaCosts()) {
costRef.add(manaCost);
}
boolean result = owner.cast(castByMadness, game, false);
return result;
}
@ -254,14 +238,10 @@ enum MadnessCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof MadnessTriggeredAbility) {
if (ability.isActivated()) {
return true;
}
}
MageObject madnessSpell = game.getLastKnownInformation(source.getSourceId(), Zone.STACK, source.getSourceObjectZoneChangeCounter() - 1);
if (madnessSpell instanceof Spell) {
if (((Spell) madnessSpell).getSpellAbility() != null) {
return ((Spell) madnessSpell).getSpellAbility().getSpellAbilityCastMode() == SpellAbilityCastMode.MADNESS;
}
}
return false;

View file

@ -32,7 +32,6 @@ import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAllTriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.constants.TargetController;
@ -47,6 +46,7 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetControlledPermanent;
import mage.util.GameLog;
/**
* 702.94. Soulbond
@ -168,7 +168,9 @@ class SoulboundEntersSelfEffect extends OneShotEffect {
Permanent chosen = game.getPermanent(target.getFirstTarget());
if (chosen != null) {
chosen.setPairedCard(new MageObjectReference(permanent, game));
chosen.addInfo("soulbond", "Soulbond to " + GameLog.getColoredObjectIdNameForTooltip(permanent), game);
permanent.setPairedCard(new MageObjectReference(chosen, game));
permanent.addInfo("soulbond", "Soulbond to " + GameLog.getColoredObjectIdNameForTooltip(chosen), game);
if (!game.isSimulation()) {
game.informPlayers(controller.getLogName() + " soulbonds " + permanent.getLogName() + " with " + chosen.getLogName());
}

View file

@ -58,7 +58,7 @@ public enum CardRepository {
// raise this if db structure was changed
private static final long CARD_DB_VERSION = 51;
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 103;
private static final long CARD_CONTENT_VERSION = 104;
private Dao<CardInfo, Object> cardDao;
private Set<String> classNames;

View file

@ -0,0 +1,48 @@
/*
* 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.constants;
/**
*
* @author LevelX2
*/
public enum SpellAbilityCastMode {
NORMAL("Normal"),
MADNESS("Madness");
private final String text;
SpellAbilityCastMode(String text) {
this.text = text;
}
@Override
public String toString() {
return text;
}
}

View file

@ -30,9 +30,7 @@ package mage.game.draft;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import mage.cards.Card;
import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.util.RandomUtil;

View file

@ -1333,6 +1333,10 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
@Override
public void setPairedCard(MageObjectReference pairedCard) {
this.pairedPermanent = pairedCard;
if (pairedCard == null) {
// remove existing soulbond info text
this.addInfo("soulbond", null, null);
}
}
@Override