Merge remote-tracking branch 'upstream/master'

This commit is contained in:
CCTV-1
2020-03-20 11:07:17 +08:00
53 changed files with 550 additions and 241 deletions

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
</parent>
<artifactId>forge-ai</artifactId>

View File

@@ -2323,7 +2323,7 @@ public class ComputerUtil {
return chosen;
}
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes) {
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes, Player forPlayer) {
final Card source = sa.getHostCard();
final Player controller = source.getController();
final Game game = controller.getGame();

View File

@@ -513,8 +513,8 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes) {
return ComputerUtil.vote(player, options, sa, votes);
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) {
return ComputerUtil.vote(player, options, sa, votes, forPlayer);
}
@Override

View File

@@ -1295,6 +1295,26 @@ public class SpecialCardAi {
}
}
// Timmerian Fiends
public static class TimmerianFiends {
public static boolean consider(final Player ai, final SpellAbility sa) {
final Card targeted = sa.getParentTargetingCard().getTargetCard();
if (targeted == null) {
return false;
}
if (targeted.isCreature()) {
if (ComputerUtil.aiLifeInDanger(ai, true, 0)) {
return true; // do it, hoping to save a valuable potential blocker etc.
}
return ComputerUtilCard.evaluateCreature(targeted) >= 200; // might need tweaking
} else {
// TODO: this currently compares purely by CMC. To be somehow improved, especially for stuff like the Power Nine etc.
return ComputerUtilCard.evaluatePermanentList(new CardCollection(targeted)) >= 3;
}
}
}
// Volrath's Shapeshifter
public static class VolrathsShapeshifter {
public static boolean consider(final Player ai, final SpellAbility sa) {

View File

@@ -1,15 +1,10 @@
package forge.ai.ability;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -24,6 +19,11 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class MillAi extends SpellAbilityAi {
@Override
@@ -196,6 +196,10 @@ public class MillAi extends SpellAbilityAi {
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if ("TimmerianFiends".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.TimmerianFiends.consider(player, sa);
}
return true;
}

View File

@@ -46,6 +46,12 @@ public class VoteAi extends SpellAbilityAi {
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
if (params.containsKey("Voter")) {
Player p = (Player)params.get("Voter");
if (p.isOpponentOf(player)) {
return min;
}
}
if (sa.getActivatingPlayer().isOpponentOf(player)) {
return min;
}

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
</parent>
<artifactId>forge-core</artifactId>

View File

@@ -6,6 +6,7 @@ import forge.card.CardRarity;
import forge.card.CardRules;
import forge.card.CardType.CoreType;
import forge.card.MagicColor;
import forge.util.PredicateCard;
import forge.util.PredicateString;
import org.apache.commons.lang3.StringUtils;
@@ -60,6 +61,8 @@ public interface IPaperCard extends InventoryItem {
return new PredicateNames(what);
}
public static PredicateCards cards(final List<PaperCard> what) { return new PredicateCards(what); }
private static final class PredicateColor implements Predicate<PaperCard> {
private final byte operand;
@@ -161,6 +164,25 @@ public interface IPaperCard extends InventoryItem {
}
}
private static final class PredicateCards extends PredicateCard<PaperCard> {
private final List<PaperCard> operand;
@Override
public boolean apply(final PaperCard card) {
for (final PaperCard element : this.operand) {
if (this.op(card, element)) {
return true;
}
}
return false;
}
private PredicateCards(final List<PaperCard> operand) {
super(StringOp.EQUALS);
this.operand = operand;
}
}
/**
* Pre-built predicates are stored here to allow their re-usage and
* easier access from code.

View File

@@ -566,12 +566,8 @@ public class BoosterGenerator {
toAdd = IPaperCard.Predicates.printedInSets(sets);
} else if (operator.startsWith("fromSheet(") && invert) {
String sheetName = StringUtils.strip(operator.substring(9), "()\" ");
Iterable<PaperCard> src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
List<String> cardNames = Lists.newArrayList();
for (PaperCard card : src) {
cardNames.add(card.getName());
}
toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames));
Iterable<PaperCard> cards = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
toAdd = IPaperCard.Predicates.cards(Lists.newArrayList(cards));
}
if (toAdd == null) {

View File

@@ -0,0 +1,83 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2020 Jamin W. Collins
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.util;
import com.google.common.base.Predicate;
import forge.item.PaperCard;
/**
* Special predicate class to perform string operations.
*
* @param <T>
* the generic type
*/
public abstract class PredicateCard<T> implements Predicate<T> {
/** Possible operators for string operands. */
public enum StringOp {
/** The EQUALS. */
EQUALS,
}
/** The operator. */
private final StringOp operator;
/**
* Op.
*
* @param op1
* the op1
* @param op2
* the op2
* @return true, if successful
*/
protected final boolean op(final PaperCard op1, final PaperCard op2) {
switch (this.getOperator()) {
case EQUALS:
return op1.equals(op2);
default:
return false;
}
}
/**
* Instantiates a new predicate string.
*
* @param operator
* the operator
*/
public PredicateCard(final StringOp operator) {
this.operator = operator;
}
/**
* @return the operator
*/
public StringOp getOperator() {
return operator;
}
public static PredicateCard<PaperCard> equals(final PaperCard what) {
return new PredicateCard<PaperCard>(StringOp.EQUALS) {
@Override
public boolean apply(PaperCard subject) {
return op(subject, what);
}
};
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
</parent>
<artifactId>forge-game</artifactId>

View File

@@ -914,4 +914,17 @@ public class Game {
}
return false;
}
public Player getControlVote() {
Player result = null;
long maxValue = 0;
for (Player p : getPlayers()) {
Long v = p.getHighestControlVote();
if (v != null && v > maxValue) {
maxValue = v;
result = p;
}
}
return result;
}
}

View File

@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -37,7 +37,7 @@ import com.google.common.collect.Maps;
* <p>
* StaticEffect class.
* </p>
*
*
* @author Forge
* @version $Id$
*/
@@ -72,7 +72,7 @@ public class StaticEffect {
/**
* setTimestamp TODO Write javadoc for this method.
*
*
* @param t
* a long
*/
@@ -82,7 +82,7 @@ public class StaticEffect {
/**
* getTimestamp. TODO Write javadoc for this method.
*
*
* @return a long
*/
public final long getTimestamp() {
@@ -93,7 +93,7 @@ public class StaticEffect {
* <p>
* Getter for the field <code>source</code>.
* </p>
*
*
* @return a {@link forge.game.card.Card} object.
*/
public final Card getSource() {
@@ -104,7 +104,7 @@ public class StaticEffect {
* <p>
* Getter for the field <code>affectedCards</code>.
* </p>
*
*
* @return a {@link forge.CardList} object.
*/
public final CardCollectionView getAffectedCards() {
@@ -115,7 +115,7 @@ public class StaticEffect {
* <p>
* Setter for the field <code>affectedCards</code>.
* </p>
*
*
* @param list
* a {@link forge.CardList} object.
*/
@@ -125,7 +125,7 @@ public class StaticEffect {
/**
* Gets the affected players.
*
*
* @return the affected players
*/
public final List<Player> getAffectedPlayers() {
@@ -134,7 +134,7 @@ public class StaticEffect {
/**
* Sets the affected players.
*
*
* @param list
* the new affected players
*/
@@ -144,7 +144,7 @@ public class StaticEffect {
/**
* setParams. TODO Write javadoc for this method.
*
*
* @param params
* a HashMap
*/
@@ -154,7 +154,7 @@ public class StaticEffect {
/**
* Gets the params.
*
*
* @return the params
*/
public final Map<String, String> getParams() {
@@ -171,13 +171,12 @@ public class StaticEffect {
/**
* Undo everything that was changed by this effect.
*
*
* @return a {@link CardCollectionView} of all affected cards.
*/
final CardCollectionView remove() {
final CardCollectionView affectedCards = getAffectedCards();
final List<Player> affectedPlayers = getAffectedPlayers();
//final Map<String, String> params = getParams();
String changeColorWordsTo = null;
@@ -245,6 +244,10 @@ public class StaticEffect {
p.removeMaxLandPlays(getTimestamp());
p.removeMaxLandPlaysInfinite(getTimestamp());
p.removeControlVote(getTimestamp());
p.removeAdditionalVote(getTimestamp());
p.removeAdditionalOptionalVote(getTimestamp());
}
// modify the affected card

View File

@@ -70,7 +70,7 @@ public class DebuffEffect extends SpellAbilityEffect {
for (final Card tgtC : getTargetCards(sa)) {
final List<String> addedKW = Lists.newArrayList();
final List<String> removedKW = Lists.newArrayList();
if (tgtC.isInPlay() && tgtC.canBeTargetedBy(sa)) {
if (tgtC.isInPlay() && (!sa.usesTargeting() || tgtC.canBeTargetedBy(sa))) {
if (sa.hasParam("AllSuffixKeywords")) {
String suffix = sa.getParam("AllSuffixKeywords");
for (final KeywordInterface kw : tgtC.getKeywords()) {

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -9,9 +10,9 @@ import forge.game.ability.AbilityKey;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
@@ -19,10 +20,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
@@ -67,33 +65,29 @@ public class VoteEffect extends SpellAbilityEffect {
return;
}
// starting with the activator
int pSize = tgtPlayers.size();
Player activator = sa.getActivatingPlayer();
while (tgtPlayers.contains(activator) && !activator.equals(Iterables.getFirst(tgtPlayers, null))) {
tgtPlayers.add(pSize - 1, tgtPlayers.remove(0));
// starting with the activator
int aidx = tgtPlayers.indexOf(activator);
if (aidx != -1) {
Collections.rotate(tgtPlayers, -aidx);
}
ListMultimap<Object, Player> votes = ArrayListMultimap.create();
Player voter = null;
PlayerCollection voters = game.getPlayers().filter(PlayerPredicates.hasKeyword("You choose how each player votes this turn."));
if (voters.size() > 1) {
List<Card> illusions = CardLists.filter(voters.getCardsIn(ZoneType.Command), CardPredicates.nameEquals("Illusion of Choice Effect"));
voter = Collections.max(illusions, CardPredicates.compareByTimestamp()).getController();
} else if (voters.size() == 1) {
voter = voters.get(0);
}
Player voter = game.getControlVote();
for (final Player p : tgtPlayers) {
int voteAmount = p.getKeywords().getAmount("You get an additional vote.") + 1;
int optionalVotes = p.getKeywords().getAmount("You may vote an additional time.");
voteAmount += p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyAdditionalVotesDoYouWant"), 0, optionalVotes);
int voteAmount = p.getAdditionalVotesAmount() + 1;
int optionalVotes = p.getAdditionalOptionalVotesAmount();
Player realVoter = voter == null ? p : voter;
Map<String, Object> params = Maps.newHashMap();
params.put("Voter", realVoter);
voteAmount += p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyAdditionalVotesDoYouWant"), 0, optionalVotes, params);
for (int i = 0; i < voteAmount; i++) {
Object result = realVoter.getController().vote(sa, host + Localizer.getInstance().getMessage("lblVote") + ":", voteType, votes);
Object result = realVoter.getController().vote(sa, host + Localizer.getInstance().getMessage("lblVote") + ":", voteType, votes, p);
votes.put(result, p);
host.getGame().getAction().nofityOfValue(sa, p, result + "\r\n" + Localizer.getInstance().getMessage("lblCurrentVote") + ":" + votes, p);
@@ -104,34 +98,49 @@ public class VoteEffect extends SpellAbilityEffect {
runParams.put(AbilityKey.AllVotes, votes);
game.getTriggerHandler().runTrigger(TriggerType.Vote, runParams, false);
List<String> subAbs = Lists.newArrayList();
final List<Object> mostVotes = getMostVotes(votes);
if (sa.hasParam("Tied") && mostVotes.size() > 1) {
subAbs.add(sa.getParam("Tied"));
} else if (sa.hasParam("VoteSubAbility")) {
for (final Object o : mostVotes) {
host.addRemembered(o);
}
subAbs.add(sa.getParam("VoteSubAbility"));
} else {
for (Object type : mostVotes) {
subAbs.add(sa.getParam("Vote" + type.toString()));
}
}
if (sa.hasParam("StoreVoteNum")) {
for (final Object type : voteType) {
host.setSVar("VoteNum" + type, "Number$" + votes.get(type).size());
}
} else {
for (final String subAb : subAbs) {
final SpellAbility action = AbilityFactory.getAbility(host.getSVar(subAb), host);
if (sa.hasParam("EachVote")) {
for (Map.Entry<Object, Collection<Player>> e : votes.asMap().entrySet()) {
final SpellAbility action = AbilityFactory.getAbility(host, sa.getParam("Vote" + e.getKey().toString()));
action.setActivatingPlayer(sa.getActivatingPlayer());
((AbilitySub) action).setParent(sa);
AbilityUtils.resolve(action);
for (Player p : e.getValue()) {
host.addRemembered(p);
AbilityUtils.resolve(action);
host.removeRemembered(p);
}
}
} else {
List<String> subAbs = Lists.newArrayList();
final List<Object> mostVotes = getMostVotes(votes);
if (sa.hasParam("Tied") && mostVotes.size() > 1) {
subAbs.add(sa.getParam("Tied"));
} else if (sa.hasParam("VoteSubAbility")) {
for (final Object o : mostVotes) {
host.addRemembered(o);
}
subAbs.add(sa.getParam("VoteSubAbility"));
} else {
for (Object type : mostVotes) {
subAbs.add(sa.getParam("Vote" + type.toString()));
}
}
if (sa.hasParam("StoreVoteNum")) {
for (final Object type : voteType) {
host.setSVar("VoteNum" + type, "Number$" + votes.get(type).size());
}
} else {
for (final String subAb : subAbs) {
final SpellAbility action = AbilityFactory.getAbility(host, subAb);
action.setActivatingPlayer(sa.getActivatingPlayer());
((AbilitySub) action).setParent(sa);
AbilityUtils.resolve(action);
}
}
if (sa.hasParam("VoteSubAbility")) {
host.clearRemembered();
}
}
if (sa.hasParam("VoteSubAbility")) {
host.clearRemembered();
}
}

View File

@@ -3938,7 +3938,9 @@ public class Card extends GameEntity implements Comparable<Card> {
keywordsGrantedByTextChanges.add(newKw);
}
}
addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, false, timestamp, true);
if (!addKeywords.isEmpty() || !removeKeywords.isEmpty()) {
addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, false, timestamp, true);
}
}
private void updateKeywordsOnRemoveChangedText(final KeywordsChange k) {

View File

@@ -162,6 +162,10 @@ public class Player extends GameEntity implements Comparable<Player> {
private Card blessingEffect = null;
private Card keywordEffect = null;
private Map<Long, Integer> additionalVotes = Maps.newHashMap();
private Map<Long, Integer> additionalOptionalVotes = Maps.newHashMap();
private SortedSet<Long> controlVotes = Sets.newTreeSet();
private final AchievementTracker achievementTracker = new AchievementTracker();
private final PlayerView view;
@@ -3067,4 +3071,86 @@ public class Player extends GameEntity implements Comparable<Player> {
this.updateZoneForView(com);
return keywordEffect;
}
public void addAdditionalVote(long timestamp, int value) {
additionalVotes.put(timestamp, value);
getView().updateAdditionalVote(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
}
public void removeAdditionalVote(long timestamp) {
if (additionalVotes.remove(timestamp) != null) {
getView().updateAdditionalVote(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
}
}
public int getAdditionalVotesAmount() {
int value = 0;
for (Integer i : additionalVotes.values()) {
value += i;
}
return value;
}
public void addAdditionalOptionalVote(long timestamp, int value) {
additionalOptionalVotes.put(timestamp, value);
getView().updateOptionalAdditionalVote(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
}
public void removeAdditionalOptionalVote(long timestamp) {
if (additionalOptionalVotes.remove(timestamp) != null) {
getView().updateOptionalAdditionalVote(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
}
}
public int getAdditionalOptionalVotesAmount() {
int value = 0;
for (Integer i : additionalOptionalVotes.values()) {
value += i;
}
return value;
}
public boolean addControlVote(long timestamp) {
if (controlVotes.add(timestamp)) {
updateControlVote();
return true;
}
return false;
}
public boolean removeControlVote(long timestamp) {
if (controlVotes.remove(timestamp)) {
updateControlVote();
return true;
}
return false;
}
void updateControlVote() {
// need to update all players because it can't know
Player control = getGame().getControlVote();
for (Player pl : getGame().getPlayers()) {
pl.getView().updateControlVote(pl.equals(control));
getGame().fireEvent(new GameEventPlayerStatsChanged(pl, false));
}
}
public Set<Long> getControlVote() {
return controlVotes;
}
public void setControlVote(Set<Long> value) {
controlVotes.clear();
controlVotes.addAll(value);
updateControlVote();
}
public Long getHighestControlVote() {
if (controlVotes.isEmpty()) {
return null;
}
return controlVotes.last();
}
}

View File

@@ -166,7 +166,7 @@ public abstract class PlayerController {
return chooseSomeType(kindOfType, sa, validTypes, invalidTypes, false);
}
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes);
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer);
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question);
public abstract CardCollectionView getCardsToMulligan(Player firstPlayer);

View File

@@ -271,6 +271,27 @@ public class PlayerView extends GameEntityView {
set(TrackableProperty.NumDrawnThisTurn, p.getNumDrawnThisTurn());
}
public int getAdditionalVote() {
return get(TrackableProperty.AdditionalVote);
}
public void updateAdditionalVote(Player p) {
set(TrackableProperty.AdditionalVote, p.getAdditionalVotesAmount());
}
public int getOptionalAdditionalVote() {
return get(TrackableProperty.OptionalAdditionalVote);
}
public void updateOptionalAdditionalVote(Player p) {
set(TrackableProperty.OptionalAdditionalVote, p.getAdditionalOptionalVotesAmount());
}
public boolean getControlVote() {
return get(TrackableProperty.ControlVotes);
}
public void updateControlVote(boolean val) {
set(TrackableProperty.ControlVotes, val);
}
public ImmutableMultiset<String> getKeywords() {
return get(TrackableProperty.Keywords);
}
@@ -497,6 +518,19 @@ public class PlayerView extends GameEntityView {
details.add(Localizer.getInstance().getMessage("lblCardDrawnThisTurnHas", String.valueOf(getNumDrawnThisTurn())));
details.add(Localizer.getInstance().getMessage("lblDamagepreventionHas", String.valueOf(getPreventNextDamage())));
int v = getAdditionalVote();
if (v > 0) {
details.add(Localizer.getInstance().getMessage("lblAdditionalVotes", String.valueOf(v)));
}
v = getOptionalAdditionalVote();
if (v > 0) {
details.add(Localizer.getInstance().getMessage("lblOptionalAdditionalVotes", String.valueOf(v)));
}
if (getControlVote()) {
details.add(Localizer.getInstance().getMessage("lblControlsVote"));
}
if (getIsExtraTurn()) {
details.add(Localizer.getInstance().getMessage("lblIsExtraTurn"));
}

View File

@@ -181,15 +181,9 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
layers.add(StaticAbilityLayer.MODIFYPT);
}
if (hasParam("AddHiddenKeyword")) {
layers.add(StaticAbilityLayer.RULES);
}
if (hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")) {
layers.add(StaticAbilityLayer.RULES);
}
if (hasParam("AdjustLandPlays")) {
if (hasParam("AddHiddenKeyword")
|| hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")
|| hasParam("AdjustLandPlays") || hasParam("ControlVote") || hasParam("AdditionalVote") || hasParam("AdditionalOptionalVote")) {
layers.add(StaticAbilityLayer.RULES);
}

View File

@@ -513,6 +513,20 @@ public final class StaticAbilityContinuous {
}
}
if (params.containsKey("ControlVote")) {
p.addControlVote(se.getTimestamp());
}
if (params.containsKey("AdditionalVote")) {
String mhs = params.get("AdditionalVote");
int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb);
p.addAdditionalVote(se.getTimestamp(), add);
}
if (params.containsKey("AdditionalOptionalVote")) {
String mhs = params.get("AdditionalOptionalVote");
int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb);
p.addAdditionalOptionalVote(se.getTimestamp(), add);
}
if (params.containsKey("RaiseMaxHandSize")) {
String rmhs = params.get("RaiseMaxHandSize");
int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb);

View File

@@ -134,6 +134,9 @@ public enum TrackableProperty {
HasUnlimitedLandPlay(TrackableTypes.BooleanType),
NumLandThisTurn(TrackableTypes.IntegerType),
NumDrawnThisTurn(TrackableTypes.IntegerType),
AdditionalVote(TrackableTypes.IntegerType),
OptionalAdditionalVote(TrackableTypes.IntegerType),
ControlVotes(TrackableTypes.BooleanType),
Keywords(TrackableTypes.KeywordCollectionViewType, FreezeMode.IgnoresFreeze),
Commander(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze),
CommanderCast(TrackableTypes.IntegerMapType),

View File

@@ -19,7 +19,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-android</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-desktop</artifactId>
@@ -213,7 +213,7 @@
<plugin>
<groupId>com.akathist.maven.plugins.launch4j</groupId>
<artifactId>launch4j-maven-plugin</artifactId>
<version>1.5.2</version>
<version>1.7.25</version>
<executions>
<execution>
<id>l4j-gui</id>
@@ -235,7 +235,10 @@
</classPath>
<jre>
<minVersion>1.8.0</minVersion>
<maxHeapSize>1024</maxHeapSize>
<maxHeapSize>4096</maxHeapSize>
<opts>
<opt>-Dfile.encoding=UTF-8</opt>
</opts>
</jre>
<versionInfo>
<fileVersion>
@@ -367,7 +370,7 @@
<plugin>
<groupId>com.akathist.maven.plugins.launch4j</groupId>
<artifactId>launch4j-maven-plugin</artifactId>
<version>1.5.2</version>
<version>1.7.25</version>
<executions>
<execution>
<id>l4j-gui</id>
@@ -389,7 +392,10 @@
</classPath>
<jre>
<minVersion>1.8.0</minVersion>
<maxHeapSize>1024</maxHeapSize>
<maxHeapSize>4096</maxHeapSize>
<opts>
<opt>-Dfile.encoding=UTF-8</opt>
</opts>
</jre>
<versionInfo>
<fileVersion>
@@ -564,7 +570,7 @@
<option value="-Dcom.apple.macos.useScreenMenuBar=true" />
<option value="-Dcom.apple.mrj.application.apple.menu.about.name=Forge" />
<option value="-Dcom.apple.smallTabs=true" />
<option value="-Xmx1024M" />
<option value="-Xmx4096M" />
<option value="-Dapp.dir=$APP_ROOT/Contents/Resources/" />
</bundleapp>
<copy todir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res">
@@ -675,7 +681,7 @@
<option value="-Dcom.apple.macos.useScreenMenuBar=true" />
<option value="-Dcom.apple.mrj.application.apple.menu.about.name=Forge" />
<option value="-Dcom.apple.smallTabs=true" />
<option value="-Xmx1024M" />
<option value="-Xmx4096M" />
<option value="-Dapp.dir=$APP_ROOT/Contents/Resources/" />
</bundleapp>
<copy todir="${project.build.directory}/${project.build.finalName}-osx/Forge.app/Contents/Resources/res">

View File

@@ -1,3 +1,3 @@
#!/bin/sh
cd "`dirname \"$0\"`"
java -Xmx1024m -jar $project.build.finalName$
cd $(dirname "${0}")
java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$

View File

@@ -1,3 +1,3 @@
#!/bin/sh
cd "`dirname \"$0\"`"
java -Xmx1024m -jar $project.build.finalName$
cd $(dirname "${0}")
java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$

View File

@@ -486,7 +486,7 @@ public class PlayerControllerForTests extends PlayerController {
}
@Override
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes) {
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) {
return chooseItem(options);
}

View File

@@ -12,7 +12,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-ios</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile-dev</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
</parent>
<artifactId>forge-gui-mobile</artifactId>

View File

@@ -164,7 +164,7 @@ public class ImageCache {
}
return image;
}
public static void preloadCache(Iterable keys) {
public static void preloadCache(Iterable<String> keys) {
cache.getAll(keys);
}
public static TextureRegion croppedBorderImage(Texture image, boolean fullborder) {
@@ -173,10 +173,9 @@ public class ImageCache {
float rscale = 0.96f;
int rw = Math.round(image.getWidth()*rscale);
int rh = Math.round(image.getHeight()*rscale);
int rx = Math.round((image.getWidth() - rw)/2);
int ry = Math.round((image.getHeight() - rh)/2)-2;
TextureRegion rimage = new TextureRegion(image, rx, ry, rw, rh);
return rimage;
int rx = Math.round((image.getWidth() - rw)/2f);
int ry = Math.round((image.getHeight() - rh)/2f)-2;
return new TextureRegion(image, rx, ry, rw, rh);
}
public static boolean isWhiteBordered(IPaperCard c) {
if (c == null)

View File

@@ -535,6 +535,9 @@ public class CardRenderer {
}
public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos) {
drawCardWithOverlays(g, card, x, y, w, h, pos, false);
}
public static void drawCardWithOverlays(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean stackview) {
boolean canShow = MatchController.instance.mayView(card);
float oldAlpha = g.getfloatAlphaComposite();
boolean unselectable = !MatchController.instance.isSelectable(card) && MatchController.instance.isSelecting();
@@ -555,32 +558,10 @@ public class CardRenderer {
Color color = FSkinColor.fromRGB(borderColor.r, borderColor.g, borderColor.b);
color = FSkinColor.tintColor(Color.WHITE, color, CardRenderer.PT_BOX_TINT);
//draw name and mana cost overlays if card is small or default card image being used
if (h <= NAME_COST_THRESHOLD && canShow) {
if (showCardNameOverlay(card)) {
g.drawOutlinedText(CardTranslation.getTranslatedName(details.getName()), FSkinFont.forHeight(h * 0.15f), Color.WHITE, Color.BLACK, x + padding -1f, y + padding, w - 2 * padding, h * 0.4f, true, Align.left, false);
}
if (showCardManaCostOverlay(card)) {
float manaSymbolSize = w / 4.5f;
if (card.isSplitCard() && card.hasAlternateState()) {
if (!card.isFaceDown()) { // no need to draw mana symbols on face down split cards (e.g. manifested)
float dy = manaSymbolSize / 2 + Utils.scale(5);
PaperCard pc = StaticData.instance().getCommonCards().getCard(card.getName());
if (Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH)){
dy *= -1; // flip card costs for Aftermath cards
}
drawManaCost(g, card.getAlternateState().getManaCost(), x - padding, y - dy, w + 2 * padding, h, manaSymbolSize);
drawManaCost(g, card.getCurrentState().getManaCost(), x - padding, y + dy, w + 2 * padding, h, manaSymbolSize);
}
}
else {
drawManaCost(g, card.getCurrentState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
}
}
}
//card name && manacost original position is here moved at the bottom...
if (stackview)
return; //override
if (pos == CardStackPosition.BehindVert) { return; } //remaining rendering not needed if card is behind another card in a vertical stack
boolean onTop = (pos == CardStackPosition.Top);
@@ -656,6 +637,7 @@ public class CardRenderer {
float abiSpace = cw / 5.7f;
float abiCount = 0;
if (unselectable){ g.setAlphaComposite(0.6f); }
if (onbattlefield && onTop && showAbilityIcons(card)) {
if (card.isToken()){
CardFaceSymbols.drawSymbol("token", g, abiX, abiY, abiScale, abiScale);
@@ -664,85 +646,63 @@ public class CardRenderer {
}
if (card.getCurrentState().hasFlying()) {
CardFaceSymbols.drawSymbol("flying", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasHaste()) {
CardFaceSymbols.drawSymbol("haste", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasDoubleStrike()) {
CardFaceSymbols.drawSymbol("doublestrike", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().hasFirstStrike()) {
CardFaceSymbols.drawSymbol("firststrike", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasDeathtouch()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("deathtouch", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasIndestructible()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("indestructible", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasMenace()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("menace", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasFear()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("fear", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasIntimidate()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("intimidate", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasShadow()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("shadow", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasHorsemanship()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("horsemanship", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
@@ -753,57 +713,41 @@ public class CardRenderer {
List<String> listHK = Arrays.asList(splitK);
if (listHK.contains("generic")) {
CardFaceSymbols.drawSymbol("hexproof", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (listHK.contains("R")) {
CardFaceSymbols.drawSymbol("hexproofR", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (listHK.contains("B")) {
CardFaceSymbols.drawSymbol("hexproofB", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (listHK.contains("U")) {
CardFaceSymbols.drawSymbol("hexproofU", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (listHK.contains("G")) {
CardFaceSymbols.drawSymbol("hexproofG", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (listHK.contains("W")) {
CardFaceSymbols.drawSymbol("hexproofW", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (listHK.contains("monocolored")) {
CardFaceSymbols.drawSymbol("hexproofC", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
} else {
CardFaceSymbols.drawSymbol("hexproof", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
@@ -811,48 +755,36 @@ public class CardRenderer {
else if (card.getCurrentState().hasShroud()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("shroud", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasVigilance()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("vigilance", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasTrample()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("trample", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasReach()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("reach", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasLifelink()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("lifelink", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
if (card.getCurrentState().hasDefender()) {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
CardFaceSymbols.drawSymbol("defender", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
@@ -861,132 +793,138 @@ public class CardRenderer {
if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); }
if (card.getCurrentState().getProtectionKey().contains("everything") || card.getCurrentState().getProtectionKey().contains("allcolors")) {
CardFaceSymbols.drawSymbol("protectAll", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().contains("coloredspells")) {
CardFaceSymbols.drawSymbol("protectColoredSpells", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("R")) {
CardFaceSymbols.drawSymbol("protectR", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("G")) {
CardFaceSymbols.drawSymbol("protectG", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("B")) {
CardFaceSymbols.drawSymbol("protectB", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("U")) {
CardFaceSymbols.drawSymbol("protectU", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("W")) {
CardFaceSymbols.drawSymbol("protectW", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("RG")||card.getCurrentState().getProtectionKey().equals("GR")) {
CardFaceSymbols.drawSymbol("protectRG", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("RB")||card.getCurrentState().getProtectionKey().equals("BR")) {
CardFaceSymbols.drawSymbol("protectRB", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("RU")||card.getCurrentState().getProtectionKey().equals("UR")) {
CardFaceSymbols.drawSymbol("protectRU", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("RW")||card.getCurrentState().getProtectionKey().equals("WR")) {
CardFaceSymbols.drawSymbol("protectRW", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("GB")||card.getCurrentState().getProtectionKey().equals("BG")) {
CardFaceSymbols.drawSymbol("protectGB", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("GU")||card.getCurrentState().getProtectionKey().equals("UG")) {
CardFaceSymbols.drawSymbol("protectGU", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("GW")||card.getCurrentState().getProtectionKey().equals("WG")) {
CardFaceSymbols.drawSymbol("protectGW", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("BU")||card.getCurrentState().getProtectionKey().equals("UB")) {
CardFaceSymbols.drawSymbol("protectBU", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("BW")||card.getCurrentState().getProtectionKey().equals("WB")) {
CardFaceSymbols.drawSymbol("protectBW", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().equals("UW")||card.getCurrentState().getProtectionKey().equals("WU")) {
CardFaceSymbols.drawSymbol("protectUW", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
else if (card.getCurrentState().getProtectionKey().contains("generic") || card.getCurrentState().getProtectionKey().length() > 2) {
CardFaceSymbols.drawSymbol("protectGeneric", g, abiX, abiY, abiScale, abiScale);
if (unselectable){
g.setAlphaComposite(0.6f); g.fillRect(Color.BLACK, abiX, abiY, abiScale, abiScale ); g.setAlphaComposite(oldAlpha);}
abiY += abiSpace;
abiCount += 1;
}
}
}
//draw name and mana cost overlays if card is small or default card image being used
if (h <= NAME_COST_THRESHOLD && canShow) {
if (showCardNameOverlay(card)) {
float multiplier;
switch (Forge.extrawide) {
case "default":
multiplier = 0.145f; //good for tablets with 16:10 or similar
break;
case "wide":
multiplier = 0.150f;
break;
case "extrawide":
multiplier = 0.155f; //good for tall phones with 21:9 or similar
break;
default:
multiplier = 0.150f;
break;
}
g.drawOutlinedText(CardTranslation.getTranslatedName(details.getName()), FSkinFont.forHeight(h * multiplier), Color.WHITE, Color.BLACK, x + padding -1f, y + padding, w - 2 * padding, h * 0.4f, true, Align.left, false);
}
if (showCardManaCostOverlay(card)) {
float manaSymbolSize = w / 4.5f;
if (card.isSplitCard() && card.hasAlternateState()) {
if (!card.isFaceDown()) { // no need to draw mana symbols on face down split cards (e.g. manifested)
float dy = manaSymbolSize / 2 + Utils.scale(5);
PaperCard pc = StaticData.instance().getCommonCards().getCard(card.getName());
if (Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH)){
dy *= -1; // flip card costs for Aftermath cards
}
drawManaCost(g, card.getAlternateState().getManaCost(), x - padding, y - dy, w + 2 * padding, h, manaSymbolSize);
drawManaCost(g, card.getCurrentState().getManaCost(), x - padding, y + dy, w + 2 * padding, h, manaSymbolSize);
}
}
else {
drawManaCost(g, card.getCurrentState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
}
}
}
//reset alpha
g.setAlphaComposite(oldAlpha);
}
private static void drawCounterTabs(final CardView card, final Graphics g, final float x, final float y, final float w, final float h) {

View File

@@ -219,7 +219,7 @@ public class CardZoom extends FOverlay {
float w = getWidth();
float h = getHeight();
float messageHeight = FDialog.MSG_HEIGHT;
float AspectRatioMultiplier = 2;
float AspectRatioMultiplier;
switch (Forge.extrawide) {
case "default":
AspectRatioMultiplier = 3; //good for tablets with 16:10 or similar

View File

@@ -40,6 +40,7 @@ import forge.toolbox.FEvent;
import forge.toolbox.FEvent.FEventHandler;
import forge.toolbox.FLabel;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
import forge.util.Utils;
@@ -372,12 +373,35 @@ public class VStack extends FDropDown {
x += PADDING;
y += PADDING;
CardRenderer.drawCardWithOverlays(g, stackInstance.getSourceCard(), x, y, CARD_WIDTH, CARD_HEIGHT, CardStackPosition.Top);
CardRenderer.drawCardWithOverlays(g, stackInstance.getSourceCard(), x, y, CARD_WIDTH, CARD_HEIGHT, CardStackPosition.Top, true);
x += CARD_WIDTH + PADDING;
w -= x + PADDING - BORDER_THICKNESS;
h -= y + PADDING - BORDER_THICKNESS;
textRenderer.drawText(g, text, FONT, foreColor, x, y, w, h, y, h, true, Align.left, true);
String name = stackInstance.getSourceCard().getName();
int index = text.indexOf(name);
String newtext = "";
String cId = "(" + stackInstance.getSourceCard().getId() + ")";
if (index == -1) {
newtext = TextUtil.fastReplace(TextUtil.fastReplace(text.trim(),"--","-"),"- -","-");
textRenderer.drawText(g, name + " " + (name.length() > 1 ? cId : "") + "\n" + (newtext.length() > 1 ? newtext : ""),
FONT, foreColor, x, y, w, h, y, h, true, Align.left, true);
} else {
String trimFirst = TextUtil.fastReplace("\n" + text.substring(0, index) + text.substring(index + name.length()), "- -", "-");
String trimSecond = TextUtil.fastReplace(trimFirst, name+" "+cId, name);
newtext = TextUtil.fastReplace(trimSecond, " "+cId, name);
if(newtext.equals("\n"+name))
textRenderer.drawText(g, name + " " + cId, FONT, foreColor, x, y, w, h, y, h, true, Align.left, true);
else {
newtext = TextUtil.fastReplace(TextUtil.fastReplace(newtext,name+" -","-"), "\n ", "\n");
newtext = "\n"+ TextUtil.fastReplace(newtext.trim(),"--","-");
textRenderer.drawText(g, name+" "+cId+newtext, FONT, foreColor, x, y, w, h, y, h, true, Align.left, true);
}
}
g.endClip();

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
</parent>
<artifactId>forge-gui</artifactId>

View File

@@ -2,5 +2,5 @@ Name:Ballot Broker
ManaCost:2 W
Types:Creature Human Advisor
PT:2/3
S:Mode$ Continuous | Affected$ You | AddKeyword$ You may vote an additional time. | Description$ While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)
Oracle:While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)
S:Mode$ Continuous | Affected$ You | AdditionalOptionalVote$ 1 | Description$ While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)
Oracle:While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)

View File

@@ -2,6 +2,6 @@ Name:Brago's Representative
ManaCost:2 W
Types:Creature Human Advisor
PT:1/4
S:Mode$ Continuous | Affected$ You | AddKeyword$ You get an additional vote. | Description$ While voting, you get an additional vote. (The votes can be for different choices or for the same choice.)
S:Mode$ Continuous | Affected$ You | AdditionalVote$ 1 | Description$ While voting, you get an additional vote. (The votes can be for different choices or for the same choice.)
SVar:Picture:http://www.wizards.com/global/images/magic/general/bragos_representative.jpg
Oracle:While voting, you get an additional vote. (The votes can be for different choices or for the same choice.)

View File

@@ -0,0 +1,9 @@
Name:Expropriate
ManaCost:7 U U
Types:Sorcery
A:SP$ Vote | Cost$ 7 U U | Defined$ Player | VoteType$ Time,Money | VoteTime$ DBTime | VoteMoney$ DBMoney | EachVote$ True | SubAbility$ DBChange | SpellDescription$ Councils dilemma — Starting with you, each player votes for time or money. For each time vote, take an extra turn after this one. For each money vote, choose a permanent owned by the voter and gain control of it. Exile Expropriate.
SVar:DBTime:DB$ AddTurn | Defined$ You | NumTurns$ 1
SVar:DBMoney:DB$ ChooseCard | Defined$ You | Choices$ Permanent.RememberedPlayerOwn | SubAbility$ DBControl
SVar:DBControl:DB$ GainControl | Defined$ ChosenCard | NewController$ You
SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ None
Oracle:Councils dilemma — Starting with you, each player votes for time or money. For each time vote, take an extra turn after this one. For each money vote, choose a permanent owned by the voter and gain control of it. Exile Expropriate.

View File

@@ -0,0 +1,10 @@
Name:Glyph of Reincarnation
ManaCost:G
Types:Instant
A:SP$ Pump | Cost$ G | ValidTgts$ Wall | TgtPrompt$ Select target Wall | AILogic$ Pump | ActivationPhases$ Main2->End of Turn | SubAbility$ DBDestroyAll | StackDescription$ SpellDescription | SpellDescription$ Cast this spell only after combat.
SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Creature.blockedByValidThisTurn ParentTarget | NoRegen$ True | RememberDestroyed$ True | SubAbility$ DBChoose | StackDescription$ SpellDescription | SpellDescription$ Destroy all creatures that were blocked by target Wall this turn. They cant be regenerated.
SVar:DBChoose:DB$ ChooseCard | Defined$ You | Choices$ Creature.OwnedBy Player.Active | ChoiceTitle$ Choose creatures to put on the battlefield | ChoiceZone$ Graveyard | Amount$ X | References$ X | Mandatory$ True | AILogic$ WorstCard | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ For each creature that died this way, put a creature card from the graveyard of the player who controlled that creature the last time it became blocked by that Wall onto the battlefield under its owners control.
SVar:DBChangeZone:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Graveyard | Destination$ Battlefield | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$ValidGraveyard Creature.IsRemembered
Oracle:Cast this spell only after combat.\nDestroy all creatures that were blocked by target Wall this turn. They cant be regenerated. For each creature that died this way, put a creature card from the graveyard of the player who controlled that creature the last time it became blocked by that Wall onto the battlefield under its owners control.

View File

@@ -3,6 +3,6 @@ ManaCost:U
Types:Instant
A:SP$ Effect | Cost$ U | Name$ Illusion of Choice Effect | StaticAbilities$ STVoter | SubAbility$ DBDraw | SpellDescription$ You choose how each player votes this turn. Draw a card.
SVar:DBDraw:DB$ Draw | NumCards$ 1
SVar:STVoter:Mode$ Continuous | EffectZone$ Command | Affected$ You | AddKeyword$ You choose how each player votes this turn. | Description$ You choose how each player votes this turn.
SVar:STVoter:Mode$ Continuous | EffectZone$ Command | Affected$ You | ControlVote$ True | Description$ You choose how each player votes this turn.
AI:RemoveDeck:All
Oracle:You choose how each player votes this turn. Draw a card.
Oracle:You choose how each player votes this turn. Draw a card.

View File

@@ -0,0 +1,17 @@
Name:Krovikan Vampire
ManaCost:3 B B
Types:Creature Vampire
PT:3/3
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.DamagedBy | TriggerZones$ Battlefield | Execute$ TrigPump | Static$ True
SVar:TrigPump:DB$ Pump | RememberObjects$ TriggeredCard
T:Mode$ ChangesZone | Origin$ Graveyard | Destination$ Any | ValidCard$ Card.IsRemembered | TriggerZones$ Battlefield | Execute$ LoseTrack | Static$ True
SVar:LoseTrack:DB$ Pump | ForgetObjects$ TriggeredCard
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True
T:Mode$ TurnBegin | Execute$ DBCleanup | Static$ True
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE1 | IsPresent$ Card.Self | Execute$ TrigChange | TriggerDescription$ At the beginning of each end step, if a creature dealt damage by CARDNAME this turn died, put that card onto the battlefield under your control. Sacrifice it when you lose control of CARDNAME.
SVar:TrigChange:DB$ ChangeZoneAll | ChangeType$ Creature.IsRemembered+ThisTurnEntered_Graveyard_from_Battlefield | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | RememberChanged$ True | ForgetOtherRemembered$ True | SubAbility$ TrigDelay
SVar:TrigDelay:DB$ DelayedTrigger | Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | RememberObjects$ Remembered | Execute$ TrigSac | Secondary$ True | SubAbility$ DBCleanup | SpellDescription$ Sacrifice it when you lose control of CARDNAME.
SVar:TrigSac:DB$ SacrificeAll | Defined$ DelayTriggerRemembered | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$Amount
Oracle:At the beginning of each end step, if a creature dealt damage by Krovikan Vampire this turn died, put that card onto the battlefield under your control. Sacrifice it when you lose control of Krovikan Vampire.

View File

@@ -6,6 +6,7 @@ S:Mode$ Continuous | Affected$ Card.Self+counters_GE3_P1P1 | AddKeyword$ Trample
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl+powerGTX+Other | References$ X | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever another creature you control enters the battlefield or dies, if that creature's power is greater than CARDNAME's, put a +1/+1 counter on CARDNAME.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl+powerGTX+Other | References$ X | TriggerZones$ Battlefield | Secondary$ True | Execute$ TrigPutCounter | TriggerDescription$ Whenever another creature you control enters the battlefield or dies, if that creature's power is greater than CARDNAME's, put a +1/+1 counter on CARDNAME.
SVar:X:Count$CardPower
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
SVar:Y:TriggeredCard$CardPower
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ LTY | References$ X,Y
DeckHas:Ability$Counters
Oracle:Whenever another creature you control enters the battlefield or dies, if that creature's power is greater than Pelt Collector's, put a +1/+1 counter on Pelt Collector.\nAs long as Pelt Collector has three or more +1/+1 counters on it, it has trample.

View File

@@ -0,0 +1,13 @@
Name:Timmerian Fiends
ManaCost:1 B B
Types:Creature Horror
PT:1/1
K:Remove CARDNAME from your deck before playing if you're not playing for ante.
A:AB$ Pump | Cost$ B B B Sac<1/CARDNAME> | ValidTgts$ Artifact | SubAbility$ DBAnte | ImprintCards$ Targeted | RememberObjects$ TargetedController | StackDescription$ SpellDescription | SpellDescription$ The owner of target artifact may ante the top card of their library. If that player doesnt, exchange ownership of that artifact and CARDNAME. Put the artifact card into your graveyard and CARDNAME from anywhere into that players graveyard. This change in ownership is permanent.
SVar:DBAnte:DB$ Mill | Defined$ Player.IsRemembered | Destination$ Ante | NumCards$ 1 | RememberMilled$ True | Optional$ True | SubAbility$ ExchangeOwn1 | AILogic$ TimmerianFiends | StackDescription$ None
SVar:ExchangeOwn1:DB$ GainOwnership | Defined$ Imprinted | DefinedPlayer$ You | SubAbility$ ToGrave1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0
SVar:ToGrave1:DB$ ChangeZone | Defined$ Imprinted | Origin$ Battlefield | Destination$ Graveyard | SubAbility$ ExchangeOwn2 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0 | StackDescription$ None
SVar:ExchangeOwn2:DB$ GainOwnership | Defined$ Self | DefinedPlayer$ Remembered | SubAbility$ ToGrave | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0
SVar:ToGrave:DB$ ChangeZone | Defined$ Self | Origin$ All | Destination$ Graveyard | SubAbility$ DBCleanup | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
Oracle:Remove Timmerian Fiends from your deck before playing if youre not playing for ante.\n{B}{B}{B}, Sacrifice Timmerian Fiends: The owner of target artifact may ante the top card of their library. If that player doesnt, exchange ownership of that artifact and Timmerian Fiends. Put the artifact card into your graveyard and Timmerian Fiends from anywhere into that players graveyard. This change in ownership is permanent.

View File

@@ -2021,6 +2021,9 @@ lblDamagepreventionHas=Damage prevention: {0}
lblIsExtraTurn=Extra Turn: Yes
lblExtraTurnCountHas=Extra Turn Count: {0}
lblAntedHas=Ante''d: {0}
lblAdditionalVotes=You get {0} additional votes.
lblOptionalAdditionalVotes=You may vote {0} additional times.
lblControlsVote=You choose how each player votes.
#VStack.java
lblAlwaysYes=Always Yes
lblAlwaysNo=Always No

View File

@@ -1,3 +0,0 @@
#!/bin/sh
cd "`dirname \"$0\"`"
java -Xmx1024m -jar $project.build.finalName$

View File

@@ -1,3 +0,0 @@
#!/bin/sh
cd "`dirname \"$0\"`"
java -Xmx1024m -jar $project.build.finalName$

View File

@@ -156,6 +156,9 @@ public class DeckgenUtil {
//}
}
//remove any cards not valid in format
selectedCards = Lists.newArrayList(Iterables.filter(selectedCards, format.getFilterPrinted()));
List<PaperCard> toRemove = new ArrayList<>();
//randomly remove cards
@@ -252,6 +255,9 @@ public class DeckgenUtil {
//}
}
//remove any cards not valid in format
selectedCards = Lists.newArrayList(Iterables.filter(selectedCards, format.getFilterPrinted()));
List<PaperCard> toRemove = new ArrayList<>();
//randomly remove cards

View File

@@ -1181,7 +1181,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
@Override
public Object vote(final SpellAbility sa, final String prompt, final List<Object> options,
final ListMultimap<Object, Player> votes) {
final ListMultimap<Object, Player> votes, Player forPlayer) {
return getGui().one(prompt, options);
}

View File

@@ -5,7 +5,7 @@
<artifactId>forge</artifactId>
<packaging>pom</packaging>
<name>Forge Parent</name>
<version>1.6.33-SNAPSHOT</version>
<version>1.6.34-SNAPSHOT</version>
<description>
Forge lets you play the card game Magic: The Gathering against a computer opponent using all of the rules.