Merge branch 'master' into master

This commit is contained in:
Evan Kranzler 2019-04-20 08:52:33 -04:00 committed by GitHub
commit 7cea71d6f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
116 changed files with 3982 additions and 328 deletions

View file

@ -335,26 +335,31 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
if (this.getMaxModesFilter() != null) {
sb.append("choose one or more. Each mode must target ").append(getMaxModesFilter().getMessage());
} else if (this.getMinModes() == 0 && this.getMaxModes() == 1) {
sb.append("choose up to one ");
sb.append("choose up to one");
} else if (this.getMinModes() == 1 && this.getMaxModes() > 2) {
sb.append("choose one or more ");
sb.append("choose one or more");
} else if (this.getMinModes() == 1 && this.getMaxModes() == 2) {
sb.append("choose one or both ");
sb.append("choose one or both");
} else if (this.getMinModes() == 2 && this.getMaxModes() == 2) {
sb.append("choose two ");
sb.append("choose two");
} else if (this.getMinModes() == 3 && this.getMaxModes() == 3) {
sb.append("choose three ");
sb.append("choose three");
} else if (this.getMinModes() == 4 && this.getMaxModes() == 4) {
sb.append("choose four");
} else {
sb.append("choose one ");
sb.append("choose one");
}
if (isEachModeOnlyOnce()) {
sb.append("that hasn't been chosen ");
sb.append(" that hasn't been chosen");
}
if (isEachModeMoreThanOnce()) {
sb.append(". You may choose the same mode more than once.<br>");
} else {
sb.append("&mdash;<br>");
sb.append(" &mdash;<br>");
}
for (Mode mode : this.values()) {
sb.append("&bull ");
sb.append(mode.getEffects().getTextStartingUpperCase(mode));

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.MageObjectReference;
@ -28,10 +27,12 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkEventType(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
return zEvent.getFromZone() == Zone.BATTLEFIELD
&& (zEvent.getToZone() == Zone.GRAVEYARD
|| zEvent.getToZone() == Zone.EXILED);
if (event.getType() == GameEvent.EventType.ZONE_CHANGE) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
return zEvent.getFromZone() == Zone.BATTLEFIELD
&& (zEvent.getToZone() == Zone.GRAVEYARD || zEvent.getToZone() == Zone.EXILED);
}
return false;
}
@Override

View file

