* Fixed provisional game freezing bugs of the show playable cards feature #457 (caused by DelverAbility, OfferingAbility, Rooftop Storm, Omniscience, Aluren).

This commit is contained in:
LevelX2 2014-07-21 13:03:41 +02:00
parent ad9b73c34c
commit 142e95fe42
20 changed files with 106 additions and 34 deletions

View file

@ -114,7 +114,7 @@ class CantBeBlockedUnlessAllEffect extends RestrictionEffect {
// check if all creatures of defender are able to block this permanent
// permanent.canBlock() can't be used because causing recursive call
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(filter, blocker.getControllerId(), game)) {
if (permanent.isTapped() && !game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, blocker.getControllerId(), game)) {
if (permanent.isTapped() && !game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, source, blocker.getControllerId(), game)) {
return false;
}
// check blocker restrictions

View file

@ -42,6 +42,7 @@ import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
import mage.abilities.ActivatedAbility;
/**
*
@ -100,9 +101,10 @@ class RooftopStormCostReductionEffect extends CostModificationEffectImpl {
Card sourceCard = game.getCard(spell.getSourceId());
if (sourceCard != null && sourceCard.hasSubtype("Zombie")) {
Player player = game.getPlayer(spell.getControllerId());
if (player != null && player.chooseUse(Outcome.Benefit, "Pay {0} rather than pay the mana cost for Zombie creature", game)) {
if (player != null &&
(CardUtil.isCheckPlayableMode(spell) || player.chooseUse(Outcome.Benefit, "Pay {0} rather than pay the mana cost for Zombie creature", game))) {
spell.getManaCostsToPay().clear();
spell.getManaCostsToPay().addAll(new ManaCostsImpl("{0}"));
spell.getManaCostsToPay().addAll(new ManaCostsImpl<>("{0}"));
return true;
}
}

View file

@ -42,6 +42,7 @@ import mage.game.stack.StackObject;
import mage.players.Player;
import java.util.UUID;
import mage.util.CardUtil;
/**
*
@ -99,7 +100,8 @@ class OmniscienceEffect extends CostModificationEffectImpl {
&& !sourceCard.getCardType().contains(CardType.LAND)) {
Player player = game.getPlayer(source.getControllerId());
String message = "Cast " + sourceCard.getName() + " without paying its mana costs?";
if (player != null && player.chooseUse(outcome, message, game)) {
if (player != null &&
(CardUtil.isCheckPlayableMode(abilityToModify) || player.chooseUse(outcome, message, game))) {
return true;
}
}

View file

@ -49,6 +49,7 @@ import mage.game.Game;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.util.CardUtil;
/**
*
@ -113,7 +114,8 @@ class AlurenEffect extends CostModificationEffectImpl {
if (sourceCard != null && sourceCard.getCardType().contains(CardType.CREATURE) && sourceCard.getManaCost().convertedManaCost() <= 3) {
Player player = game.getPlayer(stackObject.getControllerId());
String message = "Cast " + sourceCard.getName() + " without paying its mana costs?";
if (player != null && player.chooseUse(outcome, message, game)) {
if (player != null &&
(CardUtil.isCheckPlayableMode(abilityToModify) || player.chooseUse(outcome, message, game))) {
return true;
}
}

View file

@ -38,4 +38,20 @@ import mage.game.Game;
public interface ActivatedAbility extends Ability {
boolean canActivate(UUID playerId, Game game);
/**
* Creates a fresh copy of this activated ability.
*
* @return A new copy of this ability.
*/
@Override
ActivatedAbility copy();
/**
* Set a flag to know, that the ability is only created adn used to check
* what's playbable for the player.
*/
void setCheckPlayableMode();
boolean isCheckPlayableMode();
}

View file

@ -53,9 +53,11 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
protected TimingRule timing = TimingRule.INSTANT;
protected TargetController mayActivate = TargetController.YOU;
protected UUID activatorId;
protected boolean checkPlayableMode;
protected ActivatedAbilityImpl(AbilityType abilityType, Zone zone) {
super(abilityType, zone);
this.checkPlayableMode = false;
}
public ActivatedAbilityImpl(ActivatedAbilityImpl ability) {
@ -63,6 +65,7 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
timing = ability.timing;
mayActivate = ability.mayActivate;
activatorId = ability.activatorId;
checkPlayableMode = ability.checkPlayableMode;
}
public ActivatedAbilityImpl(Zone zone) {
@ -217,4 +220,14 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
this.timing = timing;
}
@Override
public void setCheckPlayableMode() {
checkPlayableMode = true;
}
@Override
public boolean isCheckPlayableMode() {
return checkPlayableMode;
}
}

