Cheat engine: fixed that it doesn't init effects and other staff from battlefield permanents (#6888);

This commit is contained in:
Oleg Agafonov 2020-07-27 20:46:21 +04:00
parent e5529d5835
commit 96155ec799
4 changed files with 86 additions and 55 deletions

View file

@ -22,6 +22,7 @@ import mage.game.command.CommandObject;
import mage.game.command.Plane; import mage.game.command.Plane;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.util.CardUtil;
import mage.util.RandomUtil; import mage.util.RandomUtil;
import java.io.File; import java.io.File;
@ -572,7 +573,7 @@ public final class SystemUtil {
// move card from exile to stack // move card from exile to stack
for (Card card : cardsToLoad) { for (Card card : cardsToLoad) {
swapWithAnyCard(game, player, card, Zone.STACK); putCardToZone(game, player, card, Zone.STACK);
} }
continue; continue;
@ -647,7 +648,7 @@ public final class SystemUtil {
} else { } else {
// as other card // as other card
for (Card card : cardsToLoad) { for (Card card : cardsToLoad) {
swapWithAnyCard(game, player, card, gameZone); putCardToZone(game, player, card, gameZone);
} }
} }
} }
@ -657,38 +658,33 @@ public final class SystemUtil {
} }
/** /**
* Swap cards between specified card from library and any hand card. * Put card to specified (battlefield zone will be tranformed to permanent with initialized effects)
*
* @param game
* @param card Card to put to player's hand
*/ */
private static void swapWithAnyCard(Game game, Player player, Card card, Zone zone) { private static void putCardToZone(Game game, Player player, Card card, Zone zone) {
// Put the card in Exile to start. Otherwise the game doesn't know where to remove the card from. // TODO: replace by player.move?
game.getExile().getPermanentExile().add(card);
game.setZone(card.getId(), Zone.EXILED);
switch (zone) { switch (zone) {
case BATTLEFIELD: case BATTLEFIELD:
card.putOntoBattlefield(game, Zone.EXILED, null, player.getId()); CardUtil.putCardOntoBattlefieldWithEffects(game, card, player);
break; break;
case LIBRARY: case LIBRARY:
card.setZone(Zone.LIBRARY, game); card.setZone(Zone.LIBRARY, game);
game.getExile().getPermanentExile().remove(card);
player.getLibrary().putOnTop(card, game); player.getLibrary().putOnTop(card, game);
break; break;
case STACK: case STACK:
card.cast(game, Zone.EXILED, card.getSpellAbility(), player.getId()); card.cast(game, game.getState().getZone(card.getId()), card.getSpellAbility(), player.getId());
break; break;
case EXILED: case EXILED:
// nothing to do card.setZone(Zone.EXILED, game);
game.getExile().getPermanentExile().add(card);
break; break;
case OUTSIDE: case OUTSIDE:
card.setZone(Zone.OUTSIDE, game); card.setZone(Zone.OUTSIDE, game);
game.getExile().getPermanentExile().remove(card);
break; break;
default: default:
card.moveToZone(zone, null, game, false); card.moveToZone(zone, null, game, false);
break; break;
} }
game.applyEffects();
logger.info("Added card to player's " + zone.toString() + ": " + card.getName() + ", player = " + player.getName()); logger.info("Added card to player's " + zone.toString() + ": " + card.getName() + ", player = " + player.getName());
} }

View file

