Reworked Kicker.

This commit is contained in:
LevelX2 2012-12-08 02:20:29 +01:00
parent 4eccfa716c
commit 67ed36e315
18 changed files with 681 additions and 275 deletions

View file

@ -32,7 +32,6 @@ import java.io.Serializable;
import java.util.List;
import java.util.UUID;
import mage.Constants.Zone;
import mage.abilities.keyword.KickerAbility;
import mage.abilities.keyword.ProtectionAbility;
import mage.abilities.mana.ManaAbility;
import mage.game.Game;
@ -142,16 +141,6 @@ public interface Abilities<T extends Ability> extends List<T>, Serializable {
*/
public Abilities<ProtectionAbility> getProtectionAbilities();
/**
* Retrieves all {@link KickerAbility kicker abilities}.
*
* @return All found {@link KickerAbility kicker abilities}.
*
* @see mage.players.PlayerImpl#cast(mage.abilities.SpellAbility, mage.game.Game, boolean)
* @see mage.game.stack.Spell#resolveKicker(mage.game.Game)
*/
public Abilities<KickerAbility> getKickerAbilities();
/**
* TODO Method is unused, keep it around?
*

View file

@ -28,20 +28,18 @@
package mage.abilities;
import mage.Constants.Zone;
import mage.abilities.common.ZoneChangeTriggeredAbility;
import mage.abilities.costs.AlternativeCost;
import mage.abilities.costs.Cost;
import mage.abilities.keyword.KickerAbility;
import mage.abilities.keyword.ProtectionAbility;
import mage.abilities.mana.ManaAbility;
import mage.game.Game;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import mage.abilities.costs.mana.KickerManaCost;
import mage.Constants.Zone;
import mage.abilities.common.ZoneChangeTriggeredAbility;
import mage.abilities.costs.AlternativeCost;
import mage.abilities.costs.Cost;
import mage.abilities.keyword.ProtectionAbility;
import mage.abilities.mana.ManaAbility;
import mage.game.Game;
/**
*
@ -81,10 +79,6 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
}
}
if (ability instanceof SpellAbility) {
String kickerRule = getKickerRule(ability);
if (!kickerRule.isEmpty()) {
rules.add(kickerRule);
}
if (ability.getAlternativeCosts().size() > 0) {
StringBuilder sbRule = new StringBuilder();
for (AlternativeCost cost: ability.getAlternativeCosts()) {
@ -122,25 +116,6 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
return rules;
}
private String getKickerRule(Ability ability) {
StringBuilder sb = new StringBuilder();
int numberKicker = 0;
for (Object cost : ability.getOptionalCosts()) {
if (cost instanceof KickerManaCost) {
if (numberKicker == 0) {
sb.append(((KickerManaCost)cost).getText(true));
} else {
sb.append(" and/or ").append(((KickerManaCost)cost).getText(true));
}
++numberKicker;
}
}
if (numberKicker > 0) {
return "Kicker " + sb.toString() + " <i>(You may pay an additional " + sb.toString() + " as you cast this spell.)</i>";
}
return sb.toString();
}
@Override
public Abilities<ActivatedAbility> getActivatedAbilities(Zone zone) {
Abilities<ActivatedAbility> zonedAbilities = new AbilitiesImpl<ActivatedAbility>();
@ -168,8 +143,9 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
Abilities<ManaAbility> abilities = new AbilitiesImpl<ManaAbility>();
for (T ability: this) {
if (ability instanceof ManaAbility && ability.getZone().match(zone)) {
if ((((ManaAbility)ability).canActivate(ability.getControllerId(), game)))
if ((((ManaAbility)ability).canActivate(ability.getControllerId(), game))) {
abilities.add((ManaAbility)ability);
}
}
}
return abilities;
@ -225,17 +201,6 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
return abilities;
}
@Override
public Abilities<KickerAbility> getKickerAbilities() {
Abilities<KickerAbility> abilities = new AbilitiesImpl<KickerAbility>();
for (T ability: this) {
if (ability instanceof KickerAbility) {
abilities.add((KickerAbility)ability);
}
}
return abilities;
}
@Override
public void setControllerId(UUID controllerId) {
for (Ability ability: this) {
@ -276,11 +241,13 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
@Override
public boolean containsAll(Abilities<T> abilities) {
if (this.size() < abilities.size())
if (this.size() < abilities.size()) {
return false;
}
for (T ability: abilities) {
if (!contains(ability))
if (!contains(ability)) {
return false;
}
}
return true;
}
@ -288,8 +255,9 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
@Override
public boolean containsKey(UUID abilityId) {
for (T ability: this) {
if (ability.getId().equals(abilityId))
if (ability.getId().equals(abilityId)) {
return true;
}
}
return false;
}
@ -297,8 +265,9 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
@Override
public T get(UUID abilityId) {
for (T ability: this) {
if (ability.getId().equals(abilityId))
if (ability.getId().equals(abilityId)) {
return ability;
}
}
return null;
}

View file

@ -28,17 +28,28 @@
package mage.abilities;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.Constants.AbilityType;
import mage.Constants.EffectType;
import mage.Constants.Outcome;
import mage.Constants.Zone;
import mage.MageObject;
import mage.abilities.costs.*;
import mage.abilities.costs.mana.KickerManaCost;
import mage.abilities.costs.AdjustingSourceCosts;
import mage.abilities.costs.AlternativeCost;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.*;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.PostResolveEffect;
import mage.abilities.mana.ManaAbility;
import mage.cards.Card;
import mage.choices.Choice;
@ -49,9 +60,6 @@ import mage.target.Target;
import mage.target.Targets;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
@ -144,8 +152,9 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
if (checkIfClause(game)) {
for (Effect effect: getEffects()) {
if (effect instanceof OneShotEffect) {
if (!(effect instanceof PostResolveEffect))
if (!(effect instanceof PostResolveEffect)) {
result &= effect.apply(game, this);
}
}
else {
game.addEffect((ContinuousEffect) effect, this);
@ -158,8 +167,9 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
@Override
public boolean activate(Game game, boolean noMana) {
// 20110204 - 700.2
if (!modes.choose(game, this))
if (!modes.choose(game, this)) {
return false;
}
//20100716 - 601.2b
Card card = game.getCard(sourceId);
if (card != null) {
@ -170,22 +180,29 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
return false;
}
// 20121001 - 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
// or her intentions to pay any or all of those costs (see rule 601.2e).
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof OptionalAdditionalSourceCosts) {
((OptionalAdditionalSourceCosts)ability).addOptionalAdditionalCosts(this, game);
}
}
}
//20121001 - 601.2c
if (card != null) {
card.adjustTargets(this, game);
}
//20100716 - 601.2b
if (getTargets().size() > 0 && getTargets().chooseTargets(getEffects().get(0).getOutcome(), this.controllerId, this, game) == false) {
logger.debug("activate failed - target");
return false;
}
for (Cost cost : optionalCosts) {
if (cost instanceof KickerManaCost) {
cost.clearPaid();
if (game.getPlayer(this.controllerId).chooseUse(Outcome.Benefit, "Pay " + cost.getText() + "?", game)) {
manaCostsToPay.add((ManaCost) cost);
}
} else if (cost instanceof ManaCost) {
if (cost instanceof ManaCost) {
cost.clearPaid();
if (game.getPlayer(this.controllerId).chooseUse(Outcome.Benefit, "Pay optional cost " + cost.getText() + "?", game)) {
manaCostsToPay.add((ManaCost) cost);
@ -454,8 +471,9 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
@Override
public boolean canChooseTarget(Game game) {
for (Mode mode: modes.values()) {
if (mode.getTargets().canChoose(sourceId, controllerId, game))
if (mode.getTargets().canChoose(sourceId, controllerId, game)) {
return true;
}
}
return false;
}
@ -477,15 +495,15 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
}
MageObject object;
UUID sourceId;
UUID parameterSourceId;
// for singleton abilities like Flying we can't rely on abilities' source
// so will use the one that came as a parameter if it is not null
if (this instanceof MageSingleton && source != null) {
object = source;
sourceId = source.getId();
parameterSourceId = source.getId();
} else {
object = game.getObject(getSourceId());
sourceId = getSourceId();
parameterSourceId = getSourceId();
}
if (object != null && !object.getAbilities().contains(this)) {
@ -501,7 +519,7 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
}
// check against current state
Zone test = game.getState().getZone(sourceId);
Zone test = game.getState().getZone(parameterSourceId);
return test != null && zone.match(test);
}

View file

@ -28,26 +28,24 @@
package mage.abilities;
import java.util.UUID;
import mage.Constants.AbilityType;
import mage.Constants.TimingRule;
import mage.Constants.Zone;
import mage.MageObject;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.PhyrexianManaCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.cards.Card;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.game.stack.StackAbility;
import mage.target.Target;
import java.util.UUID;
import mage.abilities.costs.mana.KickerManaCost;
import mage.abilities.keyword.MultikickerAbility;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
/**
*
@ -82,8 +80,9 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
if (effect != null) {
this.addEffect(effect);
}
if (cost != null)
if (cost != null) {
this.addManaCost(cost);
}
}
public ActivatedAbilityImpl(Zone zone, Effects effects, ManaCosts cost) {
@ -93,8 +92,9 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
this.addEffect(effect);
}
}
if (cost != null)
if (cost != null) {
this.addManaCost(cost);
}
}
public ActivatedAbilityImpl(Zone zone, Effect effect, Cost cost) {
@ -130,8 +130,9 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
this.addEffect(effect);
}
}
if (cost != null)
if (cost != null) {
this.addCost(cost);
}
}
public ActivatedAbilityImpl(Zone zone, Effects effects, Costs<Cost> costs) {
@ -151,8 +152,9 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
@Override
public boolean canActivate(UUID playerId, Game game) {
//20091005 - 602.2
if (!controlsAbility(playerId, game))
if (!controlsAbility(playerId, game)) {
return false;
}
//20091005 - 602.5d/602.5e
if (timing == TimingRule.INSTANT || game.canPlaySorcery(playerId)) {
if (costs.canPay(sourceId, controllerId, game) && canChooseTarget(game)) {
@ -163,12 +165,14 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
}
protected boolean controlsAbility(UUID playerId, Game game) {
if (this.controllerId != null && this.controllerId.equals(playerId))
if (this.controllerId != null && this.controllerId.equals(playerId)) {
return true;
}
else {
Card card = (Card)game.getObject(this.sourceId);
if (card != null && game.getState().getZone(this.sourceId) != Zone.BATTLEFIELD)
if (card != null && game.getState().getZone(this.sourceId) != Zone.BATTLEFIELD) {
return card.getOwnerId().equals(playerId);
}
}
return false;
}
@ -200,7 +204,7 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
if (spell.getFromZone() == Zone.GRAVEYARD) {
sb.append(" from graveyard");
}
sb.append(getKickerText(game, spell));
sb.append(getOptionalTextSuffix(game, spell));
} else {
sb.append(object.getName());
}
@ -217,37 +221,13 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
return sb.toString();
}
String getKickerText(Game game, Spell spell) {
String getOptionalTextSuffix(Game game, Spell spell) {
StringBuilder sb = new StringBuilder();
int numberPaid = 0;
for (Object cost : spell.getSpellAbility().getOptionalCosts()) {
if (cost instanceof KickerManaCost) {
if (((KickerManaCost) cost).isPaid()) {
if (numberPaid == 0) {
sb.append(" with ").append(((KickerManaCost)cost).getText(true));
} else {
sb.append(" and ").append(((KickerManaCost)cost).getText(true));
}
++numberPaid;
}
for (Ability ability : (Abilities<Ability>) spell.getAbilities()) {
if (ability instanceof OptionalAdditionalSourceCosts) {
sb.append(((OptionalAdditionalSourceCosts) ability).getCastMessageSuffix());
}
}
if (numberPaid > 0) {
sb.append(" kicker");
}
// Multikicker
int multikickerCount = 0;
Card card = game.getCard(this.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof MultikickerAbility) {
multikickerCount = ((MultikickerAbility)ability).getActivateCount();
}
}
}
if (multikickerCount > 0) {
sb.append(" with ").append(multikickerCount).append(multikickerCount > 1? " times":" time").append(" multikicker");
}
return sb.toString();
}
}

View file

@ -1,15 +1,45 @@
/*
* 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.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.costs.mana.KickerManaCost;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.keyword.KickerAbility;
import mage.cards.Card;
import mage.game.Game;
/**
* Describes condition when spell was kicked.
*
* @author nantuko
* @author LevelX2
*/
public class KickedCondition implements Condition {
@ -26,19 +56,23 @@ public class KickedCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
Card p = game.getCard(source.getSourceId());
boolean kicked = false;
if (p != null) {
for (Object cost : p.getSpellAbility().getOptionalCosts()) {
if (cost instanceof KickerManaCost) {
if (((KickerManaCost) cost).isPaid()) {
kicked = true;
}
Card card = game.getCard(source.getSourceId());
if (card != null) {
KickerAbility kickerAbility = null;
for (Ability ability: card.getAbilities()) {
if (ability instanceof KickerAbility) {
kickerAbility = (KickerAbility) ability;
}
}
boolean kicked = false;
if (kickerAbility != null) {
for (OptionalAdditionalCost cost: kickerAbility.getKickerCosts()) {
kicked = cost.isActivated();
break;
}
}
return kicked;
}
return kicked;
return false;
}
}

View file

@ -2,7 +2,9 @@ package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.mana.KickerManaCost;
import mage.abilities.keyword.KickerAbility;
import mage.cards.Card;
import mage.game.Game;
@ -21,18 +23,22 @@ public class KickedCostCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
Card p = game.getCard(source.getSourceId());
boolean kicked = false;
if (p != null) {
for (Object cost : p.getSpellAbility().getOptionalCosts()) {
if (cost.equals(kickerManaCost)) {
if (((KickerManaCost) cost).isPaid()) {
kicked = true;
Card card = game.getCard(source.getSourceId());
if (card != null) {
KickerAbility kickerAbility = null;
for (Ability ability: card.getAbilities()) {
if (ability instanceof KickerAbility) {
kickerAbility = (KickerAbility) ability;
}
}
if (kickerAbility != null) {
for (OptionalAdditionalCost cost: kickerAbility.getKickerCosts()) {
if (cost.equals(kickerManaCost)) {
return cost.isActivated();
}
}
}
}
return kicked;
return false;
}
}

View file

@ -0,0 +1,97 @@
/*
* 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.costs;
/**
* @author LevelX2
*/
public interface OptionalAdditionalCost extends Cost {
public String getName();
/**
* Returns the complete text for the addional coast or if onlyCost is true
* only the pure text fore the included native cost
*
* @param onlyCost
* @return
*/
public String getText(boolean onlyCost);
/**
* Returns a reminder text, if the cost has one
*
* @return
*/
public String getReminderText();
/**
* Returns a text suffix for the game log, that can be added to
* the cast message.
*
* @param position - if there are multiple costs, it's the postion the cost is set (starting with 0)
* @return
*/
public String getCastSuffixMessage(int position);
/**
* If the player intends to pay the cost, the cost will be activated
*
* @param activated
*/
public void activate();
/**
* Reset the activate and count information
*
*/
public void reset();
/**
* Can the cost be multiple times activated
*
* @return
*/
public boolean isRepeatable();
/**
* Returns if the cost was activated
*
* @return
*/
public boolean isActivated();
/**
* Returns the number of times the cost was activated
* @return
*/
public int getActivateCount();
}

View file

@ -0,0 +1,165 @@
/*
* 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.costs;
/**
*
* @author LevelX2
*/
public class OptionalAdditionalCostImpl <T extends OptionalAdditionalCostImpl<T>> extends CostsImpl<Cost> implements OptionalAdditionalCost{
protected String name;
protected String reminderText;
protected boolean activated;
protected int activatedCounter;
protected boolean repeatable;
public OptionalAdditionalCostImpl(String name, String reminderText, Cost cost) {
this.activated = false;
this.name = name;
this.reminderText = reminderText;
this.activatedCounter = 0;
this.add((Cost) cost);
}
public OptionalAdditionalCostImpl(final OptionalAdditionalCostImpl cost) {
super(cost);
this.name = cost.name;
this.reminderText = cost.reminderText;
this.activated = cost.activated;
this.activatedCounter = cost.activatedCounter;
}
@Override
public String getName() {
return this.name;
}
/**
* Returns the complete text for the addional coast or if onlyCost is true
* only the pure text fore the included native cost
*
* @param onlyCost
* @return
*/
@Override
public String getText(boolean onlyCost) {
if (onlyCost) {
return getText();
} else {
return name + " " + getText();
}
}
/**
* Returns a reminder text, if the cost has one
*
* @return
*/
@Override
public String getReminderText() {
String replace = "";
if (reminderText != null && !reminderText.isEmpty()) {
replace = reminderText.replace("{cost}", this.getText(true));
}
return replace;
}
/**
* Returns a text suffix for the game log, that can be added to
* the cast message.
*
* @param position - if there are multiple costs, it's the postion the cost is set (starting with 0)
* @return
*/
@Override
public String getCastSuffixMessage(int position) {
return "";
}
/**
* If the player intends to pay the cost, the cost will be activated
*
* @param activated
*/
@Override
public void activate() {
activated = true;
++activatedCounter;
};
/**
* Reset the activate and count information
*
*/
@Override
public void reset() {
activated = false;
activatedCounter = 0;
}
/**
* Can the cost be multiple times activated
*
* @return
*/
@Override
public boolean isRepeatable() {
return repeatable;
};
/**
* Returns if the cost was activated
*
* @return
*/
@Override
public boolean isActivated(){
return activated;
};
/**
* Returns the number of times the cost was activated
* @return
*/
@Override
public int getActivateCount(){
return activatedCounter;
};
@Override
public OptionalAdditionalCostImpl copy() {
return new OptionalAdditionalCostImpl(this);
}
}

View file

@ -0,0 +1,43 @@
/*
* 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.costs;
import mage.abilities.Ability;
import mage.game.Game;
/**
* Interface for abilities that add addional costs to the source.
*
* Example of such additional source costs: {@link mage.abilities.keyword.KickerAbility}
*
* @author LevelX2
*/
public interface OptionalAdditionalSourceCosts {
void addOptionalAdditionalCosts(Ability ability, Game game);
String getCastMessageSuffix();
}

View file

@ -1,12 +1,44 @@
/*
* 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.costs.mana;
import mage.abilities.costs.OptionalAdditionalCostImpl;
/**
* This cost must be used only as optional mana cost in cards with Kicker
* @author Loki
*/
public class KickerManaCost extends ManaCostsImpl {
* This cost defines the Kicker cost
*
* @author LevelX2
*/
public class KickerManaCost extends OptionalAdditionalCostImpl {
public KickerManaCost(String manaString) {
super(manaString);
super("Kicker","(You may pay an additional {cost} as you cast this spell.)",new ManaCostsImpl(manaString));
}
public KickerManaCost(final KickerManaCost cost) {
@ -18,17 +50,12 @@ public class KickerManaCost extends ManaCostsImpl {
return new KickerManaCost(this);
}
public String getText(boolean onlyMana) {
if (onlyMana) {
return super.getText();
@Override
public String getCastSuffixMessage(int position) {
if (position == 0) {
return " with " + getText(false);
} else {
return this.getText();
return " and " + getText(true);
}
}
@Override
public String getText() {
return "Kicker - " + super.getText();
}
}
}

View file

@ -0,0 +1,58 @@
/*
* 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.costs.mana;
import mage.abilities.costs.OptionalAdditionalCostImpl;
/**
*
* @author LevelX2
*/
public class MultikickerManaCost extends OptionalAdditionalCostImpl{
public MultikickerManaCost(String manaString) {
super("Multikicker","(You may pay an additional {cost} any number of times as you cast this spell.)",new ManaCostsImpl(manaString));
repeatable = true;
}
public MultikickerManaCost(final MultikickerManaCost cost) {
super(cost);
}
@Override
public MultikickerManaCost copy() {
return new MultikickerManaCost(this);
}
@Override
public String getCastSuffixMessage(int position) {
return (position > 0 ? " and ":"") + " with " + getActivateCount() + (getActivateCount() > 1? " times":" time") + " multikicker";
}
}

View file

@ -28,10 +28,13 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.mana.KickerManaCost;
import mage.abilities.costs.mana.MultikickerManaCost;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.keyword.MultikickerAbility;
import mage.abilities.keyword.KickerAbility;
import mage.cards.Card;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
@ -44,14 +47,23 @@ public class MultikickerCount implements DynamicValue {
@Override
public int calculate(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
for (Ability ability : permanent.getAbilities()) {
if (ability instanceof MultikickerAbility) {
return ((MultikickerAbility)ability).getActivateCount();
}
int count = 0;
Card card = game.getCard(source.getSourceId());
if (card != null) {
KickerAbility kickerAbility = null;
for (Ability ability: card.getAbilities()) {
if (ability instanceof KickerAbility) {
kickerAbility = (KickerAbility) ability;
}
}
if (kickerAbility != null) {
for (OptionalAdditionalCost cost: kickerAbility.getKickerCosts()) {
count = cost.getActivateCount();
break;
}
}
return count;
}
return 0;
}

View file

@ -50,6 +50,7 @@ public class AffinityForArtifactsAbility extends SimpleStaticAbility implements
public AffinityForArtifactsAbility() {
super(Constants.Zone.OUTSIDE, new AffinityEffect(filter));
setRuleAtTheTop(true);
}
public AffinityForArtifactsAbility(final AffinityForArtifactsAbility ability) {

View file

@ -28,89 +28,123 @@
package mage.abilities.keyword;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import mage.Constants;
import mage.Constants.Zone;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.effects.Effect;
import mage.cards.Card;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class KickerAbility extends StaticAbility<KickerAbility> {
public class KickerAbility extends StaticAbility<KickerAbility> implements OptionalAdditionalSourceCosts {
protected boolean kicked = false;
protected boolean replaces = false;
protected List<OptionalAdditionalCost> kickerCosts = new LinkedList<OptionalAdditionalCost>();
public KickerAbility(Effect effect, boolean replaces) {
super(Zone.STACK, effect);
this.replaces = replaces;
public KickerAbility(OptionalAdditionalCost kickerCost) {
super(Zone.STACK, null);
kickerCosts.add(kickerCost);
setRuleAtTheTop(true);
}
public KickerAbility(final KickerAbility ability) {
super(ability);
this.kicked = ability.kicked;
this.replaces = ability.replaces;
super(ability);
this.kickerCosts = ability.kickerCosts;
}
@Override
public KickerAbility copy() {
return new KickerAbility(this);
return new KickerAbility(this);
}
@Override
public boolean activate(Game game, boolean noMana) {
Player player = game.getPlayer(this.getControllerId());
String message = getKickerText(false) + "?";
Card card = game.getCard(sourceId);
// replace by card name or just plain "this"
String text = card == null ? "this" : card.getName();
message = message.replace("{this}", text).replace("{source}", text);
if (player.chooseUse(getEffects().get(0).getOutcome(), message, game)) {
int bookmark = game.bookmarkState();
if (super.activate(game, noMana)) {
game.removeBookmark(bookmark);
kicked = true;
}
else {
game.restoreState(bookmark);
kicked = false;
}
return kicked;
public void resetKicker() {
for (OptionalAdditionalCost cost: kickerCosts) {
cost.reset();
}
return false;
}
public boolean isKicked() {
return kicked;
public List<OptionalAdditionalCost> getKickerCosts () {
return kickerCosts;
}
public boolean isReplaces() {
return replaces;
public void addKickerManaCost(OptionalAdditionalCost kickerCost) {
kickerCosts.add(kickerCost);
}
@Override
public void addOptionalAdditionalCosts(Ability ability, Game game) {
if (ability instanceof SpellAbility) {
Player player = game.getPlayer(controllerId);
if (player != null) {
this.resetKicker();
for (OptionalAdditionalCost kickerCost: kickerCosts) {
boolean again = true;
while (again) {
String times = "";
if (kickerCost.isRepeatable()) {
int activated = kickerCost.getActivateCount();
times = Integer.toString(activated + 1) + (activated == 0 ? " time ":" times ");
}
if (player.chooseUse(Constants.Outcome.Benefit, "Pay " + times + kickerCost.getText(false) + " ?", game)) {
kickerCost.activate();
for (Iterator it = ((Costs) kickerCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
}
}
again = kickerCost.isRepeatable();
} else {
again = false;
}
}
}
}
}
}
@Override
public String getRule() {
return getKickerText(true);
StringBuilder sb = new StringBuilder();
int numberKicker = 0;
String remarkText = "";
for (OptionalAdditionalCost kickerCost: kickerCosts) {
if (numberKicker == 0) {
sb.append(kickerCost.getText(false));
remarkText = kickerCost.getReminderText();
} else {
sb.append(" and/or ").append(kickerCost.getText(true));
}
++numberKicker;
}
if (numberKicker == 1) {
sb.append(" ").append(remarkText);
}
return sb.toString();
}
public String getKickerText(boolean withRemainder) {
@Override
public String getCastMessageSuffix() {
StringBuilder sb = new StringBuilder();
sb.append("Kicker - ");
if (manaCosts.size() > 0) {
sb.append(manaCosts.getText());
if (costs.size() > 0)
sb.append(",");
int position = 0;
for (OptionalAdditionalCost cost : kickerCosts) {
if (cost.isActivated()) {
sb.append(cost.getCastSuffixMessage(position));
++position;
}
}
if (costs.size() > 0)
sb.append(costs.getText());
sb.append(":").append(modes.getText());
if (replaces)
sb.append(" instead");
return sb.toString();
}
}

View file

@ -28,7 +28,7 @@
package mage.abilities.keyword;
import mage.abilities.common.EmptyEffect;
import mage.abilities.costs.mana.KickerManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.effects.Effect;
import mage.game.Game;
@ -42,11 +42,14 @@ public class MultikickerAbility extends KickerAbility {
int activateCount;
public MultikickerAbility(Effect effect, boolean replaces) {
super(effect, replaces);
super(new KickerManaCost("{1}"));
// super(effect, replaces);
}
public MultikickerAbility(ManaCosts manaCosts) {
super(new EmptyEffect(""), false);
super((KickerManaCost) manaCosts);
//super(new EmptyEffect(""), false);
this.addManaCost(manaCosts);
}
@ -70,8 +73,9 @@ public class MultikickerAbility extends KickerAbility {
break;
activateCount++;
}
kicked = activateCount > 0;
return kicked;
// kicked = activateCount > 0;
// return kicked;
return false;
}
@Override
@ -83,24 +87,24 @@ public class MultikickerAbility extends KickerAbility {
return result;
}
@Override
public String getKickerText(boolean withRemainder) {
StringBuilder sb = new StringBuilder();
sb.append("Multikicker ");
if (manaCosts.size() > 0) {
sb.append(manaCosts.getText());
if (costs.size() > 0) {
sb.append(",");
}
}
if (costs.size() > 0) {
sb.append(costs.getText());
}
if (withRemainder) {
sb.append(" (You may pay an additional ").append(manaCosts.getText()).append(" any number of times as you cast this spell.)");
}
return sb.toString();
}
// @Override
// public String getKickerText(boolean withRemainder) {
// StringBuilder sb = new StringBuilder();
// sb.append("Multikicker ");
// if (manaCosts.size() > 0) {
// sb.append(manaCosts.getText());
// if (costs.size() > 0) {
// sb.append(",");
// }
// }
// if (costs.size() > 0) {
// sb.append(costs.getText());
// }
// if (withRemainder) {
// sb.append(" (You may pay an additional ").append(manaCosts.getText()).append(" any number of times as you cast this spell.)");
// }
// return sb.toString();
// }
public int getActivateCount() {
return activateCount;

View file

@ -447,9 +447,6 @@ public class GameState implements Serializable, Copyable<GameState> {
@Deprecated
public void addAbility(Ability ability, MageObject attachedTo) {
if (ability instanceof StaticAbility) {
if (ability instanceof KickerAbility) {
return;
}
for (Mode mode: ability.getModes().values()) {
for (Effect effect: mode.getEffects()) {
if (effect instanceof ContinuousEffect) {
@ -465,9 +462,6 @@ public class GameState implements Serializable, Copyable<GameState> {
public void addAbility(Ability ability, UUID sourceId, MageObject attachedTo) {
if (ability instanceof StaticAbility) {
if (ability instanceof KickerAbility) {
return;
}
for (Mode mode: ability.getModes().values()) {
for (Effect effect: mode.getEffects()) {
if (effect instanceof ContinuousEffect) {

View file

@ -43,7 +43,6 @@ import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.effects.Effect;
import mage.abilities.effects.PostResolveEffect;
import mage.abilities.keyword.KickerAbility;
import mage.cards.Card;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
@ -88,12 +87,7 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
if (card.getCardType().contains(CardType.INSTANT) || card.getCardType().contains(CardType.SORCERY)) {
if (ability.getTargets().stillLegal(ability, game)) {
updateOptionalCosts();
boolean replaced = resolveKicker(game);
if (!replaced)
result = ability.resolve(game);
else
result = true;
result = ability.resolve(game);
if (!copiedSpell) {
for (Effect effect : ability.getEffects()) {
if (effect instanceof PostResolveEffect) {
@ -126,8 +120,6 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
return false;
} else {
updateOptionalCosts();
resolveKicker(game);
result = card.putOntoBattlefield(game, Zone.HAND, ability.getId(), controllerId);
return result;
}
@ -157,18 +149,6 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
}
}
protected boolean resolveKicker(Game game) {
boolean replaced = false;
for (KickerAbility kicker: card.getAbilities().getKickerAbilities()) {
if (kicker.isKicked()) {
if (kicker.isReplaces()) {
replaced = true;
}
kicker.resolve(game);
}
}
return replaced;
}
/**
* Choose new targets for the spell

View file

@ -507,11 +507,6 @@ public abstract class PlayerImpl<T extends PlayerImpl<T>> implements Player, Ser
SpellAbility spellAbility = game.getStack().getSpell(ability.getId()).getSpellAbility();
if (spellAbility.activate(game, noMana)) {
for (KickerAbility kicker: card.getAbilities().getKickerAbilities()) {
if (kicker.getCosts().canPay(ability.getSourceId(), playerId, game) && kicker.canChooseTarget(game)) {
kicker.activate(game, false);
}
}
GameEvent event = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST, spellAbility.getId(), spellAbility.getSourceId(), playerId);
event.setZone(fromZone);
game.fireEvent(event);