Merge origin/master

This commit is contained in:
fireshoes 2015-01-07 11:46:44 -06:00
commit f03ac0374d
17 changed files with 605 additions and 62 deletions

View file

@ -106,6 +106,7 @@ class FeldonOfTheThirdPathEffect extends OneShotEffect {
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (card != null) {
EmptyToken token = new EmptyToken();
// This fails if a card will be copied, that uses adjustTargets() method.
CardUtil.copyTo(token).from(card);
token.addAbility(HasteAbility.getInstance());

View file

@ -28,15 +28,18 @@
package mage.sets.darkascension;
import java.util.UUID;
import mage.abilities.Ability;
import mage.constants.AttachmentType;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.continious.GainAbilityAttachedEffect;
import mage.abilities.keyword.EquipAbility;
@ -65,11 +68,20 @@ public class WolfhuntersQuiver extends CardImpl {
this.subtype.add("Equipment");
// Equipped creature has "{tap}: This creature deals 1 damage to target creature or player"
WolfhuntersQuiverAbility ability = new WolfhuntersQuiverAbility(1, new TargetCreatureOrPlayer());
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(ability, AttachmentType.EQUIPMENT)));
// and "{tap}: This creature deals 3 damage to target Werewolf creature."
ability = new WolfhuntersQuiverAbility(3, new TargetCreaturePermanent(filter));
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(ability, AttachmentType.EQUIPMENT)));
Ability abilityToGain = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(1), new TapSourceCost());
abilityToGain.addTarget(new TargetCreatureOrPlayer());
Effect effect = new GainAbilityAttachedEffect(abilityToGain, AttachmentType.EQUIPMENT);
effect.setText("Equipped creature has \"{tap}: This creature deals 1 damage to target creature or player\"");
SimpleStaticAbility ability = new SimpleStaticAbility(Zone.BATTLEFIELD, effect);
// and "{T}: This creature deals 3 damage to target Werewolf creature."
abilityToGain = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(3), new TapSourceCost());
abilityToGain.addTarget(new TargetCreaturePermanent(filter));
effect = new GainAbilityAttachedEffect(abilityToGain, AttachmentType.EQUIPMENT);
effect.setText("and \"{T}: This creature deals 3 damage to target Werewolf creature");
ability.addEffect(effect);
this.addAbility(ability);
// Equip {5}
this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(5)));
}
@ -83,30 +95,3 @@ public class WolfhuntersQuiver extends CardImpl {
return new WolfhuntersQuiver(this);
}
}
class WolfhuntersQuiverAbility extends ActivatedAbilityImpl {
private String ruleText;
public WolfhuntersQuiverAbility(int amount, Target target) {
super(Zone.BATTLEFIELD, new DamageTargetEffect(amount), new TapSourceCost());
this.addTarget(target);
ruleText = "This creature deals " + amount + " damage to target " + target.getTargetName();
}
public WolfhuntersQuiverAbility(WolfhuntersQuiverAbility ability) {
super(ability);
this.ruleText = ability.ruleText;
}
@Override
public WolfhuntersQuiverAbility copy() {
return new WolfhuntersQuiverAbility(this);
}
@Override
public String getRule() {
return ruleText;
}
}

View file

@ -102,7 +102,7 @@ public class NecroticPlague extends CardImpl {
if (creatureController != null) {
ability.setControllerId(creatureController.getId());
ability.getTargets().clear();
TargetCreaturePermanent target = new TargetCreaturePermanent(filter);
TargetPermanent target = new TargetPermanent(filter);
ability.getTargets().add(target);
}
}

View file