@ -1,9 +1,5 @@
package mage.game; package mage.game;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import mage.MageException; import mage.MageException;
import mage.MageObject; import mage.MageObject;
import mage.abilities.*; import mage.abilities.*;
@ -64,6 +60,7 @@ import mage.target.Target;
import mage.target.TargetCard; import mage.target.TargetCard;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
import mage.util.CardUtil;
import mage.util.GameLog; import mage.util.GameLog;
import mage.util.MessageToClient; import mage.util.MessageToClient;
import mage.util.RandomUtil; import mage.util.RandomUtil;
@ -71,6 +68,11 @@ import mage.util.functions.ApplyToPermanent;
import mage.watchers.common.*; import mage.watchers.common.*;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
public abstract class GameImpl implements Game, Serializable { public abstract class GameImpl implements Game, Serializable {
private static final int ROLLBACK_TURNS_MAX = 4; private static final int ROLLBACK_TURNS_MAX = 4;
@ -1805,7 +1807,7 @@ public abstract class GameImpl implements Game, Serializable {
break; break;
} }
// triggered abilities that don't use the stack have to be executed first (e.g. Banisher Priest Return exiled creature // triggered abilities that don't use the stack have to be executed first (e.g. Banisher Priest Return exiled creature
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext();) { for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext(); ) {
TriggeredAbility triggeredAbility = it.next(); TriggeredAbility triggeredAbility = it.next();
if (!triggeredAbility.isUsesStack()) { if (!triggeredAbility.isUsesStack()) {
state.removeTriggeredAbility(triggeredAbility); state.removeTriggeredAbility(triggeredAbility);
@ -1880,7 +1882,7 @@ public abstract class GameImpl implements Game, Serializable {
Zone currentZone = this.getState().getZone(card.getId()); Zone currentZone = this.getState().getZone(card.getId());
String currentZoneInfo = (currentZone == null ? "(error)" : "(" + currentZone.name() + ")"); String currentZoneInfo = (currentZone == null ? "(error)" : "(" + currentZone.name() + ")");
if (player.chooseUse(Outcome.Benefit, "Move " + card.getIdName() if (player.chooseUse(Outcome.Benefit, "Move " + card.getIdName()
+ " to the command zone or leave it in current zone " + currentZoneInfo + "?", "You can only make this choice once per object", + " to the command zone or leave it in current zone " + currentZoneInfo + "?", "You can only make this choice once per object",
"Move to command", "Leave in current zone " + currentZoneInfo, null, this)) { "Move to command", "Leave in current zone " + currentZoneInfo, null, this)) {
toMove.add(card); toMove.add(card);
} else { } else {
@ -2602,7 +2604,7 @@ public abstract class GameImpl implements Game, Serializable {
} }
//20100423 - 800.4a //20100423 - 800.4a
Set<Card> toOutside = new HashSet<>(); Set<Card> toOutside = new HashSet<>();
for (Iterator<Permanent> it = getBattlefield().getAllPermanents().iterator(); it.hasNext();) { for (Iterator<Permanent> it = getBattlefield().getAllPermanents().iterator(); it.hasNext(); ) {
Permanent perm = it.next(); Permanent perm = it.next();
if (perm.isOwnedBy(playerId)) { if (perm.isOwnedBy(playerId)) {
if (perm.getAttachedTo() != null) { if (perm.getAttachedTo() != null) {
@ -2652,7 +2654,7 @@ public abstract class GameImpl implements Game, Serializable {
player.moveCards(toOutside, Zone.OUTSIDE, null, this); player.moveCards(toOutside, Zone.OUTSIDE, null, this);
// triggered abilities that don't use the stack have to be executed // triggered abilities that don't use the stack have to be executed
List<TriggeredAbility> abilities = state.getTriggered(player.getId()); List<TriggeredAbility> abilities = state.getTriggered(player.getId());
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext();) { for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext(); ) {
TriggeredAbility triggeredAbility = it.next(); TriggeredAbility triggeredAbility = it.next();
if (!triggeredAbility.isUsesStack()) { if (!triggeredAbility.isUsesStack()) {
state.removeTriggeredAbility(triggeredAbility); state.removeTriggeredAbility(triggeredAbility);
@ -2672,7 +2674,7 @@ public abstract class GameImpl implements Game, Serializable {
// Remove cards from the player in all exile zones // Remove cards from the player in all exile zones
for (ExileZone exile : this.getExile().getExileZones()) { for (ExileZone exile : this.getExile().getExileZones()) {
for (Iterator<UUID> it = exile.iterator(); it.hasNext();) { for (Iterator<UUID> it = exile.iterator(); it.hasNext(); ) {
Card card = this.getCard(it.next()); Card card = this.getCard(it.next());
if (card != null && card.isOwnedBy(playerId)) { if (card != null && card.isOwnedBy(playerId)) {
it.remove(); it.remove();
@ -2682,7 +2684,7 @@ public abstract class GameImpl implements Game, Serializable {
//Remove all commander/emblems/plane the player controls //Remove all commander/emblems/plane the player controls
boolean addPlaneAgain = false; boolean addPlaneAgain = false;
for (Iterator<CommandObject> it = this.getState().getCommand().iterator(); it.hasNext();) { for (Iterator<CommandObject> it = this.getState().getCommand().iterator(); it.hasNext(); ) {
CommandObject obj = it.next(); CommandObject obj = it.next();
if (obj.isControlledBy(playerId)) { if (obj.isControlledBy(playerId)) {
if (obj instanceof Emblem) { if (obj instanceof Emblem) {
@ -3063,28 +3065,10 @@ public abstract class GameImpl implements Game, Serializable {
throw new IllegalArgumentException("Command zone supports in commander test games"); throw new IllegalArgumentException("Command zone supports in commander test games");
} }
// warning, permanents go to battlefield without resolve, continuus effects must be init
for (PermanentCard permanentCard : battlefield) { for (PermanentCard permanentCard : battlefield) {
permanentCard.setZone(Zone.BATTLEFIELD, this); CardUtil.putCardOntoBattlefieldWithEffects(this, permanentCard, player);
permanentCard.setOwnerId(ownerId);
PermanentCard newPermanent = new PermanentCard(permanentCard.getCard(), ownerId, this);
getPermanentsEntering().put(newPermanent.getId(), newPermanent);
newPermanent.entersBattlefield(newPermanent.getId(), this, Zone.OUTSIDE, false);
addPermanent(newPermanent, getState().getNextPermanentOrderNumber());
getPermanentsEntering().remove(newPermanent.getId());
newPermanent.removeSummoningSickness();
if (permanentCard.isTapped()) {
newPermanent.setTapped(true);
}
// init effects on static abilities (init continuous effects, warning, game state contains copy)
for (ContinuousEffect effect : this.getState().getContinuousEffects().getLayeredEffects(this)) {
Optional<Ability> ability = this.getState().getContinuousEffects().getLayeredEffectAbilities(effect).stream().findFirst();
if (ability.isPresent() && newPermanent.getId().equals(ability.get().getSourceId())) {
effect.init(ability.get(), this, activePlayerId); // game is not setup yet, game.activePlayer is null -- need direct id
}
}
} }
applyEffects(); applyEffects();
} }
} }

View file

@ -1,6 +1,5 @@
package mage.game; package mage.game;
import java.util.*;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.Cards; import mage.cards.Cards;
import mage.cards.CardsImpl; import mage.cards.CardsImpl;
@ -19,6 +18,8 @@ import mage.game.stack.Spell;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetCard; import mage.target.TargetCard;
import java.util.*;
/** /**
* Created by samuelsandeen on 9/6/16. * Created by samuelsandeen on 9/6/16.
*/ */
@ -52,7 +53,7 @@ public final class ZonesHandler {
public static List<ZoneChangeInfo> moveCards(List<ZoneChangeInfo> zoneChangeInfos, Game game) { public static List<ZoneChangeInfo> moveCards(List<ZoneChangeInfo> zoneChangeInfos, Game game) {
// Handle Unmelded Meld Cards // Handle Unmelded Meld Cards
for (ListIterator<ZoneChangeInfo> itr = zoneChangeInfos.listIterator(); itr.hasNext();) { for (ListIterator<ZoneChangeInfo> itr = zoneChangeInfos.listIterator(); itr.hasNext(); ) {
ZoneChangeInfo info = itr.next(); ZoneChangeInfo info = itr.next();
MeldCard card = game.getMeldCard(info.event.getTargetId()); MeldCard card = game.getMeldCard(info.event.getTargetId());
// Copies should be handled as normal cards. // Copies should be handled as normal cards.
@ -201,7 +202,7 @@ public final class ZonesHandler {
if (info instanceof ZoneChangeInfo.Unmelded) { if (info instanceof ZoneChangeInfo.Unmelded) {
ZoneChangeInfo.Unmelded unmelded = (ZoneChangeInfo.Unmelded) info; ZoneChangeInfo.Unmelded unmelded = (ZoneChangeInfo.Unmelded) info;
MeldCard meld = game.getMeldCard(info.event.getTargetId()); MeldCard meld = game.getMeldCard(info.event.getTargetId());
for (Iterator<ZoneChangeInfo> itr = unmelded.subInfo.iterator(); itr.hasNext();) { for (Iterator<ZoneChangeInfo> itr = unmelded.subInfo.iterator(); itr.hasNext(); ) {
ZoneChangeInfo subInfo = itr.next(); ZoneChangeInfo subInfo = itr.next();
if (!maybeRemoveFromSourceZone(subInfo, game)) { if (!maybeRemoveFromSourceZone(subInfo, game)) {
itr.remove(); itr.remove();
@ -233,6 +234,7 @@ public final class ZonesHandler {
if (!game.replaceEvent(event)) { if (!game.replaceEvent(event)) {
Zone fromZone = event.getFromZone(); Zone fromZone = event.getFromZone();
if (event.getToZone() == Zone.BATTLEFIELD) { if (event.getToZone() == Zone.BATTLEFIELD) {
// prepare card and permanent
// If needed take attributes from the spell (e.g. color of spell was changed) // If needed take attributes from the spell (e.g. color of spell was changed)
card = takeAttributesFromSpell(card, event, game); card = takeAttributesFromSpell(card, event, game);
// controlling player can be replaced so use event player now // controlling player can be replaced so use event player now
@ -245,15 +247,18 @@ public final class ZonesHandler {
} else { } else {
permanent = new PermanentCard(card, event.getPlayerId(), game); permanent = new PermanentCard(card, event.getPlayerId(), game);
} }
// put onto battlefield with possible counters
game.getPermanentsEntering().put(permanent.getId(), permanent); game.getPermanentsEntering().put(permanent.getId(), permanent);
card.checkForCountersToAdd(permanent, game); card.checkForCountersToAdd(permanent, game);
permanent.setTapped( permanent.setTapped(info instanceof ZoneChangeInfo.Battlefield
info instanceof ZoneChangeInfo.Battlefield && ((ZoneChangeInfo.Battlefield) info).tapped); && ((ZoneChangeInfo.Battlefield) info).tapped);
permanent.setFaceDown(info.faceDown, game);
permanent.setFaceDown(info.faceDown, game);
if (info.faceDown) { if (info.faceDown) {
card.setFaceDown(false, game); card.setFaceDown(false, game);
} }
// make sure the controller of all continuous effects of this card are switched to the current controller // make sure the controller of all continuous effects of this card are switched to the current controller
game.setScopeRelevant(true); game.setScopeRelevant(true);
game.getContinuousEffects().setController(permanent.getId(), permanent.getControllerId()); game.getContinuousEffects().setController(permanent.getId(), permanent.getControllerId());

View file

@ -8,18 +8,20 @@ import mage.abilities.Mode;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.costs.VariableCost; import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.*; import mage.abilities.costs.mana.*;
import mage.abilities.effects.ContinuousEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.ColoredManaSymbol; import mage.cards.MeldCard;
import mage.constants.EmptyNames; import mage.constants.*;
import mage.constants.ManaType;
import mage.constants.SpellAbilityType;
import mage.filter.Filter; import mage.filter.Filter;
import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.mageobject.NamePredicate;
import mage.game.CardState; import mage.game.CardState;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import mage.game.permanent.PermanentMeld;
import mage.game.permanent.token.Token; import mage.game.permanent.token.Token;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.Target; import mage.target.Target;
import mage.util.functions.CopyTokenFunction; import mage.util.functions.CopyTokenFunction;
@ -829,4 +831,48 @@ public final class CardUtil {
.flatMap(Collection::stream) .flatMap(Collection::stream)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
/**
* Put card to battlefield without resolve
*
* @param game
* @param card
* @param player
*/
public static void putCardOntoBattlefieldWithEffects(Game game, Card card, Player player) {
// same logic as ZonesHandler->maybeRemoveFromSourceZone
// prepare card and permanent
card.setZone(Zone.BATTLEFIELD, game);
card.setOwnerId(player.getId());
PermanentCard permanent;
if (card instanceof MeldCard) {
permanent = new PermanentMeld(card, player.getId(), game);
} else {
permanent = new PermanentCard(card, player.getId(), game);
}
// put onto battlefield with possible counters
game.getPermanentsEntering().put(permanent.getId(), permanent);
card.checkForCountersToAdd(permanent, game);
permanent.entersBattlefield(permanent.getId(), game, Zone.OUTSIDE, false);
game.addPermanent(permanent, game.getState().getNextPermanentOrderNumber());
game.getPermanentsEntering().remove(permanent.getId());
// workaround for special tapped status from test framework's command (addCard)
if (card instanceof PermanentCard && ((PermanentCard) card).isTapped()) {
permanent.setTapped(true);
}
// remove sickness
permanent.removeSummoningSickness();
// init effects on static abilities (init continuous effects; warning, game state contains copy)
for (ContinuousEffect effect : game.getState().getContinuousEffects().getLayeredEffects(game)) {
Optional<Ability> ability = game.getState().getContinuousEffects().getLayeredEffectAbilities(effect).stream().findFirst();
if (ability.isPresent() && permanent.getId().equals(ability.get().getSourceId())) {
effect.init(ability.get(), game, player.getId());
}
}
}
} }