Improved some source related filters in effects:

* Fixed that some cards ignore range of influence or source related filters;
* Improved ChosenSubtypePredicate to work with gain abilities;
This commit is contained in:
Oleg Agafonov 2020-12-25 19:05:49 +04:00
parent fdcf2c616b
commit a307e5934f
32 changed files with 291 additions and 197 deletions

View file

@ -2,11 +2,7 @@ package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.predicate.Predicates;
import mage.game.Game;
@ -65,7 +61,7 @@ class LegendarySpellAbilityCheckEffect extends ContinuousRuleModifyingEffectImpl
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return event.getSourceId().equals(source.getSourceId())
&& !game.getBattlefield().contains(filter, event.getPlayerId(), 1, game);
&& !game.getBattlefield().containsControlled(filter, source, game, 1);
}
@Override

View file

@ -22,7 +22,7 @@ public enum MetalcraftCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
return game.getBattlefield().contains(filter, source.getControllerId(), 3, game);
return game.getBattlefield().containsControlled(filter, source, game, 3);
}
@Override

View file

@ -1,15 +1,15 @@
package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import java.util.UUID;
import mage.abilities.costs.Cost;
public class ControlPermanentCost extends CostImpl {
private FilterControlledPermanent filter;
private final FilterControlledPermanent filter;
public ControlPermanentCost(FilterControlledPermanent filter) {
this.filter = filter.copy();
@ -23,7 +23,7 @@ public class ControlPermanentCost extends CostImpl {
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return game.getBattlefield().contains(filter, controllerId, 1, game);
return game.getBattlefield().containsControlled(filter, source.getSourceId(), controllerId, game, 1);
}
@Override

View file

@ -1,7 +1,5 @@
package mage.abilities.effects.common;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
@ -14,6 +12,8 @@ import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author LevelX2
*/
@ -56,15 +56,19 @@ public class ChooseCreatureTypeEffect extends OneShotEffect {
return new ChooseCreatureTypeEffect(this);
}
public static SubType getChosenCreatureType(UUID objectId, Game game) {
return getChosenCreatureType(objectId, game, "_type");
}
/**
*
* @param objectId sourceId the effect was exeuted under
* @param objectId sourceId the effect was exeuted under
* @param game
* @param typePostfix special postfix if you want to store multiple choices from different effects
* @return
*/
public static SubType getChosenCreatureType(UUID objectId, Game game) {
public static SubType getChosenCreatureType(UUID objectId, Game game, String typePostfix) {
SubType creatureType = null;
Object savedCreatureType = game.getState().getValue(objectId + "_type");
Object savedCreatureType = game.getState().getValue(objectId + typePostfix);
if (savedCreatureType != null) {
creatureType = SubType.byDescription(savedCreatureType.toString());
}

View file

@ -96,10 +96,23 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl {
}
if (permanent != null) {
permanent.addAbility(ability, source.getSourceId(), game);
afterGain(game, source, permanent, ability);
}
return true;
}
/**
* Calls after ability gain. Override it to apply additional data (example: transfer ability's settings from original to destination source)
*
* @param game
* @param source
* @param permanent
* @param addedAbility
*/
public void afterGain(Game game, Ability source, Permanent permanent, Ability addedAbility) {
//
}
private void setText() {
StringBuilder sb = new StringBuilder();
sb.append(attachmentType.verb());

View file

@ -61,7 +61,7 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
&& playerId.equals(source.getControllerId())
&& cardToCheck.isOwnedBy(source.getControllerId())
&& (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand())
&& filter.match(cardToCheck, game)) {
&& filter.match(cardToCheck, source.getSourceId(), source.getControllerId(), game)) {
Player player = game.getPlayer(cardToCheck.getOwnerId());
UUID needCardID = player.getLibrary().getFromTop(game) == null ? null : player.getLibrary().getFromTop(game).getId();

View file

@ -67,7 +67,7 @@ public class AmassEffect extends OneShotEffect {
if (player == null) {
return false;
}
if (!game.getBattlefield().contains(filter, source.getControllerId(), 1, game)) {
if (!game.getBattlefield().containsControlled(filter, source, game, 1)) {
new CreateTokenEffect(new ZombieArmyToken()).apply(game, source);
}
Target target = new TargetPermanent(filter);

View file

@ -104,7 +104,7 @@ public class ConvokeAbility extends SimpleStaticAbility implements AlternateMana
@Override
public void addSpecialAction(Ability source, Game game, ManaCost unpaid) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null && game.getBattlefield().contains(filterUntapped, controller.getId(), 1, game)) {
if (controller != null && game.getBattlefield().containsControlled(filterUntapped, source, game, 1)) {
if (source.getAbilityType() == AbilityType.SPELL) {
SpecialAction specialAction = new ConvokeSpecialAction(unpaid, this);
specialAction.setControllerId(source.getControllerId());

View file

@ -14,6 +14,12 @@ import mage.game.permanent.Permanent;
*/
public class LandwalkAbility extends EvasionAbility {
/**
* Don't use source related filters here (example: landwalk for user selected land type).
* If you want it then use workaround from Traveler's Cloak to transfer settings after gain
*
* @param filter
*/
public LandwalkAbility(FilterLandPermanent filter) {
this(filter, true);
}
@ -39,7 +45,6 @@ public class LandwalkAbility extends EvasionAbility {
}
return ruleText;
}
}
class LandwalkEffect extends RestrictionEffect {
@ -59,7 +64,7 @@ class LandwalkEffect extends RestrictionEffect {
@Override
public boolean canBeBlocked(Permanent attacker, Permanent blocker, Ability source, Game game, boolean canUseChooseDialogs) {
if (game.getBattlefield().contains(filter, blocker.getControllerId(), 1, game)
if (game.getBattlefield().contains(filter, source.getSourceId(), blocker.getControllerId(), game, 1)
&& null == game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_LANDWALK, source, blocker.getControllerId(), game)) {
switch (filter.getMessage()) {
case "plains":
@ -74,7 +79,6 @@ class LandwalkEffect extends RestrictionEffect {
return null != game.getContinuousEffects().asThough(blocker.getId(), AsThoughEffectType.BLOCK_FORESTWALK, source, blocker.getControllerId(), game);
default:
return false;
}
}
return true;

View file

@ -1,4 +1,3 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
@ -9,6 +8,9 @@ import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
/**
* Warning, chosen type assign to original source ability, but after gain you will see another sourceId,
* see Traveler's Cloak for workaround to trasfer settings
*
* @author LoneFox
*/
public enum ChosenSubtypePredicate implements ObjectSourcePlayerPredicate<ObjectSourcePlayer<MageObject>> {

View file

@ -2276,7 +2276,7 @@ public abstract class GameImpl implements Game, Serializable {
filterLegendName.add(SuperType.LEGENDARY.getPredicate());
filterLegendName.add(new NamePredicate(legend.getName()));
filterLegendName.add(new ControllerIdPredicate(legend.getControllerId()));
if (getBattlefield().contains(filterLegendName, legend.getControllerId(), this, 2)) {
if (getBattlefield().contains(filterLegendName, null, legend.getControllerId(), this, 2)) {
if (!replaceEvent(GameEvent.getEvent(GameEvent.EventType.DESTROY_PERMANENT_BY_LEGENDARY_RULE, legend.getId(), legend.getControllerId()))) {
Player controller = this.getPlayer(legend.getControllerId());
if (controller != null) {

View file

@ -50,7 +50,7 @@ class GideonOfTheTrialsCantLoseEffect extends ContinuousRuleModifyingEffectImpl
public boolean applies(GameEvent event, Ability source, Game game) {
if ((event.getType() == GameEvent.EventType.WINS && game.getOpponents(source.getControllerId()).contains(event.getPlayerId()))
|| (event.getType() == GameEvent.EventType.LOSES && event.getPlayerId().equals(source.getControllerId()))) {
return game.getBattlefield().contains(filter, source.getControllerId(), 1, game);
return game.getBattlefield().containsControlled(filter, source, game, 1);
}
return false;
}

View file

@ -1,5 +1,6 @@
package mage.game.permanent;
import mage.abilities.Ability;
import mage.abilities.keyword.PhasingAbility;
import mage.constants.CardType;
import mage.constants.RangeOfInfluence;
@ -92,21 +93,8 @@ public class Battlefield implements Serializable {
}
}
/**
* Returns true if the battlefield contains at least 1 {@link Permanent}
* that matches the filter. This method ignores the range of influence.
*
* @param filter
* @param num
* @param game
* @return boolean
*/
public boolean contains(FilterPermanent filter, int num, Game game) {
return field.values()
.stream()
.filter(permanent -> filter.match(permanent, game)
&& permanent.isPhasedIn()).count() >= num;
public boolean containsControlled(FilterPermanent filter, Ability source, Game game, int num) {
return containsControlled(filter, source.getSourceId(), source.getControllerId(), game, num);
}
/**
@ -115,43 +103,49 @@ public class Battlefield implements Serializable {
* ignores the range of influence.
*
* @param filter
* @param controllerId
* @param sourceId
* @param controllerId controller and source can be different (from different players)
* @param num
* @param game
* @return boolean
*/
public boolean contains(FilterPermanent filter, UUID controllerId, int num, Game game) {
public boolean containsControlled(FilterPermanent filter, UUID sourceId, UUID controllerId, Game game, int num) {
return field.values()
.stream()
.filter(permanent -> permanent.isControlledBy(controllerId)
&& filter.match(permanent, game)
&& filter.match(permanent, sourceId, controllerId, game)
&& permanent.isPhasedIn())
.count() >= num;
}
public boolean contains(FilterPermanent filter, Ability source, Game game, int num) {
return contains(filter, source.getSourceId(), source.getControllerId(), game, num);
}
/**
* Returns true if the battlefield contains num or more {@link Permanent}
* that is within the range of influence of the specified player id and that
* matches the supplied filter.
*
* @param filter
* @param sourceId can be null for default SBA checks like legendary rule
* @param sourcePlayerId
* @param game
* @param num
* @return boolean
*/
public boolean contains(FilterPermanent filter, UUID sourcePlayerId, Game game, int num) {
public boolean contains(FilterPermanent filter, UUID sourceId, UUID sourcePlayerId, Game game, int num) {
if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) {
return field.values().stream()
.filter(permanent -> filter.match(permanent, null, sourcePlayerId, game)
.filter(permanent -> filter.match(permanent, sourceId, sourcePlayerId, game)
&& permanent.isPhasedIn()).count() >= num;
} else {
List<UUID> range = game.getState().getPlayersInRange(sourcePlayerId, game);
return field.values().stream()
.filter(permanent -> range.contains(permanent.getControllerId())
&& filter.match(permanent, null, sourcePlayerId, game)
&& filter.match(permanent, sourceId, sourcePlayerId, game)
&& permanent.isPhasedIn())
.count() >= num;
}