@ -0,0 +1,78 @@
/*
* 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.sets.shadowmoor;
import java.util.UUID;
import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.keyword.ConspireAbility;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.filter.FilterPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.target.TargetPermanent;
/**
*
* @author jeffwadsworth
*/
public class GleefulSabotage extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent("target artifact or enchantment");
static {
filter.add(Predicates.or(new CardTypePredicate(CardType.ARTIFACT),
(new CardTypePredicate(CardType.ENCHANTMENT))));
}
public GleefulSabotage(UUID ownerId) {
super(ownerId, 116, "Gleeful Sabotage", Rarity.COMMON, new CardType[]{CardType.SORCERY}, "{1}{G}");
this.expansionSetCode = "SHM";
this.color.setGreen(true);
// Destroy target artifact or enchantment.
this.getSpellAbility().addEffect(new DestroyTargetEffect());
this.getSpellAbility().addTarget(new TargetPermanent(filter));
// Conspire
this.addAbility(new ConspireAbility(this));
}
public GleefulSabotage(final GleefulSabotage card) {
super(card);
}
@Override
public GleefulSabotage copy() {
return new GleefulSabotage(this);
}
}

View file

@ -0,0 +1,62 @@
/*
* 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 org.mage.test.cards.abilities.enters;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class SepulchralPrimordialTest extends CardTestPlayerBase {
@Test
public void testETB2Effect() {
// When Sepulchral Primordial enters the battlefield, for each opponent, you may put up to one
// target creature card from that player's graveyard onto the battlefield under your control.
addCard(Zone.HAND, playerA, "Sepulchral Primordial", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7);
addCard(Zone.GRAVEYARD, playerB, "Silvercoat Lion", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sepulchral Primordial");
addTarget(playerA, "Silvercoat Lion"); // target for ETB Sepulchral Primordial
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Sepulchral Primordial", 1);
assertPermanentCount(playerA, "Silvercoat Lion", 1);
assertLife(playerA, 20);
assertLife(playerB, 20);
}
}

View file

@ -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 org.mage.test.cards.copy;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* Test the funtions of Feldon of the Third Path - {1}{R}{R} Legendary Creature
* - Human Artificer 2/3 {2}{R}, {T} : Put a token onto the battlefield that's a
* copy of target creature card in your graveyard, except it's an artifact in
* addition to its other types. It gains haste. Sacrifice it at the beginning of
* the next end step.
*
*
* @author LevelX2
*/
public class FeldonOfTheThirdPathTest extends CardTestPlayerBase {
/**
* Checking that enters the battlefield abilities of the copied creature
* card works.
*
*/
@Test
public void testETBEffect() {
// When Highway Robber enters the battlefield, target opponent loses 2 life and you gain 2 life.
addCard(Zone.GRAVEYARD, playerA, "Highway Robber", 1);
addCard(Zone.BATTLEFIELD, playerA, "Feldon of the Third Path", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA,
"{2}{R},{T}: Put a token onto the battlefield that's a copy of target creature card in your graveyard, except it's an artifact in addition to its other types. It gains haste. Sacrifice it at the beginning of the next end step.",
"Highway Robber");
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Highway Robber", 1);
assertPermanentCount(playerA, "Feldon of the Third Path", 1);
assertLife(playerA, 22); // +2 from Robber
assertLife(playerB, 18); // -2 from Robber
}
@Test
public void testETB2Effect() {
// When Sepulchral Primordial enters the battlefield, for each opponent, you may put up to one
// target creature card from that player's graveyard onto the battlefield under your control.
addCard(Zone.GRAVEYARD, playerA, "Sepulchral Primordial", 1);
addCard(Zone.BATTLEFIELD, playerA, "Feldon of the Third Path", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
addCard(Zone.GRAVEYARD, playerB, "Silvercoat Lion", 1);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA,
"{2}{R},{T}: Put a token onto the battlefield that's a copy of target creature card in your graveyard, except it's an artifact in addition to its other types. It gains haste. Sacrifice it at the beginning of the next end step.",
"Sepulchral Primordial");
addTarget(playerA, "Silvercoat Lion"); // target for ETB Sepulchral Primordial
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Feldon of the Third Path", 1);
assertPermanentCount(playerA, "Sepulchral Primordial", 1);
assertPermanentCount(playerA, "Silvercoat Lion", 1);
assertLife(playerA, 20);
assertLife(playerB, 20);
}
}

