Fix handling of multiple simultaneous control changing effects

This commit is contained in:
Quercitron 2014-06-26 03:30:05 +04:00
parent c519814f03
commit bba23e05cb
5 changed files with 53 additions and 39 deletions

View file

@ -701,12 +701,23 @@ public class ContinuousEffects implements Serializable {
} }
layer = filterLayeredEffects(layerEffects, Layer.ControlChangingEffects_2); layer = filterLayeredEffects(layerEffects, Layer.ControlChangingEffects_2);
// apply control changing effects multiple times if it's needed
// for cases when control over permanents with change control abilities is changed
// e.g. Mind Control is controlled by Steal Enchantment
while (true) {
for (ContinuousEffect effect: layer) { for (ContinuousEffect effect: layer) {
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId()); HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) { for (Ability ability : abilities) {
effect.apply(Layer.ControlChangingEffects_2, SubLayer.NA, ability, game); effect.apply(Layer.ControlChangingEffects_2, SubLayer.NA, ability, game);
} }
} }
// if control over all permanent has not changed, we can no longer reapply control changing effects
if (!game.getBattlefield().fireControlChangeEvents(game)) {
break;
}
// reset control before reapplying control changing effects
game.getBattlefield().resetPermanentsControl();
}
layer = filterLayeredEffects(layerEffects, Layer.TextChangingEffects_3); layer = filterLayeredEffects(layerEffects, Layer.TextChangingEffects_3);
for (ContinuousEffect effect: layer) { for (ContinuousEffect effect: layer) {
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId()); HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());

View file

@ -403,7 +403,6 @@ public class GameState implements Serializable, Copyable<GameState> {
combat.reset(); combat.reset();
this.reset(); this.reset();
effects.apply(game); effects.apply(game);
battlefield.fireControlChangeEvents(game);
} }
// Remove End of Combat effects // Remove End of Combat effects

View file

@ -412,18 +412,28 @@ public class Battlefield implements Serializable {
return phasedOut; return phasedOut;
} }
public void resetPermanentsControl() {
for (Permanent perm: field.values()) {
if (perm.isPhasedIn()) {
perm.resetControl();
}
}
}
/** /**
* since control could change several times during applyEvents we only want to fire * since control could change several times during applyEvents we only want to fire
* control changed events after all control change effects have been applied * control changed events after all control change effects have been applied
* *
* @param game * @param game
*/ */
public void fireControlChangeEvents(Game game) { public boolean fireControlChangeEvents(Game game) {
boolean controlChanged = false;
for (Permanent perm: field.values()) { for (Permanent perm: field.values()) {
if (perm.isPhasedIn()) { if (perm.isPhasedIn()) {
perm.checkControlChanged(game); controlChanged |= perm.checkControlChanged(game);
} }
} }
return controlChanged;
} }
} }

View file

@ -76,7 +76,6 @@ public interface Permanent extends Card, Controllable {
boolean addAttachment(UUID permanentId, Game game); boolean addAttachment(UUID permanentId, Game game);
boolean removeAttachment(UUID permanentId, Game game); boolean removeAttachment(UUID permanentId, Game game);
boolean changeControllerId(UUID controllerId, Game game);
boolean canBeTargetedBy(MageObject source, UUID controllerId, Game game); boolean canBeTargetedBy(MageObject source, UUID controllerId, Game game);
boolean hasProtectionFrom(MageObject source, Game game); boolean hasProtectionFrom(MageObject source, Game game);
boolean hasSummoningSickness(); boolean hasSummoningSickness();
@ -119,9 +118,12 @@ public interface Permanent extends Card, Controllable {
void setLoyaltyUsed(boolean used); void setLoyaltyUsed(boolean used);
boolean isLoyaltyUsed(); boolean isLoyaltyUsed();
public void resetControl();
boolean changeControllerId(UUID controllerId, Game game);
boolean checkControlChanged(Game game);
void beginningOfTurn(Game game); void beginningOfTurn(Game game);
void endOfTurn(Game game); void endOfTurn(Game game);
void checkControlChanged(Game game);
int getTurnsOnBattlefield(); int getTurnsOnBattlefield();
void addPower(int power); void addPower(int power);

View file

@ -80,7 +80,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected UUID originalControllerId; protected UUID originalControllerId;
protected UUID controllerId; protected UUID controllerId;
protected UUID beforeResetControllerId; protected UUID beforeResetControllerId;
protected boolean controllerChanged;
protected int damage; protected int damage;
protected boolean controlledFromStartOfControllerTurn; protected boolean controlledFromStartOfControllerTurn;
protected int turnsOnBattlefield; protected int turnsOnBattlefield;
@ -169,9 +168,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
*/ */
@Override @Override
public void reset(Game game) { public void reset(Game game) {
this.beforeResetControllerId = this.controllerId; this.resetControl();
this.controllerId = originalControllerId;
controllerChanged = !controllerId.equals(beforeResetControllerId);
this.maxBlocks = 1; this.maxBlocks = 1;
this.minBlockedBy = 1; this.minBlockedBy = 1;
this.maxBlockedBy = 0; this.maxBlockedBy = 0;
@ -448,42 +445,37 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
return this.controllerId; return this.controllerId;
} }
@Override
public void resetControl() {
this.beforeResetControllerId = this.controllerId;
this.controllerId = this.originalControllerId;
}
@Override @Override
public boolean changeControllerId(UUID controllerId, Game game) { public boolean changeControllerId(UUID controllerId, Game game) {
if (!controllerId.equals(this.controllerId)) {
Player newController = game.getPlayer(controllerId); Player newController = game.getPlayer(controllerId);
if (newController != null && (!newController.hasLeft() || !newController.hasLost())) { if (newController != null && (!newController.hasLeft() || !newController.hasLost())) {
// changeControllerId can be called by continuous effect
// so it will lead to this.controlledFromStartOfControllerTurn set to false over and over
// because of reset(game) method called before applying effect as state-based action
// that changes this.controllerId to original one (actually owner)
if (!controllerId.equals(beforeResetControllerId)) {
this.removeFromCombat(game);
this.controlledFromStartOfControllerTurn = false;
this.controllerChanged = true;
} else {
this.controllerChanged = false;
}
this.controllerId = controllerId; this.controllerId = controllerId;
this.abilities.setControllerId(controllerId);
game.getContinuousEffects().setController(this.objectId, controllerId);
return true; return true;
} }
}
return false; return false;
} }
@Override @Override
public void checkControlChanged(Game game) { public boolean checkControlChanged(Game game) {
if (this.controllerChanged) { if (!controllerId.equals(beforeResetControllerId)) {
game.fireEvent(new GameEvent(EventType.LOST_CONTROL, objectId, objectId, beforeResetControllerId)); this.removeFromCombat(game);
// reset the original controller to abilities and ContinuousEffects this.controlledFromStartOfControllerTurn = false;
if (controllerId.equals(originalControllerId)) {
this.abilities.setControllerId(controllerId); this.abilities.setControllerId(controllerId);
game.getContinuousEffects().setController(this.objectId, controllerId); game.getContinuousEffects().setController(objectId, controllerId);
}
game.fireEvent(new GameEvent(EventType.LOST_CONTROL, objectId, objectId, beforeResetControllerId));
game.fireEvent(new GameEvent(EventType.GAINED_CONTROL, objectId, objectId, controllerId)); game.fireEvent(new GameEvent(EventType.GAINED_CONTROL, objectId, objectId, controllerId));
return true;
} }
return false;
} }
@Override @Override