@ -1,9 +1,5 @@
package mage.abilities.effects.common.counter;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
@ -14,14 +10,30 @@ import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetPermanentOrPlayerWithCounter;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author nantuko
*/
public class ProliferateEffect extends OneShotEffect {
public ProliferateEffect() {
this("", true);
}
public ProliferateEffect(boolean showAbilityHint) {
this("", showAbilityHint);
}
public ProliferateEffect(String afterText, boolean showAbilityHint) {
super(Outcome.Benefit);
staticText = "proliferate. <i>(You choose any number of permanents and/or players with counters on them, then give each another counter of each kind already there.)</i>";
staticText = "proliferate" + afterText;
if (showAbilityHint) {
staticText += ". <i>(You choose any number of permanents and/or players with counters on them, then give each another counter of each kind already there.)</i>";
}
}
public ProliferateEffect(ProliferateEffect effect) {

View file

@ -38,7 +38,7 @@ public class AmassEffect extends OneShotEffect {
public AmassEffect(int amassNumber) {
this(new StaticValue(amassNumber));
staticText = "amass " + amassNumber + ". <i>(Put " + CardUtil.numberToText(amassNumber)
+ " +1/+1 counter " + (amassNumber > 1 ? "s" : "")
+ " +1/+1 counter" + (amassNumber > 1 ? "s " : " ")
+ "on an Army you control. If you dont control one, "
+ "create a 0/0 black Zombie Army creature token first.)</i>";
}

View file

@ -1,4 +1,3 @@
package mage.abilities.keyword;
import mage.abilities.Ability;
@ -19,19 +18,17 @@ import mage.game.events.GameEvent.EventType;
import mage.game.events.ZoneChangeEvent;
/**
*
* @author BetaSteward_at_googlemail.com
*
*
* <p>
* <p>
* 702.82. Unearth
*
* <p>
* 702.82a Unearth is an activated ability that functions while the card with
* unearth is in a graveyard. "Unearth [cost]" means "[Cost]: Return this card
* from your graveyard to the battlefield. It gains haste. Exile it at the
* beginning of the next end step. If it would leave the battlefield, exile it
* instead of putting it anywhere else. Activate this ability only any time you
* could cast a sorcery."
*
*/
public class UnearthAbility extends ActivatedAbilityImpl {
@ -111,7 +108,7 @@ class UnearthLeavesBattlefieldEffect extends ReplacementEffectImpl {
@Override
public boolean checksEventType(GameEvent event, Game game) {
return EventType.ZONE_CHANGE == event.getType();
return event.getType() == EventType.ZONE_CHANGE;
}
@Override

View file

@ -135,6 +135,7 @@ public enum CounterType {
VERSE("verse"),
VITALITY("vitality"),
VORTEX("vortex"),
WAGE("wage"),
WINCH("winch"),
WIND("wind"),
WISH("wish");

View file

@ -1,22 +1,15 @@
package mage.game;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.stream.Collectors;
import mage.cards.Card;
import mage.filter.FilterCard;
import mage.util.Copyable;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class Exile implements Serializable, Copyable<Exile> {
@ -114,4 +107,17 @@ public class Exile implements Serializable, Copyable<Exile> {
exile.clear();
}
}
public void cleanupEndOfTurnZones(Game game) {
// moves cards from outdated zone to main exile zone
ExileZone mainZone = getExileZone(PERMANENT);
for (ExileZone zone : exileZones.values()) {
if (zone.isCleanupOnEndTurn()) {
for (Card card : zone.getCards(game)) {
mainZone.add(card);
zone.remove(card);
}
}
}
}
}

View file

@ -1,13 +1,10 @@
package mage.game;
import java.util.UUID;
import mage.cards.CardsImpl;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class ExileZone extends CardsImpl {
@ -15,16 +12,22 @@ public class ExileZone extends CardsImpl {
private UUID id;
private String name;
private boolean hidden;
private boolean cleanupOnEndTurn = false; // moved cards from that zone to default on end of turn (to cleanup exile windows)
public ExileZone(UUID id, String name) {
this(id, name, false);
}
public ExileZone(UUID id, String name, boolean hidden) {
this(id, name, false, false);
}
public ExileZone(UUID id, String name, boolean hidden, boolean cleanupOnEndTurn) {
super();
this.id = id;
this.name = name;
this.hidden = hidden;
this.cleanupOnEndTurn = cleanupOnEndTurn;
}
public ExileZone(final ExileZone zone) {
@ -32,6 +35,7 @@ public class ExileZone extends CardsImpl {
this.id = zone.id;
this.name = zone.name;
this.hidden = zone.hidden;
this.cleanupOnEndTurn = zone.cleanupOnEndTurn;
}
public UUID getId() {
@ -46,6 +50,14 @@ public class ExileZone extends CardsImpl {
return hidden;
}
public boolean isCleanupOnEndTurn() {
return cleanupOnEndTurn;
}
public void setCleanupOnEndTurn(boolean cleanupOnEndTurn) {
this.cleanupOnEndTurn = cleanupOnEndTurn;
}
@Override
public ExileZone copy() {
return new ExileZone(this);

View file

@ -579,6 +579,7 @@ public class GameState implements Serializable, Copyable<GameState> {
public void removeEotEffects(Game game) {
effects.removeEndOfTurnEffects();
delayed.removeEndOfTurnAbilities();
exile.cleanupEndOfTurnZones(game);
game.applyEffects();
}

View file

@ -1,17 +1,15 @@
package mage.game.permanent.token;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import mage.MageInt;
import mage.abilities.keyword.FlyingAbility;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public final class DragonToken extends TokenImpl {
@ -19,7 +17,7 @@ public final class DragonToken extends TokenImpl {
static final private List<String> tokenImageSets = new ArrayList<>();
static {
tokenImageSets.addAll(Arrays.asList("DTK", "MMA", "ALA", "MM3", "C17"));
tokenImageSets.addAll(Arrays.asList("DTK", "MMA", "ALA", "MM3", "C17", "WAR"));
}
public DragonToken() {

View file

@ -0,0 +1,33 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.abilities.keyword.VigilanceAbility;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author TheElk801
*/
public final class GodEternalOketraToken extends TokenImpl {
public GodEternalOketraToken() {
super("Zombie Warrior", "4/4 black Zombie Warrior creature token with vigilance");
setExpansionSetCodeForImage("WAR"); // default
cardType.add(CardType.CREATURE);
color.setBlack(true);
subtype.add(SubType.ZOMBIE);
subtype.add(SubType.WARRIOR);
power = new MageInt(4);
toughness = new MageInt(4);
addAbility(VigilanceAbility.getInstance());
}
private GodEternalOketraToken(final GodEternalOketraToken token) {
super(token);
}
@Override
public GodEternalOketraToken copy() {
return new GodEternalOketraToken(this);
}
}

View file

@ -0,0 +1,34 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
*
* @author TheElk801
*/
public final class PlanewideCelebrationToken extends TokenImpl {
public PlanewideCelebrationToken() {
super("Citizen", "2/2 Citizen creature token that's all colors");
cardType.add(CardType.CREATURE);
color.setWhite(true);
color.setBlue(true);
color.setBlack(true);
color.setRed(true);
color.setGreen(true);
subtype.add(SubType.CITIZEN);
power = new MageInt(2);
toughness = new MageInt(2);
}
public PlanewideCelebrationToken(final PlanewideCelebrationToken token) {
super(token);
}
public PlanewideCelebrationToken copy() {
return new PlanewideCelebrationToken(this);
}
}

View file

@ -0,0 +1,29 @@
package mage.game.permanent.token;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SubType;
/**
* @author TheElk801
*/
public final class UginTheIneffableToken extends TokenImpl {
public UginTheIneffableToken() {
super("Spirit", "2/2 colorless Spirit creature token");
setExpansionSetCodeForImage("WAR"); // default
cardType.add(CardType.CREATURE);
subtype.add(SubType.SPIRIT);
power = new MageInt(2);
toughness = new MageInt(2);
}
private UginTheIneffableToken(final UginTheIneffableToken token) {
super(token);
}
@Override
public UginTheIneffableToken copy() {
return new UginTheIneffableToken(this);
}
}

View file

@ -188,6 +188,11 @@ public abstract class StackObjImpl implements StackObject {
newTarget.clearChosen();
}
}
// workaround to stop infinite AI choose (remove after chooseTarget can be called with extra filter to disable some ids)
if (iteration > 10) {
break;
}
}
while (targetController.canRespond() && (targetId.equals(newTarget.getFirstTarget()) || newTarget.getTargets().size() != 1));
// choose a new target

View file

@ -880,30 +880,27 @@ public abstract class PlayerImpl implements Player, Serializable {
if (!cardsToLibrary.isEmpty()) {
Cards cards = new CardsImpl(cardsToLibrary); // prevent possible ConcurrentModificationException
if (!anyOrder) {
while (!cards.isEmpty()) {
Card card = cards.getRandom(game);
if (card != null) {
cards.remove(card);
moveObjectToLibrary(card.getId(), source == null ? null : source.getSourceId(), game, false, false);
} else {
return false;// probably cards were removed because player left the game
}
// random order
List<UUID> ids = new ArrayList<>(cards);
Collections.shuffle(ids);
for (UUID id : ids) {
moveObjectToLibrary(id, source == null ? null : source.getSourceId(), game, false, false);
}
} else {
// user defined order
TargetCard target = new TargetCard(Zone.ALL, new FilterCard("card ORDER to put on the BOTTOM of your library (last one chosen will be bottommost)"));
target.setRequired(true);
while (cards.size() > 1) {
this.choose(Outcome.Neutral, cards, target, game);
if (!canRespond()) {
return false;
}
while (cards.size() > 1 && this.canRespond() && this.choose(Outcome.Neutral, cards, target, game)) {
UUID targetObjectId = target.getFirstTarget();
if (targetObjectId == null) {
break;
}
cards.remove(targetObjectId);
moveObjectToLibrary(targetObjectId, source == null ? null : source.getSourceId(), game, false, false);
target.clearChosen();
}
if (cards.size() == 1) {
moveObjectToLibrary(cards.iterator().next(), source == null ? null : source.getSourceId(), game, false, false);
for (UUID c : cards) {
moveObjectToLibrary(c, source == null ? null : source.getSourceId(), game, false, false);
}
}
}
@ -945,30 +942,27 @@ public abstract class PlayerImpl implements Player, Serializable {
Cards cards = new CardsImpl(cardsToLibrary); // prevent possible ConcurrentModificationException
UUID sourceId = (source == null ? null : source.getSourceId());
if (!anyOrder) {
while (!cards.isEmpty()) {
Card card = cards.getRandom(game);
if (card != null) {
cards.remove(card.getId());
moveObjectToLibrary(card.getId(), source == null ? null : source.getSourceId(), game, true, false);
} else {
return false; // probably cards were removed because player left the game
}
// random order
List<UUID> ids = new ArrayList<>(cards);
Collections.shuffle(ids);
for (UUID id : ids) {
moveObjectToLibrary(id, source == null ? null : source.getSourceId(), game, true, false);
}
} else {
TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card ORDER to put on the TOP of your library (last one chosen will be topmost)"));
// user defined order
TargetCard target = new TargetCard(Zone.ALL, new FilterCard("card ORDER to put on the TOP of your library (last one chosen will be topmost)"));
target.setRequired(true);
while (cards.size() > 1) {
this.choose(Outcome.Neutral, cards, target, game);
if (!canRespond()) {
return false;
}
while (cards.size() > 1 && this.canRespond() && this.choose(Outcome.Neutral, cards, target, game)) {
UUID targetObjectId = target.getFirstTarget();
if (targetObjectId == null) {
break;
}
cards.remove(targetObjectId);
moveObjectToLibrary(targetObjectId, sourceId, game, true, false);
moveObjectToLibrary(targetObjectId, source == null ? null : source.getSourceId(), game, true, false);
target.clearChosen();
}
if (cards.size() == 1) {
moveObjectToLibrary(cards.iterator().next(), sourceId, game, true, false);
for (UUID c : cards) {
moveObjectToLibrary(c, source == null ? null : source.getSourceId(), game, true, false);
}
}
}

View file

@ -4,10 +4,7 @@ import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.target.targetpointer.FirstTargetPointer;
import mage.target.targetpointer.SecondTargetPointer;
import mage.target.targetpointer.TargetPointer;
import mage.target.targetpointer.ThirdTargetPointer;
import mage.target.targetpointer.*;
import org.apache.log4j.Logger;
import java.util.ArrayList;
@ -170,9 +167,13 @@ public class Targets extends ArrayList<Target> {
}
}
if (targetPointer instanceof FixedTarget || targetPointer instanceof FixedTargets) {
// fixed target = direct ID, you can't find target type and description
proccessed = true;
}
if (!proccessed) {
logger.error("Unknown target pointer " + (targetPointer != null ? targetPointer : "null"), new Throwable());
// TODO: add other target types?
}
return null;

View file

@ -1,15 +1,17 @@
package mage.target.targetpointer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class FixedTarget implements TargetPointer {
private final UUID targetId;
@ -21,6 +23,10 @@ public class FixedTarget implements TargetPointer {
this.initialized = false;
}
public FixedTarget(MageObjectReference mor) {
this(mor.getSourceId(), mor.getZoneChangeCounter());
}
public FixedTarget(Card card, Game game) {
this.targetId = card.getId();
this.zoneChangeCounter = card.getZoneChangeCounter(game);