View file

@ -277,7 +277,7 @@ public abstract class AbilityImpl implements Ability {
// and/or zones become the target of a spell trigger at this point; they'll wait to be put on
// the stack until the spell has finished being cast.)
if (sourceObject != null) {
if (sourceObject != null && !this.getAbilityType().equals(AbilityType.TRIGGERED)) { // triggered abilities check this already TriggeredAbilities.checkTriggers()
sourceObject.adjustTargets(this, game);
}
if (getTargets().size() > 0 && getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, game) == false) {

View file

@ -44,7 +44,6 @@ import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import org.apache.log4j.Logger;
/**
*

View file

@ -72,7 +72,7 @@ public class LimitedTimesPerTurnActivatedAbility extends ActivatedAbilityImpl {
@Override
public boolean canActivate(UUID playerId, Game game) {
if (super.canActivate(playerId, game)) {
ActivationInfo activationInfo = (ActivationInfo) game.getState().getValue(CardUtil.getCardZoneString("activations", sourceId, game));
ActivationInfo activationInfo = getActivationInfo(game);
return activationInfo == null || activationInfo.turnNum != game.getTurnNum() || activationInfo.activationCounter < maxActivationsPerTurn;
}
return false;
@ -82,7 +82,7 @@ public class LimitedTimesPerTurnActivatedAbility extends ActivatedAbilityImpl {
public boolean activate(Game game, boolean noMana) {
if (canActivate(this.controllerId, game)) {
if (super.activate(game, noMana)) {
ActivationInfo activationInfo = (ActivationInfo) game.getState().getValue(CardUtil.getCardZoneString("activations", sourceId, game));
ActivationInfo activationInfo = getActivationInfo(game);
if (activationInfo == null) {
activationInfo = new ActivationInfo(game.getTurnNum(), 1);
} else {
@ -93,7 +93,7 @@ public class LimitedTimesPerTurnActivatedAbility extends ActivatedAbilityImpl {
activationInfo.activationCounter++;
}
}
game.getState().setValue(CardUtil.getCardZoneString("activations", sourceId, game), activationInfo);
setActivationInfo(activationInfo, game);
return true;
}
}
@ -126,5 +126,17 @@ public class LimitedTimesPerTurnActivatedAbility extends ActivatedAbilityImpl {
public LimitedTimesPerTurnActivatedAbility copy() {
return new LimitedTimesPerTurnActivatedAbility(this);
}
private ActivationInfo getActivationInfo(Game game) {
Integer turnNum = (Integer) game.getState().getValue(CardUtil.getCardZoneString("activationsTurn", sourceId, game));
Integer activationCount = (Integer) game.getState().getValue(CardUtil.getCardZoneString("activationsCount", sourceId, game));
if (turnNum == null || activationCount == null) {
return null;
}
return new ActivationInfo(turnNum, activationCount);
}
private void setActivationInfo(ActivationInfo activationInfo, Game game) {
game.getState().setValue(CardUtil.getCardZoneString("activationsTurn", sourceId, game), activationInfo.turnNum);
game.getState().setValue(CardUtil.getCardZoneString("activationsCount", sourceId, game), activationInfo.activationCounter);
}
}

View file

@ -0,0 +1,256 @@
/*
* 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.keyword;
import java.util.Iterator;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.common.TapTargetCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.SharesColorWithSourcePredicate;
import mage.filter.predicate.permanent.TappedPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.common.TargetControlledPermanent;
/**
* 702.77. Conspire 702.77a Conspire is a keyword that represents two abilities.
* The first is a static ability that functions while the spell with conspire is
* on the stack. The second is a triggered ability that functions while the
* spell with conspire is on the stack. "Conspire" means "As an additional cost
* to cast this spell, you may tap two untapped creatures you control that each
* share a color with it" and "When you cast this spell, if its conspire cost
* was paid, copy it. If the spell has any targets, you may choose new targets
* for the copy." Paying a spells conspire cost follows the rules for paying
* additional costs in rules 601.2b and 601.2eg. 702.77b If a spell has
* multiple instances of conspire, each is paid separately and triggers based on
* its own payment, not any other instance of conspire. *
*
* @author jeffwadsworth heavily based off the replicate keyword by LevelX
*/
public class ConspireAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
private static final String keywordText = "Conspire";
private static final String reminderTextCost = "<i>As you cast this spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose a new target for the copy.)</i>";
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("two untapped creatures you control that share a color with it");
static {
filter.add(Predicates.not(new TappedPredicate()));
filter.add(new SharesColorWithSourcePredicate());
}
Cost costConspire = new TapTargetCost(new TargetControlledPermanent(2, 2, filter, false));
OptionalAdditionalCost conspireCost = new OptionalAdditionalCostImpl(keywordText, "-", reminderTextCost, costConspire);
public ConspireAbility(Card card) {
super(Zone.STACK, null);
setRuleAtTheTop(false);
card.addAbility(new ConspireTriggeredAbility());
}
public ConspireAbility(final ConspireAbility ability) {
super(ability);
conspireCost = ability.conspireCost;
}
@Override
public ConspireAbility copy() {
return new ConspireAbility(this);
}
@Override
public void addCost(Cost cost) {
if (conspireCost != null) {
((Costs) conspireCost).add(cost);
}
}
@Override
public boolean isActivated() {
if (conspireCost != null) {
return conspireCost.isActivated();
}
return false;
}
public void resetConspire() {
if (conspireCost != null) {
conspireCost.reset();
}
}
@Override
public void addOptionalAdditionalCosts(Ability ability, Game game) {
if (ability instanceof SpellAbility) {
Player player = game.getPlayer(controllerId);
if (player != null) {
this.resetConspire();
if (player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(conspireCost.getText(false)).append(" ?").toString(), game)) {
conspireCost.activate();
for (Iterator it = ((Costs) conspireCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
ability.getCosts().add(cost.copy());
}
}
}
}
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
if (conspireCost != null) {
sb.append(conspireCost.getText(false));
sb.append(" ").append(conspireCost.getReminderText());
}
return sb.toString();
}
@Override
public String getCastMessageSuffix() {
if (conspireCost != null) {
return conspireCost.getCastSuffixMessage(0);
} else {
return "";
}
}
public String getReminderText() {
if (conspireCost != null) {
return conspireCost.getReminderText();
} else {
return "";
}
}
}
class ConspireTriggeredAbility extends TriggeredAbilityImpl {
public ConspireTriggeredAbility() {
super(Zone.STACK, new ConspireEffect());
this.setRuleVisible(false);
}
private ConspireTriggeredAbility(final ConspireTriggeredAbility ability) {
super(ability);
}
@Override
public ConspireTriggeredAbility copy() {
return new ConspireTriggeredAbility(this);
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.SPELL_CAST
&& event.getSourceId().equals(this.sourceId)) {
StackObject spell = game.getStack().getStackObject(this.sourceId);
if (spell instanceof Spell) {
Card card = game.getCard(spell.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof ConspireAbility) {
if (((ConspireAbility) ability).isActivated()) {
for (Effect effect : this.getEffects()) {
effect.setValue("ConspireSpell", spell);
}
return true;
}
}
}
}
}
}
return false;
}
@Override
public String getRule() {
return "Conspire: <i>As you cast this spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose a new target for the copy.)</i>";
}
}
class ConspireEffect extends OneShotEffect {
public ConspireEffect() {
super(Outcome.Copy);
}
public ConspireEffect(final ConspireEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Spell spell = (Spell) this.getValue("ConspireSpell");
if (spell != null) {
Card card = game.getCard(spell.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof ConspireAbility) {
if (((ConspireAbility) ability).isActivated()) {
((ConspireAbility) ability).resetConspire();
}
}
}
Spell copy = spell.copySpell();
copy.setControllerId(source.getControllerId());
copy.setCopiedSpell(true);
game.getStack().push(copy);
copy.chooseNewTargets(game, source.getControllerId());
game.informPlayers(new StringBuilder(controller.getName()).append(copy.getActivatedMessage(game)).toString());
return true;
}
}
}
return false;
}
@Override
public ConspireEffect copy() {
return new ConspireEffect(this);
}
}

