This commit is contained in:
Jeff 2021-08-28 15:41:20 -05:00
commit f35ca8112c
39 changed files with 769 additions and 202 deletions

View file

@ -241,7 +241,7 @@ public abstract class ManaCostImpl extends CostImpl implements ManaCost {
assignPayment(game, ability, player.getManaPool(), costToPay != null ? costToPay : this);
}
game.getState().getSpecialActions().removeManaActions();
while (!isPaid()) {
while (player.canRespond() && !isPaid()) {
ManaCost unpaid = this.getUnpaid();
String promptText = ManaUtil.addSpecialManaPayAbilities(ability, game, unpaid);
if (player.playMana(ability, unpaid, promptText, game)) {
@ -251,7 +251,7 @@ public abstract class ManaCostImpl extends CostImpl implements ManaCost {
}
game.getState().getSpecialActions().removeManaActions();
}
return true;
return isPaid();
}
@Override

View file

@ -128,7 +128,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
assignPayment(game, ability, player.getManaPool(), this);
}
game.getState().getSpecialActions().removeManaActions();
while (!isPaid()) {
while (player.canRespond() && !isPaid()) {
ManaCost unpaid = this.getUnpaid();
String promptText = ManaUtil.addSpecialManaPayAbilities(ability, game, unpaid);
if (player.playMana(ability, unpaid, promptText, game)) {
@ -138,7 +138,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
}
game.getState().getSpecialActions().removeManaActions();
}
return true;
return isPaid();
}
/**

View file

@ -4,48 +4,102 @@ import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.Card;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
* @author TheElk801
*/
public class LookAtTopCardOfLibraryAnyTimeEffect extends ContinuousEffectImpl {
private final TargetController targetLibrary;
public LookAtTopCardOfLibraryAnyTimeEffect() {
super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit);
staticText = "You may look at the top card of your library any time.";
this(TargetController.YOU, Duration.WhileOnBattlefield);
}
private LookAtTopCardOfLibraryAnyTimeEffect(final LookAtTopCardOfLibraryAnyTimeEffect effect) {
public LookAtTopCardOfLibraryAnyTimeEffect(TargetController targetLibrary, Duration duration) {
super(duration, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit);
this.targetLibrary = targetLibrary;
String libInfo;
switch (this.targetLibrary) {
case YOU:
libInfo = "your library";
break;
case OPPONENT:
libInfo = "opponents libraries";
break;
case SOURCE_TARGETS:
libInfo = "target player's library";
break;
default:
throw new IllegalArgumentException("Unknown target library type: " + targetLibrary);
}
staticText = duration.toString().isEmpty() ? "" : duration + " you may look at the top card of " + libInfo + " any time.";
}
protected LookAtTopCardOfLibraryAnyTimeEffect(final LookAtTopCardOfLibraryAnyTimeEffect effect) {
super(effect);
this.targetLibrary = effect.targetLibrary;
}
@Override
public boolean apply(Game game, Ability source) {
if (game.inCheckPlayableState()) { // Ignored - see https://github.com/magefree/mage/issues/6994
return false;
}
}
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Card topCard = controller.getLibrary().getFromTop(game);
if (topCard == null) {
if (!canLookAtNextTopLibraryCard(game)) {
return false;
}
MageObject obj = source.getSourceObject(game);
if (obj == null) {
return false;
}
if (!canLookAtNextTopLibraryCard(game)) {
Set<UUID> needPlayers = new HashSet<>();
switch (this.targetLibrary) {
case YOU: {
needPlayers.add(source.getControllerId());
break;
}
case OPPONENT: {
needPlayers.addAll(game.getOpponents(source.getControllerId()));
break;
}
case SOURCE_TARGETS: {
needPlayers.addAll(CardUtil.getAllSelectedTargets(source, game));
break;
}
}
Set<Card> needCards = new HashSet<>();
needPlayers.stream()
.map(game::getPlayer)
.filter(Objects::nonNull)
.map(player -> player.getLibrary().getFromTop(game))
.filter(Objects::nonNull)
.forEach(needCards::add);
if (needCards.isEmpty()) {
return false;
}
controller.lookAtCards("Top card of " + obj.getIdName() + " controller's library", topCard, game);
// all fine, can show top card
needCards.forEach(topCard -> {
Player owner = game.getPlayer(topCard.getOwnerId());
controller.lookAtCards(String.format("%s: top card of %s", obj.getName(), owner == null ? "error" : owner.getName()), topCard, game);
});
return true;
}

View file

@ -0,0 +1,23 @@
package mage.abilities.effects.common.continuous;
import mage.constants.Duration;
import mage.constants.TargetController;
/**
* @author JayDi85
*/
public class LookAtTopCardOfLibraryAnyTimeTargetEffect extends LookAtTopCardOfLibraryAnyTimeEffect {
public LookAtTopCardOfLibraryAnyTimeTargetEffect(Duration duration) {
super(TargetController.SOURCE_TARGETS, duration);
}
private LookAtTopCardOfLibraryAnyTimeTargetEffect(final LookAtTopCardOfLibraryAnyTimeTargetEffect effect) {
super(effect);
}
@Override
public LookAtTopCardOfLibraryAnyTimeTargetEffect copy() {
return new LookAtTopCardOfLibraryAnyTimeTargetEffect(this);
}
}

View file

@ -6,11 +6,14 @@ import mage.cards.Card;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
/**
@ -19,20 +22,44 @@ import java.util.UUID;
public class PlayTheTopCardEffect extends AsThoughEffectImpl {
private final FilterCard filter;
private final TargetController targetLibrary;
// can play card or can play lands/cast spells, see two modes below
private final boolean canPlayCardOnly;
/**
* Support targets, use TargetController.SOURCE_TARGETS
*/
public PlayTheTopCardEffect() {
this(new FilterCard("play lands and cast spells"), false);
this(TargetController.YOU);
}
public PlayTheTopCardEffect(FilterCard filter, boolean canPlayCardOnly) {
public PlayTheTopCardEffect(TargetController targetLibrary) {
this(targetLibrary, new FilterCard("play lands and cast spells"), false);
}
public PlayTheTopCardEffect(TargetController targetLibrary, FilterCard filter, boolean canPlayCardOnly) {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
this.filter = filter;
this.targetLibrary = targetLibrary;
this.canPlayCardOnly = canPlayCardOnly;
this.staticText = "You may " + filter.getMessage() + " from the top of your library";
String libInfo;
switch (this.targetLibrary) {
case YOU:
libInfo = "your library";
break;
case OPPONENT:
libInfo = "opponents libraries";
break;
case SOURCE_TARGETS:
libInfo = "target player's library";
break;
default:
throw new IllegalArgumentException("Unknown target library type: " + targetLibrary);
}
this.staticText = "You may " + filter.getMessage() + " from the top of " + libInfo;
// verify check: if you see "card" text in the rules then use card mode
// (there aren't any real cards after oracle update, but can be added in the future)
@ -44,6 +71,7 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
public PlayTheTopCardEffect(final PlayTheTopCardEffect effect) {
super(effect);
this.filter = effect.filter;
this.targetLibrary = effect.targetLibrary;
this.canPlayCardOnly = effect.canPlayCardOnly;
}
@ -71,7 +99,7 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
}
if (this.canPlayCardOnly) {
// check whole card intead part
// check whole card instead part
cardToCheck = cardToCheck.getMainCard();
}
@ -80,16 +108,50 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
return false;
}
// must be your card
Player player = game.getPlayer(cardToCheck.getOwnerId());
if (player == null || !player.getId().equals(affectedControllerId)) {
Player cardOwner = game.getPlayer(cardToCheck.getOwnerId());
Player controller = game.getPlayer(source.getControllerId());
if (cardOwner == null || controller == null) {
return false;
}
// must be from your library
Card topCard = player.getLibrary().getFromTop(game);
if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) {
return false;
// must be your or opponents library
switch (this.targetLibrary) {
case YOU: {
Card topCard = controller.getLibrary().getFromTop(game);
if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) {
return false;
}
break;
}
case OPPONENT: {
if (!game.getOpponents(controller.getId()).contains(cardOwner.getId())) {
return false;
}
Card topCard = cardOwner.getLibrary().getFromTop(game);
if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) {
return false;
}
break;
}
case SOURCE_TARGETS: {
UUID needCardId = cardToCheck.getMainCard().getId();
if (CardUtil.getAllSelectedTargets(source, game).stream()
.map(game::getPlayer)
.filter(Objects::nonNull)
.noneMatch(player -> {
Card topCard = player.getLibrary().getFromTop(game);
return topCard != null && topCard.getId().equals(needCardId);
})) {
return false;
}
break;
}
default: {
return false;
}
}
// can't cast without mana cost

View file

@ -0,0 +1,22 @@
package mage.abilities.effects.common.continuous;
import mage.constants.TargetController;
/**
* @author JayDi85
*/
public class PlayTheTopCardTargetEffect extends PlayTheTopCardEffect {
public PlayTheTopCardTargetEffect() {
super(TargetController.SOURCE_TARGETS);
}
public PlayTheTopCardTargetEffect(final PlayTheTopCardTargetEffect effect) {
super(effect);
}
@Override
public PlayTheTopCardTargetEffect copy() {
return new PlayTheTopCardTargetEffect(this);
}
}

View file

@ -28,7 +28,7 @@ public enum Outcome {
PutManaInPool(true),
Regenerate(true),
PreventDamage(true), // TODO: check good or bad
PreventCast(false), // TODO: check good or bad
PreventCast(false),
RedirectDamage(true), // TODO: check good or bad
Tap(false),
Transform(true),

View file

@ -25,7 +25,8 @@ public enum TargetController {
OWNER,
CONTROLLER_ATTACHED_TO,
NEXT,
EACH_PLAYER;
EACH_PLAYER,
SOURCE_TARGETS;
private final OwnerPredicate ownerPredicate;
private final PlayerPredicate playerPredicate;

View file

@ -11,11 +11,24 @@ import mage.players.Player;
*/
public enum CardOnTopOfLibraryPredicate implements ObjectPlayerPredicate<ObjectPlayer<Card>> {
instance;
YOUR,
ANY;
@Override
public boolean apply(ObjectPlayer<Card> input, Game game) {
Player player = game.getPlayer(input.getObject().getOwnerId());
Player player;
switch (this) {
case YOUR:
player = game.getPlayer(input.getPlayerId());
break;
case ANY:
default:
player = game.getPlayer(input.getObject().getOwnerId());
break;
}
if (player == null) {
return false;
}

View file

@ -24,10 +24,11 @@ public class BoosterDraft extends DraftImpl {
cardNum = 1;
fireUpdatePlayersEvent();
while (!isAbort() && pickCards()) {
if ((boosterNum + 1) % 2 == 1) {
passLeft();
// pass booster order: left -> right -> left
if (boosterNum % 2 == 1) {
passBoosterToLeft();
} else {
passRight();
passBoosterToRight();
}
fireUpdatePlayersEvent();
}

View file

@ -149,11 +149,11 @@ public abstract class DraftImpl implements Draft {
this.addPick(playerId, booster.get(booster.size()-1).getId(), null);
}
protected void passLeft() {
protected void passBoosterToLeft() {
synchronized (players) {
UUID startId = table.get(0);
UUID currentId = startId;
UUID nextId = table.getNext();
UUID nextId = table.getNext(); // getNext return left player by default
DraftPlayer current = players.get(currentId);
DraftPlayer next = players.get(nextId);
List<Card> currentBooster = current.booster;
@ -170,11 +170,11 @@ public abstract class DraftImpl implements Draft {
}
}
protected void passRight() {
protected void passBoosterToRight() {
synchronized (players) {
UUID startId = table.get(0);
UUID currentId = startId;
UUID prevId = table.getPrevious();
UUID prevId = table.getPrevious(); // getPrevious return right player by default
DraftPlayer current = players.get(currentId);
DraftPlayer prev = players.get(prevId);
List<Card> currentBooster = current.booster;

View file

@ -32,7 +32,8 @@ public class RichManBoosterDraft extends DraftImpl {
cardNum = 1;
fireUpdatePlayersEvent();
while (!isAbort() && pickCards()) {
passLeft();
// new booster each time, so order is irrelevant
passBoosterToLeft();
fireUpdatePlayersEvent();
}
boosterNum++;
@ -42,7 +43,7 @@ public class RichManBoosterDraft extends DraftImpl {
}
@Override
protected void passLeft() {
protected void passBoosterToLeft() {
synchronized (players) {
UUID startId = table.get(0);
UUID currentId = startId;

View file

@ -30,7 +30,8 @@ public class RichManCubeBoosterDraft extends DraftImpl {
cardNum = 1;
fireUpdatePlayersEvent();
while (!isAbort() && pickCards()) {
passLeft();
// new booster each time, so order is irrelevant
passBoosterToLeft();
fireUpdatePlayersEvent();
}
boosterNum++;
@ -40,7 +41,7 @@ public class RichManCubeBoosterDraft extends DraftImpl {
}
@Override
protected void passLeft() {
protected void passBoosterToLeft() {
synchronized (players) {
UUID startId = table.get(0);
UUID currentId = startId;

View file

@ -6,6 +6,8 @@ import mage.util.CircularList;
import java.util.UUID;
/**
* Default players order: left (next player seated to the active player's left)
*
* @author BetaSteward_at_googlemail.com
*/
public class PlayerList extends CircularList<UUID> {
@ -34,7 +36,9 @@ public class PlayerList extends CircularList<UUID> {
}
/**
* checkNextTurnReached - use it turns/priority code only to mark leaved player as "reached next turn end" (need for some continous effects)
* Find next player. Default order: next player from the left
*
* @checkNextTurnReached - use it turns/priority code only to mark leaved player as "reached next turn end" (need for some continous effects)
*/
public Player getNext(Game game, boolean checkNextTurnReached) {
UUID start = this.get();