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,11 +701,22 @@ public class ContinuousEffects implements Serializable {
}
layer = filterLayeredEffects(layerEffects, Layer.ControlChangingEffects_2);
for (ContinuousEffect effect: layer) {
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
effect.apply(Layer.ControlChangingEffects_2, SubLayer.NA, ability, game);
// 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) {
HashSet<Ability> abilities = layeredEffects.getAbility(effect.getId());
for (Ability ability : abilities) {
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);
for (ContinuousEffect effect: layer) {

View file

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

View file

@ -412,18 +412,28 @@ public class Battlefield implements Serializable {
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
* control changed events after all control change effects have been applied
*
* @param game
*/
public void fireControlChangeEvents(Game game) {
public boolean fireControlChangeEvents(Game game) {
boolean controlChanged = false;
for (Permanent perm: field.values()) {
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 removeAttachment(UUID permanentId, Game game);
boolean changeControllerId(UUID controllerId, Game game);
boolean canBeTargetedBy(MageObject source, UUID controllerId, Game game);
boolean hasProtectionFrom(MageObject source, Game game);
boolean hasSummoningSickness();
@ -119,9 +118,12 @@ public interface Permanent extends Card, Controllable {
void setLoyaltyUsed(boolean used);
boolean isLoyaltyUsed();
public void resetControl();
boolean changeControllerId(UUID controllerId, Game game);
boolean checkControlChanged(Game game);
void beginningOfTurn(Game game);
void endOfTurn(Game game);
void checkControlChanged(Game game);
int getTurnsOnBattlefield();
void addPower(int power);

View file

@ -80,7 +80,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected UUID originalControllerId;
protected UUID controllerId;
protected UUID beforeResetControllerId;
protected boolean controllerChanged;
protected int damage;
protected boolean controlledFromStartOfControllerTurn;
protected int turnsOnBattlefield;
@ -169,9 +168,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
*/
@Override
public void reset(Game game) {
this.beforeResetControllerId = this.controllerId;
this.controllerId = originalControllerId;
controllerChanged = !controllerId.equals(beforeResetControllerId);
this.resetControl();
this.maxBlocks = 1;
this.minBlockedBy = 1;
this.maxBlockedBy = 0;
@ -448,42 +445,37 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
return this.controllerId;
}
@Override
public void resetControl() {
this.beforeResetControllerId = this.controllerId;
this.controllerId = this.originalControllerId;
}
@Override
public boolean changeControllerId(UUID controllerId, Game game) {
if (!controllerId.equals(this.controllerId)) {
Player newController = game.getPlayer(controllerId);
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.abilities.setControllerId(controllerId);
game.getContinuousEffects().setController(this.objectId, controllerId);
return true;
}
Player newController = game.getPlayer(controllerId);
if (newController != null && (!newController.hasLeft() || !newController.hasLost())) {
this.controllerId = controllerId;
return true;
}
return false;
}
@Override
public void checkControlChanged(Game game) {
if (this.controllerChanged) {
public boolean checkControlChanged(Game game) {
if (!controllerId.equals(beforeResetControllerId)) {
this.removeFromCombat(game);
this.controlledFromStartOfControllerTurn = false;
this.abilities.setControllerId(controllerId);
game.getContinuousEffects().setController(objectId, controllerId);
game.fireEvent(new GameEvent(EventType.LOST_CONTROL, objectId, objectId, beforeResetControllerId));
// reset the original controller to abilities and ContinuousEffects
if (controllerId.equals(originalControllerId)) {
this.abilities.setControllerId(controllerId);
game.getContinuousEffects().setController(this.objectId, controllerId);
}
game.fireEvent(new GameEvent(EventType.GAINED_CONTROL, objectId, objectId, controllerId));
return true;
}
return false;
}
@Override