View file

@ -71,7 +71,7 @@ public class ActivateOncePerTurnManaAbility extends ManaAbility {
@Override
public boolean canActivate(UUID playerId, Game game) {
if (super.canActivate(playerId, game)) {
ActivationInfo activationInfo = (ActivationInfo) game.getState().getValue(CardUtil.getCardZoneString("activations", sourceId, game));
ActivationInfo activationInfo = getActivationInfo(game);
if (activationInfo == null || activationInfo.turnNum != game.getTurnNum() || activationInfo.activationCounter < 1) {
return true;
}
@ -83,7 +83,7 @@ public class ActivateOncePerTurnManaAbility extends ManaAbility {
public boolean activate(Game game, boolean noMana) {
if (canActivate(this.controllerId, game)) {
if (super.activate(game, noMana)) {
ActivationInfo activationInfo = (ActivationInfo) game.getState().getValue(CardUtil.getCardZoneString("activations", sourceId, game));
ActivationInfo activationInfo = getActivationInfo(game);
if (activationInfo == null) {
activationInfo = new ActivationInfo(game.getTurnNum(), 1);
} else {
@ -94,21 +94,13 @@ public class ActivateOncePerTurnManaAbility extends ManaAbility {
activationInfo.activationCounter++;
}
}
game.getState().setValue(CardUtil.getCardZoneString("activations", sourceId, game), activationInfo);
setActivationInfo(activationInfo, game);
return true;
}
}
return false;
}
@Override
public boolean resolve(Game game) {
if (super.resolve(game)) {
return true;
}
return false;
}
@Override
public String getRule() {
return super.getRule() + " Activate this ability only once each turn.";
@ -119,4 +111,18 @@ public class ActivateOncePerTurnManaAbility extends ManaAbility {
return new ActivateOncePerTurnManaAbility(this);
}
private ActivationInfo getActivationInfo(Game game) {
Integer turnNum = (Integer) game.getState().getValue(CardUtil.getCardZoneString("activationsTurn", sourceId, game));
Integer activationCount = (Integer) game.getState().getValue(CardUtil.getCardZoneString("activationsCount", sourceId, game));
if (turnNum == null || activationCount == null) {
return null;
}
return new ActivationInfo(turnNum, activationCount);
}
private void setActivationInfo(ActivationInfo activationInfo, Game game) {
game.getState().setValue(CardUtil.getCardZoneString("activationsTurn", sourceId, game), activationInfo.turnNum);
game.getState().setValue(CardUtil.getCardZoneString("activationsCount", sourceId, game), activationInfo.activationCounter);
}
}

View file

@ -152,13 +152,8 @@ public class GameState implements Serializable, Copyable<GameState> {
this.turnMods = state.turnMods.copy();
this.watchers = state.watchers.copy();
for (Map.Entry<String, Object> entry: state.values.entrySet()) {
if (entry.getValue() instanceof Boolean) { // AI changed values of Boolean for cards like Wall of Roots TODO: copy other types than Boolean
this.values.put(entry.getKey(), Boolean.valueOf(((Boolean)entry.getValue()).toString()));
} else {
this.values.put(entry.getKey(), entry.getValue());
}
}
this.zones.putAll(state.zones);
for (Map.Entry<UUID, Abilities<Ability>> entry: state.otherAbilities.entrySet()) {
otherAbilities.put(entry.getKey(), entry.getValue().copy());
@ -646,6 +641,14 @@ public class GameState implements Serializable, Copyable<GameState> {
return values.get(valueId);
}
/**
* Best only use immutable objects, otherwise the states/values of the object may be
* changed by AI simulation, because the Value objects are not copied as the state
* class is copied.
*
* @param valueId
* @param value
*/
public void setValue(String valueId, Object value) {
values.put(valueId, value);
}

View file

@ -134,4 +134,24 @@ public class PermanentToken extends PermanentImpl {
abilities.add(copyAbility);
}
}
@Override
public void adjustTargets(Ability ability, Game game) {
if (getToken().getCopySourceCard() != null) {
getToken().getCopySourceCard().adjustTargets(ability, game);
} else {
super.adjustTargets(ability, game);
}
}
@Override
public void adjustCosts(Ability ability, Game game) {
if (getToken().getCopySourceCard() != null) {
getToken().getCopySourceCard().adjustCosts(ability, game);
} else {
super.adjustCosts(ability, game);
}
}
}

View file

@ -31,13 +31,13 @@ package mage.game.permanent.token;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.MageObjectImpl;
import mage.ObjectColor;
import mage.abilities.Abilities;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
@ -55,6 +55,7 @@ public class Token extends MageObjectImpl {
private int tokenType;
private int originalCardNumber;
private String originalExpansionSetCode;
private Card copySourceCard; // the card the Token is a copy from
public enum Type {
FIRST(1),
@ -96,6 +97,7 @@ public class Token extends MageObjectImpl {
this.lastAddedTokenIds.addAll(token.lastAddedTokenIds);
this.originalCardNumber = token.originalCardNumber;
this.originalExpansionSetCode = token.originalExpansionSetCode;
this.copySourceCard = token.copySourceCard; // will never be changed
}
public String getDescription() {
@ -193,4 +195,15 @@ public class Token extends MageObjectImpl {
this.originalExpansionSetCode = originalExpansionSetCode;
}
public Card getCopySourceCard() {
return copySourceCard;
}
public void setCopySourceCard(Card copySourceCard) {
if (copySourceCard != null) {
this.copySourceCard = copySourceCard.copy();
}
}
}

View file

@ -1113,7 +1113,11 @@ public abstract class PlayerImpl implements Player, Serializable {
//20091005 - 603.3c, 603.3d
int bookmark = game.bookmarkState();
TriggeredAbility ability = source.copy();
if (ability != null && ability.canChooseTarget(game)) {
MageObject sourceObject = game.getObject(ability.getSourceId());
if (sourceObject != null) {
sourceObject.adjustTargets(ability, game);
}
if (ability.canChooseTarget(game)) {
if (ability.isUsesStack()) {
game.getStack().push(new StackAbility(ability, playerId));
}

View file

@ -163,7 +163,7 @@ public class CardUtil {
for (AlternativeCost alternativeCost : ability.getAlternativeCosts()) {
if (alternativeCost instanceof AlternativeCostImpl) {
AlternativeCostImpl impl = (AlternativeCostImpl) alternativeCost;
ManaCosts<ManaCost> adjustedCost = new ManaCostsImpl<ManaCost>();
ManaCosts<ManaCost> adjustedCost = new ManaCostsImpl<>();
boolean updated = false;
Iterator it = impl.iterator();
while (it.hasNext()) {

View file

@ -62,13 +62,18 @@ public class CopyTokenFunction implements Function<Token, Card> {
// to show the source image, the original values have to be used
target.setOriginalExpansionSetCode(((Token)sourceObj).getOriginalExpansionSetCode());
target.setOriginalCardNumber(((Token)sourceObj).getOriginalCardNumber());
target.setCopySourceCard(((PermanentToken)source).getToken().getCopySourceCard());
} else if (source instanceof PermanentCard) {
sourceObj = ((PermanentCard) source).getCard();
target.setOriginalExpansionSetCode(source.getExpansionSetCode());
target.setOriginalCardNumber(source.getCardNumber());
target.setCopySourceCard((Card)sourceObj);
} else {
target.setOriginalExpansionSetCode(source.getExpansionSetCode());
target.setOriginalCardNumber(source.getCardNumber());
if (source instanceof Card) {
target.setCopySourceCard((Card)source);
}
}
target.setName(sourceObj.getName());