fix ContinuousEffect that access affectedObjectsSet before it is initialized (#12080)

This commit is contained in:
Susucre 2024-04-13 16:50:06 +02:00 committed by GitHub
parent cce7f79d89
commit 017286ed94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 351 additions and 150 deletions

View file

@ -37,7 +37,9 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
// If your ability/effect supports multi use cases (one time use, static, target pointers) then affectedObjectsSet can be useful:
// * affectedObjectsSet - true on static objects and false on dynamic objects (see rules from 611.2c)
// Full implement example: GainAbilityTargetEffect
protected boolean affectedObjectsSet = false;
//
// is null before being initialized. Any access attempt computes it.
private Boolean affectedObjectsSet = null;
protected List<MageObjectReference> affectedObjectList = new ArrayList<>();
protected boolean temporary = false;
@ -62,7 +64,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
private int effectStartingStepNum = 0; // Some continuous are waiting for the next step of a kind.
// Avoid miscancelling if the start step is of that kind.
public ContinuousEffectImpl(Duration duration, Outcome outcome) {
protected ContinuousEffectImpl(Duration duration, Outcome outcome) {
super(outcome);
this.duration = duration;
this.order = 0;
@ -71,7 +73,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.dependendToTypes = EnumSet.noneOf(DependencyType.class);
}
public ContinuousEffectImpl(Duration duration, Layer layer, SubLayer sublayer, Outcome outcome) {
protected ContinuousEffectImpl(Duration duration, Layer layer, SubLayer sublayer, Outcome outcome) {
this(duration, outcome);
this.layer = layer;
this.sublayer = sublayer;
@ -166,6 +168,16 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
@Override
public void init(Ability source, Game game, UUID activePlayerId) {
getTargetPointer().init(game, source);
if (this.affectedObjectsSet == null) {
initAffectedObjectsSet(source);
}
setStartingControllerAndTurnNum(game, source.getControllerId(), activePlayerId);
}
/**
* Computes affectedObjectsSet boolean from the source object.
*/
private void initAffectedObjectsSet(Ability source) {
// 20210112 - 611.2c
// 611.2c If a continuous effect generated by the resolution of a spell or ability modifies the
// characteristics or changes the controller of any objects, the set of objects it affects is
@ -177,6 +189,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
// effect began.If a single continuous effect has parts that modify the characteristics or
// changes the controller of any objects and other parts that dont, the set of objects
// each part applies to is determined independently.
this.affectedObjectsSet = false;
if (AbilityType.STATIC != source.getAbilityType()) {
if (layer != null) {
switch (layer) {
@ -199,7 +212,35 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.affectedObjectsSet = true;
}
}
setStartingControllerAndTurnNum(game, source.getControllerId(), activePlayerId);
}
/**
* This is a workaround when trying to access affectObjectsSet during init, before
* ContinuousEffectImpl::init is called.
* <p>
* TODO: should be investigated how to modify all continuous effects to call super.init()
* before doing their own changes. At which point there is no longer need for this
* workaround.
*/
protected boolean getAffectedObjectsSetAtInit(Ability source) {
if (this.affectedObjectsSet == null) {
initAffectedObjectsSet(source);
}
return this.affectedObjectsSet;
}
/**
* Use this getter in other places than overriden calls, most likely the apply method.
*/
protected boolean getAffectedObjectsSet() {
if (this.affectedObjectsSet == null) {
return false;
}
return this.affectedObjectsSet;
}
protected void setAffectedObjectsSet(boolean affectedObjectsSet) {
this.affectedObjectsSet = affectedObjectsSet;
}
@Override

View file

@ -49,7 +49,7 @@ public class CantBlockAttachedEffect extends RestrictionEffect {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
Permanent equipment = game.getPermanent(source.getSourceId());
if (equipment != null && equipment.getAttachedTo() != null) {
this.setTargetPointer(new FixedTarget(equipment.getAttachedTo(), game.getState().getZoneChangeCounter(equipment.getAttachedTo())));
@ -59,7 +59,7 @@ public class CantBlockAttachedEffect extends RestrictionEffect {
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
return getTargetPointer().getFirst(game, source).equals(permanent.getId());
}
return permanent.getAttachments().contains(source.getSourceId());

View file

@ -30,7 +30,7 @@ public class GoadAllEffect extends ContinuousEffectImpl {
public GoadAllEffect(Duration duration, FilterPermanent filter, boolean affectedObjectsSet) {
super(duration, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment);
this.filter = filter;
this.affectedObjectsSet = affectedObjectsSet;
this.setAffectedObjectsSet(affectedObjectsSet);
}
private GoadAllEffect(final GoadAllEffect effect) {
@ -46,7 +46,7 @@ public class GoadAllEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
game.getBattlefield()
.getActivePermanents(
filter, source.getControllerId(), source, game
@ -58,7 +58,7 @@ public class GoadAllEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
this.affectedObjectList.removeIf(mor -> !mor.zoneCounterIsCurrent(game)
|| mor.getPermanent(game) == null);
if (affectedObjectList.isEmpty()) {

View file

@ -56,7 +56,7 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Permanent perm : game.getBattlefield().getActivePermanents(
filter, source.getControllerId(), source, game)) {
affectedObjectList.add(new MageObjectReference(perm, game));
@ -72,7 +72,7 @@ public class BecomesCreatureAllEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Set<Permanent> affectedPermanents = new HashSet<>();
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (MageObjectReference ref : affectedObjectList) {
affectedPermanents.add(ref.getPermanent(game));
}

View file

@ -90,7 +90,7 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
affectedObjectList.add(new MageObjectReference(source.getSourceId(), game));
}
}
@ -98,7 +98,7 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Permanent permanent;
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
permanent = affectedObjectList.get(0).getPermanent(game);
} else {
permanent = game.getPermanent(source.getSourceId());

View file

@ -7,7 +7,6 @@ import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
@ -103,7 +102,7 @@ public class BoostAllEffect extends ContinuousEffectImpl {
public void init(Ability source, Game game) {
super.init(source, game);
setRuntimeData(source, game);
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
if (!(excludeSource && perm.getId().equals(source.getSourceId())) && selectedByRuntimeData(perm, source, game)) {
affectedObjectList.add(new MageObjectReference(perm, game));
@ -116,7 +115,7 @@ public class BoostAllEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) { // filter may not be used again, because object can have changed filter relevant attributes but still geets boost
Permanent permanent = it.next().getPermanent(game);
if (permanent != null) {

View file

@ -76,7 +76,7 @@ public class BoostControlledEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
if (perm.isControlledBy(source.getControllerId())
&& !(excludeSource && perm.getId().equals(source.getSourceId()))) {
@ -90,7 +90,7 @@ public class BoostControlledEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) {
Permanent permanent = it.next().getPermanent(game);
if (permanent != null) {

View file

@ -53,7 +53,7 @@ public class BoostEnchantedEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
// Added boosts of activated or triggered abilities exist independent from the source they are created by
// so a continuous effect for the permanent itself with the attachment is created
Permanent equipment = game.getPermanentOrLKIBattlefield(source.getSourceId());
@ -68,7 +68,7 @@ public class BoostEnchantedEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = null;
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null) {
discard();

View file

@ -49,7 +49,7 @@ public class BoostOpponentsEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
Set<UUID> opponents = game.getOpponents(source.getControllerId());
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
if (opponents.contains(perm.getControllerId())) {
@ -62,7 +62,7 @@ public class BoostOpponentsEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
Set<UUID> opponents = game.getOpponents(source.getControllerId());
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) { // filter may not be used again, because object can have changed filter relevant attributes but still geets boost
Permanent perm = it.next().getPermanent(game);
if (perm != null) {

View file

@ -54,7 +54,7 @@ public class BoostSourceEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
try {
affectedObjectList.add(new MageObjectReference(source.getSourceId(), game));
} catch (IllegalArgumentException ex) {
@ -68,7 +68,7 @@ public class BoostSourceEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
Permanent target;
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
target = affectedObjectList.get(0).getPermanent(game);
} else {
target = game.getPermanent(source.getSourceId());

View file

@ -59,7 +59,7 @@ public class BoostTargetEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
power = StaticValue.get(power.calculate(game, source, this));
toughness = StaticValue.get(toughness.calculate(game, source, this));
}

View file

@ -61,7 +61,7 @@ public class GainAbilityAllEffect extends ContinuousEffectImpl {
public void init(Ability source, Game game) {
super.init(source, game);
setRuntimeData(source, game);
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
if (!(excludeSource && perm.getId().equals(source.getSourceId())) && selectedByRuntimeData(perm, source, game)) {
affectedObjectList.add(new MageObjectReference(perm, game));
@ -77,7 +77,7 @@ public class GainAbilityAllEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) { // filter may not be used again, because object can have changed filter relevant attributes but still geets boost
Permanent permanent = it.next().getPermanentOrLKIBattlefield(game); //LKI is neccessary for "dies triggered abilities" to work given to permanets (e.g. Showstopper)
if (permanent != null) {

View file

@ -77,7 +77,7 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
if (affectedObjectsSet) {
if (getAffectedObjectsSetAtInit(source)) {
Permanent equipment = game.getPermanentOrLKIBattlefield(source.getSourceId());
if (equipment != null && equipment.getAttachedTo() != null) {
this.setTargetPointer(new FixedTarget(equipment.getAttachedTo(), game.getState().getZoneChangeCounter(equipment.getAttachedTo())));
@ -89,7 +89,7 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent;
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null) {
discard();

View file

@ -65,7 +65,7 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
if (perm.isControlledBy(source.getControllerId())
&& !(excludeSource && perm.getId().equals(source.getSourceId()))) {
@ -82,7 +82,7 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) { // filter may not be used again, because object can have changed filter relevant attributes but still geets boost
Permanent perm = it.next().getPermanentOrLKIBattlefield(game); //LKI is neccessary for "dies triggered abilities" to work given to permanets (e.g. Showstopper)
if (perm != null) {

View file

@ -63,7 +63,7 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl {
return;
}
}
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
Permanent permanent = game.getPermanentEntering(source.getSourceId());
if (permanent != null) {
affectedObjectList.add(new MageObjectReference(source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()) + 1, game));
@ -77,7 +77,7 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
if (onCard) {
Card card;
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
card = affectedObjectList.get(0).getCard(game);
} else {
card = game.getCard(source.getSourceId());
@ -89,7 +89,7 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl {
}
} else {
Permanent permanent;
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
permanent = affectedObjectList.get(0).getPermanent(game);
} else {
permanent = game.getPermanent(source.getSourceId());

View file

@ -39,7 +39,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, boolean useOnCard) {
super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability.getEffects().getOutcome(ability, Outcome.AddAbility));
this.ability = copyAbility(ability); // See the method's comment, ability.copy() is not enough.
this.staticText = rule;
this.useOnCard = useOnCard;
@ -58,7 +58,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
super.init(source, game);
// must support dynamic targets from static ability and static targets from activated abilities
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
// target permanents (by default)
getTargetPointer().getTargets(game, source)
.stream()
@ -93,7 +93,7 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
int affectedTargets = 0;
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
// STATIC TARGETS
List<MageObjectReference> newWaitingPermanents = new ArrayList<>();
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) {

View file

@ -60,7 +60,7 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
if (affectedObjectsSet) {
if (getAffectedObjectsSetAtInit(source)) {
Permanent equipment = game.getPermanentOrLKIBattlefield(source.getSourceId());
if (equipment != null && equipment.getAttachedTo() != null) {
this.setTargetPointer(new FixedTarget(equipment.getAttachedTo(), game.getState().getZoneChangeCounter(equipment.getAttachedTo())));
@ -72,7 +72,7 @@ public class GainAbilityWithAttachmentEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = null;
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null) {
discard();

View file

@ -1,7 +1,5 @@
package mage.abilities.effects.common.continuous;
import java.util.Iterator;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.CompoundAbility;
@ -15,6 +13,8 @@ import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.Iterator;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -61,7 +61,7 @@ public class LoseAbilityAllEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
if (!(excludeSource && perm.getId().equals(source.getSourceId()))) {
affectedObjectList.add(new MageObjectReference(perm, game));
@ -77,7 +77,7 @@ public class LoseAbilityAllEffect extends ContinuousEffectImpl {
@Override
public boolean apply(Game game, Ability source) {
if (this.affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) { // filter may not be used again, because object can have changed filter relevant attributes but still geets boost
Permanent perm = it.next().getPermanentOrLKIBattlefield(game); //LKI is neccessary for "dies triggered abilities" to work given to permanets (e.g. Showstopper)
if (perm != null) {

View file

@ -60,7 +60,7 @@ public class SetBasePowerToughnessAllEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Permanent perm : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
affectedObjectList.add(new MageObjectReference(perm, game));
}
@ -73,7 +73,7 @@ public class SetBasePowerToughnessAllEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
int newPower = power.calculate(game, source, this);
int newToughness = toughness.calculate(game, source, this);
if (affectedObjectsSet) {
if (getAffectedObjectsSet()) {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) {
Permanent permanent = it.next().getPermanent(game);
if (permanent != null) {

View file

@ -2,11 +2,8 @@
package mage.abilities.effects.common.continuous;
import java.util.Iterator;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
@ -17,6 +14,8 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.Iterator;
/**
* @author LevelX2
*/
@ -42,7 +41,7 @@ public class SwitchPowerToughnessAllEffect extends ContinuousEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
if (this.affectedObjectsSet && game.getPlayer(source.getControllerId()) != null) {
if (getAffectedObjectsSet() && game.getPlayer(source.getControllerId()) != null) {
for (Permanent perm : game.getState().getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game)) {
affectedObjectList.add(new MageObjectReference(perm, game));
}
@ -56,7 +55,7 @@ public class SwitchPowerToughnessAllEffect extends ContinuousEffectImpl {
return false;
}
if (!this.affectedObjectsSet) {
if (!getAffectedObjectsSet()) {
game.getState().getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game).forEach(Permanent::switchPowerToughness);
} else {
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) { // filter may not be used again, because object can have changed filter relevant attributes but still gets boost