View file

@ -84,8 +84,8 @@ public class SpellAbility extends ActivatedAbilityImpl {
@Override
public boolean canActivate(UUID playerId, Game game) {
if (this.spellCanBeActivatedRegularlyNow(playerId, game) ||
game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST, playerId, game)) {
if (game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST, this, playerId, game) // check this first to allow Offering in main phase
|| this.spellCanBeActivatedRegularlyNow(playerId, game)) {
if (spellAbilityType.equals(SpellAbilityType.SPLIT)) {
return false;
}

View file

@ -57,7 +57,7 @@ public class AlternativeCost2Impl <T extends AlternativeCost2Impl<T>> extends Co
if (reminderText != null) {
this.reminderText = new StringBuilder("<i>").append(reminderText).append("</i>").toString();
}
this.add((Cost) cost);
this.add(cost);
}
public AlternativeCost2Impl(final AlternativeCost2Impl cost) {

View file

@ -28,8 +28,6 @@
package mage.abilities.costs;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;

View file

@ -39,6 +39,7 @@ import mage.game.Game;
*/
public interface AsThoughEffect extends ContinuousEffect {
boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game);
boolean applies(UUID sourceId, Ability source, Game game);
AsThoughEffectType getAsThoughEffectType();

View file

@ -28,10 +28,13 @@
package mage.abilities.effects;
import java.util.UUID;
import mage.abilities.Ability;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.EffectType;
import mage.constants.Outcome;
import mage.game.Game;
/**
*
@ -52,6 +55,11 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
this.type = effect.type;
}
@Override
public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game) {
return applies(sourceId, source, game);
}
@Override
public AsThoughEffectType getAsThoughEffectType() {
return type;

View file

@ -420,19 +420,31 @@ public class ContinuousEffects implements Serializable {
}
public boolean asThough(UUID objectId, AsThoughEffectType type, UUID controllerId, Game game) {
return asThough(objectId, type, null, controllerId, game);
}
public boolean asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(type, game);
for (AsThoughEffect effect: asThoughEffectsList) {
HashSet<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId());
for (Ability ability : abilities) {
if (controllerId.equals(ability.getControllerId())) {
if (affectedAbility == null) {
if (effect.applies(objectId, ability, game)) {
return true;
}
} else {
if (effect.applies(objectId, affectedAbility, ability, game)) {
return true;
}
}
}
}
return false;
}
return false;
}
/**
* Filters out asThough effects that are not active.

View file

@ -32,13 +32,10 @@ import mage.constants.Duration;
import mage.constants.EffectType;
import mage.constants.Outcome;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.CostModificationEffect;
import mage.cards.Card;
import mage.constants.CostModificationType;
import mage.game.Game;
import mage.game.stack.Spell;
/**
* Simple implementation of a {@link CostModificationEffect} offering simplified

View file

@ -85,7 +85,7 @@ import mage.util.CardUtil;
if (!target.canChoose(sourceId, controllerId, game)) {
return;
}
if (player.chooseUse(Outcome.Detriment, "Delve cards from your graveyard?", game)) {
if (!CardUtil.isCheckPlayableMode(ability) && player.chooseUse(Outcome.Detriment, "Delve cards from your graveyard?", game)) {
player.chooseTarget(Outcome.Detriment, target, ability, game);
if (target.getTargets().size() > 0) {
int adjCost = 0;
@ -94,11 +94,11 @@ import mage.util.CardUtil;
if (card == null) {
continue;
}
card.moveToExile(null, null, this.getSourceId(), game);
player.moveCardToExileWithInfo(card, null, "", getSourceId(), game, Zone.GRAVEYARD);
++adjCost;
}
game.informPlayers(new StringBuilder(player.getName()).append(" delved ")
.append(adjCost).append(" creature").append(adjCost != 1?"s":"").append(" from his or her graveyard").toString());
game.informPlayers(new StringBuilder("Delve: ").append(player.getName()).append(" exiled ")
.append(adjCost).append(" card").append(adjCost != 1?"s":"").append(" from his or her graveyard").toString());
CardUtil.adjustCost((SpellAbility)ability, adjCost);
}
}

View file

@ -128,6 +128,11 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID sourceId, Ability source, Game game) {
return false;
}
@Override
public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game) {
if (sourceId.equals(source.getSourceId())) {
Card card = game.getCard(sourceId);
if (!card.getOwnerId().equals(source.getControllerId())) {
@ -139,10 +144,7 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
Object alreadyConfirmed = game.getState().getValue("offering_ok_" + card.getId());
game.getState().setValue("offering_" + card.getId(), null);
game.getState().setValue("offering_ok_" + card.getId(), null);
if (alreadyConfirmed != null) {
return true;
}
return false;
return alreadyConfirmed != null;
} else {
// first call -> remove previous Ids
game.getState().setValue("offering_Id_" + card.getId(), null);
@ -153,7 +155,8 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
FilterControlledCreaturePermanent filter = ((OfferingAbility) source).getFilter();
Card spellToCast = game.getCard(source.getSourceId());
Player player = game.getPlayer(source.getControllerId());
if (player != null && player.chooseUse(Outcome.Benefit, "Offer a " + filter.getMessage() + " to cast " + spellToCast.getName() + "?", game)) {
if (player != null && !CardUtil.isCheckPlayableMode(affectedAbility) &&
player.chooseUse(Outcome.Benefit, "Offer a " + filter.getMessage() + " to cast " + spellToCast.getName() + "?", game)) {
Target target = new TargetControlledCreaturePermanent(1,1,filter,true);
player.chooseTarget(Outcome.Sacrifice, target, source, game);
if (!target.isChosen()) {
@ -185,9 +188,9 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
class OfferingCostReductionEffect extends CostModificationEffectImpl {
private UUID spellAbilityId;
private UUID activationId;
private ManaCosts<ManaCost> manaCostsToReduce;
private final UUID spellAbilityId;
private final UUID activationId;
private final ManaCosts<ManaCost> manaCostsToReduce;
OfferingCostReductionEffect (UUID spellAbilityId, ManaCosts<ManaCost> manaCostsToReduce, UUID activationId) {
super(Duration.OneUse, Outcome.Benefit, CostModificationType.REDUCE_COST);

View file

@ -62,7 +62,7 @@ class ShadowEffect extends RestrictionEffect implements MageSingleton {
@Override
public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game) {
if (blocker.getAbilities().containsKey(ShadowAbility.getInstance().getId())
|| game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, blocker.getControllerId(), game)) {
|| game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_SHADOW, source, blocker.getControllerId(), game)) {
return true;
}
return false;

View file

@ -193,7 +193,7 @@ public class SuspendAbility extends ActivatedAbilityImpl {
MageObject object = game.getObject(sourceId);
return (object.getCardType().contains(CardType.INSTANT) ||
object.hasAbility(FlashAbility.getInstance().getId(), game) ||
game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST, playerId, game) ||
game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST, this, playerId, game) ||
game.canPlaySorcery(playerId));
}

View file

@ -118,7 +118,7 @@ public class ManaPool implements Serializable {
// check if any mana can be spend to cast the mana cost of an ability
private boolean spendAnyMana(Ability ability, Game game) {
return game.getContinuousEffects().asThough(ability.getSourceId(), AsThoughEffectType.SPEND_ANY_MANA, ability.getControllerId(), game);
return game.getContinuousEffects().asThough(ability.getSourceId(), AsThoughEffectType.SPEND_ANY_MANA, ability, ability.getControllerId(), game);
}
public int get(ManaType manaType) {

View file

@ -1834,8 +1834,12 @@ public abstract class PlayerImpl implements Player, Serializable {
}
protected boolean canPlay(ActivatedAbility ability, ManaOptions available, Game game) {
if (!(ability instanceof ManaAbility) && ability.canActivate(playerId, game)) {
Ability copy = ability.copy();
if (!(ability instanceof ManaAbility)) {
ActivatedAbility copy = ability.copy();
copy.setCheckPlayableMode(); // prevents from endless loops for asking player to use effects by checking this mode
if (!copy.canActivate(playerId, game)) {
return false;
}
game.getContinuousEffects().costModification(copy, game);
Card card = game.getCard(ability.getSourceId());

View file

@ -33,6 +33,7 @@ import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility;
import mage.abilities.costs.AlternativeCost;
import mage.abilities.costs.AlternativeCostImpl;
@ -490,4 +491,17 @@ public class CardUtil {
}
return uniqueString.toString();
}
/**
* Returns if the ability is used to check which cards
* are playable on hand. (Issue #457)
* @param ability - ability to check
* @return
*/
public static boolean isCheckPlayableMode(Ability ability) {
if (ability instanceof ActivatedAbility) {
return ((ActivatedAbility) ability).isCheckPlayableMode();
}
return false;
}
}