mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-13 09:17:59 +00:00
Compare commits
62 Commits
fix-spm-dr
...
forge-2.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a69333b528 | ||
|
|
2beb71cdce | ||
|
|
b50cdea5df | ||
|
|
a7568b0845 | ||
|
|
e158b5966e | ||
|
|
bea5ffb0a8 | ||
|
|
ac492fac35 | ||
|
|
35e6268a95 | ||
|
|
90bd0c73d0 | ||
|
|
ea293a46b1 | ||
|
|
6ec6d64cb2 | ||
|
|
9bc6ab747d | ||
|
|
a72df6ad16 | ||
|
|
1105b3fcbd | ||
|
|
08239def86 | ||
|
|
cb7fc3df4e | ||
|
|
9b8020bb30 | ||
|
|
398917d010 | ||
|
|
10a68c27d5 | ||
|
|
ba4ee98c3b | ||
|
|
c2c3add52b | ||
|
|
1c7bc1c0d3 | ||
|
|
14f146f8d0 | ||
|
|
0cab03b96c | ||
|
|
8f5f2059a2 | ||
|
|
34323107c9 | ||
|
|
dbf9568866 | ||
|
|
7933893dcb | ||
|
|
415e2c12e1 | ||
|
|
930019db40 | ||
|
|
72139b523c | ||
|
|
6cf2f20cdc | ||
|
|
cebddb7f4b | ||
|
|
4a6275d282 | ||
|
|
bdeaf8cef1 | ||
|
|
40f6a5b472 | ||
|
|
f4bff30680 | ||
|
|
5f32c23dc5 | ||
|
|
ae7159297b | ||
|
|
cedfa68c16 | ||
|
|
1f235c5e71 | ||
|
|
9f8bb8ae4d | ||
|
|
cd3a26e434 | ||
|
|
d2d6b7bc53 | ||
|
|
052e72d8ea | ||
|
|
b902d516a7 | ||
|
|
79fd4a3f8d | ||
|
|
10d359e7d7 | ||
|
|
1cb9e78b21 | ||
|
|
d999c5e213 | ||
|
|
1f7862c970 | ||
|
|
cdc696c506 | ||
|
|
64897f1ab4 | ||
|
|
905cf52c5e | ||
|
|
3139d593a3 | ||
|
|
8f71a5b06e | ||
|
|
e9742a2292 | ||
|
|
a6c893693d | ||
|
|
327ee6853f | ||
|
|
3e7f98db6a | ||
|
|
e49021ffdd | ||
|
|
0c5ff17e94 |
4
.github/workflows/maven-publish.yml
vendored
4
.github/workflows/maven-publish.yml
vendored
@@ -129,7 +129,9 @@ jobs:
|
|||||||
makeLatest: true
|
makeLatest: true
|
||||||
|
|
||||||
- name: 🔧 Install XML tools
|
- name: 🔧 Install XML tools
|
||||||
run: sudo apt-get install -y libxml2-utils
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libxml2-utils
|
||||||
|
|
||||||
- name: 🔼 Bump versionCode in root POM
|
- name: 🔼 Bump versionCode in root POM
|
||||||
id: bump_version
|
id: bump_version
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-ai</artifactId>
|
<artifactId>forge-ai</artifactId>
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ public class AiController {
|
|||||||
private int lastAttackAggression;
|
private int lastAttackAggression;
|
||||||
private boolean useLivingEnd;
|
private boolean useLivingEnd;
|
||||||
private List<SpellAbility> skipped;
|
private List<SpellAbility> skipped;
|
||||||
|
private boolean timeoutReached;
|
||||||
|
|
||||||
public AiController(final Player computerPlayer, final Game game0) {
|
public AiController(final Player computerPlayer, final Game game0) {
|
||||||
player = computerPlayer;
|
player = computerPlayer;
|
||||||
@@ -1664,6 +1665,9 @@ public class AiController {
|
|||||||
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// in case of infinite loop reset below would not be reached
|
||||||
|
timeoutReached = false;
|
||||||
|
|
||||||
FutureTask<SpellAbility> future = new FutureTask<>(() -> {
|
FutureTask<SpellAbility> future = new FutureTask<>(() -> {
|
||||||
//avoid ComputerUtil.aiLifeInDanger in loops as it slows down a lot.. call this outside loops will generally be fast...
|
//avoid ComputerUtil.aiLifeInDanger in loops as it slows down a lot.. call this outside loops will generally be fast...
|
||||||
boolean isLifeInDanger = useLivingEnd && ComputerUtil.aiLifeInDanger(player, true, 0);
|
boolean isLifeInDanger = useLivingEnd && ComputerUtil.aiLifeInDanger(player, true, 0);
|
||||||
@@ -1673,6 +1677,11 @@ public class AiController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeoutReached) {
|
||||||
|
timeoutReached = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (sa.getHostCard().hasKeyword(Keyword.STORM)
|
if (sa.getHostCard().hasKeyword(Keyword.STORM)
|
||||||
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
|
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
|
||||||
&& player.getZone(ZoneType.Hand).contains(
|
&& player.getZone(ZoneType.Hand).contains(
|
||||||
@@ -1752,7 +1761,10 @@ public class AiController {
|
|||||||
t.stop();
|
t.stop();
|
||||||
} catch (UnsupportedOperationException ex) {
|
} catch (UnsupportedOperationException ex) {
|
||||||
// Android and Java 20 dropped support to stop so sadly thread will keep running
|
// Android and Java 20 dropped support to stop so sadly thread will keep running
|
||||||
|
timeoutReached = true;
|
||||||
future.cancel(true);
|
future.cancel(true);
|
||||||
|
// TODO wait a few more seconds to try and exit at a safe point before letting the engine continue
|
||||||
|
// TODO mark some as skipped to increase chance to find something playable next priority
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,13 +298,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
|
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
try {
|
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
// This happens when Origin is something like
|
|
||||||
// "Graveyard,Library" (Doomsday)
|
|
||||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
final String destination = sa.getParam("Destination");
|
final String destination = sa.getParam("Destination");
|
||||||
|
|
||||||
@@ -908,8 +902,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
||||||
}
|
}
|
||||||
|
|
||||||
// list = CardLists.canSubsequentlyTarget(list, sa);
|
|
||||||
|
|
||||||
if (sa.hasParam("AttachedTo")) {
|
if (sa.hasParam("AttachedTo")) {
|
||||||
list = CardLists.filter(list, c -> {
|
list = CardLists.filter(list, c -> {
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
@@ -1252,53 +1244,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if max CMC exceeded, do not choose this card (but keep looking for other options)
|
|
||||||
if (sa.hasParam("MaxTotalTargetCMC")) {
|
|
||||||
if (choice.getCMC() > sa.getTargetRestrictions().getMaxTotalCMC(choice, sa) - sa.getTargets().getTotalTargetedCMC()) {
|
|
||||||
list.remove(choice);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if max power exceeded, do not choose this card (but keep looking for other options)
|
|
||||||
if (sa.hasParam("MaxTotalTargetPower")) {
|
|
||||||
if (choice.getNetPower() > sa.getTargetRestrictions().getMaxTotalPower(choice, sa) -sa.getTargets().getTotalTargetedPower()) {
|
|
||||||
list.remove(choice);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// honor the Same Creature Type restriction
|
|
||||||
if (sa.getTargetRestrictions().isWithSameCreatureType()) {
|
|
||||||
Card firstTarget = sa.getTargetCard();
|
|
||||||
if (firstTarget != null && !choice.sharesCreatureTypeWith(firstTarget)) {
|
|
||||||
list.remove(choice);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
if (sa.canTarget(choice)) {
|
if (sa.canTarget(choice)) {
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Honor the Single Zone restriction. For now, simply remove targets that do not belong to the same zone as the first targeted card.
|
|
||||||
// TODO: ideally the AI should consider at this point which targets exactly to pick (e.g. one card in the first player's graveyard
|
|
||||||
// vs. two cards in the second player's graveyard, which cards are more relevant to be targeted, etc.). Consider improving.
|
|
||||||
if (sa.getTargetRestrictions().isSingleZone()) {
|
|
||||||
Card firstTgt = sa.getTargetCard();
|
|
||||||
CardCollection toRemove = new CardCollection();
|
|
||||||
if (firstTgt != null) {
|
|
||||||
for (Card t : sa.getTargets().getTargetCards()) {
|
|
||||||
if (!t.getController().equals(firstTgt.getController())) {
|
|
||||||
toRemove.add(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sa.getTargets().removeAll(toRemove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-core</artifactId>
|
<artifactId>forge-core</artifactId>
|
||||||
|
|||||||
@@ -168,21 +168,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTransformable() {
|
public boolean isTransformable() {
|
||||||
if (CardSplitType.Transform == getSplitType()) {
|
return CardSplitType.Transform == getSplitType() || CardSplitType.Modal == getSplitType();
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (CardSplitType.Modal != getSplitType()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (ICardFace face : getAllFaces()) {
|
|
||||||
for (String spell : face.getAbilities()) {
|
|
||||||
if (spell.contains("AB$ SetState") && spell.contains("Mode$ Transform")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO check keywords if needed
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICardFace getWSpecialize() {
|
public ICardFace getWSpecialize() {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ package forge.card;
|
|||||||
import com.google.common.collect.UnmodifiableIterator;
|
import com.google.common.collect.UnmodifiableIterator;
|
||||||
import forge.card.MagicColor.Color;
|
import forge.card.MagicColor.Color;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.card.mana.ManaCostShard;
|
|
||||||
import forge.util.BinaryUtil;
|
import forge.util.BinaryUtil;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -41,27 +40,97 @@ import java.util.stream.Stream;
|
|||||||
public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Serializable {
|
public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Serializable {
|
||||||
private static final long serialVersionUID = 794691267379929080L;
|
private static final long serialVersionUID = 794691267379929080L;
|
||||||
|
|
||||||
|
// needs to be before other static
|
||||||
|
private static final ColorSet[] cache = new ColorSet[MagicColor.ALL_COLORS + 1];
|
||||||
|
static {
|
||||||
|
byte COLORLESS = MagicColor.COLORLESS;
|
||||||
|
byte WHITE = MagicColor.WHITE;
|
||||||
|
byte BLUE = MagicColor.BLUE;
|
||||||
|
byte BLACK = MagicColor.BLACK;
|
||||||
|
byte RED = MagicColor.RED;
|
||||||
|
byte GREEN = MagicColor.GREEN;
|
||||||
|
Color C = Color.COLORLESS;
|
||||||
|
Color W = Color.WHITE;
|
||||||
|
Color U = Color.BLUE;
|
||||||
|
Color B = Color.BLACK;
|
||||||
|
Color R = Color.RED;
|
||||||
|
Color G = Color.GREEN;
|
||||||
|
|
||||||
|
//colorless
|
||||||
|
cache[COLORLESS] = new ColorSet(C);
|
||||||
|
|
||||||
|
//mono-color
|
||||||
|
cache[WHITE] = new ColorSet(W);
|
||||||
|
cache[BLUE] = new ColorSet(U);
|
||||||
|
cache[BLACK] = new ColorSet(B);
|
||||||
|
cache[RED] = new ColorSet(R);
|
||||||
|
cache[GREEN] = new ColorSet(G);
|
||||||
|
|
||||||
|
//two-color
|
||||||
|
cache[WHITE | BLUE] = new ColorSet(W, U);
|
||||||
|
cache[WHITE | BLACK] = new ColorSet(W, B);
|
||||||
|
cache[BLUE | BLACK] = new ColorSet(U, B);
|
||||||
|
cache[BLUE | RED] = new ColorSet(U, R);
|
||||||
|
cache[BLACK | RED] = new ColorSet(B, R);
|
||||||
|
cache[BLACK | GREEN] = new ColorSet(B, G);
|
||||||
|
cache[RED | GREEN] = new ColorSet(R, G);
|
||||||
|
cache[RED | WHITE] = new ColorSet(R, W);
|
||||||
|
cache[GREEN | WHITE] = new ColorSet(G, W);
|
||||||
|
cache[GREEN | BLUE] = new ColorSet(G, U);
|
||||||
|
|
||||||
|
//three-color
|
||||||
|
cache[WHITE | BLUE | BLACK] = new ColorSet(W, U, B);
|
||||||
|
cache[WHITE | BLACK | GREEN] = new ColorSet(W, B, G);
|
||||||
|
cache[BLUE | BLACK | RED] = new ColorSet(U, B, R);
|
||||||
|
cache[BLUE | RED | WHITE] = new ColorSet(U, R, W);
|
||||||
|
cache[BLACK | RED | GREEN] = new ColorSet(B, R, G);
|
||||||
|
cache[BLACK | GREEN | BLUE] = new ColorSet(B, G, U);
|
||||||
|
cache[RED | GREEN | WHITE] = new ColorSet(R, G, W);
|
||||||
|
cache[RED | WHITE | BLACK] = new ColorSet(R, W, B);
|
||||||
|
cache[GREEN | WHITE | BLUE] = new ColorSet(G, W, U);
|
||||||
|
cache[GREEN | BLUE | RED] = new ColorSet(G, U, R);
|
||||||
|
|
||||||
|
//four-color
|
||||||
|
cache[WHITE | BLUE | BLACK | RED] = new ColorSet(W, U, B, R);
|
||||||
|
cache[BLUE | BLACK | RED | GREEN] = new ColorSet(U, B, R, G);
|
||||||
|
cache[BLACK | RED | GREEN | WHITE] = new ColorSet(B, R, G, W);
|
||||||
|
cache[RED | GREEN | WHITE | BLUE] = new ColorSet(R, G, W, U);
|
||||||
|
cache[GREEN | WHITE | BLUE | BLACK] = new ColorSet(G, W, U, B);
|
||||||
|
|
||||||
|
//five-color
|
||||||
|
cache[WHITE | BLUE | BLACK | RED | GREEN] = new ColorSet(W, U, B, R, G);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Collection<Color> orderedShards;
|
||||||
private final byte myColor;
|
private final byte myColor;
|
||||||
private final float orderWeight;
|
private final float orderWeight;
|
||||||
|
private final Set<Color> enumSet;
|
||||||
private static final ColorSet[] cache = new ColorSet[32];
|
private final String desc;
|
||||||
|
|
||||||
public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS);
|
public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS);
|
||||||
private static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
|
public static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
|
||||||
|
|
||||||
private ColorSet(final byte mask) {
|
private ColorSet(final Color... ordered) {
|
||||||
this.myColor = mask;
|
this.orderedShards = Arrays.asList(ordered);
|
||||||
|
this.myColor = orderedShards.stream().map(Color::getColorMask).reduce((byte)0, (a, b) -> (byte)(a | b));
|
||||||
this.orderWeight = this.getOrderWeight();
|
this.orderWeight = this.getOrderWeight();
|
||||||
|
this.enumSet = EnumSet.copyOf(orderedShards);
|
||||||
|
this.desc = orderedShards.stream().map(Color::getShortName).collect(Collectors.joining());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ColorSet fromMask(final int mask) {
|
public static ColorSet fromMask(final int mask) {
|
||||||
final int mask32 = mask & MagicColor.ALL_COLORS;
|
final int mask32 = mask & MagicColor.ALL_COLORS;
|
||||||
if (cache[mask32] == null) {
|
|
||||||
cache[mask32] = new ColorSet((byte) mask32);
|
|
||||||
}
|
|
||||||
return cache[mask32];
|
return cache[mask32];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ColorSet fromEnums(final Color... colors) {
|
||||||
|
byte mask = 0;
|
||||||
|
for (Color e : colors) {
|
||||||
|
mask |= e.getColorMask();
|
||||||
|
}
|
||||||
|
return fromMask(mask);
|
||||||
|
}
|
||||||
|
|
||||||
public static ColorSet fromNames(final String... colors) {
|
public static ColorSet fromNames(final String... colors) {
|
||||||
byte mask = 0;
|
byte mask = 0;
|
||||||
for (final String s : colors) {
|
for (final String s : colors) {
|
||||||
@@ -293,17 +362,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final ManaCostShard[] orderedShards = getOrderedShards();
|
return desc;
|
||||||
return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the null color.
|
|
||||||
*
|
|
||||||
* @return the nullColor
|
|
||||||
*/
|
|
||||||
public static ColorSet getNullColor() {
|
|
||||||
return NO_COLORS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,16 +384,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Set<Color> toEnumSet() {
|
public Set<Color> toEnumSet() {
|
||||||
if (isColorless()) {
|
return EnumSet.copyOf(enumSet);
|
||||||
return EnumSet.of(Color.COLORLESS);
|
|
||||||
}
|
|
||||||
List<Color> list = new ArrayList<>();
|
|
||||||
for (Color c : Color.values()) {
|
|
||||||
if (hasAnyColor(c.getColormask())) {
|
|
||||||
list.add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return EnumSet.copyOf(list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -372,72 +422,12 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<MagicColor.Color> stream() {
|
public Stream<Color> stream() {
|
||||||
return this.toEnumSet().stream();
|
return this.toEnumSet().stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get array of mana cost shards for color set in the proper order
|
//Get array of mana cost shards for color set in the proper order
|
||||||
public ManaCostShard[] getOrderedShards() {
|
public Collection<Color> getOrderedColors() {
|
||||||
return shardOrderLookup[myColor];
|
return orderedShards;
|
||||||
}
|
|
||||||
|
|
||||||
private static final ManaCostShard[][] shardOrderLookup = new ManaCostShard[MagicColor.ALL_COLORS + 1][];
|
|
||||||
static {
|
|
||||||
byte COLORLESS = MagicColor.COLORLESS;
|
|
||||||
byte WHITE = MagicColor.WHITE;
|
|
||||||
byte BLUE = MagicColor.BLUE;
|
|
||||||
byte BLACK = MagicColor.BLACK;
|
|
||||||
byte RED = MagicColor.RED;
|
|
||||||
byte GREEN = MagicColor.GREEN;
|
|
||||||
ManaCostShard C = ManaCostShard.COLORLESS;
|
|
||||||
ManaCostShard W = ManaCostShard.WHITE;
|
|
||||||
ManaCostShard U = ManaCostShard.BLUE;
|
|
||||||
ManaCostShard B = ManaCostShard.BLACK;
|
|
||||||
ManaCostShard R = ManaCostShard.RED;
|
|
||||||
ManaCostShard G = ManaCostShard.GREEN;
|
|
||||||
|
|
||||||
//colorless
|
|
||||||
shardOrderLookup[COLORLESS] = new ManaCostShard[] { C };
|
|
||||||
|
|
||||||
//mono-color
|
|
||||||
shardOrderLookup[WHITE] = new ManaCostShard[] { W };
|
|
||||||
shardOrderLookup[BLUE] = new ManaCostShard[] { U };
|
|
||||||
shardOrderLookup[BLACK] = new ManaCostShard[] { B };
|
|
||||||
shardOrderLookup[RED] = new ManaCostShard[] { R };
|
|
||||||
shardOrderLookup[GREEN] = new ManaCostShard[] { G };
|
|
||||||
|
|
||||||
//two-color
|
|
||||||
shardOrderLookup[WHITE | BLUE] = new ManaCostShard[] { W, U };
|
|
||||||
shardOrderLookup[WHITE | BLACK] = new ManaCostShard[] { W, B };
|
|
||||||
shardOrderLookup[BLUE | BLACK] = new ManaCostShard[] { U, B };
|
|
||||||
shardOrderLookup[BLUE | RED] = new ManaCostShard[] { U, R };
|
|
||||||
shardOrderLookup[BLACK | RED] = new ManaCostShard[] { B, R };
|
|
||||||
shardOrderLookup[BLACK | GREEN] = new ManaCostShard[] { B, G };
|
|
||||||
shardOrderLookup[RED | GREEN] = new ManaCostShard[] { R, G };
|
|
||||||
shardOrderLookup[RED | WHITE] = new ManaCostShard[] { R, W };
|
|
||||||
shardOrderLookup[GREEN | WHITE] = new ManaCostShard[] { G, W };
|
|
||||||
shardOrderLookup[GREEN | BLUE] = new ManaCostShard[] { G, U };
|
|
||||||
|
|
||||||
//three-color
|
|
||||||
shardOrderLookup[WHITE | BLUE | BLACK] = new ManaCostShard[] { W, U, B };
|
|
||||||
shardOrderLookup[WHITE | BLACK | GREEN] = new ManaCostShard[] { W, B, G };
|
|
||||||
shardOrderLookup[BLUE | BLACK | RED] = new ManaCostShard[] { U, B, R };
|
|
||||||
shardOrderLookup[BLUE | RED | WHITE] = new ManaCostShard[] { U, R, W };
|
|
||||||
shardOrderLookup[BLACK | RED | GREEN] = new ManaCostShard[] { B, R, G };
|
|
||||||
shardOrderLookup[BLACK | GREEN | BLUE] = new ManaCostShard[] { B, G, U };
|
|
||||||
shardOrderLookup[RED | GREEN | WHITE] = new ManaCostShard[] { R, G, W };
|
|
||||||
shardOrderLookup[RED | WHITE | BLACK] = new ManaCostShard[] { R, W, B };
|
|
||||||
shardOrderLookup[GREEN | WHITE | BLUE] = new ManaCostShard[] { G, W, U };
|
|
||||||
shardOrderLookup[GREEN | BLUE | RED] = new ManaCostShard[] { G, U, R };
|
|
||||||
|
|
||||||
//four-color
|
|
||||||
shardOrderLookup[WHITE | BLUE | BLACK | RED] = new ManaCostShard[] { W, U, B, R };
|
|
||||||
shardOrderLookup[BLUE | BLACK | RED | GREEN] = new ManaCostShard[] { U, B, R, G };
|
|
||||||
shardOrderLookup[BLACK | RED | GREEN | WHITE] = new ManaCostShard[] { B, R, G, W };
|
|
||||||
shardOrderLookup[RED | GREEN | WHITE | BLUE] = new ManaCostShard[] { R, G, W, U };
|
|
||||||
shardOrderLookup[GREEN | WHITE | BLUE | BLACK] = new ManaCostShard[] { G, W, U, B };
|
|
||||||
|
|
||||||
//five-color
|
|
||||||
shardOrderLookup[WHITE | BLUE | BLACK | RED | GREEN] = new ManaCostShard[] { W, U, B, R, G };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package forge.card;
|
package forge.card;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import forge.deck.DeckRecognizer;
|
import forge.util.Localizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds byte values for each color magic has.
|
* Holds byte values for each color magic has.
|
||||||
@@ -158,20 +158,23 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum Color {
|
public enum Color {
|
||||||
WHITE(Constant.WHITE, MagicColor.WHITE, "{W}"),
|
WHITE(Constant.WHITE, MagicColor.WHITE, "W", "lblWhite"),
|
||||||
BLUE(Constant.BLUE, MagicColor.BLUE, "{U}"),
|
BLUE(Constant.BLUE, MagicColor.BLUE, "U", "lblBlue"),
|
||||||
BLACK(Constant.BLACK, MagicColor.BLACK, "{B}"),
|
BLACK(Constant.BLACK, MagicColor.BLACK, "B", "lblBlack"),
|
||||||
RED(Constant.RED, MagicColor.RED, "{R}"),
|
RED(Constant.RED, MagicColor.RED, "R", "lblRed"),
|
||||||
GREEN(Constant.GREEN, MagicColor.GREEN, "{G}"),
|
GREEN(Constant.GREEN, MagicColor.GREEN, "G", "lblGreen"),
|
||||||
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "{C}");
|
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "C", "lblColorless");
|
||||||
|
|
||||||
private final String name, symbol;
|
private final String name, shortName, symbol;
|
||||||
|
private final String label;
|
||||||
private final byte colormask;
|
private final byte colormask;
|
||||||
|
|
||||||
Color(String name0, byte colormask0, String symbol0) {
|
Color(String name0, byte colormask0, String shortName, String label) {
|
||||||
name = name0;
|
name = name0;
|
||||||
colormask = colormask0;
|
colormask = colormask0;
|
||||||
symbol = symbol0;
|
this.shortName = shortName;
|
||||||
|
symbol = "{" + shortName + "}";
|
||||||
|
this.label = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color fromByte(final byte color) {
|
public static Color fromByte(final byte color) {
|
||||||
@@ -188,13 +191,15 @@ public final class MagicColor {
|
|||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
public String getShortName() {
|
||||||
public String getLocalizedName() {
|
return shortName;
|
||||||
//Should probably move some of this logic back here, or at least to a more general location.
|
|
||||||
return DeckRecognizer.getLocalisedMagicColorName(getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getColormask() {
|
public String getLocalizedName() {
|
||||||
|
return Localizer.getInstance().getMessage(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getColorMask() {
|
||||||
return colormask;
|
return colormask;
|
||||||
}
|
}
|
||||||
public String getSymbol() {
|
public String getSymbol() {
|
||||||
@@ -202,7 +207,7 @@ public final class MagicColor {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name;
|
return getLocalizedName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1015,7 +1015,7 @@ public class DeckRecognizer {
|
|||||||
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
||||||
String localisedName1 = magicColor1.getLocalizedName();
|
String localisedName1 = magicColor1.getLocalizedName();
|
||||||
String localisedName2 = magicColor2.getLocalizedName();
|
String localisedName2 = magicColor2.getLocalizedName();
|
||||||
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask());
|
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColorMask() | magicColor2.getColorMask());
|
||||||
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -593,7 +593,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
|
|
||||||
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
|
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
|
||||||
if(markedColors == null)
|
if(markedColors == null)
|
||||||
markedColors = ColorSet.getNullColor();
|
markedColors = ColorSet.NO_COLORS;
|
||||||
return new PaperCardFlags(this, markedColors, null);
|
return new PaperCardFlags(this, markedColors, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
|||||||
return false;
|
return false;
|
||||||
CardSplitType cst = this.cardRules.getSplitType();
|
CardSplitType cst = this.cardRules.getSplitType();
|
||||||
//expand this on future for other tokens that has other backsides besides transform..
|
//expand this on future for other tokens that has other backsides besides transform..
|
||||||
return cst == CardSplitType.Transform;
|
return cst == CardSplitType.Transform || cst == CardSplitType.Modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -633,7 +633,10 @@ public class BoosterGenerator {
|
|||||||
System.out.println("Parsing from main code: " + mainCode);
|
System.out.println("Parsing from main code: " + mainCode);
|
||||||
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
|
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
|
||||||
System.out.println("Attempting to lookup: " + sheetName);
|
System.out.println("Attempting to lookup: " + sheetName);
|
||||||
src = tryGetStaticSheet(sheetName).toFlatList();
|
PrintSheet fromSheet = tryGetStaticSheet(sheetName);
|
||||||
|
if (fromSheet == null)
|
||||||
|
throw new RuntimeException("PrintSheet Error: " + ps.getName() + " didn't find " + sheetName + " from " + mainCode);
|
||||||
|
src = fromSheet.toFlatList();
|
||||||
setPred = x -> true;
|
setPred = x -> true;
|
||||||
|
|
||||||
} else if (mainCode.startsWith("promo") || mainCode.startsWith("name")) { // get exactly the named cards, that's a tiny inlined print sheet
|
} else if (mainCode.startsWith("promo") || mainCode.startsWith("name")) { // get exactly the named cards, that's a tiny inlined print sheet
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-game</artifactId>
|
<artifactId>forge-game</artifactId>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class ForgeScript {
|
|||||||
boolean withSource = property.endsWith("Source");
|
boolean withSource = property.endsWith("Source");
|
||||||
final ColorSet colors;
|
final ColorSet colors;
|
||||||
if (withSource && StaticAbilityColorlessDamageSource.colorlessDamageSource(cardState)) {
|
if (withSource && StaticAbilityColorlessDamageSource.colorlessDamageSource(cardState)) {
|
||||||
colors = ColorSet.getNullColor();
|
colors = ColorSet.NO_COLORS;
|
||||||
} else {
|
} else {
|
||||||
colors = cardState.getCard().getColor(cardState);
|
colors = cardState.getCard().getColor(cardState);
|
||||||
}
|
}
|
||||||
@@ -412,6 +412,8 @@ public class ForgeScript {
|
|||||||
return !sa.isPwAbility() && !sa.getRestrictions().isSorcerySpeed();
|
return !sa.isPwAbility() && !sa.getRestrictions().isSorcerySpeed();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
} else if(property.startsWith("NamedAbility")) {
|
||||||
|
return sa.getName().equals(property.substring(12));
|
||||||
} else if (sa.getHostCard() != null) {
|
} else if (sa.getHostCard() != null) {
|
||||||
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -845,6 +845,8 @@ public class Game {
|
|||||||
p.revealFaceDownCards();
|
p.revealFaceDownCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO free any mindslaves
|
||||||
|
|
||||||
for (Card c : cards) {
|
for (Card c : cards) {
|
||||||
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
||||||
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
||||||
@@ -880,8 +882,6 @@ public class Game {
|
|||||||
}
|
}
|
||||||
triggerList.put(c.getZone().getZoneType(), null, c);
|
triggerList.put(c.getZone().getZoneType(), null, c);
|
||||||
getAction().ceaseToExist(c, false);
|
getAction().ceaseToExist(c, false);
|
||||||
// CR 603.2f owner of trigger source lost game
|
|
||||||
getTriggerHandler().clearDelayedTrigger(c);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// return stolen permanents
|
// return stolen permanents
|
||||||
|
|||||||
@@ -220,10 +220,6 @@ public class GameAction {
|
|||||||
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
|
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c.isTransformed()) {
|
|
||||||
copied.incrementTransformedTimestamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
||||||
copied.setCastSA(cause);
|
copied.setCastSA(cause);
|
||||||
copied.setSplitStateToPlayAbility(cause);
|
copied.setSplitStateToPlayAbility(cause);
|
||||||
@@ -974,6 +970,7 @@ public class GameAction {
|
|||||||
// in some corner cases there's no zone yet (copied spell that failed targeting)
|
// in some corner cases there's no zone yet (copied spell that failed targeting)
|
||||||
if (z != null) {
|
if (z != null) {
|
||||||
z.remove(c);
|
z.remove(c);
|
||||||
|
c.setZone(c.getOwner().getZone(ZoneType.None));
|
||||||
if (z.is(ZoneType.Battlefield)) {
|
if (z.is(ZoneType.Battlefield)) {
|
||||||
c.runLeavesPlayCommands();
|
c.runLeavesPlayCommands();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -993,9 +993,6 @@ public final class GameActionUtil {
|
|||||||
oldCard.setBackSide(false);
|
oldCard.setBackSide(false);
|
||||||
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
||||||
oldCard.unanimateBestow();
|
oldCard.unanimateBestow();
|
||||||
if (ability.isDisturb() || ability.hasParam("CastTransformed")) {
|
|
||||||
oldCard.undoIncrementTransformedTimestamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ability.hasParam("Prototype")) {
|
if (ability.hasParam("Prototype")) {
|
||||||
oldCard.removeCloneState(oldCard.getPrototypeTimestamp());
|
oldCard.removeCloneState(oldCard.getPrototypeTimestamp());
|
||||||
|
|||||||
@@ -239,6 +239,10 @@ public final class AbilityFactory {
|
|||||||
spellAbility.putParam("PrecostDesc", "Exhaust — ");
|
spellAbility.putParam("PrecostDesc", "Exhaust — ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mapParams.containsKey("Named")) {
|
||||||
|
spellAbility.setName(mapParams.get("Named"));
|
||||||
|
}
|
||||||
|
|
||||||
// *********************************************
|
// *********************************************
|
||||||
// set universal properties of the SpellAbility
|
// set universal properties of the SpellAbility
|
||||||
|
|
||||||
@@ -359,9 +363,6 @@ public final class AbilityFactory {
|
|||||||
if (mapParams.containsKey("TargetUnique")) {
|
if (mapParams.containsKey("TargetUnique")) {
|
||||||
abTgt.setUniqueTargets(true);
|
abTgt.setUniqueTargets(true);
|
||||||
}
|
}
|
||||||
if (mapParams.containsKey("TargetsFromSingleZone")) {
|
|
||||||
abTgt.setSingleZone(true);
|
|
||||||
}
|
|
||||||
if (mapParams.containsKey("TargetsWithoutSameCreatureType")) {
|
if (mapParams.containsKey("TargetsWithoutSameCreatureType")) {
|
||||||
abTgt.setWithoutSameCreatureType(true);
|
abTgt.setWithoutSameCreatureType(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1870,6 +1870,14 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
return doXMath(v, expr, c, ctb);
|
return doXMath(v, expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count$FromNamedAbility[abilityName].<True>.<False>
|
||||||
|
if (sq[0].startsWith("FromNamedAbility")) {
|
||||||
|
String abilityNamed = sq[0].substring(16);
|
||||||
|
SpellAbility trigSA = sa.getHostCard().getCastSA();
|
||||||
|
boolean fromNamedAbility = trigSA != null && trigSA.getName().equals(abilityNamed);
|
||||||
|
return doXMath(calculateAmount(c, sq[fromNamedAbility ? 1 : 2], ctb), expr, c, ctb);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// fallback if ctb isn't a spellability
|
// fallback if ctb isn't a spellability
|
||||||
if (sq[0].startsWith("LastStateBattlefield")) {
|
if (sq[0].startsWith("LastStateBattlefield")) {
|
||||||
|
|||||||
@@ -928,7 +928,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
List<ZoneType> origin = Lists.newArrayList();
|
List<ZoneType> origin = Lists.newArrayList();
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
origin.addAll(ZoneType.listValueOf(sa.getParam("Origin")));
|
||||||
}
|
}
|
||||||
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
|
|
||||||
@@ -1474,7 +1474,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
if (ZoneType.Exile.equals(destination) && sa.hasParam("WithCountersType")) {
|
if (ZoneType.Exile.equals(destination) && sa.hasParam("WithCountersType")) {
|
||||||
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
||||||
int cAmount = AbilityUtils.calculateAmount(sa.getOriginalHost(), sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
int cAmount = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
||||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
movedCard.addCounter(cType, cAmount, player, table);
|
movedCard.addCounter(cType, cAmount, player, table);
|
||||||
table.replaceCounterEffect(game, sa, true);
|
table.replaceCounterEffect(game, sa, true);
|
||||||
|
|||||||
@@ -287,22 +287,17 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
|||||||
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
||||||
// need to create a physical card first, i need the original card faces
|
// need to create a physical card first, i need the original card faces
|
||||||
copy = CardFactory.getCard(original.getPaperCard(), newOwner, id, host.getGame());
|
copy = CardFactory.getCard(original.getPaperCard(), newOwner, id, host.getGame());
|
||||||
|
|
||||||
|
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
|
||||||
|
// force update the now set State
|
||||||
if (original.isTransformable()) {
|
if (original.isTransformable()) {
|
||||||
|
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
||||||
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
||||||
// the resulting token is a transforming token that has both a front face and a back face.
|
// the resulting token is a transforming token that has both a front face and a back face.
|
||||||
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
||||||
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
||||||
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
||||||
copy.setBackSide(original.isBackSide());
|
copy.setBackSide(original.isBackSide());
|
||||||
if (original.isTransformed()) {
|
|
||||||
copy.incrementTransformedTimestamp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
|
|
||||||
// force update the now set State
|
|
||||||
if (original.isTransformable()) {
|
|
||||||
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
|
||||||
} else {
|
} else {
|
||||||
copy.setState(copy.getCurrentStateName(), true, true);
|
copy.setState(copy.getCurrentStateName(), true, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ public class DamageResolveEffect extends SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
CardDamageMap damageMap = sa.getDamageMap();
|
CardDamageMap damageMap = sa.getDamageMap();
|
||||||
|
if (damageMap == null) {
|
||||||
|
// this can happen if damagesource was missing
|
||||||
|
return;
|
||||||
|
}
|
||||||
CardDamageMap preventMap = sa.getPreventMap();
|
CardDamageMap preventMap = sa.getPreventMap();
|
||||||
GameEntityCounterTable counterTable = sa.getCounterTable();
|
GameEntityCounterTable counterTable = sa.getCounterTable();
|
||||||
|
|
||||||
|
|||||||
@@ -428,6 +428,10 @@ public class PlayEffect extends SpellAbilityEffect {
|
|||||||
tgtSA.getTargetRestrictions().setMandatory(true);
|
tgtSA.getTargetRestrictions().setMandatory(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("Named")) {
|
||||||
|
tgtSA.setName(sa.getName());
|
||||||
|
}
|
||||||
|
|
||||||
// can't be done later
|
// can't be done later
|
||||||
if (sa.hasParam("ReplaceGraveyard")) {
|
if (sa.hasParam("ReplaceGraveyard")) {
|
||||||
if (!sa.hasParam("ReplaceGraveyardValid")
|
if (!sa.hasParam("ReplaceGraveyardValid")
|
||||||
|
|||||||
@@ -92,12 +92,11 @@ public class SacrificeEffect extends SpellAbilityEffect {
|
|||||||
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
|
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
|
||||||
|
|
||||||
if (valid.equals("Self") && game.getZoneOf(host) != null) {
|
if (valid.equals("Self") && game.getZoneOf(host) != null) {
|
||||||
if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield)) {
|
if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield) &&
|
||||||
if (!optional || activator.getController().confirmAction(sa, null,
|
(!optional || activator.getController().confirmAction(sa, null,
|
||||||
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null)) {
|
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null))) {
|
||||||
if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) {
|
if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) {
|
||||||
host.addRemembered(host);
|
host.addRemembered(host);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import forge.game.GameEntityCounterTable;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
@@ -20,7 +21,6 @@ import forge.game.spellability.SpellAbility;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.CardTranslation;
|
import forge.util.CardTranslation;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
import forge.util.collect.FCollection;
|
|
||||||
|
|
||||||
public class TimeTravelEffect extends SpellAbilityEffect {
|
public class TimeTravelEffect extends SpellAbilityEffect {
|
||||||
|
|
||||||
@@ -41,10 +41,8 @@ public class TimeTravelEffect extends SpellAbilityEffect {
|
|||||||
final CounterType counterType = CounterEnumType.TIME;
|
final CounterType counterType = CounterEnumType.TIME;
|
||||||
|
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
FCollection<Card> list = new FCollection<>();
|
|
||||||
|
|
||||||
// card you own that is suspended
|
// card you own that is suspended
|
||||||
list.addAll(CardLists.filter(activator.getCardsIn(ZoneType.Exile), CardPredicates.hasSuspend()));
|
CardCollection list = CardLists.filter(activator.getCardsIn(ZoneType.Exile), CardPredicates.hasSuspend());
|
||||||
// permanent you control with time counter
|
// permanent you control with time counter
|
||||||
list.addAll(CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.hasCounter(counterType)));
|
list.addAll(CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.hasCounter(counterType)));
|
||||||
|
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|
|
||||||
private long worldTimestamp = -1;
|
private long worldTimestamp = -1;
|
||||||
private long bestowTimestamp = -1;
|
private long bestowTimestamp = -1;
|
||||||
private long transformedTimestamp = 0;
|
private long transformedTimestamp = -1;
|
||||||
private long prototypeTimestamp = -1;
|
private long prototypeTimestamp = -1;
|
||||||
private long mutatedTimestamp = -1;
|
private long mutatedTimestamp = -1;
|
||||||
private int timesMutated = 0;
|
private int timesMutated = 0;
|
||||||
@@ -425,8 +425,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
public long getPrototypeTimestamp() { return prototypeTimestamp; }
|
public long getPrototypeTimestamp() { return prototypeTimestamp; }
|
||||||
|
|
||||||
public long getTransformedTimestamp() { return transformedTimestamp; }
|
public long getTransformedTimestamp() { return transformedTimestamp; }
|
||||||
public void incrementTransformedTimestamp() { this.transformedTimestamp++; }
|
public void setTransformedTimestamp(long ts) { this.transformedTimestamp = ts; }
|
||||||
public void undoIncrementTransformedTimestamp() { this.transformedTimestamp--; }
|
|
||||||
|
|
||||||
// The following methods are used to selectively update certain view components (text,
|
// The following methods are used to selectively update certain view components (text,
|
||||||
// P/T, card types) in order to avoid card flickering due to aggressive full update
|
// P/T, card types) in order to avoid card flickering due to aggressive full update
|
||||||
@@ -696,7 +695,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
|
||||||
getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false);
|
getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false);
|
||||||
}
|
}
|
||||||
incrementTransformedTimestamp();
|
setTransformedTimestamp(ts);
|
||||||
|
|
||||||
return retResult;
|
return retResult;
|
||||||
} else if (mode.equals("Flip")) {
|
} else if (mode.equals("Flip")) {
|
||||||
@@ -1070,7 +1069,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isDoubleFaced() {
|
public final boolean isDoubleFaced() {
|
||||||
return isTransformable() || isMeldable() || isModal();
|
return isTransformable() || isMeldable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isFlipCard() {
|
public final boolean isFlipCard() {
|
||||||
@@ -1132,7 +1131,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isTransformed() {
|
public final boolean isTransformed() {
|
||||||
return getTransformedTimestamp() != 0;
|
if (isMeldable() || hasMergedCard()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.isTransformable() && isBackSide();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isFlipped() {
|
public final boolean isFlipped() {
|
||||||
@@ -2263,7 +2265,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
|
|
||||||
public final ColorSet getMarkedColors() {
|
public final ColorSet getMarkedColors() {
|
||||||
if (markedColor == null) {
|
if (markedColor == null) {
|
||||||
return ColorSet.getNullColor();
|
return ColorSet.NO_COLORS;
|
||||||
}
|
}
|
||||||
return markedColor;
|
return markedColor;
|
||||||
}
|
}
|
||||||
@@ -7638,9 +7640,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
if (sa.isBestow()) {
|
if (sa.isBestow()) {
|
||||||
animateBestow();
|
animateBestow();
|
||||||
}
|
}
|
||||||
if (sa.isDisturb() || sa.hasParam("CastTransformed")) {
|
|
||||||
incrementTransformedTimestamp();
|
|
||||||
}
|
|
||||||
if (sa.hasParam("Prototype") && prototypeTimestamp == -1) {
|
if (sa.hasParam("Prototype") && prototypeTimestamp == -1) {
|
||||||
long next = game.getNextTimestamp();
|
long next = game.getNextTimestamp();
|
||||||
addCloneState(CardFactory.getCloneStates(this, this, sa), next);
|
addCloneState(CardFactory.getCloneStates(this, this, sa), next);
|
||||||
|
|||||||
@@ -131,9 +131,7 @@ public class CardCopyService {
|
|||||||
|
|
||||||
c.setState(in.getCurrentStateName(), false);
|
c.setState(in.getCurrentStateName(), false);
|
||||||
c.setRules(in.getRules());
|
c.setRules(in.getRules());
|
||||||
if (in.isTransformed()) {
|
c.setBackSide(in.isBackSide());
|
||||||
c.incrementTransformedTimestamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@@ -168,9 +166,6 @@ public class CardCopyService {
|
|||||||
// The characteristics of its front and back face are determined by the copiable values of the same face of the spell it is a copy of, as modified by any other copy effects.
|
// The characteristics of its front and back face are determined by the copiable values of the same face of the spell it is a copy of, as modified by any other copy effects.
|
||||||
// If the spell it is a copy of has its back face up, the copy is created with its back face up. The token that’s put onto the battlefield as that spell resolves is a transforming token.
|
// If the spell it is a copy of has its back face up, the copy is created with its back face up. The token that’s put onto the battlefield as that spell resolves is a transforming token.
|
||||||
to.setBackSide(copyFrom.isBackSide());
|
to.setBackSide(copyFrom.isBackSide());
|
||||||
if (copyFrom.isTransformed()) {
|
|
||||||
to.incrementTransformedTimestamp();
|
|
||||||
}
|
|
||||||
} else if (fromIsTransformedCard) {
|
} else if (fromIsTransformedCard) {
|
||||||
copyState(copyFrom, copyFrom.getCurrentStateName(), to, CardStateName.Original);
|
copyState(copyFrom, copyFrom.getCurrentStateName(), to, CardStateName.Original);
|
||||||
} else {
|
} else {
|
||||||
@@ -274,9 +269,6 @@ public class CardCopyService {
|
|||||||
}
|
}
|
||||||
newCopy.setFlipped(copyFrom.isFlipped());
|
newCopy.setFlipped(copyFrom.isFlipped());
|
||||||
newCopy.setBackSide(copyFrom.isBackSide());
|
newCopy.setBackSide(copyFrom.isBackSide());
|
||||||
if (copyFrom.isTransformed()) {
|
|
||||||
newCopy.incrementTransformedTimestamp();
|
|
||||||
}
|
|
||||||
if (newCopy.hasAlternateState()) {
|
if (newCopy.hasAlternateState()) {
|
||||||
newCopy.setState(copyFrom.getCurrentStateName(), false, true);
|
newCopy.setState(copyFrom.getCurrentStateName(), false, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,22 +87,16 @@ public class CardFactory {
|
|||||||
// need to create a physical card first, i need the original card faces
|
// need to create a physical card first, i need the original card faces
|
||||||
final Card copy = getCard(original.getPaperCard(), controller, id, game);
|
final Card copy = getCard(original.getPaperCard(), controller, id, game);
|
||||||
|
|
||||||
|
copy.setStates(getCloneStates(original, copy, sourceSA));
|
||||||
|
// force update the now set State
|
||||||
if (original.isTransformable()) {
|
if (original.isTransformable()) {
|
||||||
|
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
||||||
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
// 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield,
|
||||||
// the resulting token is a transforming token that has both a front face and a back face.
|
// the resulting token is a transforming token that has both a front face and a back face.
|
||||||
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
// The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent.
|
||||||
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
// If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up.
|
||||||
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
// This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect.
|
||||||
copy.setBackSide(original.isBackSide());
|
copy.setBackSide(original.isBackSide());
|
||||||
if (original.isTransformed()) {
|
|
||||||
copy.incrementTransformedTimestamp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copy.setStates(getCloneStates(original, copy, sourceSA));
|
|
||||||
// force update the now set State
|
|
||||||
if (original.isTransformable()) {
|
|
||||||
copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true);
|
|
||||||
} else {
|
} else {
|
||||||
copy.setState(copy.getCurrentStateName(), true, true);
|
copy.setState(copy.getCurrentStateName(), true, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ import java.util.*;
|
|||||||
public class PhaseHandler implements java.io.Serializable {
|
public class PhaseHandler implements java.io.Serializable {
|
||||||
private static final long serialVersionUID = 5207222278370963197L;
|
private static final long serialVersionUID = 5207222278370963197L;
|
||||||
|
|
||||||
|
// used for debugging phase timing
|
||||||
|
private final StopWatch sw = new StopWatch();
|
||||||
|
|
||||||
// Start turn at 0, since we start even before first untap
|
// Start turn at 0, since we start even before first untap
|
||||||
private PhaseType phase = null;
|
private PhaseType phase = null;
|
||||||
private int turn = 0;
|
private int turn = 0;
|
||||||
@@ -92,6 +95,7 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
|
|
||||||
private final transient Game game;
|
private final transient Game game;
|
||||||
|
|
||||||
|
|
||||||
public PhaseHandler(final Game game0) {
|
public PhaseHandler(final Game game0) {
|
||||||
game = game0;
|
game = game0;
|
||||||
}
|
}
|
||||||
@@ -1003,12 +1007,7 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
|
|
||||||
private final static boolean DEBUG_PHASES = false;
|
private final static boolean DEBUG_PHASES = false;
|
||||||
|
|
||||||
public void startFirstTurn(Player goesFirst) {
|
public void setupFirstTurn(Player goesFirst, Runnable startGameHook) {
|
||||||
startFirstTurn(goesFirst, null);
|
|
||||||
}
|
|
||||||
public void startFirstTurn(Player goesFirst, Runnable startGameHook) {
|
|
||||||
StopWatch sw = new StopWatch();
|
|
||||||
|
|
||||||
if (phase != null) {
|
if (phase != null) {
|
||||||
throw new IllegalStateException("Turns already started, call this only once per game");
|
throw new IllegalStateException("Turns already started, call this only once per game");
|
||||||
}
|
}
|
||||||
@@ -1024,132 +1023,146 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
startGameHook.run();
|
startGameHook.run();
|
||||||
givePriorityToPlayer = true;
|
givePriorityToPlayer = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startFirstTurn(Player goesFirst) {
|
||||||
|
startFirstTurn(goesFirst, null);
|
||||||
|
}
|
||||||
|
public void startFirstTurn(Player goesFirst, Runnable startGameHook) {
|
||||||
|
setupFirstTurn(goesFirst, startGameHook);
|
||||||
|
mainGameLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mainGameLoop() {
|
||||||
// MAIN GAME LOOP
|
// MAIN GAME LOOP
|
||||||
while (!game.isGameOver()) {
|
while (!game.isGameOver() && !(game.getAge() == GameStage.RestartedByKarn)) {
|
||||||
if (givePriorityToPlayer) {
|
mainLoopStep();
|
||||||
if (DEBUG_PHASES) {
|
}
|
||||||
sw.start();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
game.fireEvent(new GameEventPlayerPriority(playerTurn, phase, getPriorityPlayer()));
|
public void mainLoopStep() {
|
||||||
List<SpellAbility> chosenSa = null;
|
if (givePriorityToPlayer) {
|
||||||
|
if (DEBUG_PHASES) {
|
||||||
int loopCount = 0;
|
sw.start();
|
||||||
do {
|
|
||||||
if (checkStateBasedEffects()) {
|
|
||||||
// state-based effects check could lead to game over
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
game.stashGameState();
|
|
||||||
|
|
||||||
chosenSa = pPlayerPriority.getController().chooseSpellAbilityToPlay();
|
|
||||||
|
|
||||||
// this needs to come after chosenSa so it sees you conceding on own turn
|
|
||||||
if (playerTurn.hasLost() && pPlayerPriority.equals(playerTurn) && pFirstPriority.equals(playerTurn)) {
|
|
||||||
// If the active player has lost, and they have priority, set the next player to have priority
|
|
||||||
System.out.println("Active player is no longer in the game...");
|
|
||||||
pPlayerPriority = game.getNextPlayerAfter(getPriorityPlayer());
|
|
||||||
pFirstPriority = pPlayerPriority;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chosenSa == null) {
|
|
||||||
break; // that means 'I pass'
|
|
||||||
}
|
|
||||||
if (DEBUG_PHASES) {
|
|
||||||
System.out.print("... " + pPlayerPriority + " plays " + chosenSa);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean rollback = false;
|
|
||||||
for (SpellAbility sa : chosenSa) {
|
|
||||||
Card saHost = sa.getHostCard();
|
|
||||||
final Zone originZone = saHost.getZone();
|
|
||||||
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
|
|
||||||
|
|
||||||
if (pPlayerPriority.getController().playChosenSpellAbility(sa)) {
|
|
||||||
// 117.3c If a player has priority when they cast a spell, activate an ability, [play a land]
|
|
||||||
// that player receives priority afterward.
|
|
||||||
pFirstPriority = pPlayerPriority; // all opponents have to pass before stack is allowed to resolve
|
|
||||||
} else if (game.EXPERIMENTAL_RESTORE_SNAPSHOT) {
|
|
||||||
rollback = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
saHost = game.getCardState(saHost);
|
|
||||||
final Zone currentZone = saHost.getZone();
|
|
||||||
|
|
||||||
// Need to check if Zone did change
|
|
||||||
if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa.isLandAbility())) {
|
|
||||||
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played
|
|
||||||
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);
|
|
||||||
triggerList.triggerChangesZoneAll(game, sa);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Don't copy last state if we're in the middle of rolling back a spell...
|
|
||||||
if (!rollback) {
|
|
||||||
game.copyLastState();
|
|
||||||
}
|
|
||||||
loopCount++;
|
|
||||||
} while (loopCount < 999 || !pPlayerPriority.getController().isAI());
|
|
||||||
|
|
||||||
if (loopCount >= 999 && pPlayerPriority.getController().isAI()) {
|
|
||||||
System.out.print("AI looped too much with: " + chosenSa);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG_PHASES) {
|
|
||||||
sw.stop();
|
|
||||||
System.out.print("... passed in " + sw.getTime()/1000f + " s\n");
|
|
||||||
System.out.println("\t\tStack: " + game.getStack());
|
|
||||||
sw.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (DEBUG_PHASES) {
|
|
||||||
System.out.print(" >> (no priority given to " + getPriorityPlayer() + ")\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// actingPlayer is the player who may act
|
game.fireEvent(new GameEventPlayerPriority(playerTurn, phase, getPriorityPlayer()));
|
||||||
// the firstAction is the player who gained Priority First in this segment
|
List<SpellAbility> chosenSa = null;
|
||||||
// of Priority
|
|
||||||
Player nextPlayer = game.getNextPlayerAfter(getPriorityPlayer());
|
|
||||||
|
|
||||||
if (game.isGameOver() || nextPlayer == null) { return; } // conceded?
|
int loopCount = 0;
|
||||||
|
do {
|
||||||
|
if (checkStateBasedEffects()) {
|
||||||
|
// state-based effects check could lead to game over
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game.stashGameState();
|
||||||
|
|
||||||
|
chosenSa = pPlayerPriority.getController().chooseSpellAbilityToPlay();
|
||||||
|
|
||||||
|
// this needs to come after chosenSa so it sees you conceding on own turn
|
||||||
|
if (playerTurn.hasLost() && pPlayerPriority.equals(playerTurn) && pFirstPriority.equals(playerTurn)) {
|
||||||
|
// If the active player has lost, and they have priority, set the next player to have priority
|
||||||
|
System.out.println("Active player is no longer in the game...");
|
||||||
|
pPlayerPriority = game.getNextPlayerAfter(getPriorityPlayer());
|
||||||
|
pFirstPriority = pPlayerPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chosenSa == null) {
|
||||||
|
break; // that means 'I pass'
|
||||||
|
}
|
||||||
|
if (DEBUG_PHASES) {
|
||||||
|
System.out.print("... " + pPlayerPriority + " plays " + chosenSa);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean rollback = false;
|
||||||
|
for (SpellAbility sa : chosenSa) {
|
||||||
|
Card saHost = sa.getHostCard();
|
||||||
|
final Zone originZone = saHost.getZone();
|
||||||
|
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
|
||||||
|
|
||||||
|
if (pPlayerPriority.getController().playChosenSpellAbility(sa)) {
|
||||||
|
// 117.3c If a player has priority when they cast a spell, activate an ability, [play a land]
|
||||||
|
// that player receives priority afterward.
|
||||||
|
pFirstPriority = pPlayerPriority; // all opponents have to pass before stack is allowed to resolve
|
||||||
|
} else if (game.EXPERIMENTAL_RESTORE_SNAPSHOT) {
|
||||||
|
rollback = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
saHost = game.getCardState(saHost);
|
||||||
|
final Zone currentZone = saHost.getZone();
|
||||||
|
|
||||||
|
// Need to check if Zone did change
|
||||||
|
if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa.isLandAbility())) {
|
||||||
|
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played
|
||||||
|
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);
|
||||||
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't copy last state if we're in the middle of rolling back a spell...
|
||||||
|
if (!rollback) {
|
||||||
|
game.copyLastState();
|
||||||
|
}
|
||||||
|
loopCount++;
|
||||||
|
} while (loopCount < 999 || !pPlayerPriority.getController().isAI());
|
||||||
|
|
||||||
|
if (loopCount >= 999 && pPlayerPriority.getController().isAI()) {
|
||||||
|
System.out.print("AI looped too much with: " + chosenSa);
|
||||||
|
}
|
||||||
|
|
||||||
if (DEBUG_PHASES) {
|
if (DEBUG_PHASES) {
|
||||||
System.out.println(TextUtil.concatWithSpace(playerTurn.toString(),TextUtil.addSuffix(phase.toString(),":"), pPlayerPriority.toString(),"is active, previous was", nextPlayer.toString()));
|
sw.stop();
|
||||||
|
System.out.print("... passed in " + sw.getTime()/1000f + " s\n");
|
||||||
|
System.out.println("\t\tStack: " + game.getStack());
|
||||||
|
sw.reset();
|
||||||
}
|
}
|
||||||
if (pFirstPriority == nextPlayer) {
|
}
|
||||||
if (game.getStack().isEmpty()) {
|
else if (DEBUG_PHASES) {
|
||||||
if (playerTurn.hasLost()) {
|
System.out.print(" >> (no priority given to " + getPriorityPlayer() + ")\n");
|
||||||
setPriority(game.getNextPlayerAfter(playerTurn));
|
}
|
||||||
} else {
|
|
||||||
setPriority(playerTurn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// end phase
|
// actingPlayer is the player who may act
|
||||||
givePriorityToPlayer = true;
|
// the firstAction is the player who gained Priority First in this segment
|
||||||
onPhaseEnd();
|
// of Priority
|
||||||
advanceToNextPhase();
|
Player nextPlayer = game.getNextPlayerAfter(getPriorityPlayer());
|
||||||
onPhaseBegin();
|
|
||||||
|
if (game.isGameOver() || nextPlayer == null) { return; } // conceded?
|
||||||
|
|
||||||
|
if (DEBUG_PHASES) {
|
||||||
|
System.out.println(TextUtil.concatWithSpace(playerTurn.toString(),TextUtil.addSuffix(phase.toString(),":"), pPlayerPriority.toString(),"is active, previous was", nextPlayer.toString()));
|
||||||
|
}
|
||||||
|
if (pFirstPriority == nextPlayer) {
|
||||||
|
if (game.getStack().isEmpty()) {
|
||||||
|
if (playerTurn.hasLost()) {
|
||||||
|
setPriority(game.getNextPlayerAfter(playerTurn));
|
||||||
|
} else {
|
||||||
|
setPriority(playerTurn);
|
||||||
}
|
}
|
||||||
else if (!game.getStack().hasSimultaneousStackEntries()) {
|
|
||||||
game.getStack().resolveStack();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// pass the priority to other player
|
|
||||||
pPlayerPriority = nextPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If ever the karn's ultimate resolved
|
// end phase
|
||||||
if (game.getAge() == GameStage.RestartedByKarn) {
|
givePriorityToPlayer = true;
|
||||||
setPhase(null);
|
onPhaseEnd();
|
||||||
game.updatePhaseForView();
|
advanceToNextPhase();
|
||||||
game.fireEvent(new GameEventGameRestarted(playerTurn));
|
onPhaseBegin();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else if (!game.getStack().hasSimultaneousStackEntries()) {
|
||||||
|
game.getStack().resolveStack();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// pass the priority to other player
|
||||||
|
pPlayerPriority = nextPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
// update Priority for all players
|
// If ever the karn's ultimate resolved
|
||||||
for (final Player p : game.getPlayers()) {
|
if (game.getAge() == GameStage.RestartedByKarn) {
|
||||||
p.setHasPriority(getPriorityPlayer() == p);
|
setPhase(null);
|
||||||
}
|
game.updatePhaseForView();
|
||||||
|
game.fireEvent(new GameEventGameRestarted(playerTurn));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update Priority for all players
|
||||||
|
for (final Player p : game.getPlayers()) {
|
||||||
|
p.setHasPriority(getPriorityPlayer() == p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1120,13 +1120,14 @@ public class Player extends GameEntity implements Comparable<Player> {
|
|||||||
getGame().fireEvent(new GameEventSurveil(this, numToTop, numToGrave));
|
getGame().fireEvent(new GameEventSurveil(this, numToTop, numToGrave));
|
||||||
}
|
}
|
||||||
|
|
||||||
surveilThisTurn++;
|
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||||
runParams.put(AbilityKey.FirstTime, surveilThisTurn == 1);
|
runParams.put(AbilityKey.FirstTime, surveilThisTurn == 0);
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
runParams.putAll(params);
|
runParams.putAll(params);
|
||||||
}
|
}
|
||||||
getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false);
|
getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false);
|
||||||
|
|
||||||
|
surveilThisTurn++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSurveilThisTurn() {
|
public int getSurveilThisTurn() {
|
||||||
|
|||||||
@@ -280,15 +280,8 @@ public class ReplacementHandler {
|
|||||||
host = game.getCardState(host);
|
host = game.getCardState(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replacementEffect.getOverridingAbility() == null && replacementEffect.hasParam("ReplaceWith")) {
|
// TODO: the source of replacement effect should be the source of the original effect
|
||||||
// TODO: the source of replacement effect should be the source of the original effect
|
effectSA = replacementEffect.ensureAbility();
|
||||||
effectSA = AbilityFactory.getAbility(host, replacementEffect.getParam("ReplaceWith"), replacementEffect);
|
|
||||||
//replacementEffect.setOverridingAbility(effectSA);
|
|
||||||
//effectSA.setTrigger(true);
|
|
||||||
} else if (replacementEffect.getOverridingAbility() != null) {
|
|
||||||
effectSA = replacementEffect.getOverridingAbility();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effectSA != null) {
|
if (effectSA != null) {
|
||||||
SpellAbility tailend = effectSA;
|
SpellAbility tailend = effectSA;
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
private CardZoneTable changeZoneTable;
|
private CardZoneTable changeZoneTable;
|
||||||
private Map<Player, Integer> loseLifeMap;
|
private Map<Player, Integer> loseLifeMap;
|
||||||
|
|
||||||
|
private String name = "";
|
||||||
|
|
||||||
public CardCollection getLastStateBattlefield() {
|
public CardCollection getLastStateBattlefield() {
|
||||||
return lastStateBattlefield;
|
return lastStateBattlefield;
|
||||||
}
|
}
|
||||||
@@ -1246,6 +1248,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
clone.mayChooseNewTargets = false;
|
clone.mayChooseNewTargets = false;
|
||||||
|
|
||||||
clone.triggeringObjects = AbilityKey.newMap(this.triggeringObjects);
|
clone.triggeringObjects = AbilityKey.newMap(this.triggeringObjects);
|
||||||
|
if (!lki) {
|
||||||
|
clone.replacingObjects = AbilityKey.newMap();
|
||||||
|
}
|
||||||
|
|
||||||
clone.setPayCosts(getPayCosts().copy());
|
clone.setPayCosts(getPayCosts().copy());
|
||||||
if (manaPart != null) {
|
if (manaPart != null) {
|
||||||
@@ -2675,4 +2680,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
public void clearOptionalKeywordAmount() {
|
public void clearOptionalKeywordAmount() {
|
||||||
optionalKeywordAmount.clear();
|
optionalKeywordAmount.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ public class TargetRestrictions {
|
|||||||
|
|
||||||
// Additional restrictions that may not fit into Valid
|
// Additional restrictions that may not fit into Valid
|
||||||
private boolean uniqueTargets = false;
|
private boolean uniqueTargets = false;
|
||||||
private boolean singleZone = false;
|
|
||||||
private boolean forEachPlayer = false;
|
private boolean forEachPlayer = false;
|
||||||
private boolean differentControllers = false;
|
private boolean differentControllers = false;
|
||||||
private boolean differentCMC = false;
|
private boolean differentCMC = false;
|
||||||
@@ -100,7 +99,6 @@ public class TargetRestrictions {
|
|||||||
this.tgtZone = target.getZone();
|
this.tgtZone = target.getZone();
|
||||||
this.saValidTargeting = target.getSAValidTargeting();
|
this.saValidTargeting = target.getSAValidTargeting();
|
||||||
this.uniqueTargets = target.isUniqueTargets();
|
this.uniqueTargets = target.isUniqueTargets();
|
||||||
this.singleZone = target.isSingleZone();
|
|
||||||
this.forEachPlayer = target.isForEachPlayer();
|
this.forEachPlayer = target.isForEachPlayer();
|
||||||
this.differentControllers = target.isDifferentControllers();
|
this.differentControllers = target.isDifferentControllers();
|
||||||
this.differentCMC = target.isDifferentCMC();
|
this.differentCMC = target.isDifferentCMC();
|
||||||
@@ -538,12 +536,6 @@ public class TargetRestrictions {
|
|||||||
public final void setUniqueTargets(final boolean unique) {
|
public final void setUniqueTargets(final boolean unique) {
|
||||||
this.uniqueTargets = unique;
|
this.uniqueTargets = unique;
|
||||||
}
|
}
|
||||||
public final boolean isSingleZone() {
|
|
||||||
return this.singleZone;
|
|
||||||
}
|
|
||||||
public final void setSingleZone(final boolean single) {
|
|
||||||
this.singleZone = single;
|
|
||||||
}
|
|
||||||
public boolean isWithoutSameCreatureType() {
|
public boolean isWithoutSameCreatureType() {
|
||||||
return withoutSameCreatureType;
|
return withoutSameCreatureType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -392,6 +392,10 @@ public abstract class Trigger extends TriggerReplacementBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (condition == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if ("LifePaid".equals(condition)) {
|
if ("LifePaid".equals(condition)) {
|
||||||
final SpellAbility trigSA = (SpellAbility) runParams.get(AbilityKey.SpellAbility);
|
final SpellAbility trigSA = (SpellAbility) runParams.get(AbilityKey.SpellAbility);
|
||||||
if (trigSA != null && trigSA.getAmountLifePaid() <= 0) {
|
if (trigSA != null && trigSA.getAmountLifePaid() <= 0) {
|
||||||
@@ -442,7 +446,15 @@ public abstract class Trigger extends TriggerReplacementBase {
|
|||||||
if (game.getCombat().getAttackersAndDefenders().values().containsAll(attacker.getOpponents())) {
|
if (game.getCombat().getAttackersAndDefenders().values().containsAll(attacker.getOpponents())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (condition.startsWith("FromNamedAbility")) {
|
||||||
|
var rest = condition.substring(16);
|
||||||
|
final SpellAbility trigSA = (SpellAbility) runParams.get(AbilityKey.Cause);
|
||||||
|
|
||||||
|
if (trigSA != null && !trigSA.getName().equals(rest)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -464,7 +464,7 @@ public class TrackableTypes {
|
|||||||
public static final TrackableType<ColorSet> ColorSetType = new TrackableType<ColorSet>() {
|
public static final TrackableType<ColorSet> ColorSetType = new TrackableType<ColorSet>() {
|
||||||
@Override
|
@Override
|
||||||
public ColorSet getDefaultValue() {
|
public ColorSet getDefaultValue() {
|
||||||
return ColorSet.getNullColor();
|
return ColorSet.NO_COLORS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-android</artifactId>
|
<artifactId>forge-gui-android</artifactId>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-desktop</artifactId>
|
<artifactId>forge-gui-desktop</artifactId>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import java.awt.Graphics;
|
|||||||
import javax.swing.JTable;
|
import javax.swing.JTable;
|
||||||
|
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
import forge.card.mana.ManaCostShard;
|
import forge.card.MagicColor;
|
||||||
import forge.toolbox.CardFaceSymbols;
|
import forge.toolbox.CardFaceSymbols;
|
||||||
|
|
||||||
public class ColorSetRenderer extends ItemCellRenderer {
|
public class ColorSetRenderer extends ItemCellRenderer {
|
||||||
@@ -33,7 +33,7 @@ public class ColorSetRenderer extends ItemCellRenderer {
|
|||||||
this.cs = (ColorSet) value;
|
this.cs = (ColorSet) value;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.cs = ColorSet.getNullColor();
|
this.cs = ColorSet.NO_COLORS;
|
||||||
}
|
}
|
||||||
this.setToolTipText(cs.toString());
|
this.setToolTipText(cs.toString());
|
||||||
return super.getTableCellRendererComponent(table, "", isSelected, hasFocus, row, column);
|
return super.getTableCellRendererComponent(table, "", isSelected, hasFocus, row, column);
|
||||||
@@ -57,8 +57,8 @@ public class ColorSetRenderer extends ItemCellRenderer {
|
|||||||
final int offsetIfNoSpace = cntGlyphs > 1 ? (cellWidth - padding0 - elemtWidth) / (cntGlyphs - 1) : elemtWidth + elemtGap;
|
final int offsetIfNoSpace = cntGlyphs > 1 ? (cellWidth - padding0 - elemtWidth) / (cntGlyphs - 1) : elemtWidth + elemtGap;
|
||||||
final int dx = Math.min(elemtWidth + elemtGap, offsetIfNoSpace);
|
final int dx = Math.min(elemtWidth + elemtGap, offsetIfNoSpace);
|
||||||
|
|
||||||
for (final ManaCostShard s : cs.getOrderedShards()) {
|
for (final MagicColor.Color s : cs.getOrderedColors()) {
|
||||||
CardFaceSymbols.drawManaSymbol(s.getImageKey(), g, x, y);
|
CardFaceSymbols.drawManaSymbol(s.getShortName(), g, x, y);
|
||||||
x += dx;
|
x += dx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.StringTokenizer;
|
|||||||
import com.esotericsoftware.minlog.Log;
|
import com.esotericsoftware.minlog.Log;
|
||||||
|
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.card.mana.ManaCostShard;
|
import forge.card.mana.ManaCostShard;
|
||||||
import forge.gui.GuiBase;
|
import forge.gui.GuiBase;
|
||||||
@@ -204,9 +205,9 @@ public class CardFaceSymbols {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void drawColorSet(Graphics g, ColorSet colorSet, int x, int y, int imageSize, boolean vertical) {
|
public static void drawColorSet(Graphics g, ColorSet colorSet, int x, int y, int imageSize, boolean vertical) {
|
||||||
for (final ManaCostShard s : colorSet.getOrderedShards()) {
|
for (final MagicColor.Color s : colorSet.getOrderedColors()) {
|
||||||
if (DECK_COLORSET.get(s.getImageKey())!=null)
|
if (DECK_COLORSET.get(s.getShortName())!=null)
|
||||||
FSkin.drawImage(g, DECK_COLORSET.get(s.getImageKey()), x, y, imageSize, imageSize);
|
FSkin.drawImage(g, DECK_COLORSET.get(s.getShortName()), x, y, imageSize, imageSize);
|
||||||
if (!vertical)
|
if (!vertical)
|
||||||
x += imageSize;
|
x += imageSize;
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import org.testng.AssertJUnit;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
public class AIIntegrationTests extends AITest {
|
||||||
|
@Test
|
||||||
|
public void testSwingForLethal() {
|
||||||
|
Game game = initAndCreateGame();
|
||||||
|
Player p = game.getPlayers().get(1);
|
||||||
|
p.setTeam(0);
|
||||||
|
addCard("Nest Robber", p);
|
||||||
|
addCard("Nest Robber", p);
|
||||||
|
|
||||||
|
Player opponent = game.getPlayers().get(0);
|
||||||
|
opponent.setTeam(1);
|
||||||
|
|
||||||
|
addCard("Runeclaw Bear", opponent);
|
||||||
|
opponent.setLife(2, null);
|
||||||
|
|
||||||
|
this.playUntilPhase(game, PhaseType.END_OF_TURN);
|
||||||
|
|
||||||
|
AssertJUnit.assertTrue(game.isGameOver());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSuspendAI() {
|
||||||
|
// Test that the AI can cast a suspend spell
|
||||||
|
// They should suspend it, then deal three damage to their opponent
|
||||||
|
Game game = initAndCreateGame();
|
||||||
|
Player p = game.getPlayers().get(1);
|
||||||
|
p.setTeam(0);
|
||||||
|
addCard("Mountain", p);
|
||||||
|
addCardToZone("Rift Bolt", p, ZoneType.Hand);
|
||||||
|
|
||||||
|
Player opponent = game.getPlayers().get(0);
|
||||||
|
opponent.setTeam(1);
|
||||||
|
|
||||||
|
// Fill deck with lands so we can goldfish a few turns
|
||||||
|
for (int i = 0; i < 60; i++) {
|
||||||
|
addCardToZone("Island", opponent, ZoneType.Library);
|
||||||
|
// Add something they can't cast
|
||||||
|
addCardToZone("Stone Golem", p, ZoneType.Library);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
this.playUntilNextTurn(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
AssertJUnit.assertEquals(17, opponent.getLife());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttackTriggers() {
|
||||||
|
// Test that attack triggers actually trigger
|
||||||
|
Game game = initAndCreateGame();
|
||||||
|
Player p = game.getPlayers().get(1);
|
||||||
|
p.setTeam(0);
|
||||||
|
addCard("Hellrider", p);
|
||||||
|
addCard("Raging Goblin", p);
|
||||||
|
|
||||||
|
Player opponent = game.getPlayers().get(0);
|
||||||
|
opponent.setTeam(1);
|
||||||
|
|
||||||
|
// Fill deck with lands so we can goldfish a few turns
|
||||||
|
for (int i = 0; i < 60; i++) {
|
||||||
|
addCardToZone("Island", opponent, ZoneType.Library);
|
||||||
|
// Add something they can't cast
|
||||||
|
addCardToZone("Stone Golem", p, ZoneType.Library);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.playUntilNextTurn(game);
|
||||||
|
|
||||||
|
AssertJUnit.assertEquals(14, opponent.getLife());
|
||||||
|
}
|
||||||
|
}
|
||||||
190
forge-gui-desktop/src/test/java/forge/ai/AITest.java
Normal file
190
forge-gui-desktop/src/test/java/forge/ai/AITest.java
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package forge.ai;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.GuiDesktop;
|
||||||
|
import forge.StaticData;
|
||||||
|
import forge.deck.Deck;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.GameRules;
|
||||||
|
import forge.game.GameStage;
|
||||||
|
import forge.game.GameType;
|
||||||
|
import forge.game.Match;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollectionView;
|
||||||
|
import forge.game.card.CardFactory;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.RegisteredPlayer;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.gui.GuiBase;
|
||||||
|
import forge.item.IPaperCard;
|
||||||
|
import forge.item.PaperToken;
|
||||||
|
import forge.localinstance.properties.ForgePreferences.FPref;
|
||||||
|
import forge.model.FModel;
|
||||||
|
|
||||||
|
public class AITest {
|
||||||
|
private static boolean initialized = false;
|
||||||
|
|
||||||
|
public Game resetGame() {
|
||||||
|
// need to be done after FModel.initialize, or the Localizer isn't loaded yet
|
||||||
|
List<RegisteredPlayer> players = Lists.newArrayList();
|
||||||
|
Deck d1 = new Deck();
|
||||||
|
players.add(new RegisteredPlayer(d1).setPlayer(new LobbyPlayerAi("p2", null)));
|
||||||
|
players.add(new RegisteredPlayer(d1).setPlayer(new LobbyPlayerAi("p1", null)));
|
||||||
|
GameRules rules = new GameRules(GameType.Constructed);
|
||||||
|
Match match = new Match(rules, players, "Test");
|
||||||
|
Game game = new Game(players, rules, match);
|
||||||
|
Player p = game.getPlayers().get(1);
|
||||||
|
game.setAge(GameStage.Play);
|
||||||
|
game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p);
|
||||||
|
game.getPhaseHandler().onStackResolved();
|
||||||
|
// game.getAction().checkStateEffects(true);
|
||||||
|
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Game initAndCreateGame() {
|
||||||
|
if (!initialized) {
|
||||||
|
GuiBase.setInterface(new GuiDesktop());
|
||||||
|
FModel.initialize(null, preferences -> {
|
||||||
|
preferences.setPref(FPref.LOAD_CARD_SCRIPTS_LAZILY, false);
|
||||||
|
preferences.setPref(FPref.UI_LANGUAGE, "en-US");
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resetGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int countCardsWithName(Game game, String name) {
|
||||||
|
int i = 0;
|
||||||
|
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
|
if (c.getName().equals(name)) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Card findCardWithName(Game game, String name) {
|
||||||
|
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
|
if (c.getName().equals(name)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected SpellAbility findSAWithPrefix(Card c, String prefix) {
|
||||||
|
return findSAWithPrefix(c.getSpellAbilities(), prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SpellAbility findSAWithPrefix(Iterable<SpellAbility> abilities, String prefix) {
|
||||||
|
for (SpellAbility sa : abilities) {
|
||||||
|
if (sa.getDescription().startsWith(prefix)) {
|
||||||
|
return sa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Card createCard(String name, Player p) {
|
||||||
|
IPaperCard paperCard = FModel.getMagicDb().getCommonCards().getCard(name);
|
||||||
|
if (paperCard == null) {
|
||||||
|
StaticData.instance().attemptToLoadCard(name);
|
||||||
|
paperCard = FModel.getMagicDb().getCommonCards().getCard(name);
|
||||||
|
}
|
||||||
|
return Card.fromPaperCard(paperCard, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Card addCardToZone(String name, Player p, ZoneType zone) {
|
||||||
|
Card c = createCard(name, p);
|
||||||
|
// card need a new Timestamp otherwise Static Abilities might collide
|
||||||
|
c.setGameTimestamp(p.getGame().getNextTimestamp());
|
||||||
|
p.getZone(zone).add(c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Card addCard(String name, Player p) {
|
||||||
|
return addCardToZone(name, p, ZoneType.Battlefield);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Card> addCards(String name, int count, Player p) {
|
||||||
|
List<Card> cards = Lists.newArrayList();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
cards.add(addCard(name, p));
|
||||||
|
}
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Card createToken(String name, Player p) {
|
||||||
|
PaperToken token = FModel.getMagicDb().getAllTokens().getToken(name);
|
||||||
|
if (token == null) {
|
||||||
|
System.out.println("Failed to find token name " + name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return CardFactory.getCard(token, p, p.getGame());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Card> addTokens(String name, int amount, Player p) {
|
||||||
|
List<Card> cards = new ArrayList<>();
|
||||||
|
|
||||||
|
for(int i = 0; i < amount; i++) {
|
||||||
|
cards.add(addToken(name, p));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Card addToken(String name, Player p) {
|
||||||
|
Card c = createToken(name, p);
|
||||||
|
// card need a new Timestamp otherwise Static Abilities might collide
|
||||||
|
c.setGameTimestamp(p.getGame().getNextTimestamp());
|
||||||
|
p.getZone(ZoneType.Battlefield).add(c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void playUntilStackClear(Game game) {
|
||||||
|
do {
|
||||||
|
game.getPhaseHandler().mainLoopStep();
|
||||||
|
} while (!game.isGameOver() && !game.getStack().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void playUntilPhase(Game game, PhaseType phase) {
|
||||||
|
do {
|
||||||
|
game.getPhaseHandler().mainLoopStep();
|
||||||
|
} while (!game.isGameOver() && !game.getPhaseHandler().is(phase));
|
||||||
|
}
|
||||||
|
|
||||||
|
void playUntilNextTurn(Game game) {
|
||||||
|
Player current = game.getPhaseHandler().getPlayerTurn();
|
||||||
|
do {
|
||||||
|
game.getPhaseHandler().mainLoopStep();
|
||||||
|
} while (!game.isGameOver() && game.getPhaseHandler().getPlayerTurn().equals(current));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String gameStateToString(Game game) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (ZoneType zone : ZoneType.values()) {
|
||||||
|
CardCollectionView cards = game.getCardsIn(zone);
|
||||||
|
if (!cards.isEmpty()) {
|
||||||
|
sb.append("Zone ").append(zone.name()).append(":\n");
|
||||||
|
for (Card c : game.getCardsIn(zone)) {
|
||||||
|
sb.append(" ").append(c);
|
||||||
|
if (c.isTapped()) {
|
||||||
|
sb.append(" (T)");
|
||||||
|
}
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
package forge.ai.simulation;
|
package forge.ai.simulation;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.GuiDesktop;
|
|
||||||
import forge.StaticData;
|
|
||||||
import forge.ai.AIOption;
|
import forge.ai.AIOption;
|
||||||
|
import forge.ai.AITest;
|
||||||
import forge.ai.LobbyPlayerAi;
|
import forge.ai.LobbyPlayerAi;
|
||||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||||
import forge.deck.Deck;
|
import forge.deck.Deck;
|
||||||
@@ -18,21 +16,12 @@ import forge.game.GameRules;
|
|||||||
import forge.game.GameStage;
|
import forge.game.GameStage;
|
||||||
import forge.game.GameType;
|
import forge.game.GameType;
|
||||||
import forge.game.Match;
|
import forge.game.Match;
|
||||||
import forge.game.card.Card;
|
|
||||||
import forge.game.card.CardCollectionView;
|
|
||||||
import forge.game.card.CardFactory;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.RegisteredPlayer;
|
import forge.game.player.RegisteredPlayer;
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.gui.GuiBase;
|
|
||||||
import forge.item.IPaperCard;
|
|
||||||
import forge.item.PaperToken;
|
|
||||||
import forge.localinstance.properties.ForgePreferences.FPref;
|
import forge.localinstance.properties.ForgePreferences.FPref;
|
||||||
import forge.model.FModel;
|
import forge.model.FModel;
|
||||||
|
|
||||||
public class SimulationTest {
|
public class SimulationTest extends AITest {
|
||||||
private static boolean initialized = false;
|
|
||||||
|
|
||||||
public Game resetGame() {
|
public Game resetGame() {
|
||||||
// need to be done after FModel.initialize, or the Localizer isn't loaded yet
|
// need to be done after FModel.initialize, or the Localizer isn't loaded yet
|
||||||
@@ -53,19 +42,6 @@ public class SimulationTest {
|
|||||||
return game;
|
return game;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Game initAndCreateGame() {
|
|
||||||
if (!initialized) {
|
|
||||||
GuiBase.setInterface(new GuiDesktop());
|
|
||||||
FModel.initialize(null, preferences -> {
|
|
||||||
preferences.setPref(FPref.LOAD_CARD_SCRIPTS_LAZILY, false);
|
|
||||||
preferences.setPref(FPref.UI_LANGUAGE, "en-US");
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resetGame();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected GameSimulator createSimulator(Game game, Player p) {
|
protected GameSimulator createSimulator(Game game, Player p) {
|
||||||
return new GameSimulator(new SimulationController(new Score(0)) {
|
return new GameSimulator(new SimulationController(new Score(0)) {
|
||||||
@@ -75,106 +51,4 @@ public class SimulationTest {
|
|||||||
}
|
}
|
||||||
}, game, p, null);
|
}, game, p, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int countCardsWithName(Game game, String name) {
|
|
||||||
int i = 0;
|
|
||||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
|
||||||
if (c.getName().equals(name)) {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Card findCardWithName(Game game, String name) {
|
|
||||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
|
||||||
if (c.getName().equals(name)) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String gameStateToString(Game game) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (ZoneType zone : ZoneType.values()) {
|
|
||||||
CardCollectionView cards = game.getCardsIn(zone);
|
|
||||||
if (!cards.isEmpty()) {
|
|
||||||
sb.append("Zone ").append(zone.name()).append(":\n");
|
|
||||||
for (Card c : game.getCardsIn(zone)) {
|
|
||||||
sb.append(" ").append(c).append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SpellAbility findSAWithPrefix(Card c, String prefix) {
|
|
||||||
return findSAWithPrefix(c.getSpellAbilities(), prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SpellAbility findSAWithPrefix(Iterable<SpellAbility> abilities, String prefix) {
|
|
||||||
for (SpellAbility sa : abilities) {
|
|
||||||
if (sa.getDescription().startsWith(prefix)) {
|
|
||||||
return sa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Card createCard(String name, Player p) {
|
|
||||||
IPaperCard paperCard = FModel.getMagicDb().getCommonCards().getCard(name);
|
|
||||||
if (paperCard == null) {
|
|
||||||
StaticData.instance().attemptToLoadCard(name);
|
|
||||||
paperCard = FModel.getMagicDb().getCommonCards().getCard(name);
|
|
||||||
}
|
|
||||||
return Card.fromPaperCard(paperCard, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Card addCardToZone(String name, Player p, ZoneType zone) {
|
|
||||||
Card c = createCard(name, p);
|
|
||||||
// card need a new Timestamp otherwise Static Abilities might collide
|
|
||||||
c.setGameTimestamp(p.getGame().getNextTimestamp());
|
|
||||||
p.getZone(zone).add(c);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Card addCard(String name, Player p) {
|
|
||||||
return addCardToZone(name, p, ZoneType.Battlefield);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Card> addCards(String name, int count, Player p) {
|
|
||||||
List<Card> cards = Lists.newArrayList();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
cards.add(addCard(name, p));
|
|
||||||
}
|
|
||||||
return cards;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Card createToken(String name, Player p) {
|
|
||||||
PaperToken token = FModel.getMagicDb().getAllTokens().getToken(name);
|
|
||||||
if (token == null) {
|
|
||||||
System.out.println("Failed to find token name " + name);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return CardFactory.getCard(token, p, p.getGame());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Card> addTokens(String name, int amount, Player p) {
|
|
||||||
List<Card> cards = new ArrayList<>();
|
|
||||||
|
|
||||||
for(int i = 0; i < amount; i++) {
|
|
||||||
cards.add(addToken(name, p));
|
|
||||||
}
|
|
||||||
|
|
||||||
return cards;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Card addToken(String name, Player p) {
|
|
||||||
Card c = createToken(name, p);
|
|
||||||
// card need a new Timestamp otherwise Static Abilities might collide
|
|
||||||
c.setGameTimestamp(p.getGame().getNextTimestamp());
|
|
||||||
p.getZone(ZoneType.Battlefield).add(c);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-ios</artifactId>
|
<artifactId>forge-gui-ios</artifactId>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-mobile-dev</artifactId>
|
<artifactId>forge-gui-mobile-dev</artifactId>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui-mobile</artifactId>
|
<artifactId>forge-gui-mobile</artifactId>
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ public class Forge implements ApplicationListener {
|
|||||||
public static boolean allowCardBG = false;
|
public static boolean allowCardBG = false;
|
||||||
public static boolean altPlayerLayout = false;
|
public static boolean altPlayerLayout = false;
|
||||||
public static boolean altZoneTabs = false;
|
public static boolean altZoneTabs = false;
|
||||||
|
public static String altZoneTabMode = "Off";
|
||||||
public static boolean animatedCardTapUntap = false;
|
public static boolean animatedCardTapUntap = false;
|
||||||
public static String enableUIMask = "Crop";
|
public static String enableUIMask = "Crop";
|
||||||
public static String selector = "Default";
|
public static String selector = "Default";
|
||||||
@@ -222,7 +223,7 @@ public class Forge implements ApplicationListener {
|
|||||||
reversedPrompt = getForgePreferences().getPrefBoolean(FPref.UI_REVERSE_PROMPT_BUTTON);
|
reversedPrompt = getForgePreferences().getPrefBoolean(FPref.UI_REVERSE_PROMPT_BUTTON);
|
||||||
autoAIDeckSelection = getForgePreferences().getPrefBoolean(FPref.UI_AUTO_AIDECK_SELECTION);
|
autoAIDeckSelection = getForgePreferences().getPrefBoolean(FPref.UI_AUTO_AIDECK_SELECTION);
|
||||||
altPlayerLayout = getForgePreferences().getPrefBoolean(FPref.UI_ALT_PLAYERINFOLAYOUT);
|
altPlayerLayout = getForgePreferences().getPrefBoolean(FPref.UI_ALT_PLAYERINFOLAYOUT);
|
||||||
altZoneTabs = getForgePreferences().getPrefBoolean(FPref.UI_ALT_PLAYERZONETABS);
|
setAltZoneTabMode(getForgePreferences().getPref(FPref.UI_ALT_PLAYERZONETABS));
|
||||||
animatedCardTapUntap = getForgePreferences().getPrefBoolean(FPref.UI_ANIMATED_CARD_TAPUNTAP);
|
animatedCardTapUntap = getForgePreferences().getPrefBoolean(FPref.UI_ANIMATED_CARD_TAPUNTAP);
|
||||||
enableUIMask = getForgePreferences().getPref(FPref.UI_ENABLE_BORDER_MASKING);
|
enableUIMask = getForgePreferences().getPref(FPref.UI_ENABLE_BORDER_MASKING);
|
||||||
if (getForgePreferences().getPref(FPref.UI_ENABLE_BORDER_MASKING).equals("true")) //override old settings if not updated
|
if (getForgePreferences().getPref(FPref.UI_ENABLE_BORDER_MASKING).equals("true")) //override old settings if not updated
|
||||||
@@ -260,6 +261,17 @@ public class Forge implements ApplicationListener {
|
|||||||
FThreads.invokeInBackgroundThread(() -> AssetsDownloader.checkForUpdates(exited, runnable));
|
FThreads.invokeInBackgroundThread(() -> AssetsDownloader.checkForUpdates(exited, runnable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static void setAltZoneTabMode(String mode) {
|
||||||
|
Forge.altZoneTabMode = mode;
|
||||||
|
switch (Forge.altZoneTabMode) {
|
||||||
|
case "Vertical", "Horizontal" -> Forge.altZoneTabs = true;
|
||||||
|
case "Off" -> Forge.altZoneTabs = false;
|
||||||
|
default -> Forge.altZoneTabs = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static boolean isHorizontalTabLayout() {
|
||||||
|
return Forge.altZoneTabs && "Horizontal".equalsIgnoreCase(Forge.altZoneTabMode);
|
||||||
|
}
|
||||||
public static boolean hasGamepad() {
|
public static boolean hasGamepad() {
|
||||||
//Classic Mode Various Screen GUI are not yet supported, needs control mapping for each screens
|
//Classic Mode Various Screen GUI are not yet supported, needs control mapping for each screens
|
||||||
if (isMobileAdventureMode) {
|
if (isMobileAdventureMode) {
|
||||||
@@ -337,8 +349,11 @@ public class Forge implements ApplicationListener {
|
|||||||
GuiBase.setIsAdventureMode(true);
|
GuiBase.setIsAdventureMode(true);
|
||||||
advStartup = false;
|
advStartup = false;
|
||||||
isMobileAdventureMode = true;
|
isMobileAdventureMode = true;
|
||||||
if (GuiBase.isAndroid()) //force it for adventure mode
|
//force it for adventure mode if the prefs is not updated from boolean value to string value
|
||||||
altZoneTabs = true;
|
if ("true".equalsIgnoreCase(FModel.getPreferences().getPref(FPref.UI_ALT_PLAYERZONETABS)) ||
|
||||||
|
"false".equalsIgnoreCase(FModel.getPreferences().getPref(FPref.UI_ALT_PLAYERZONETABS))) {
|
||||||
|
setAltZoneTabMode("Vertical");
|
||||||
|
}
|
||||||
//pixl cursor for adventure
|
//pixl cursor for adventure
|
||||||
setCursor(null, "0");
|
setCursor(null, "0");
|
||||||
if (!GuiBase.isAndroid() || !getDeviceAdapter().getGamepads().isEmpty())
|
if (!GuiBase.isAndroid() || !getDeviceAdapter().getGamepads().isEmpty())
|
||||||
@@ -755,7 +770,7 @@ public class Forge implements ApplicationListener {
|
|||||||
isMobileAdventureMode = false;
|
isMobileAdventureMode = false;
|
||||||
GuiBase.setIsAdventureMode(false);
|
GuiBase.setIsAdventureMode(false);
|
||||||
setCursor(FSkin.getCursor().get(0), "0");
|
setCursor(FSkin.getCursor().get(0), "0");
|
||||||
altZoneTabs = FModel.getPreferences().getPrefBoolean(FPref.UI_ALT_PLAYERZONETABS);
|
setAltZoneTabMode(FModel.getPreferences().getPref(FPref.UI_ALT_PLAYERZONETABS));
|
||||||
Gdx.input.setInputProcessor(getInputProcessor());
|
Gdx.input.setInputProcessor(getInputProcessor());
|
||||||
clearTransitionScreen();
|
clearTransitionScreen();
|
||||||
openHomeDefault();
|
openHomeDefault();
|
||||||
@@ -1468,7 +1483,7 @@ public class Forge implements ApplicationListener {
|
|||||||
|
|
||||||
boolean handled;
|
boolean handled;
|
||||||
if (KeyInputAdapter.isShiftKeyDown()) {
|
if (KeyInputAdapter.isShiftKeyDown()) {
|
||||||
handled = pan(mouseMovedX, mouseMovedY, -Utils.AVG_FINGER_WIDTH * amountX, 0, false);
|
handled = pan(mouseMovedX, mouseMovedY, -Utils.AVG_FINGER_WIDTH * amountY, 0, false);
|
||||||
} else {
|
} else {
|
||||||
handled = pan(mouseMovedX, mouseMovedY, 0, -Utils.AVG_FINGER_HEIGHT * amountY, true);
|
handled = pan(mouseMovedX, mouseMovedY, 0, -Utils.AVG_FINGER_HEIGHT * amountY, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ public class AdventureEventData implements Serializable {
|
|||||||
Random placeholder = MyRandom.getRandom();
|
Random placeholder = MyRandom.getRandom();
|
||||||
MyRandom.setRandom(getEventRandom());
|
MyRandom.setRandom(getEventRandom());
|
||||||
if (draft == null && (eventStatus == AdventureEventController.EventStatus.Available || eventStatus == AdventureEventController.EventStatus.Entered)) {
|
if (draft == null && (eventStatus == AdventureEventController.EventStatus.Available || eventStatus == AdventureEventController.EventStatus.Entered)) {
|
||||||
draft = BoosterDraft.createDraft(LimitedPoolType.Block, getCardBlock(), packConfiguration);
|
draft = BoosterDraft.createDraft(LimitedPoolType.Block, getCardBlock(), packConfiguration, 8);
|
||||||
registeredDeck = draft.getHumanPlayer().getDeck();
|
registeredDeck = draft.getHumanPlayer().getDeck();
|
||||||
assignPlayerNames(draft);
|
assignPlayerNames(draft);
|
||||||
}
|
}
|
||||||
@@ -607,15 +607,18 @@ public class AdventureEventData implements Serializable {
|
|||||||
description += "Block: " + getCardBlock() + "\n";
|
description += "Block: " + getCardBlock() + "\n";
|
||||||
description += "Boosters: " + String.join(", ", packConfiguration) + "\n";
|
description += "Boosters: " + String.join(", ", packConfiguration) + "\n";
|
||||||
description += "Competition Style: " + participants.length + " players, matches played as best of " + eventRules.gamesPerMatch + ", " + (getPairingDescription()) + "\n\n";
|
description += "Competition Style: " + participants.length + " players, matches played as best of " + eventRules.gamesPerMatch + ", " + (getPairingDescription()) + "\n\n";
|
||||||
description += String.format("Pay 1 Entry Fee\n- Gold %d[][+Gold][BLACK]\n- Mana Shards %d[][+Shards][BLACK]\n", Math.round(eventRules.goldToEnter * townPriceModifier), Math.round(eventRules.shardsToEnter * townPriceModifier));
|
|
||||||
if (eventRules.acceptsBronzeChallengeCoin) {
|
if (eventStatus == AdventureEventController.EventStatus.Available) {
|
||||||
description += "- Bronze Challenge Coin [][+BronzeChallengeCoin][BLACK]\n\n";
|
description += String.format("Pay 1 Entry Fee\n- Gold %d[][+Gold][BLACK]\n- Mana Shards %d[][+Shards][BLACK]\n", Math.round(eventRules.goldToEnter * townPriceModifier), Math.round(eventRules.shardsToEnter * townPriceModifier));
|
||||||
} else if (eventRules.acceptsSilverChallengeCoin) {
|
if (eventRules.acceptsBronzeChallengeCoin) {
|
||||||
description += "- Silver Challenge Coin [][+SilverChallengeCoin][BLACK]\n\n";
|
description += "- Bronze Challenge Coin [][+BronzeChallengeCoin][BLACK]\n\n";
|
||||||
} else if (eventRules.acceptsChallengeCoin) {
|
} else if (eventRules.acceptsSilverChallengeCoin) {
|
||||||
description += "- Gold Challenge Coin [][+ChallengeCoin][BLACK]\n\n";
|
description += "- Silver Challenge Coin [][+SilverChallengeCoin][BLACK]\n\n";
|
||||||
} else {
|
} else if (eventRules.acceptsChallengeCoin) {
|
||||||
description += "\n";
|
description += "- Gold Challenge Coin [][+ChallengeCoin][BLACK]\n\n";
|
||||||
|
} else {
|
||||||
|
description += "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
description += String.format("Prizes\nChampion: Keep drafted deck\n2+ round wins: Challenge Coin \n1+ round wins: %s Booster, %s Booster\n0 round wins: %s Booster", rewardPacks[0].getComment(), rewardPacks[1].getComment(), rewardPacks[2].getComment());
|
description += String.format("Prizes\nChampion: Keep drafted deck\n2+ round wins: Challenge Coin \n1+ round wins: %s Booster, %s Booster\n0 round wins: %s Booster", rewardPacks[0].getComment(), rewardPacks[1].getComment(), rewardPacks[2].getComment());
|
||||||
} else if (format.equals(AdventureEventController.EventFormat.Jumpstart)) {
|
} else if (format.equals(AdventureEventController.EventFormat.Jumpstart)) {
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ public class DuelScene extends ForgeScene {
|
|||||||
public void enter() {
|
public void enter() {
|
||||||
GameHUD.getInstance().unloadAudio();
|
GameHUD.getInstance().unloadAudio();
|
||||||
GameType mainGameType;
|
GameType mainGameType;
|
||||||
|
boolean isDeckMissing = false;
|
||||||
|
String isDeckMissingMsg = "";
|
||||||
if (eventData != null && eventData.eventRules != null) {
|
if (eventData != null && eventData.eventRules != null) {
|
||||||
mainGameType = eventData.eventRules.gameType;
|
mainGameType = eventData.eventRules.gameType;
|
||||||
} else {
|
} else {
|
||||||
@@ -295,6 +297,12 @@ public class DuelScene extends ForgeScene {
|
|||||||
} else {
|
} else {
|
||||||
deck = currentEnemy.copyPlayerDeck ? this.playerDeck : currentEnemy.generateDeck(Current.player().isFantasyMode(), Current.player().isUsingCustomDeck() || Current.player().isHardorInsaneDifficulty());
|
deck = currentEnemy.copyPlayerDeck ? this.playerDeck : currentEnemy.generateDeck(Current.player().isFantasyMode(), Current.player().isUsingCustomDeck() || Current.player().isHardorInsaneDifficulty());
|
||||||
}
|
}
|
||||||
|
if (deck == null) {
|
||||||
|
isDeckMissing = true;
|
||||||
|
isDeckMissingMsg = "Deck for " + currentEnemy.getName() + " is missing! " + (this.eventData == null ? "Genetic AI deck will be used." : "Player deck will be used.");
|
||||||
|
System.err.println(isDeckMissingMsg);
|
||||||
|
deck = this.eventData == null ? Aggregates.random(DeckProxy.getAllGeneticAIDecks()).getDeck() : this.playerDeck;
|
||||||
|
}
|
||||||
RegisteredPlayer aiPlayer = RegisteredPlayer.forVariants(playerCount, appliedVariants, deck, null, false, null, null);
|
RegisteredPlayer aiPlayer = RegisteredPlayer.forVariants(playerCount, appliedVariants, deck, null, false, null, null);
|
||||||
|
|
||||||
LobbyPlayer enemyPlayer = GamePlayerUtil.createAiPlayer(currentEnemy.getName(), selectAI(currentEnemy.ai));
|
LobbyPlayer enemyPlayer = GamePlayerUtil.createAiPlayer(currentEnemy.getName(), selectAI(currentEnemy.ai));
|
||||||
@@ -368,9 +376,9 @@ public class DuelScene extends ForgeScene {
|
|||||||
hostedMatch.startMatch(rules, appliedVariants, players, guiMap, bossBattle ? MusicPlaylist.BOSS : MusicPlaylist.MATCH);
|
hostedMatch.startMatch(rules, appliedVariants, players, guiMap, bossBattle ? MusicPlaylist.BOSS : MusicPlaylist.MATCH);
|
||||||
MatchController.instance.setGameView(hostedMatch.getGameView());
|
MatchController.instance.setGameView(hostedMatch.getGameView());
|
||||||
boolean showMessages = enemy.getData().boss || (enemy.getData().copyPlayerDeck && Current.player().isUsingCustomDeck());
|
boolean showMessages = enemy.getData().boss || (enemy.getData().copyPlayerDeck && Current.player().isUsingCustomDeck());
|
||||||
if (chaosBattle || showMessages) {
|
if (chaosBattle || showMessages || isDeckMissing) {
|
||||||
final FBufferedImage fb = getFBEnemyAvatar();
|
final FBufferedImage fb = getFBEnemyAvatar();
|
||||||
bossDialogue = createFOption(Forge.getLocalizer().getMessage("AdvBossIntro" + Aggregates.randomInt(1, 35)),
|
bossDialogue = createFOption(isDeckMissing ? isDeckMissingMsg : Forge.getLocalizer().getMessage("AdvBossIntro" + Aggregates.randomInt(1, 35)),
|
||||||
enemy.getName(), fb, fb::dispose);
|
enemy.getName(), fb, fb::dispose);
|
||||||
matchOverlay = new LoadingOverlay(() -> FThreads.delayInEDT(300, () -> FThreads.invokeInEdtNowOrLater(() ->
|
matchOverlay = new LoadingOverlay(() -> FThreads.delayInEDT(300, () -> FThreads.invokeInEdtNowOrLater(() ->
|
||||||
bossDialogue.show())), false, true);
|
bossDialogue.show())), false, true);
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ public class SettingsScene extends UIScene {
|
|||||||
|
|
||||||
if (!GuiBase.isAndroid()) {
|
if (!GuiBase.isAndroid()) {
|
||||||
addCheckBox(Forge.getLocalizer().getMessage("lblBattlefieldTextureFiltering"), ForgePreferences.FPref.UI_LIBGDX_TEXTURE_FILTERING);
|
addCheckBox(Forge.getLocalizer().getMessage("lblBattlefieldTextureFiltering"), ForgePreferences.FPref.UI_LIBGDX_TEXTURE_FILTERING);
|
||||||
addCheckBox(Forge.getLocalizer().getMessage("lblAltZoneTabs"), ForgePreferences.FPref.UI_ALT_PLAYERZONETABS);
|
//addCheckBox(Forge.getLocalizer().getMessage("lblAltZoneTabs"), ForgePreferences.FPref.UI_ALT_PLAYERZONETABS);
|
||||||
} else {
|
} else {
|
||||||
addCheckBox(Forge.getLocalizer().getMessage("lblLandscapeMode") + " (" +
|
addCheckBox(Forge.getLocalizer().getMessage("lblLandscapeMode") + " (" +
|
||||||
Forge.getLocalizer().getMessage("lblRestartRequired") + ")",
|
Forge.getLocalizer().getMessage("lblRestartRequired") + ")",
|
||||||
|
|||||||
@@ -186,8 +186,8 @@ public class CardFaceSymbols {
|
|||||||
public static void drawColorSet(Graphics g, ColorSet colorSet, float x, float y, final float imageSize, boolean vertical) {
|
public static void drawColorSet(Graphics g, ColorSet colorSet, float x, float y, final float imageSize, boolean vertical) {
|
||||||
final float dx = imageSize;
|
final float dx = imageSize;
|
||||||
|
|
||||||
for (final ManaCostShard s : colorSet.getOrderedShards()) {
|
for (final MagicColor.Color s : colorSet.getOrderedColors()) {
|
||||||
drawSymbol(s.getImageKey(), g, x, y, imageSize, imageSize);
|
drawSymbol(s.getShortName(), g, x, y, imageSize, imageSize);
|
||||||
if (!vertical)
|
if (!vertical)
|
||||||
x += dx;
|
x += dx;
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -49,6 +49,24 @@ public class CardImageRenderer {
|
|||||||
return FSkinColor.fromRGB(detailColor.r, detailColor.g, detailColor.b);
|
return FSkinColor.fromRGB(detailColor.r, detailColor.g, detailColor.b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static float getCapHeight(FSkinFont fSkinFont) {
|
||||||
|
if (fSkinFont == null)
|
||||||
|
return 0f;
|
||||||
|
return fSkinFont.getCapHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float getAscent(FSkinFont fSkinFont) {
|
||||||
|
if (fSkinFont == null)
|
||||||
|
return 0f;
|
||||||
|
return fSkinFont.getAscent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float getBoundsWidth(String sequence, FSkinFont fSkinFont) {
|
||||||
|
if (fSkinFont == null)
|
||||||
|
return 0f;
|
||||||
|
return fSkinFont.getBounds(sequence).width;
|
||||||
|
}
|
||||||
|
|
||||||
public static void forceStaticFieldUpdate() {
|
public static void forceStaticFieldUpdate() {
|
||||||
//force static fields to be updated the next time a card image is rendered
|
//force static fields to be updated the next time a card image is rendered
|
||||||
prevImageWidth = 0;
|
prevImageWidth = 0;
|
||||||
@@ -143,7 +161,7 @@ public class CardImageRenderer {
|
|||||||
x += outerBorderThickness;
|
x += outerBorderThickness;
|
||||||
y += outerBorderThickness;
|
y += outerBorderThickness;
|
||||||
w -= 2 * outerBorderThickness;
|
w -= 2 * outerBorderThickness;
|
||||||
float headerHeight = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * NAME_FONT.getCapHeight()) + 2;
|
float headerHeight = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * getCapHeight(NAME_FONT)) + 2;
|
||||||
|
|
||||||
//draw header containing name and mana cost
|
//draw header containing name and mana cost
|
||||||
Color[] headerColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.NAME_BOX_TINT);
|
Color[] headerColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.NAME_BOX_TINT);
|
||||||
@@ -158,15 +176,15 @@ public class CardImageRenderer {
|
|||||||
|
|
||||||
float artWidth = w - 2 * artInset;
|
float artWidth = w - 2 * artInset;
|
||||||
float artHeight = !showArtBox ? 0f : artWidth / CardRenderer.CARD_ART_RATIO;
|
float artHeight = !showArtBox ? 0f : artWidth / CardRenderer.CARD_ART_RATIO;
|
||||||
float typeBoxHeight = 2 * TYPE_FONT.getCapHeight();
|
float typeBoxHeight = 2 * getCapHeight(TYPE_FONT);
|
||||||
float ptBoxHeight = 0;
|
float ptBoxHeight = 0;
|
||||||
float textBoxHeight = h - headerHeight - artHeight - typeBoxHeight - outerBorderThickness - artInset;
|
float textBoxHeight = h - headerHeight - artHeight - typeBoxHeight - outerBorderThickness - artInset;
|
||||||
|
|
||||||
if (state.isCreature() || state.isPlaneswalker() || state.hasPrintedPT() || state.isBattle()) {
|
if (state.isCreature() || state.isPlaneswalker() || state.hasPrintedPT() || state.isBattle()) {
|
||||||
ptBoxHeight = 2 * PT_FONT.getCapHeight();
|
ptBoxHeight = 2 * getCapHeight(PT_FONT);
|
||||||
}
|
}
|
||||||
//space for artist
|
//space for artist
|
||||||
textBoxHeight -= 2 * PT_FONT.getCapHeight();
|
textBoxHeight -= 2 * getCapHeight(PT_FONT);
|
||||||
PaperCard paperCard = null;
|
PaperCard paperCard = null;
|
||||||
try {
|
try {
|
||||||
paperCard = ImageUtil.getPaperCardFromImageKey(state.getImageKey());
|
paperCard = ImageUtil.getPaperCardFromImageKey(state.getImageKey());
|
||||||
@@ -246,7 +264,7 @@ public class CardImageRenderer {
|
|||||||
}
|
}
|
||||||
//draw artist
|
//draw artist
|
||||||
if (showArtist)
|
if (showArtist)
|
||||||
g.drawOutlinedText(artist, TEXT_FONT, Color.WHITE, Color.DARK_GRAY, x + (TYPE_FONT.getCapHeight() / 2), y + (TYPE_FONT.getCapHeight() / 2), w, h, false, Align.left, false);
|
g.drawOutlinedText(artist, TEXT_FONT, Color.WHITE, Color.DARK_GRAY, x + (getCapHeight(TYPE_FONT) / 2), y + (getCapHeight(TYPE_FONT) / 2), w, h, false, Align.left, false);
|
||||||
}
|
}
|
||||||
private static void drawOutlineColor(Graphics g, ColorSet colors, float x, float y, float w, float h) {
|
private static void drawOutlineColor(Graphics g, ColorSet colors, float x, float y, float w, float h) {
|
||||||
if (colors == null)
|
if (colors == null)
|
||||||
@@ -296,7 +314,7 @@ public class CardImageRenderer {
|
|||||||
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, manaSymbolSize) + HEADER_PADDING;
|
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, manaSymbolSize) + HEADER_PADDING;
|
||||||
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - manaSymbolSize) / 2, manaSymbolSize);
|
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - manaSymbolSize) / 2, manaSymbolSize);
|
||||||
//draw "//" between two parts of mana cost
|
//draw "//" between two parts of mana cost
|
||||||
manaCostWidth += NAME_FONT.getBounds("//").width + HEADER_PADDING;
|
manaCostWidth += getBoundsWidth("//", NAME_FONT) + HEADER_PADDING;
|
||||||
g.drawText("//", NAME_FONT, Color.BLACK, x + w - manaCostWidth, y, w, h, false, Align.left, true);
|
g.drawText("//", NAME_FONT, Color.BLACK, x + w - manaCostWidth, y, w, h, false, Align.left, true);
|
||||||
}
|
}
|
||||||
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, manaSymbolSize) + HEADER_PADDING;
|
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, manaSymbolSize) + HEADER_PADDING;
|
||||||
@@ -517,7 +535,7 @@ public class CardImageRenderer {
|
|||||||
} else {
|
} else {
|
||||||
//left
|
//left
|
||||||
//float headerHeight = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * TYPE_FONT.getCapHeight()) + 2;
|
//float headerHeight = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * TYPE_FONT.getCapHeight()) + 2;
|
||||||
float typeBoxHeight = 2 * TYPE_FONT.getCapHeight();
|
float typeBoxHeight = 2 * getCapHeight(TYPE_FONT);
|
||||||
drawHeader(g, card, card.getState(true), altcolors, x, y, w - (w / 2), typeBoxHeight, noText, true);
|
drawHeader(g, card, card.getState(true), altcolors, x, y, w - (w / 2), typeBoxHeight, noText, true);
|
||||||
drawTypeLine(g, card.getState(true), canShow, altcolors, x, y + typeBoxHeight, w - (w / 2), typeBoxHeight, noText, true, true);
|
drawTypeLine(g, card.getState(true), canShow, altcolors, x, y + typeBoxHeight, w - (w / 2), typeBoxHeight, noText, true, true);
|
||||||
float mod = (typeBoxHeight + typeBoxHeight);
|
float mod = (typeBoxHeight + typeBoxHeight);
|
||||||
@@ -714,7 +732,7 @@ public class CardImageRenderer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float padding = TEXT_FONT.getCapHeight() * 0.75f;
|
float padding = getCapHeight(TEXT_FONT) * 0.75f;
|
||||||
x += padding;
|
x += padding;
|
||||||
y += padding;
|
y += padding;
|
||||||
w -= 2 * padding;
|
w -= 2 * padding;
|
||||||
@@ -747,15 +765,15 @@ public class CardImageRenderer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float padding = Math.round(PT_FONT.getCapHeight() / 4);
|
float padding = Math.round(getCapHeight(PT_FONT) / 4);
|
||||||
float totalPieceWidth = -padding;
|
float totalPieceWidth = -padding;
|
||||||
float[] pieceWidths = new float[pieces.size()];
|
float[] pieceWidths = new float[pieces.size()];
|
||||||
for (int i = 0; i < pieces.size(); i++) {
|
for (int i = 0; i < pieces.size(); i++) {
|
||||||
float pieceWidth = PT_FONT.getBounds(pieces.get(i)).width + padding;
|
float pieceWidth = getBoundsWidth(pieces.get(i), PT_FONT) + padding;
|
||||||
pieceWidths[i] = pieceWidth;
|
pieceWidths[i] = pieceWidth;
|
||||||
totalPieceWidth += pieceWidth;
|
totalPieceWidth += pieceWidth;
|
||||||
}
|
}
|
||||||
float boxHeight = PT_FONT.getCapHeight() + PT_FONT.getAscent() + 3 * padding;
|
float boxHeight = getCapHeight(PT_FONT) + getAscent(PT_FONT) + 3 * padding;
|
||||||
|
|
||||||
float boxWidth = Math.max(PT_BOX_WIDTH, totalPieceWidth + 2 * padding);
|
float boxWidth = Math.max(PT_BOX_WIDTH, totalPieceWidth + 2 * padding);
|
||||||
x += w - boxWidth;
|
x += w - boxWidth;
|
||||||
@@ -951,14 +969,14 @@ public class CardImageRenderer {
|
|||||||
x += outerBorderThickness;
|
x += outerBorderThickness;
|
||||||
y += outerBorderThickness;
|
y += outerBorderThickness;
|
||||||
w -= 2 * outerBorderThickness;
|
w -= 2 * outerBorderThickness;
|
||||||
float cardNameBoxHeight = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * NAME_FONT.getCapHeight()) + 2 * TYPE_FONT.getCapHeight() + 2;
|
float cardNameBoxHeight = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * getCapHeight(NAME_FONT)) + 2 * getCapHeight(TYPE_FONT) + 2;
|
||||||
|
|
||||||
//draw name/type box
|
//draw name/type box
|
||||||
Color[] nameBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.NAME_BOX_TINT);
|
Color[] nameBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.NAME_BOX_TINT);
|
||||||
drawDetailsNameBox(g, card, state, canShow, nameBoxColors, x, y, w, cardNameBoxHeight);
|
drawDetailsNameBox(g, card, state, canShow, nameBoxColors, x, y, w, cardNameBoxHeight);
|
||||||
|
|
||||||
float innerBorderThickness = outerBorderThickness / 2;
|
float innerBorderThickness = outerBorderThickness / 2;
|
||||||
float ptBoxHeight = 2 * PT_FONT.getCapHeight();
|
float ptBoxHeight = 2 * getCapHeight(PT_FONT);
|
||||||
float textBoxHeight = h - cardNameBoxHeight - ptBoxHeight - outerBorderThickness - 3 * innerBorderThickness;
|
float textBoxHeight = h - cardNameBoxHeight - ptBoxHeight - outerBorderThickness - 3 * innerBorderThickness;
|
||||||
|
|
||||||
y += cardNameBoxHeight + innerBorderThickness;
|
y += cardNameBoxHeight + innerBorderThickness;
|
||||||
@@ -1141,7 +1159,7 @@ public class CardImageRenderer {
|
|||||||
float padding = h / 8;
|
float padding = h / 8;
|
||||||
|
|
||||||
//make sure name/mana cost row height is tall enough for both
|
//make sure name/mana cost row height is tall enough for both
|
||||||
h = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * NAME_FONT.getCapHeight());
|
h = Math.max(MANA_SYMBOL_SIZE + 2 * HEADER_PADDING, 2 * getCapHeight(NAME_FONT));
|
||||||
|
|
||||||
//draw mana cost for card
|
//draw mana cost for card
|
||||||
float manaCostWidth = 0;
|
float manaCostWidth = 0;
|
||||||
@@ -1154,7 +1172,7 @@ public class CardImageRenderer {
|
|||||||
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
|
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
|
||||||
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
|
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - MANA_SYMBOL_SIZE) / 2, MANA_SYMBOL_SIZE);
|
||||||
//draw "//" between two parts of mana cost
|
//draw "//" between two parts of mana cost
|
||||||
manaCostWidth += NAME_FONT.getBounds("//").width + HEADER_PADDING;
|
manaCostWidth += getBoundsWidth("//", NAME_FONT) + HEADER_PADDING;
|
||||||
g.drawText("//", NAME_FONT, Color.BLACK, x + w - manaCostWidth, y, w, h, false, Align.left, true);
|
g.drawText("//", NAME_FONT, Color.BLACK, x + w - manaCostWidth, y, w, h, false, Align.left, true);
|
||||||
}
|
}
|
||||||
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
|
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
|
||||||
@@ -1168,7 +1186,7 @@ public class CardImageRenderer {
|
|||||||
|
|
||||||
//draw type and set label for card
|
//draw type and set label for card
|
||||||
y += h;
|
y += h;
|
||||||
h = 2 * TYPE_FONT.getCapHeight();
|
h = 2 * getCapHeight(TYPE_FONT);
|
||||||
|
|
||||||
String set = state.getSetCode();
|
String set = state.getSetCode();
|
||||||
CardRarity rarity = state.getRarity();
|
CardRarity rarity = state.getRarity();
|
||||||
@@ -1189,7 +1207,7 @@ public class CardImageRenderer {
|
|||||||
fillColorBackground(g, colors, x, y, w, h);
|
fillColorBackground(g, colors, x, y, w, h);
|
||||||
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
|
g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h);
|
||||||
|
|
||||||
float padX = TEXT_FONT.getCapHeight() / 2;
|
float padX = getCapHeight(TEXT_FONT) / 2;
|
||||||
float padY = padX + Utils.scale(2); //add a little more vertical padding
|
float padY = padX + Utils.scale(2); //add a little more vertical padding
|
||||||
x += padX;
|
x += padX;
|
||||||
y += padY;
|
y += padY;
|
||||||
@@ -1202,8 +1220,8 @@ public class CardImageRenderer {
|
|||||||
float idWidth = 0;
|
float idWidth = 0;
|
||||||
if (canShow) {
|
if (canShow) {
|
||||||
String idText = CardDetailUtil.formatCardId(state);
|
String idText = CardDetailUtil.formatCardId(state);
|
||||||
g.drawText(idText, TYPE_FONT, idForeColor, x, y + TYPE_FONT.getCapHeight() / 2, w, h, false, Align.left, false);
|
g.drawText(idText, TYPE_FONT, idForeColor, x, y + getCapHeight(TYPE_FONT) / 2, w, h, false, Align.left, false);
|
||||||
idWidth = TYPE_FONT.getBounds(idText).width;
|
idWidth = getBoundsWidth(idText, TYPE_FONT);
|
||||||
}
|
}
|
||||||
|
|
||||||
String ptText = CardDetailUtil.formatPrimaryCharacteristic(state, canShow);
|
String ptText = CardDetailUtil.formatPrimaryCharacteristic(state, canShow);
|
||||||
@@ -1212,7 +1230,7 @@ public class CardImageRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextBounds bounds = cardTextRenderer.getBounds(ptText, PT_FONT);
|
TextBounds bounds = cardTextRenderer.getBounds(ptText, PT_FONT);
|
||||||
float padding = PT_FONT.getCapHeight() / 2;
|
float padding = getCapHeight(PT_FONT) / 2;
|
||||||
float boxWidth = Math.min(bounds.width + 2 * padding,
|
float boxWidth = Math.min(bounds.width + 2 * padding,
|
||||||
w - idWidth - padding); //prevent box overlapping ID
|
w - idWidth - padding); //prevent box overlapping ID
|
||||||
x += w - boxWidth;
|
x += w - boxWidth;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class ColorSetImage implements FImage {
|
|||||||
|
|
||||||
public ColorSetImage(ColorSet colorSet0) {
|
public ColorSetImage(ColorSet colorSet0) {
|
||||||
colorSet = colorSet0;
|
colorSet = colorSet0;
|
||||||
shardCount = colorSet.getOrderedShards().length;
|
shardCount = colorSet.getOrderedColors().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import forge.assets.*;
|
|||||||
import forge.assets.FSkinColor.Colors;
|
import forge.assets.FSkinColor.Colors;
|
||||||
import forge.card.*;
|
import forge.card.*;
|
||||||
import forge.card.CardRenderer.CardStackPosition;
|
import forge.card.CardRenderer.CardStackPosition;
|
||||||
import forge.card.mana.ManaCostShard;
|
|
||||||
import forge.deck.*;
|
import forge.deck.*;
|
||||||
import forge.deck.io.DeckPreferences;
|
import forge.deck.io.DeckPreferences;
|
||||||
import forge.game.card.CardView;
|
import forge.game.card.CardView;
|
||||||
@@ -37,7 +36,6 @@ import forge.util.Utils;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import static forge.assets.FSkin.getDefaultSkinFile;
|
import static forge.assets.FSkin.getDefaultSkinFile;
|
||||||
@@ -1100,10 +1098,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
deckSelectMode = true;
|
deckSelectMode = true;
|
||||||
deckProxy = (DeckProxy) item;
|
deckProxy = (DeckProxy) item;
|
||||||
}
|
}
|
||||||
if (item instanceof PaperCard) {
|
if (item instanceof PaperCard pc) {
|
||||||
showRanking = itemManager.getShowRanking() && FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_OVERLAY_DRAFT_RANKING);
|
showRanking = itemManager.getShowRanking() && FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_OVERLAY_DRAFT_RANKING);
|
||||||
if (showRanking) {
|
if (showRanking) {
|
||||||
double score = CardRanker.getRawScore((PaperCard) item);
|
double score = CardRanker.getRawScore(pc);
|
||||||
draftRank = score <= 0 ? 0 : score > 99 ? 99 : (int) Math.round(CardRanker.getRawScore((PaperCard) item));
|
draftRank = score <= 0 ? 0 : score > 99 ? 99 : (int) Math.round(CardRanker.getRawScore((PaperCard) item));
|
||||||
if (draftRank >= 90) {
|
if (draftRank >= 90) {
|
||||||
draftRankImage = FSkinImage.DRAFTRANK_S;
|
draftRankImage = FSkinImage.DRAFTRANK_S;
|
||||||
@@ -1115,10 +1113,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
|||||||
draftRankImage = FSkinImage.DRAFTRANK_C;
|
draftRankImage = FSkinImage.DRAFTRANK_C;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (((PaperCard) item).getMarkedColors() != null) {
|
if (pc.getMarkedColors() != null) {
|
||||||
markedColors = Arrays.stream(((PaperCard) item).getMarkedColors().getOrderedShards())
|
markedColors = pc.getMarkedColors().toString();
|
||||||
.map(ManaCostShard::toString)
|
|
||||||
.collect(Collectors.joining());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(fnPrice != null) {
|
if(fnPrice != null) {
|
||||||
|
|||||||
@@ -174,10 +174,13 @@ public class MatchController extends AbstractGuiGame {
|
|||||||
final VPlayerPanel playerPanel = new VPlayerPanel(p, isLocal || noHumans, players.size());
|
final VPlayerPanel playerPanel = new VPlayerPanel(p, isLocal || noHumans, players.size());
|
||||||
if (isLocal && !init) {
|
if (isLocal && !init) {
|
||||||
playerPanels.add(0, playerPanel); //ensure local player always first among player panels
|
playerPanels.add(0, playerPanel); //ensure local player always first among player panels
|
||||||
|
playerPanel.setBottomPlayer(true);
|
||||||
init = true;
|
init = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
playerPanels.add(playerPanel);
|
playerPanels.add(playerPanel);
|
||||||
|
if (playerPanel.equals(playerPanels.get(0)))
|
||||||
|
playerPanel.setBottomPlayer(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view = new MatchScreen(playerPanels);
|
view = new MatchScreen(playerPanels);
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
rotateCards180 = b0;
|
rotateCards180 = b0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float getCardStackOffset() {
|
||||||
|
if (Forge.isHorizontalTabLayout())
|
||||||
|
return 0.125f;
|
||||||
|
return CARD_STACK_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
protected void refreshCardPanels(Iterable<CardView> model) {
|
protected void refreshCardPanels(Iterable<CardView> model) {
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
@@ -78,7 +84,9 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setVisible(boolean b0) {
|
public void setVisible(boolean b0) {
|
||||||
if (isVisible() == b0) { return; }
|
if (isVisible() == b0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
super.setVisible(b0);
|
super.setVisible(b0);
|
||||||
if (b0) { //when zone becomes visible, ensure display area of panels is updated and panels layed out
|
if (b0) { //when zone becomes visible, ensure display area of panels is updated and panels layed out
|
||||||
for (CardAreaPanel pnl : cardPanels.get()) {
|
for (CardAreaPanel pnl : cardPanels.get()) {
|
||||||
@@ -146,7 +154,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
CardAreaPanel attachedPanel = attachedPanels.get(i);
|
CardAreaPanel attachedPanel = attachedPanels.get(i);
|
||||||
if (attachedPanel != null) {
|
if (attachedPanel != null) {
|
||||||
int count = addCards(attachedPanel, x, y, cardWidth, cardHeight);
|
int count = addCards(attachedPanel, x, y, cardWidth, cardHeight);
|
||||||
x += count * cardWidth * CARD_STACK_OFFSET;
|
x += count * cardWidth * getCardStackOffset();
|
||||||
totalCount += count;
|
totalCount += count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +164,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
cardPanel.setBounds(x, y, cardWidth, cardHeight);
|
cardPanel.setBounds(x, y, cardWidth, cardHeight);
|
||||||
|
|
||||||
if (cardPanel.getNextPanelInStack() != null) { //add next panel in stack if needed
|
if (cardPanel.getNextPanelInStack() != null) { //add next panel in stack if needed
|
||||||
x += cardWidth * CARD_STACK_OFFSET;
|
x += cardWidth * getCardStackOffset();
|
||||||
totalCount += addCards(cardPanel.getNextPanelInStack(), x, y, cardWidth, cardHeight);
|
totalCount += addCards(cardPanel.getNextPanelInStack(), x, y, cardWidth, cardHeight);
|
||||||
}
|
}
|
||||||
return totalCount + 1;
|
return totalCount + 1;
|
||||||
@@ -165,6 +173,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
protected float getCardWidth(float cardHeight) {
|
protected float getCardWidth(float cardHeight) {
|
||||||
return (cardHeight - 2 * FCardPanel.PADDING) / FCardPanel.ASPECT_RATIO + 2 * FCardPanel.PADDING; //ensure aspect ratio maintained after padding applied
|
return (cardHeight - 2 * FCardPanel.PADDING) / FCardPanel.ASPECT_RATIO + 2 * FCardPanel.PADDING; //ensure aspect ratio maintained after padding applied
|
||||||
}
|
}
|
||||||
|
|
||||||
protected float getCardHeight(float cardWidth) {
|
protected float getCardHeight(float cardWidth) {
|
||||||
return (cardWidth - 2 * FCardPanel.PADDING) * FCardPanel.ASPECT_RATIO + 2 * FCardPanel.PADDING; //ensure aspect ratio maintained after padding applied
|
return (cardWidth - 2 * FCardPanel.PADDING) * FCardPanel.ASPECT_RATIO + 2 * FCardPanel.PADDING; //ensure aspect ratio maintained after padding applied
|
||||||
}
|
}
|
||||||
@@ -181,7 +190,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
for (CardAreaPanel cardPanel : new ArrayList<>(cardPanels.get())) {
|
for (CardAreaPanel cardPanel : new ArrayList<>(cardPanels.get())) {
|
||||||
if (cardPanel != null) {
|
if (cardPanel != null) {
|
||||||
int count = addCards(cardPanel, x, y, cardWidth, cardHeight);
|
int count = addCards(cardPanel, x, y, cardWidth, cardHeight);
|
||||||
x += cardWidth + (count - 1) * cardWidth * CARD_STACK_OFFSET;
|
x += cardWidth + (count - 1) * cardWidth * getCardStackOffset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +206,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getActivateAction(int index) {
|
public String getActivateAction(int index) {
|
||||||
if(!GuiBase.isNetworkplay()) {
|
if (!GuiBase.isNetworkplay()) {
|
||||||
//causes lag on netplay client side, also index shouldn't be out of bounds
|
//causes lag on netplay client side, also index shouldn't be out of bounds
|
||||||
if (index >= 0 && index < orderedCards.get().size())
|
if (index >= 0 && index < orderedCards.get().size())
|
||||||
return MatchController.instance.getGameController().getActivateDescription(orderedCards.get().get(index));
|
return MatchController.instance.getGameController().getActivateDescription(orderedCards.get().get(index));
|
||||||
@@ -266,9 +275,11 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
public CardAreaPanel getAttachedToPanel() {
|
public CardAreaPanel getAttachedToPanel() {
|
||||||
return attachedToPanel;
|
return attachedToPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAttachedToPanel(final CardAreaPanel attachedToPanel0) {
|
public void setAttachedToPanel(final CardAreaPanel attachedToPanel0) {
|
||||||
attachedToPanel = attachedToPanel0;
|
attachedToPanel = attachedToPanel0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CardAreaPanel> getAttachedPanels() {
|
public List<CardAreaPanel> getAttachedPanels() {
|
||||||
if (attachedPanels == null) {
|
if (attachedPanels == null) {
|
||||||
attachedPanels = new ArrayList<>();
|
attachedPanels = new ArrayList<>();
|
||||||
@@ -278,15 +289,19 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
}
|
}
|
||||||
return attachedPanels;
|
return attachedPanels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardAreaPanel getNextPanelInStack() {
|
public CardAreaPanel getNextPanelInStack() {
|
||||||
return nextPanelInStack;
|
return nextPanelInStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNextPanelInStack(CardAreaPanel nextPanelInStack0) {
|
public void setNextPanelInStack(CardAreaPanel nextPanelInStack0) {
|
||||||
nextPanelInStack = nextPanelInStack0;
|
nextPanelInStack = nextPanelInStack0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardAreaPanel getPrevPanelInStack() {
|
public CardAreaPanel getPrevPanelInStack() {
|
||||||
return prevPanelInStack;
|
return prevPanelInStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPrevPanelInStack(CardAreaPanel prevPanelInStack0) {
|
public void setPrevPanelInStack(CardAreaPanel prevPanelInStack0) {
|
||||||
prevPanelInStack = prevPanelInStack0;
|
prevPanelInStack = prevPanelInStack0;
|
||||||
}
|
}
|
||||||
@@ -324,8 +339,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
attachedPanels.remove(CardAreaPanel.get(getAttachedto));
|
attachedPanels.remove(CardAreaPanel.get(getAttachedto));
|
||||||
setAttachedToPanel(null);
|
setAttachedToPanel(null);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
setAttachedToPanel(null);
|
setAttachedToPanel(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,7 +437,9 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void showZoom() {
|
public void showZoom() {
|
||||||
if (displayArea == null) { return; }
|
if (displayArea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final List<CardView> cards = displayArea.orderedCards.get();
|
final List<CardView> cards = displayArea.orderedCards.get();
|
||||||
CardZoom.show(cards, cards.indexOf(getCard()), displayArea);
|
CardZoom.show(cards, cards.indexOf(getCard()), displayArea);
|
||||||
@@ -444,10 +460,14 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<CardView> getOtherCardsToSelect(boolean selectOtherCardsInStack) {
|
private List<CardView> getOtherCardsToSelect(boolean selectOtherCardsInStack) {
|
||||||
if (!selectOtherCardsInStack) { return null; }
|
if (!selectOtherCardsInStack) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
//on double-tap select all other cards in stack if any
|
//on double-tap select all other cards in stack if any
|
||||||
if (prevPanelInStack == null && nextPanelInStack == null) { return null; }
|
if (prevPanelInStack == null && nextPanelInStack == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
List<CardView> cards = new ArrayList<>();
|
List<CardView> cards = new ArrayList<>();
|
||||||
|
|
||||||
@@ -490,7 +510,9 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
|
|
||||||
public Vector2 getTargetingArrowOrigin() {
|
public Vector2 getTargetingArrowOrigin() {
|
||||||
//don't show targeting arrow unless in display area that's visible
|
//don't show targeting arrow unless in display area that's visible
|
||||||
if (displayArea == null || !displayArea.isVisible()) { return null; }
|
if (displayArea == null || !displayArea.isVisible()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return getTargetingArrowOrigin(this, isTapped());
|
return getTargetingArrowOrigin(this, isTapped());
|
||||||
}
|
}
|
||||||
@@ -517,8 +539,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
|||||||
g.startRotateTransform(x + w / 2, y + h / 2, 180);
|
g.startRotateTransform(x + w / 2, y + h / 2, 180);
|
||||||
super.draw(g);
|
super.draw(g);
|
||||||
g.endTransform();
|
g.endTransform();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
super.draw(g);
|
super.draw(g);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.screens.match.views;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import forge.Forge;
|
||||||
import forge.game.card.CardView;
|
import forge.game.card.CardView;
|
||||||
import forge.game.card.CardView.CardStateView;
|
import forge.game.card.CardView.CardStateView;
|
||||||
import forge.game.player.PlayerView;
|
import forge.game.player.PlayerView;
|
||||||
@@ -33,6 +34,7 @@ public class VField extends FContainer {
|
|||||||
public boolean isFlipped() {
|
public boolean isFlipped() {
|
||||||
return flipped;
|
return flipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlipped(boolean flipped0) {
|
public void setFlipped(boolean flipped0) {
|
||||||
flipped = flipped0;
|
flipped = flipped0;
|
||||||
}
|
}
|
||||||
@@ -61,7 +63,9 @@ public class VField extends FContainer {
|
|||||||
clear();
|
clear();
|
||||||
|
|
||||||
Iterable<CardView> model = player.getBattlefield();
|
Iterable<CardView> model = player.getBattlefield();
|
||||||
if (model == null) { return; }
|
if (model == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (CardView card : model) {
|
for (CardView card : model) {
|
||||||
CardAreaPanel cardPanel = CardAreaPanel.get(card);
|
CardAreaPanel cardPanel = CardAreaPanel.get(card);
|
||||||
@@ -84,18 +88,15 @@ public class VField extends FContainer {
|
|||||||
if (!tryStackCard(card, creatures)) {
|
if (!tryStackCard(card, creatures)) {
|
||||||
creatures.add(card);
|
creatures.add(card);
|
||||||
}
|
}
|
||||||
}
|
} else if (details.isLand()) {
|
||||||
else if (details.isLand()) {
|
|
||||||
if (!tryStackCard(card, lands)) {
|
if (!tryStackCard(card, lands)) {
|
||||||
lands.add(card);
|
lands.add(card);
|
||||||
}
|
}
|
||||||
}
|
} else if (details.isArtifact() && (details.isContraption() || details.isAttraction())) {
|
||||||
else if (details.isArtifact() && (details.isContraption() || details.isAttraction())) {
|
|
||||||
if (contraptions == null)
|
if (contraptions == null)
|
||||||
contraptions = new ArrayList<>();
|
contraptions = new ArrayList<>();
|
||||||
contraptions.add(card); //Arrange these later.
|
contraptions.add(card); //Arrange these later.
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (!tryStackCard(card, otherPermanents)) {
|
if (!tryStackCard(card, otherPermanents)) {
|
||||||
otherPermanents.add(card);
|
otherPermanents.add(card);
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ public class VField extends FContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(contraptions != null) {
|
if (contraptions != null) {
|
||||||
contraptions = arrangeContraptions(contraptions);
|
contraptions = arrangeContraptions(contraptions);
|
||||||
otherPermanents.addAll(contraptions);
|
otherPermanents.addAll(contraptions);
|
||||||
}
|
}
|
||||||
@@ -111,8 +112,7 @@ public class VField extends FContainer {
|
|||||||
if (creatures.isEmpty()) {
|
if (creatures.isEmpty()) {
|
||||||
row1.refreshCardPanels(otherPermanents);
|
row1.refreshCardPanels(otherPermanents);
|
||||||
row2.refreshCardPanels(lands);
|
row2.refreshCardPanels(lands);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
row1.refreshCardPanels(creatures);
|
row1.refreshCardPanels(creatures);
|
||||||
lands.addAll(otherPermanents);
|
lands.addAll(otherPermanents);
|
||||||
row2.refreshCardPanels(lands);
|
row2.refreshCardPanels(lands);
|
||||||
@@ -173,13 +173,14 @@ public class VField extends FContainer {
|
|||||||
TreeSet<CardView> row = new TreeSet<>((c1, c2) -> {
|
TreeSet<CardView> row = new TreeSet<>((c1, c2) -> {
|
||||||
//Order is sprocket-less cards, then sprocket 1, sprocket 2, sprocket 3, and finally attractions.
|
//Order is sprocket-less cards, then sprocket 1, sprocket 2, sprocket 3, and finally attractions.
|
||||||
int sprocket1 = c1.getSprocket(), sprocket2 = c2.getSprocket();
|
int sprocket1 = c1.getSprocket(), sprocket2 = c2.getSprocket();
|
||||||
if(sprocket1 == 0 && c1.getCurrentState().isAttraction())
|
if (sprocket1 == 0 && c1.getCurrentState().isAttraction())
|
||||||
sprocket1 = 4;
|
sprocket1 = 4;
|
||||||
if(sprocket2 == 0 && c2.getCurrentState().isAttraction())
|
if (sprocket2 == 0 && c2.getCurrentState().isAttraction())
|
||||||
sprocket2 = 4;
|
sprocket2 = 4;
|
||||||
return sprocket1 - sprocket2;
|
return sprocket1 - sprocket2;
|
||||||
});
|
});
|
||||||
outer: for (CardView card : contraptions) {
|
outer:
|
||||||
|
for (CardView card : contraptions) {
|
||||||
if (card.hasCardAttachments()) {
|
if (card.hasCardAttachments()) {
|
||||||
row.add(card); //Don't stack contraptions or attractions with attachments.
|
row.add(card); //Don't stack contraptions or attractions with attachments.
|
||||||
continue;
|
continue;
|
||||||
@@ -187,7 +188,7 @@ public class VField extends FContainer {
|
|||||||
if (card.getCurrentState().isAttraction()) {
|
if (card.getCurrentState().isAttraction()) {
|
||||||
//Stack attractions with other attractions.
|
//Stack attractions with other attractions.
|
||||||
for (CardView c : row) {
|
for (CardView c : row) {
|
||||||
if(c.getCurrentState().isAttraction() && !c.hasCardAttachments()) {
|
if (c.getCurrentState().isAttraction() && !c.hasCardAttachments()) {
|
||||||
stackOnto(card, c);
|
stackOnto(card, c);
|
||||||
continue outer;
|
continue outer;
|
||||||
}
|
}
|
||||||
@@ -240,13 +241,17 @@ public class VField extends FContainer {
|
|||||||
if (flipped) {
|
if (flipped) {
|
||||||
y1 = cardSize;
|
y1 = cardSize;
|
||||||
y2 = 0;
|
y2 = 0;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
y1 = 0;
|
y1 = 0;
|
||||||
y2 = cardSize;
|
y2 = cardSize;
|
||||||
}
|
}
|
||||||
row1.setBounds(0, y1, width-fieldModifier, cardSize);
|
if (Forge.isHorizontalTabLayout()) {
|
||||||
row2.setBounds(0, y2, (width - commandZoneWidth)-fieldModifier, cardSize);
|
row1.setBounds(0, y1, width, cardSize);
|
||||||
|
row2.setBounds(0, y2, width, cardSize);
|
||||||
|
} else {
|
||||||
|
row1.setBounds(0, y1, width - fieldModifier, cardSize);
|
||||||
|
row2.setBounds(0, y2, (width - commandZoneWidth) - fieldModifier, cardSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FieldRow extends VCardDisplayArea {
|
public class FieldRow extends VCardDisplayArea {
|
||||||
@@ -270,13 +275,14 @@ public class VField extends FContainer {
|
|||||||
public void setNextSelected(int val) {
|
public void setNextSelected(int val) {
|
||||||
this.selected++;
|
this.selected++;
|
||||||
if (this.selected >= this.getChildCount())
|
if (this.selected >= this.getChildCount())
|
||||||
this.selected = this.getChildCount()-1;
|
this.selected = this.getChildCount() - 1;
|
||||||
if (this.selectedChild != null)
|
if (this.selectedChild != null)
|
||||||
this.selectedChild.setHovered(false);
|
this.selectedChild.setHovered(false);
|
||||||
this.selectedChild = getChildAt(this.selected);
|
this.selectedChild = getChildAt(this.selected);
|
||||||
this.selectedChild.setHovered(true);
|
this.selectedChild.setHovered(true);
|
||||||
MatchScreen.setPotentialListener(Arrays.asList(this.selectedChild));
|
MatchScreen.setPotentialListener(Arrays.asList(this.selectedChild));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectCurrent() {
|
public void selectCurrent() {
|
||||||
if (this.selectedChild != null) {
|
if (this.selectedChild != null) {
|
||||||
this.selectedChild.setHovered(true);
|
this.selectedChild.setHovered(true);
|
||||||
@@ -285,6 +291,7 @@ public class VField extends FContainer {
|
|||||||
this.setNextSelected(1);
|
this.setNextSelected(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unselectCurrent() {
|
public void unselectCurrent() {
|
||||||
if (this.selectedChild != null) {
|
if (this.selectedChild != null) {
|
||||||
this.selectedChild.setHovered(false);
|
this.selectedChild.setHovered(false);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public class VManaPool extends VDisplayArea {
|
|||||||
return FSkinColor.get(Colors.ADV_CLR_TEXT);
|
return FSkinColor.get(Colors.ADV_CLR_TEXT);
|
||||||
return FSkinColor.get(Colors.CLR_TEXT);
|
return FSkinColor.get(Colors.CLR_TEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final FSkinFont FONT = FSkinFont.get(16);
|
private static final FSkinFont FONT = FSkinFont.get(16);
|
||||||
|
|
||||||
private final PlayerView player;
|
private final PlayerView player;
|
||||||
@@ -37,7 +38,7 @@ public class VManaPool extends VDisplayArea {
|
|||||||
public VManaPool(PlayerView player0) {
|
public VManaPool(PlayerView player0) {
|
||||||
player = player0;
|
player = player0;
|
||||||
|
|
||||||
addManaLabel(FSkinProp.IMG_MANA_COLORLESS, (byte)ManaAtom.COLORLESS);
|
addManaLabel(FSkinProp.IMG_MANA_COLORLESS, (byte) ManaAtom.COLORLESS);
|
||||||
addManaLabel(FSkinProp.IMG_MANA_W, MagicColor.WHITE);
|
addManaLabel(FSkinProp.IMG_MANA_W, MagicColor.WHITE);
|
||||||
addManaLabel(FSkinProp.IMG_MANA_U, MagicColor.BLUE);
|
addManaLabel(FSkinProp.IMG_MANA_U, MagicColor.BLUE);
|
||||||
addManaLabel(FSkinProp.IMG_MANA_B, MagicColor.BLACK);
|
addManaLabel(FSkinProp.IMG_MANA_B, MagicColor.BLACK);
|
||||||
@@ -69,7 +70,7 @@ public class VManaPool extends VDisplayArea {
|
|||||||
float x = 0;
|
float x = 0;
|
||||||
float y = 0;
|
float y = 0;
|
||||||
|
|
||||||
if (Forge.isLandscapeMode()) {
|
if (Forge.isLandscapeMode() && !Forge.altZoneTabs) {
|
||||||
float labelWidth = visibleWidth / 2;
|
float labelWidth = visibleWidth / 2;
|
||||||
float labelHeight = visibleHeight / 3;
|
float labelHeight = visibleHeight / 3;
|
||||||
|
|
||||||
@@ -79,13 +80,11 @@ public class VManaPool extends VDisplayArea {
|
|||||||
if (++count % 2 == 0) {
|
if (++count % 2 == 0) {
|
||||||
x = 0;
|
x = 0;
|
||||||
y += labelHeight;
|
y += labelHeight;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
x += labelWidth;
|
x += labelWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
float labelWidth = visibleWidth / manaLabels.size();
|
float labelWidth = visibleWidth / manaLabels.size();
|
||||||
float labelHeight = visibleHeight;
|
float labelHeight = visibleHeight;
|
||||||
|
|
||||||
@@ -113,14 +112,16 @@ public class VManaPool extends VDisplayArea {
|
|||||||
activate();
|
activate();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void activate() {
|
public void activate() {
|
||||||
if(!(MatchController.instance.getGameController() instanceof PlayerControllerHuman))
|
if (!(MatchController.instance.getGameController() instanceof PlayerControllerHuman))
|
||||||
return;
|
return;
|
||||||
PlayerControllerHuman controller = (PlayerControllerHuman) MatchController.instance.getGameController();
|
PlayerControllerHuman controller = (PlayerControllerHuman) MatchController.instance.getGameController();
|
||||||
final Input ipm = controller.getInputQueue().getInput();
|
final Input ipm = controller.getInputQueue().getInput();
|
||||||
if(ipm instanceof InputPayMana && ipm.getOwner().equals(player))
|
if (ipm instanceof InputPayMana && ipm.getOwner().equals(player))
|
||||||
controller.useMana(colorCode);
|
controller.useMana(colorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean flick(float x, float y) {
|
public boolean flick(float x, float y) {
|
||||||
if (player.isLobbyPlayer(GamePlayerUtil.getGuiPlayer())) {
|
if (player.isLobbyPlayer(GamePlayerUtil.getGuiPlayer())) {
|
||||||
@@ -146,17 +147,18 @@ public class VManaPool extends VDisplayArea {
|
|||||||
if (h > maxImageHeight) {
|
if (h > maxImageHeight) {
|
||||||
h /= 2;
|
h /= 2;
|
||||||
}
|
}
|
||||||
float w = image.getWidth() * h / image.getHeight();
|
float modifier = Forge.isHorizontalTabLayout() ? 0.7f : 1f;
|
||||||
|
float w = image.getWidth() * h * modifier / image.getHeight();
|
||||||
while (w > getWidth()) {
|
while (w > getWidth()) {
|
||||||
h /= 2;
|
h /= 2;
|
||||||
w = image.getWidth() * h / image.getHeight();
|
w = image.getWidth() * h * modifier / image.getHeight();
|
||||||
}
|
}
|
||||||
float x = (getWidth() - w) / 2;
|
float x = (getWidth() - w) / 2;
|
||||||
float y = gapY + (maxImageHeight - h) / 2;
|
float y = gapY + (maxImageHeight - h) / 2;
|
||||||
|
|
||||||
if (isHovered())
|
if (isHovered())
|
||||||
g.fillRect(FSkinColor.getStandardColor(50, 200, 150).alphaColor(0.3f), 0, 0, getWidth(), getHeight());
|
g.fillRect(FSkinColor.getStandardColor(50, 200, 150).alphaColor(0.3f), 0, 0, getWidth(), getHeight());
|
||||||
g.drawImage(image, x, y, w, h);
|
g.drawImage(image, x, y, w, Forge.isHorizontalTabLayout() ? w : h);
|
||||||
|
|
||||||
x = 0;
|
x = 0;
|
||||||
y += h + gapY;
|
y += h + gapY;
|
||||||
|
|||||||
@@ -38,21 +38,31 @@ public class VPlayerPanel extends FContainer {
|
|||||||
private static final FSkinFont LIFE_FONT_ALT = FSkinFont.get(22);
|
private static final FSkinFont LIFE_FONT_ALT = FSkinFont.get(22);
|
||||||
private static final FSkinFont INFO_FONT = FSkinFont.get(12);
|
private static final FSkinFont INFO_FONT = FSkinFont.get(12);
|
||||||
private static final FSkinFont INFO2_FONT = FSkinFont.get(14);
|
private static final FSkinFont INFO2_FONT = FSkinFont.get(14);
|
||||||
|
|
||||||
private static FSkinColor getInfoForeColor() {
|
private static FSkinColor getInfoForeColor() {
|
||||||
if (Forge.isMobileAdventureMode)
|
if (Forge.isMobileAdventureMode)
|
||||||
return FSkinColor.get(Colors.ADV_CLR_TEXT);
|
return FSkinColor.get(Colors.ADV_CLR_TEXT);
|
||||||
return FSkinColor.get(Colors.CLR_TEXT);
|
return FSkinColor.get(Colors.CLR_TEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FSkinColor getDisplayAreaBackColor() {
|
private static FSkinColor getDisplayAreaBackColor() {
|
||||||
if (Forge.isMobileAdventureMode)
|
if (Forge.isMobileAdventureMode)
|
||||||
return FSkinColor.get(Colors.ADV_CLR_INACTIVE).alphaColor(0.5f);
|
return FSkinColor.get(Colors.ADV_CLR_INACTIVE).alphaColor(0.5f);
|
||||||
return FSkinColor.get(Colors.CLR_INACTIVE).alphaColor(0.5f);
|
return FSkinColor.get(Colors.CLR_INACTIVE).alphaColor(0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static FSkinColor getAltDisplayAreaBackColor() {
|
||||||
|
if (Forge.isMobileAdventureMode)
|
||||||
|
return FSkinColor.get(Colors.ADV_CLR_PHASE_INACTIVE_ENABLED).alphaColor(0.3f);
|
||||||
|
return FSkinColor.get(Colors.CLR_PHASE_INACTIVE_ENABLED).alphaColor(0.3f);
|
||||||
|
}
|
||||||
|
|
||||||
private static FSkinColor getDeliriumHighlight() {
|
private static FSkinColor getDeliriumHighlight() {
|
||||||
if (Forge.isMobileAdventureMode)
|
if (Forge.isMobileAdventureMode)
|
||||||
return FSkinColor.get(Colors.ADV_CLR_PHASE_ACTIVE_ENABLED).alphaColor(0.5f);
|
return FSkinColor.get(Colors.ADV_CLR_PHASE_ACTIVE_ENABLED).alphaColor(0.5f);
|
||||||
return FSkinColor.get(Colors.CLR_PHASE_ACTIVE_ENABLED).alphaColor(0.5f);
|
return FSkinColor.get(Colors.CLR_PHASE_ACTIVE_ENABLED).alphaColor(0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final float INFO_TAB_PADDING_X = Utils.scale(2);
|
private static final float INFO_TAB_PADDING_X = Utils.scale(2);
|
||||||
private static final float INFO_TAB_PADDING_Y = Utils.scale(2);
|
private static final float INFO_TAB_PADDING_Y = Utils.scale(2);
|
||||||
|
|
||||||
@@ -79,11 +89,13 @@ public class VPlayerPanel extends FContainer {
|
|||||||
private boolean forMultiPlayer = false;
|
private boolean forMultiPlayer = false;
|
||||||
public int adjustHeight = 1;
|
public int adjustHeight = 1;
|
||||||
private int selected = 0;
|
private int selected = 0;
|
||||||
|
private boolean isBottomPlayer = false;
|
||||||
|
|
||||||
public VPlayerPanel(PlayerView player0, boolean showHand, int playerCount) {
|
public VPlayerPanel(PlayerView player0, boolean showHand, int playerCount) {
|
||||||
player = player0;
|
player = player0;
|
||||||
phaseIndicator = add(new VPhaseIndicator());
|
phaseIndicator = add(new VPhaseIndicator());
|
||||||
|
|
||||||
if(playerCount > 2){
|
if (playerCount > 2) {
|
||||||
forMultiPlayer = true;
|
forMultiPlayer = true;
|
||||||
avatarHeight *= 0.5f;
|
avatarHeight *= 0.5f;
|
||||||
//displayAreaHeightFactor *= 0.7f;
|
//displayAreaHeightFactor *= 0.7f;
|
||||||
@@ -116,6 +128,10 @@ public class VPlayerPanel extends FContainer {
|
|||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBottomPlayer(boolean val) {
|
||||||
|
isBottomPlayer = val;
|
||||||
|
}
|
||||||
|
|
||||||
public void addZoneDisplay(ZoneType zoneType) {
|
public void addZoneDisplay(ZoneType zoneType) {
|
||||||
VZoneDisplay zoneDisplay = add(new VZoneDisplay(player, zoneType));
|
VZoneDisplay zoneDisplay = add(new VZoneDisplay(player, zoneType));
|
||||||
InfoTabZone zoneTab = add(new InfoTabZone(zoneDisplay, zoneType));
|
InfoTabZone zoneTab = add(new InfoTabZone(zoneDisplay, zoneType));
|
||||||
@@ -124,22 +140,22 @@ public class VPlayerPanel extends FContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static FSkinImage iconFromZone(ZoneType zoneType) {
|
public static FSkinImage iconFromZone(ZoneType zoneType) {
|
||||||
switch (zoneType) {
|
return switch (zoneType) {
|
||||||
case Hand: return Forge.hdbuttons ? FSkinImage.HDHAND : FSkinImage.HAND;
|
case Hand -> Forge.hdbuttons ? FSkinImage.HDHAND : FSkinImage.HAND;
|
||||||
case Library: return Forge.hdbuttons ? FSkinImage.HDLIBRARY : FSkinImage.LIBRARY;
|
case Library -> Forge.hdbuttons ? FSkinImage.HDLIBRARY : FSkinImage.LIBRARY;
|
||||||
case Graveyard: return Forge.hdbuttons ? FSkinImage.HDGRAVEYARD : FSkinImage.GRAVEYARD;
|
case Graveyard -> Forge.hdbuttons ? FSkinImage.HDGRAVEYARD : FSkinImage.GRAVEYARD;
|
||||||
case Exile: return Forge.hdbuttons ? FSkinImage.HDEXILE : FSkinImage.EXILE;
|
case Exile -> Forge.hdbuttons ? FSkinImage.HDEXILE : FSkinImage.EXILE;
|
||||||
case Sideboard: return Forge.hdbuttons ? FSkinImage.HDSIDEBOARD :FSkinImage.SIDEBOARD;
|
case Sideboard -> Forge.hdbuttons ? FSkinImage.HDSIDEBOARD : FSkinImage.SIDEBOARD;
|
||||||
case Flashback: return Forge.hdbuttons ? FSkinImage.HDFLASHBACK :FSkinImage.FLASHBACK;
|
case Flashback -> Forge.hdbuttons ? FSkinImage.HDFLASHBACK : FSkinImage.FLASHBACK;
|
||||||
case Command: return FSkinImage.COMMAND;
|
case Command -> FSkinImage.COMMAND;
|
||||||
case PlanarDeck: return FSkinImage.PLANAR;
|
case PlanarDeck -> FSkinImage.PLANAR;
|
||||||
case SchemeDeck: return FSkinImage.SCHEME;
|
case SchemeDeck -> FSkinImage.SCHEME;
|
||||||
case AttractionDeck: return FSkinImage.ATTRACTION;
|
case AttractionDeck -> FSkinImage.ATTRACTION;
|
||||||
case ContraptionDeck: return FSkinImage.CONTRAPTION;
|
case ContraptionDeck -> FSkinImage.CONTRAPTION;
|
||||||
case Ante: return FSkinImage.ANTE;
|
case Ante -> FSkinImage.ANTE;
|
||||||
case Junkyard: return FSkinImage.JUNKYARD;
|
case Junkyard -> FSkinImage.JUNKYARD;
|
||||||
default: return FSkinImage.HDLIBRARY;
|
default -> FSkinImage.HDLIBRARY;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterable<InfoTab> getTabs() {
|
public Iterable<InfoTab> getTabs() {
|
||||||
@@ -151,12 +167,12 @@ public class VPlayerPanel extends FContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void resetZoneTabs() {
|
public void resetZoneTabs() {
|
||||||
for(InfoTab tab : tabs)
|
for (InfoTab tab : tabs)
|
||||||
tab.reset();
|
tab.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedZone(ZoneType zoneType) {
|
public void setSelectedZone(ZoneType zoneType) {
|
||||||
if(zoneTabs.containsKey(zoneType))
|
if (zoneTabs.containsKey(zoneType))
|
||||||
setSelectedTab(zoneTabs.get(zoneType));
|
setSelectedTab(zoneTabs.get(zoneType));
|
||||||
else {
|
else {
|
||||||
extraTab.setActiveZone(zoneType);
|
extraTab.setActiveZone(zoneType);
|
||||||
@@ -189,6 +205,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
MatchController.getView().revalidate();
|
MatchController.getView().revalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNextSelectedTab(boolean change) {
|
public void setNextSelectedTab(boolean change) {
|
||||||
if (change) {
|
if (change) {
|
||||||
if (selectedTab != null) {
|
if (selectedTab != null) {
|
||||||
@@ -207,13 +224,13 @@ public class VPlayerPanel extends FContainer {
|
|||||||
int numExtraTabs = extraTab.displayAreas.size();
|
int numExtraTabs = extraTab.displayAreas.size();
|
||||||
if (selected < 0 || selected >= numTabs + numExtraTabs)
|
if (selected < 0 || selected >= numTabs + numExtraTabs)
|
||||||
selected = 0;
|
selected = 0;
|
||||||
if(selected >= numTabs) {
|
if (selected >= numTabs) {
|
||||||
extraTab.setActiveZoneByIndex(selected - numTabs);
|
extraTab.setActiveZoneByIndex(selected - numTabs);
|
||||||
setSelectedTab(extraTab);
|
setSelectedTab(extraTab);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
setSelectedTab(tabs.get(selected));
|
setSelectedTab(tabs.get(selected));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeSelectedTab() {
|
public void closeSelectedTab() {
|
||||||
if (selectedTab != null) {
|
if (selectedTab != null) {
|
||||||
selectedTab.setDisplayVisible(false);
|
selectedTab.setDisplayVisible(false);
|
||||||
@@ -234,6 +251,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
public boolean isFlipped() {
|
public boolean isFlipped() {
|
||||||
return field.isFlipped();
|
return field.isFlipped();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlipped(boolean flipped0) {
|
public void setFlipped(boolean flipped0) {
|
||||||
field.setFlipped(flipped0);
|
field.setFlipped(flipped0);
|
||||||
}
|
}
|
||||||
@@ -254,9 +272,11 @@ public class VPlayerPanel extends FContainer {
|
|||||||
public VField getField() {
|
public VField getField() {
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VField.FieldRow getSelectedRow() {
|
public VField.FieldRow getSelectedRow() {
|
||||||
return selectedRow;
|
return selectedRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void switchRow() {
|
public void switchRow() {
|
||||||
if (selectedRow == field.getRow1())
|
if (selectedRow == field.getRow1())
|
||||||
selectedRow = field.getRow2();
|
selectedRow = field.getRow2();
|
||||||
@@ -289,29 +309,22 @@ public class VPlayerPanel extends FContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateZone(ZoneType zoneType) {
|
public void updateZone(ZoneType zoneType) {
|
||||||
if (zoneType == ZoneType.Battlefield ) {
|
if (zoneType == ZoneType.Battlefield) {
|
||||||
field.update(true);
|
field.update(true);
|
||||||
}
|
} else if (zoneType == ZoneType.Command) {
|
||||||
else if (zoneType == ZoneType.Command) {
|
|
||||||
commandZone.update();
|
commandZone.update();
|
||||||
}
|
if (selectedTab != null && Forge.isHorizontalTabLayout())
|
||||||
else {
|
updateTabLayout(initW, initH);
|
||||||
if(zoneTabs.containsKey(zoneType))
|
} else {
|
||||||
|
if (zoneTabs.containsKey(zoneType))
|
||||||
zoneTabs.get(zoneType).update();
|
zoneTabs.get(zoneType).update();
|
||||||
else if(EXTRA_ZONES.contains(zoneType)) {
|
else if (EXTRA_ZONES.contains(zoneType)) {
|
||||||
extraTab.update(zoneType);
|
extraTab.update(zoneType);
|
||||||
}
|
}
|
||||||
|
|
||||||
//update flashback zone when graveyard, library, exile, or stack zones updated
|
//update flashback zone when graveyard, library, exile, or stack zones updated
|
||||||
switch (zoneType) {
|
switch (zoneType) {
|
||||||
case Graveyard:
|
case Graveyard, Library, Exile, Stack -> zoneTabs.get(ZoneType.Flashback).update();
|
||||||
case Library:
|
|
||||||
case Exile:
|
|
||||||
case Stack:
|
|
||||||
zoneTabs.get(ZoneType.Flashback).update();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +340,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
float x = avatarHeight;
|
float x = avatarHeight;
|
||||||
float w = width - avatarHeight;
|
float w = width - avatarHeight;
|
||||||
float indicatorScale = 1f;
|
float indicatorScale = 1f;
|
||||||
if(avatarHeight<VAvatar.HEIGHT){
|
if (avatarHeight < VAvatar.HEIGHT) {
|
||||||
indicatorScale = 0.6f;
|
indicatorScale = 0.6f;
|
||||||
}
|
}
|
||||||
float h = phaseIndicator.getPreferredHeight(w) * indicatorScale;
|
float h = phaseIndicator.getPreferredHeight(w) * indicatorScale;
|
||||||
@@ -366,8 +379,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
commandZone.setBounds(width - commandZoneWidth, y - commandZoneHeight, commandZoneWidth, commandZoneHeight);
|
commandZone.setBounds(width - commandZoneWidth, y - commandZoneHeight, commandZoneWidth, commandZoneHeight);
|
||||||
|
|
||||||
field.setCommandZoneWidth(commandZoneWidth + 1); //ensure second row of field accounts for width of command zone and its border
|
field.setCommandZoneWidth(commandZoneWidth + 1); //ensure second row of field accounts for width of command zone and its border
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
field.setCommandZoneWidth(0);
|
field.setCommandZoneWidth(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,18 +395,23 @@ public class VPlayerPanel extends FContainer {
|
|||||||
field.setFieldModifier(0);
|
field.setFieldModifier(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float initW, initH, commandZoneWidth, commandZoneCount, avatarWidth, prefWidth;
|
||||||
|
private final float mod = 2.4f;
|
||||||
|
|
||||||
private void doLandscapeLayout(float width, float height) {
|
private void doLandscapeLayout(float width, float height) {
|
||||||
|
initW = width;
|
||||||
|
initH = height;
|
||||||
float x = 0;
|
float x = 0;
|
||||||
float y = 0;
|
float y = 0;
|
||||||
float yAlt = 0;
|
float yAlt = 0;
|
||||||
float avatarWidth = Forge.altZoneTabs ? avatar.getWidth() : 0;
|
avatarWidth = Forge.altZoneTabs ? avatar.getWidth() : 0;
|
||||||
avatar.setPosition(x, y);
|
avatar.setPosition(x, y);
|
||||||
y += avatar.getHeight();
|
y += avatar.getHeight();
|
||||||
|
|
||||||
lblLife.setBounds(x, (Forge.altPlayerLayout && !Forge.altZoneTabs) ? 0 : y, avatar.getWidth(), (Forge.altPlayerLayout && !Forge.altZoneTabs) ? INFO_FONT.getLineHeight() : Forge.altZoneTabs ? LIFE_FONT_ALT.getLineHeight() : LIFE_FONT.getLineHeight());
|
lblLife.setBounds(x, (Forge.altPlayerLayout && !Forge.altZoneTabs) ? 0 : y, avatar.getWidth(), (Forge.altPlayerLayout && !Forge.altZoneTabs) ? INFO_FONT.getLineHeight() : Forge.altZoneTabs ? LIFE_FONT_ALT.getLineHeight() : LIFE_FONT.getLineHeight());
|
||||||
if (Forge.altPlayerLayout && !Forge.altZoneTabs) {
|
if (Forge.altPlayerLayout && !Forge.altZoneTabs) {
|
||||||
if (adjustHeight > 2)
|
if (adjustHeight > 2)
|
||||||
y += INFO_FONT.getLineHeight()/2;
|
y += INFO_FONT.getLineHeight() / 2;
|
||||||
} else
|
} else
|
||||||
y += lblLife.getHeight();
|
y += lblLife.getHeight();
|
||||||
|
|
||||||
@@ -412,12 +429,16 @@ public class VPlayerPanel extends FContainer {
|
|||||||
tab.setBounds(x, y, infoTabWidth, infoTabHeight);
|
tab.setBounds(x, y, infoTabWidth, infoTabHeight);
|
||||||
y += infoTabHeight;
|
y += infoTabHeight;
|
||||||
} else {
|
} else {
|
||||||
tab.setBounds(x+width-avatarWidth, yAlt, avatarWidth, infoTabHeightAlt);
|
tab.setBounds(x + width - avatarWidth, yAlt, avatarWidth, infoTabHeightAlt);
|
||||||
yAlt += infoTabHeightAlt;
|
yAlt += infoTabHeightAlt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x = avatar.getRight();
|
updateTabLayout(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTabLayout(float width, float height) {
|
||||||
|
float x = avatar.getRight();
|
||||||
phaseIndicator.resetFont();
|
phaseIndicator.resetFont();
|
||||||
phaseIndicator.setBounds(x, 0, avatar.getWidth() * 0.6f, height);
|
phaseIndicator.setBounds(x, 0, avatar.getWidth() * 0.6f, height);
|
||||||
x += phaseIndicator.getWidth();
|
x += phaseIndicator.getWidth();
|
||||||
@@ -429,38 +450,77 @@ public class VPlayerPanel extends FContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//account for command zone if needed
|
//account for command zone if needed
|
||||||
int commandZoneCount = commandZone.getCount();
|
commandZoneWidth = 0f;
|
||||||
|
commandZoneCount = commandZone.getCount();
|
||||||
if (commandZoneCount > 0) {
|
if (commandZoneCount > 0) {
|
||||||
float commandZoneHeight = height / 2;
|
float commandZoneHeight = height / 2;
|
||||||
float commandZoneWidth = Math.min(commandZoneCount, 2) * commandZone.getCardWidth(commandZoneHeight);
|
float minCommandCards = Forge.isHorizontalTabLayout() ? 5 : 2;
|
||||||
commandZone.setBounds(x + fieldWidth - commandZoneWidth, height - commandZoneHeight, commandZoneWidth, commandZoneHeight);
|
commandZoneWidth = Math.min(commandZoneCount, minCommandCards) * commandZone.getCardWidth(commandZoneHeight);
|
||||||
|
float x2 = x + fieldWidth - commandZoneWidth;
|
||||||
|
float y2 = height - commandZoneHeight;
|
||||||
|
if (Forge.isHorizontalTabLayout()) {
|
||||||
|
x2 = width - avatarWidth - commandZoneWidth;
|
||||||
|
y2 = 0;
|
||||||
|
}
|
||||||
|
commandZone.setBounds(x2, y2, commandZoneWidth, commandZoneHeight);
|
||||||
if (isFlipped()) { //flip across x-axis if needed
|
if (isFlipped()) { //flip across x-axis if needed
|
||||||
commandZone.setTop(height - commandZone.getBottom());
|
commandZone.setTop(height - commandZone.getBottom());
|
||||||
}
|
}
|
||||||
|
|
||||||
field.setCommandZoneWidth(commandZoneWidth + 1); //ensure second row of field accounts for width of command zone and its border
|
field.setCommandZoneWidth(commandZoneWidth + 1); //ensure second row of field accounts for width of command zone and its border
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
field.setCommandZoneWidth(0);
|
field.setCommandZoneWidth(0);
|
||||||
}
|
}
|
||||||
|
prefWidth = width / mod;
|
||||||
|
if (Forge.isHorizontalTabLayout()) {
|
||||||
|
field.setBounds(x, 0, width - avatarWidth, height);
|
||||||
|
field.getRow1().setWidth(width - (commandZoneCount > 0 ? commandZone.getWidth() + (avatarWidth * commandZoneCount) : avatarWidth));
|
||||||
|
field.getRow2().setWidth(width - (avatarWidth / 4f) - (selectedTab == null ? 0 : selectedTab.getIdealWidth(prefWidth) + 1) - avatarWidth * mod);
|
||||||
|
} else
|
||||||
|
field.setBounds(x, 0, fieldWidth, height);
|
||||||
|
|
||||||
field.setBounds(x, 0, fieldWidth, height);
|
x = width - displayAreaWidth - avatarWidth;
|
||||||
|
|
||||||
x = width - displayAreaWidth-avatarWidth;
|
|
||||||
for (InfoTab tab : tabs) {
|
for (InfoTab tab : tabs) {
|
||||||
tab.setDisplayBounds(x, 0, displayAreaWidth, height);
|
if (Forge.isHorizontalTabLayout()) {
|
||||||
|
float w = tab.getIdealWidth(prefWidth);
|
||||||
|
float h = height / 2f;
|
||||||
|
tab.setDisplayBounds(width - w - avatarWidth, isBottomPlayer ? h : 0, w, h);
|
||||||
|
} else {
|
||||||
|
tab.setDisplayBounds(x, 0, displayAreaWidth, height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Forge.altZoneTabs)
|
if (!Forge.altZoneTabs) {
|
||||||
field.setFieldModifier(0);
|
field.setFieldModifier(0);
|
||||||
else
|
} else {
|
||||||
field.setFieldModifier(avatarWidth/16);
|
if (!"Horizontal".equalsIgnoreCase(Forge.altZoneTabMode))
|
||||||
|
field.setFieldModifier(avatarWidth / 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawOverlay(Graphics g) {
|
||||||
|
if (Forge.isHorizontalTabLayout()) {
|
||||||
|
InfoTab infoTab = selectedTab;
|
||||||
|
if (infoTab != null) {
|
||||||
|
VDisplayArea selectedDisplayArea = infoTab.getDisplayArea();
|
||||||
|
if (selectedDisplayArea != null && selectedDisplayArea.getCount() > 0) {
|
||||||
|
float scale = avatarWidth / 2f;
|
||||||
|
float x = selectedDisplayArea.getLeft();
|
||||||
|
float y = selectedDisplayArea.getBottom() - scale;
|
||||||
|
g.fillRect(getAltDisplayAreaBackColor(), x, y, scale, scale);
|
||||||
|
infoTab.icon.draw(g, x, y, scale, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.drawOverlay(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawBackground(Graphics g) {
|
public void drawBackground(Graphics g) {
|
||||||
float y;
|
float y;
|
||||||
InfoTab infoTab = selectedTab;
|
InfoTab infoTab = selectedTab;
|
||||||
|
float pad = Forge.isHorizontalTabLayout() ? avatarWidth / 16f : 0f;
|
||||||
if (infoTab != null) { //draw background and border for selected zone if needed
|
if (infoTab != null) { //draw background and border for selected zone if needed
|
||||||
VDisplayArea selectedDisplayArea = infoTab.getDisplayArea();
|
VDisplayArea selectedDisplayArea = infoTab.getDisplayArea();
|
||||||
float x = selectedDisplayArea == null ? 0 : selectedDisplayArea.getLeft();
|
float x = selectedDisplayArea == null ? 0 : selectedDisplayArea.getLeft();
|
||||||
@@ -468,12 +528,13 @@ public class VPlayerPanel extends FContainer {
|
|||||||
float top = selectedDisplayArea == null ? 0 : selectedDisplayArea.getTop();
|
float top = selectedDisplayArea == null ? 0 : selectedDisplayArea.getTop();
|
||||||
float h = selectedDisplayArea == null ? 0 : selectedDisplayArea.getHeight();
|
float h = selectedDisplayArea == null ? 0 : selectedDisplayArea.getHeight();
|
||||||
float bottom = selectedDisplayArea == null ? 0 : selectedDisplayArea.getBottom();
|
float bottom = selectedDisplayArea == null ? 0 : selectedDisplayArea.getBottom();
|
||||||
g.fillRect(getDisplayAreaBackColor(), x, top, w, h);
|
g.fillRect(Forge.isHorizontalTabLayout() ? getAltDisplayAreaBackColor() : getDisplayAreaBackColor(), x - pad, top, w + pad, h + pad);
|
||||||
|
if (Forge.isHorizontalTabLayout())
|
||||||
|
g.drawLine(1, MatchScreen.getBorderColor(), x, isFlipped() ? bottom : top, x + w, isFlipped() ? bottom : top);
|
||||||
|
|
||||||
if (Forge.isLandscapeMode()) {
|
if (Forge.isLandscapeMode()) {
|
||||||
g.drawLine(1, MatchScreen.getBorderColor(), x, top, x, bottom);
|
g.drawLine(1, MatchScreen.getBorderColor(), x, top, x, bottom);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
y = isFlipped() ? top + 1 : bottom;
|
y = isFlipped() ? top + 1 : bottom;
|
||||||
//don't know why infotab gets null here, either way don't crash the gui..
|
//don't know why infotab gets null here, either way don't crash the gui..
|
||||||
float left = infoTab == null ? 0 : infoTab.getLeft();
|
float left = infoTab == null ? 0 : infoTab.getLeft();
|
||||||
@@ -487,6 +548,8 @@ public class VPlayerPanel extends FContainer {
|
|||||||
float x = commandZone.getLeft();
|
float x = commandZone.getLeft();
|
||||||
y = commandZone.getTop();
|
y = commandZone.getTop();
|
||||||
g.drawLine(1, MatchScreen.getBorderColor(), x, y, x, y + commandZone.getHeight());
|
g.drawLine(1, MatchScreen.getBorderColor(), x, y, x, y + commandZone.getHeight());
|
||||||
|
/*if (Forge.isHorizontalTabLayout())
|
||||||
|
g.fillRect(getAltDisplayAreaBackColor(), x - pad, y, commandZoneWidth + pad, commandZone.getHeight() + pad);*/
|
||||||
if (isFlipped()) {
|
if (isFlipped()) {
|
||||||
y += commandZone.getHeight();
|
y += commandZone.getHeight();
|
||||||
}
|
}
|
||||||
@@ -499,7 +562,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
ArrayList<FScrollPane> out = new ArrayList<>();
|
ArrayList<FScrollPane> out = new ArrayList<>();
|
||||||
out.add(field.getRow1());
|
out.add(field.getRow1());
|
||||||
out.add(field.getRow2());
|
out.add(field.getRow2());
|
||||||
for(InfoTabZone tab : zoneTabs.values())
|
for (InfoTabZone tab : zoneTabs.values())
|
||||||
out.add(tab.displayArea);
|
out.add(tab.displayArea);
|
||||||
out.add(commandZone);
|
out.add(commandZone);
|
||||||
out.addAll(extraTab.displayAreas.values());
|
out.addAll(extraTab.displayAreas.values());
|
||||||
@@ -522,7 +585,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
private void update() {
|
private void update() {
|
||||||
int vibrateDuration = 0;
|
int vibrateDuration = 0;
|
||||||
int delta = player.getLife() - life;
|
int delta = player.getLife() - life;
|
||||||
player.setAvatarLifeDifference(player.getAvatarLifeDifference()+delta);
|
player.setAvatarLifeDifference(player.getAvatarLifeDifference() + delta);
|
||||||
if (delta != 0) {
|
if (delta != 0) {
|
||||||
if (delta < 0) {
|
if (delta < 0) {
|
||||||
vibrateDuration += delta * -100;
|
vibrateDuration += delta * -100;
|
||||||
@@ -553,6 +616,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
Gdx.input.vibrate(Math.min(vibrateDuration, 2000));
|
Gdx.input.vibrate(Math.min(vibrateDuration, 2000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateShards() {
|
private void updateShards() {
|
||||||
manaShards = player.getNumManaShards();
|
manaShards = player.getNumManaShards();
|
||||||
}
|
}
|
||||||
@@ -567,55 +631,55 @@ public class VPlayerPanel extends FContainer {
|
|||||||
public void draw(Graphics g) {
|
public void draw(Graphics g) {
|
||||||
adjustHeight = 1;
|
adjustHeight = 1;
|
||||||
float divider = Gdx.app.getGraphics().getHeight() > 900 ? 1.2f : 2f;
|
float divider = Gdx.app.getGraphics().getHeight() > 900 ? 1.2f : 2f;
|
||||||
if(Forge.altPlayerLayout && !Forge.altZoneTabs && Forge.isLandscapeMode()) {
|
if (Forge.altPlayerLayout && !Forge.altZoneTabs && Forge.isLandscapeMode()) {
|
||||||
if (poisonCounters == 0 && energyCounters == 0 && experienceCounters == 0 && ticketCounters == 0 && radCounters == 0 && manaShards == 0) {
|
if (poisonCounters == 0 && energyCounters == 0 && experienceCounters == 0 && ticketCounters == 0 && radCounters == 0 && manaShards == 0) {
|
||||||
g.fillRect(Color.DARK_GRAY, 0, 0, INFO2_FONT.getBounds(lifeStr).width+1, INFO2_FONT.getBounds(lifeStr).height+1);
|
g.fillRect(Color.DARK_GRAY, 0, 0, INFO2_FONT.getBounds(lifeStr).width + 1, INFO2_FONT.getBounds(lifeStr).height + 1);
|
||||||
g.drawText(lifeStr, INFO2_FONT, getInfoForeColor().getColor(), 0, 0, getWidth(), getHeight(), false, Align.left, false);
|
g.drawText(lifeStr, INFO2_FONT, getInfoForeColor().getColor(), 0, 0, getWidth(), getHeight(), false, Align.left, false);
|
||||||
} else {
|
} else {
|
||||||
float halfHeight = getHeight() / divider;
|
float halfHeight = getHeight() / divider;
|
||||||
float textStart = halfHeight + Utils.scale(1);
|
float textStart = halfHeight + Utils.scale(1);
|
||||||
float textWidth = getWidth() - textStart;
|
float textWidth = getWidth() - textStart;
|
||||||
int mod = 1;
|
int mod = 1;
|
||||||
g.fillRect(Color.DARK_GRAY, 0, 0, INFO_FONT.getBounds(lifeStr).width+halfHeight+1, INFO_FONT.getBounds(lifeStr).height+1);
|
g.fillRect(Color.DARK_GRAY, 0, 0, INFO_FONT.getBounds(lifeStr).width + halfHeight + 1, INFO_FONT.getBounds(lifeStr).height + 1);
|
||||||
g.drawImage(FSkinImage.QUEST_LIFE, 0, 0, halfHeight, halfHeight);
|
g.drawImage(FSkinImage.QUEST_LIFE, 0, 0, halfHeight, halfHeight);
|
||||||
g.drawText(lifeStr, INFO_FONT, getInfoForeColor().getColor(), textStart, 0, textWidth, halfHeight, false, Align.left, false);
|
g.drawText(lifeStr, INFO_FONT, getInfoForeColor().getColor(), textStart, 0, textWidth, halfHeight, false, Align.left, false);
|
||||||
if (poisonCounters > 0) {
|
if (poisonCounters > 0) {
|
||||||
g.fillRect(Color.DARK_GRAY, 0, halfHeight+2, INFO_FONT.getBounds(String.valueOf(poisonCounters)).width+halfHeight+1, INFO_FONT.getBounds(String.valueOf(poisonCounters)).height+1);
|
g.fillRect(Color.DARK_GRAY, 0, halfHeight + 2, INFO_FONT.getBounds(String.valueOf(poisonCounters)).width + halfHeight + 1, INFO_FONT.getBounds(String.valueOf(poisonCounters)).height + 1);
|
||||||
g.drawImage(FSkinImage.POISON, 0, halfHeight+2, halfHeight, halfHeight);
|
g.drawImage(FSkinImage.POISON, 0, halfHeight + 2, halfHeight, halfHeight);
|
||||||
g.drawText(String.valueOf(poisonCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, halfHeight+2, textWidth, halfHeight, false, Align.left, false);
|
g.drawText(String.valueOf(poisonCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, halfHeight + 2, textWidth, halfHeight, false, Align.left, false);
|
||||||
mod+=1;
|
mod += 1;
|
||||||
}
|
}
|
||||||
if (energyCounters > 0) {
|
if (energyCounters > 0) {
|
||||||
g.fillRect(Color.DARK_GRAY, 0, (halfHeight*mod)+2, INFO_FONT.getBounds(String.valueOf(energyCounters)).width+halfHeight+1, INFO_FONT.getBounds(String.valueOf(energyCounters)).height+1);
|
g.fillRect(Color.DARK_GRAY, 0, (halfHeight * mod) + 2, INFO_FONT.getBounds(String.valueOf(energyCounters)).width + halfHeight + 1, INFO_FONT.getBounds(String.valueOf(energyCounters)).height + 1);
|
||||||
g.drawImage(FSkinImage.ENERGY, 0, (halfHeight*mod)+2, halfHeight, halfHeight);
|
g.drawImage(FSkinImage.ENERGY, 0, (halfHeight * mod) + 2, halfHeight, halfHeight);
|
||||||
g.drawText(String.valueOf(energyCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight*mod)+2, textWidth, halfHeight, false, Align.left, false);
|
g.drawText(String.valueOf(energyCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||||
mod+=1;
|
mod += 1;
|
||||||
}
|
}
|
||||||
if (experienceCounters > 0) {
|
if (experienceCounters > 0) {
|
||||||
g.fillRect(Color.DARK_GRAY, 0, (halfHeight*mod)+2, INFO_FONT.getBounds(String.valueOf(experienceCounters)).width+halfHeight+1, INFO_FONT.getBounds(String.valueOf(experienceCounters)).height+1);
|
g.fillRect(Color.DARK_GRAY, 0, (halfHeight * mod) + 2, INFO_FONT.getBounds(String.valueOf(experienceCounters)).width + halfHeight + 1, INFO_FONT.getBounds(String.valueOf(experienceCounters)).height + 1);
|
||||||
g.drawImage(FSkinImage.COMMANDER, 0, (halfHeight*mod)+2, halfHeight, halfHeight);
|
g.drawImage(FSkinImage.COMMANDER, 0, (halfHeight * mod) + 2, halfHeight, halfHeight);
|
||||||
g.drawText(String.valueOf(experienceCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight*mod)+2, textWidth, halfHeight, false, Align.left, false);
|
g.drawText(String.valueOf(experienceCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||||
mod+=1;
|
mod += 1;
|
||||||
}
|
}
|
||||||
if (radCounters > 0) {
|
if (radCounters > 0) {
|
||||||
g.fillRect(Color.DARK_GRAY, 0, (halfHeight*mod)+2, INFO_FONT.getBounds(String.valueOf(radCounters)).width+halfHeight+1, INFO_FONT.getBounds(String.valueOf(radCounters)).height+1);
|
g.fillRect(Color.DARK_GRAY, 0, (halfHeight * mod) + 2, INFO_FONT.getBounds(String.valueOf(radCounters)).width + halfHeight + 1, INFO_FONT.getBounds(String.valueOf(radCounters)).height + 1);
|
||||||
g.drawImage(FSkinImage.RAD, 0, (halfHeight*mod)+2, halfHeight, halfHeight);
|
g.drawImage(FSkinImage.RAD, 0, (halfHeight * mod) + 2, halfHeight, halfHeight);
|
||||||
g.drawText(String.valueOf(radCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight*mod)+2, textWidth, halfHeight, false, Align.left, false);
|
g.drawText(String.valueOf(radCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||||
mod+=1;
|
mod += 1;
|
||||||
}
|
}
|
||||||
if (ticketCounters > 0) {
|
if (ticketCounters > 0) {
|
||||||
g.fillRect(Color.DARK_GRAY, 0, (halfHeight*mod)+2, INFO_FONT.getBounds(String.valueOf(ticketCounters)).width+halfHeight+1, INFO_FONT.getBounds(String.valueOf(ticketCounters)).height+1);
|
g.fillRect(Color.DARK_GRAY, 0, (halfHeight * mod) + 2, INFO_FONT.getBounds(String.valueOf(ticketCounters)).width + halfHeight + 1, INFO_FONT.getBounds(String.valueOf(ticketCounters)).height + 1);
|
||||||
g.drawImage(FSkinImage.TICKET, 0, (halfHeight*mod)+2, halfHeight, halfHeight);
|
g.drawImage(FSkinImage.TICKET, 0, (halfHeight * mod) + 2, halfHeight, halfHeight);
|
||||||
g.drawText(String.valueOf(ticketCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight*mod)+2, textWidth, halfHeight, false, Align.left, false);
|
g.drawText(String.valueOf(ticketCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||||
mod+=1;
|
mod += 1;
|
||||||
}
|
}
|
||||||
if (manaShards > 0) {
|
if (manaShards > 0) {
|
||||||
g.fillRect(Color.DARK_GRAY, 0, (halfHeight*mod)+2, INFO_FONT.getBounds(String.valueOf(manaShards)).width+halfHeight+1, INFO_FONT.getBounds(String.valueOf(manaShards)).height+1);
|
g.fillRect(Color.DARK_GRAY, 0, (halfHeight * mod) + 2, INFO_FONT.getBounds(String.valueOf(manaShards)).width + halfHeight + 1, INFO_FONT.getBounds(String.valueOf(manaShards)).height + 1);
|
||||||
g.drawImage(FSkinImage.AETHER_SHARD, 0, (halfHeight*mod)+2, halfHeight, halfHeight);
|
g.drawImage(FSkinImage.AETHER_SHARD, 0, (halfHeight * mod) + 2, halfHeight, halfHeight);
|
||||||
g.drawText(String.valueOf(manaShards), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight*mod)+2, textWidth, halfHeight, false, Align.left, false);
|
g.drawText(String.valueOf(manaShards), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||||
mod+=1;
|
mod += 1;
|
||||||
}
|
}
|
||||||
adjustHeight = (mod > 2) && (avatar.getHeight() < halfHeight*mod)? mod : 1;
|
adjustHeight = (mod > 2) && (avatar.getHeight() < halfHeight * mod) ? mod : 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (poisonCounters == 0 && energyCounters == 0 && manaShards == 0) {
|
if (poisonCounters == 0 && energyCounters == 0 && manaShards == 0) {
|
||||||
@@ -632,8 +696,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
} else if (energyCounters > 0) { //prioritize showing energy counters over mana shards
|
} else if (energyCounters > 0) { //prioritize showing energy counters over mana shards
|
||||||
g.drawImage(FSkinImage.ENERGY, 0, halfHeight, halfHeight, halfHeight);
|
g.drawImage(FSkinImage.ENERGY, 0, halfHeight, halfHeight, halfHeight);
|
||||||
g.drawText(String.valueOf(energyCounters), INFO_FONT, getInfoForeColor(), textStart, halfHeight, textWidth, halfHeight, false, Align.center, true);
|
g.drawText(String.valueOf(energyCounters), INFO_FONT, getInfoForeColor(), textStart, halfHeight, textWidth, halfHeight, false, Align.center, true);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
g.drawImage(FSkinImage.MANASHARD, 0, halfHeight, halfHeight, halfHeight);
|
g.drawImage(FSkinImage.MANASHARD, 0, halfHeight, halfHeight, halfHeight);
|
||||||
g.drawText(String.valueOf(manaShards), INFO_FONT, getInfoForeColor(), textStart, halfHeight, textWidth, halfHeight, false, Align.center, true);
|
g.drawText(String.valueOf(manaShards), INFO_FONT, getInfoForeColor(), textStart, halfHeight, textWidth, halfHeight, false, Align.center, true);
|
||||||
}
|
}
|
||||||
@@ -658,18 +721,27 @@ public class VPlayerPanel extends FContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract VDisplayArea getDisplayArea();
|
public abstract VDisplayArea getDisplayArea();
|
||||||
|
|
||||||
public abstract void setDisplayVisible(boolean visible);
|
public abstract void setDisplayVisible(boolean visible);
|
||||||
|
|
||||||
public abstract void setDisplayBounds(float x, float y, float width, float height);
|
public abstract void setDisplayBounds(float x, float y, float width, float height);
|
||||||
|
|
||||||
public abstract void setRotate180(boolean rotate180);
|
public abstract void setRotate180(boolean rotate180);
|
||||||
|
|
||||||
public abstract void update();
|
public abstract void update();
|
||||||
|
|
||||||
public abstract void reset();
|
public abstract void reset();
|
||||||
|
|
||||||
|
public abstract float getIdealWidth(float pref);
|
||||||
|
|
||||||
protected boolean isSelected() {
|
protected boolean isSelected() {
|
||||||
return selectedTab == this;
|
return selectedTab == this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected FSkinColor getSelectedBackgroundColor() {
|
protected FSkinColor getSelectedBackgroundColor() {
|
||||||
return getDisplayAreaBackColor();
|
return getDisplayAreaBackColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isAlignedRightForAltDisplay() {
|
protected boolean isAlignedRightForAltDisplay() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -745,8 +817,8 @@ public class VPlayerPanel extends FContainer {
|
|||||||
if (lblLife.getRotate180()) {
|
if (lblLife.getRotate180()) {
|
||||||
g.startRotateTransform(x + w / 2, y + h / 2, 180);
|
g.startRotateTransform(x + w / 2, y + h / 2, 180);
|
||||||
}
|
}
|
||||||
float mod = isHovered() ? w/8f:0;
|
float mod = isHovered() ? w / 8f : 0;
|
||||||
g.drawImage(icon, x-mod/2, y-mod/2, w+mod, h+mod);
|
g.drawImage(icon, x - mod / 2, y - mod / 2, w + mod, h + mod);
|
||||||
if (lblLife.getRotate180()) {
|
if (lblLife.getRotate180()) {
|
||||||
g.endTransform();
|
g.endTransform();
|
||||||
}
|
}
|
||||||
@@ -773,8 +845,8 @@ public class VPlayerPanel extends FContainer {
|
|||||||
h = icon.getHeight() * w / icon.getWidth();
|
h = icon.getHeight() * w / icon.getWidth();
|
||||||
x = (getWidth() - w) / 2;
|
x = (getWidth() - w) / 2;
|
||||||
y = INFO_TAB_PADDING_Y;
|
y = INFO_TAB_PADDING_Y;
|
||||||
float mod = isHovered() ? w/8f:0;
|
float mod = isHovered() ? w / 8f : 0;
|
||||||
g.drawImage(icon, x-mod/2, y-mod/2, w+mod, h+mod);
|
g.drawImage(icon, x - mod / 2, y - mod / 2, w + mod, h + mod);
|
||||||
|
|
||||||
y += h + INFO_TAB_PADDING_Y;
|
y += h + INFO_TAB_PADDING_Y;
|
||||||
g.drawText(value, INFO_FONT, getInfoForeColor(), 0, y, getWidth(), getHeight() - y + 1, false, Align.center, false);
|
g.drawText(value, INFO_FONT, getInfoForeColor(), 0, y, getWidth(), getHeight() - y + 1, false, Align.center, false);
|
||||||
@@ -827,7 +899,13 @@ public class VPlayerPanel extends FContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {} //Mana Display does not get cleared.
|
public void reset() {
|
||||||
|
} //Mana Display does not get cleared.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getIdealWidth(float pref) {
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -842,6 +920,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final EnumSet<ZoneType> altDisplayZones = EnumSet.of(ZoneType.Hand, ZoneType.Library, ZoneType.Graveyard, ZoneType.Exile);
|
private final EnumSet<ZoneType> altDisplayZones = EnumSet.of(ZoneType.Hand, ZoneType.Library, ZoneType.Graveyard, ZoneType.Exile);
|
||||||
|
|
||||||
public boolean isAlignedRightForAltDisplay() {
|
public boolean isAlignedRightForAltDisplay() {
|
||||||
return altDisplayZones.contains(this.zoneType);
|
return altDisplayZones.contains(this.zoneType);
|
||||||
}
|
}
|
||||||
@@ -857,6 +936,23 @@ public class VPlayerPanel extends FContainer {
|
|||||||
public void reset() {
|
public void reset() {
|
||||||
displayArea.clear();
|
displayArea.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getIdealWidth(float pref) {
|
||||||
|
if (displayArea instanceof VCardDisplayArea vCardDisplayArea) {
|
||||||
|
float cardWidth = vCardDisplayArea.getCardWidth(vCardDisplayArea.getHeight());
|
||||||
|
float size = vCardDisplayArea.getCount();
|
||||||
|
return Math.min(cardWidth * size, pref);
|
||||||
|
}
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update() {
|
||||||
|
super.update();
|
||||||
|
if (selectedTab != null && Forge.isHorizontalTabLayout())
|
||||||
|
updateTabLayout(initW, initH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -873,9 +969,9 @@ public class VPlayerPanel extends FContainer {
|
|||||||
private InfoTabExtra() {
|
private InfoTabExtra() {
|
||||||
super(DEFAULT_ICON);
|
super(DEFAULT_ICON);
|
||||||
this.displayAreas = new EnumMap<>(ZoneType.class);
|
this.displayAreas = new EnumMap<>(ZoneType.class);
|
||||||
for(ZoneType zoneType : EXTRA_ZONES) {
|
for (ZoneType zoneType : EXTRA_ZONES) {
|
||||||
FCollectionView<CardView> cards = player.getCards(zoneType);
|
FCollectionView<CardView> cards = player.getCards(zoneType);
|
||||||
if(cards == null || cards.isEmpty())
|
if (cards == null || cards.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
createZoneIfMissing(zoneType);
|
createZoneIfMissing(zoneType);
|
||||||
hasCardsInExtraZone = true;
|
hasCardsInExtraZone = true;
|
||||||
@@ -887,38 +983,39 @@ public class VPlayerPanel extends FContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void createZoneIfMissing(ZoneType zone) {
|
public void createZoneIfMissing(ZoneType zone) {
|
||||||
if(this.displayAreas.containsKey(zone))
|
if (this.displayAreas.containsKey(zone))
|
||||||
return;
|
return;
|
||||||
VZoneDisplay display = VPlayerPanel.this.add(new VZoneDisplay(player, zone));
|
VZoneDisplay display = VPlayerPanel.this.add(new VZoneDisplay(player, zone));
|
||||||
this.displayAreas.put(zone, display);
|
this.displayAreas.put(zone, display);
|
||||||
this.hasCardsInExtraZone = true;
|
this.hasCardsInExtraZone = true;
|
||||||
if(zone == ZoneType.AttractionDeck || zone == ZoneType.ContraptionDeck)
|
if (zone == ZoneType.AttractionDeck || zone == ZoneType.ContraptionDeck)
|
||||||
createZoneIfMissing(ZoneType.Junkyard); //If the game uses one, it uses both.
|
createZoneIfMissing(ZoneType.Junkyard); //If the game uses one, it uses both.
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setActiveZone(ZoneType zone) {
|
public void setActiveZone(ZoneType zone) {
|
||||||
if(this.activeZone == zone)
|
if (this.activeZone == zone)
|
||||||
return;
|
return;
|
||||||
createZoneIfMissing(zone);
|
createZoneIfMissing(zone);
|
||||||
getDisplayArea().setVisible(false);
|
getDisplayArea().setVisible(false);
|
||||||
this.activeZone = zone;
|
this.activeZone = zone;
|
||||||
if(isSelected())
|
if (isSelected())
|
||||||
getDisplayArea().setVisible(true);
|
getDisplayArea().setVisible(true);
|
||||||
updateTab();
|
updateTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setActiveZoneByIndex(int index) {
|
public void setActiveZoneByIndex(int index) {
|
||||||
List<ZoneType> keyList = List.copyOf(displayAreas.keySet());
|
List<ZoneType> keyList = List.copyOf(displayAreas.keySet());
|
||||||
setActiveZone(keyList.get(index % keyList.size()));
|
setActiveZone(keyList.get(index % keyList.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTab() {
|
private void updateTab() {
|
||||||
if(!hasCardsInExtraZone)
|
if (!hasCardsInExtraZone)
|
||||||
this.value = "";
|
this.value = "";
|
||||||
else if(!getDisplayArea().isVisible())
|
else if (!getDisplayArea().isVisible())
|
||||||
this.value = "+";
|
this.value = "+";
|
||||||
else
|
else
|
||||||
this.value = String.valueOf(displayAreas.get(this.activeZone).getCount());
|
this.value = String.valueOf(displayAreas.get(this.activeZone).getCount());
|
||||||
if(getDisplayArea().isVisible())
|
if (getDisplayArea().isVisible())
|
||||||
this.icon = iconFromZone(this.activeZone);
|
this.icon = iconFromZone(this.activeZone);
|
||||||
else
|
else
|
||||||
this.icon = DEFAULT_ICON;
|
this.icon = DEFAULT_ICON;
|
||||||
@@ -932,7 +1029,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDisplayVisible(boolean visible) {
|
public void setDisplayVisible(boolean visible) {
|
||||||
if(!visible)
|
if (!visible)
|
||||||
displayAreas.values().forEach(d -> d.setVisible(false));
|
displayAreas.values().forEach(d -> d.setVisible(false));
|
||||||
else
|
else
|
||||||
getDisplayArea().setVisible(true);
|
getDisplayArea().setVisible(true);
|
||||||
@@ -954,12 +1051,13 @@ public class VPlayerPanel extends FContainer {
|
|||||||
displayAreas.values().forEach(VDisplayArea::update);
|
displayAreas.values().forEach(VDisplayArea::update);
|
||||||
updateTab();
|
updateTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(ZoneType zoneType) {
|
public void update(ZoneType zoneType) {
|
||||||
if(!displayAreas.containsKey(zoneType)) {
|
if (!displayAreas.containsKey(zoneType)) {
|
||||||
if(!EXTRA_ZONES.contains(zoneType))
|
if (!EXTRA_ZONES.contains(zoneType))
|
||||||
return;
|
return;
|
||||||
FCollectionView<CardView> cards = player.getCards(zoneType);
|
FCollectionView<CardView> cards = player.getCards(zoneType);
|
||||||
if(cards == null || cards.isEmpty())
|
if (cards == null || cards.isEmpty())
|
||||||
return;
|
return;
|
||||||
createZoneIfMissing(zoneType);
|
createZoneIfMissing(zoneType);
|
||||||
}
|
}
|
||||||
@@ -987,18 +1085,28 @@ public class VPlayerPanel extends FContainer {
|
|||||||
activeZone = ZoneType.Sideboard;
|
activeZone = ZoneType.Sideboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getIdealWidth(float pref) {
|
||||||
|
if (getDisplayArea() instanceof VCardDisplayArea vCardDisplayArea) {
|
||||||
|
float cardWidth = vCardDisplayArea.getCardWidth(vCardDisplayArea.getHeight());
|
||||||
|
float size = vCardDisplayArea.getCount();
|
||||||
|
return Math.min(cardWidth * size, pref);
|
||||||
|
}
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean tap(float x, float y, int count) {
|
public boolean tap(float x, float y, int count) {
|
||||||
if(this.displayAreas.isEmpty())
|
if (this.displayAreas.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
if(count >= 2) {
|
if (count >= 2) {
|
||||||
onClickZone(this.activeZone);
|
onClickZone(this.activeZone);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
FPopupMenu menu = new FPopupMenu() {
|
FPopupMenu menu = new FPopupMenu() {
|
||||||
@Override
|
@Override
|
||||||
protected void buildMenu() {
|
protected void buildMenu() {
|
||||||
for(ZoneType zone : displayAreas.keySet()) {
|
for (ZoneType zone : displayAreas.keySet()) {
|
||||||
String label = WordUtils.capitalize(zone.getTranslatedName());
|
String label = WordUtils.capitalize(zone.getTranslatedName());
|
||||||
addItem(new FMenuItem(label, iconFromZone(zone), (e) -> onClickZone(zone)));
|
addItem(new FMenuItem(label, iconFromZone(zone), (e) -> onClickZone(zone)));
|
||||||
}
|
}
|
||||||
@@ -1009,7 +1117,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onClickZone(ZoneType zone) {
|
public void onClickZone(ZoneType zone) {
|
||||||
if(activeZone == zone && this.isSelected()) {
|
if (activeZone == zone && this.isSelected()) {
|
||||||
setSelectedTab(null);
|
setSelectedTab(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1042,7 +1150,7 @@ public class VPlayerPanel extends FContainer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean keyDown(int keyCode) {
|
public boolean keyDown(int keyCode) {
|
||||||
if (MatchController.getView().selectedPlayerPanel() == this && !((FMenuBar)MatchController.getView().getHeader()).isShowingMenu(true)) {
|
if (MatchController.getView().selectedPlayerPanel() == this && !((FMenuBar) MatchController.getView().getHeader()).isShowingMenu(true)) {
|
||||||
if (keyCode == Input.Keys.BUTTON_B) {
|
if (keyCode == Input.Keys.BUTTON_B) {
|
||||||
MatchScreen.nullPotentialListener();
|
MatchScreen.nullPotentialListener();
|
||||||
closeSelectedTab();
|
closeSelectedTab();
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public class VZoneDisplay extends VCardDisplayArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected boolean layoutVerticallyForLandscapeMode() {
|
protected boolean layoutVerticallyForLandscapeMode() {
|
||||||
return true;
|
return !Forge.altZoneTabs || !"Horizontal".equalsIgnoreCase(Forge.altZoneTabMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -100,17 +100,17 @@ public class ConquestAEtherScreen extends FScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateFilteredPool() {
|
private void updateFilteredPool() {
|
||||||
Predicate<PaperCard> predicate = btnColorFilter.buildFilterPredicate(null);
|
Predicate<PaperCard> predicate = btnColorFilter
|
||||||
predicate = btnTypeFilter.buildFilterPredicate(predicate);
|
.and(btnTypeFilter)
|
||||||
predicate = btnRarityFilter.buildFilterPredicate(predicate);
|
.and(btnRarityFilter)
|
||||||
predicate = btnCMCFilter.buildFilterPredicate(predicate);
|
.and(btnCMCFilter);
|
||||||
|
|
||||||
final CardRarity selectedRarity = btnRarityFilter.selectedOption.getRarity();
|
final CardRarity selectedRarity = btnRarityFilter.selectedOption.getRarity();
|
||||||
|
|
||||||
filteredPool.clear();
|
filteredPool.clear();
|
||||||
strictPool.clear();
|
strictPool.clear();
|
||||||
for (PaperCard card : pool) {
|
for (PaperCard card : pool) {
|
||||||
if (predicate == null || predicate.test(card)) {
|
if (predicate.test(card)) {
|
||||||
filteredPool.add(card);
|
filteredPool.add(card);
|
||||||
if (selectedRarity == card.getRarity()) {
|
if (selectedRarity == card.getRarity()) {
|
||||||
strictPool.add(card);
|
strictPool.add(card);
|
||||||
@@ -344,7 +344,7 @@ public class ConquestAEtherScreen extends FScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FilterButton extends FLabel {
|
private class FilterButton extends FLabel implements Predicate<PaperCard> {
|
||||||
private final String caption;
|
private final String caption;
|
||||||
private final List<AEtherFilter> options;
|
private final List<AEtherFilter> options;
|
||||||
private AEtherFilter selectedOption;
|
private AEtherFilter selectedOption;
|
||||||
@@ -383,11 +383,9 @@ public class ConquestAEtherScreen extends FScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate<PaperCard> buildFilterPredicate(Predicate<PaperCard> predicate) {
|
@Override
|
||||||
if (predicate == null) {
|
public boolean test(PaperCard card) {
|
||||||
return selectedOption.getPredicate();
|
return selectedOption.test(card);
|
||||||
}
|
|
||||||
return predicate.and(selectedOption.getPredicate());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -274,14 +274,15 @@ public class SettingsPage extends TabPage<SettingsScreen> {
|
|||||||
MatchController.instance.resetPlayerPanels();
|
MatchController.instance.resetPlayerPanels();
|
||||||
}
|
}
|
||||||
}, 1);
|
}, 1);
|
||||||
lstSettings.addItem(new BooleanSetting(FPref.UI_ALT_PLAYERZONETABS,
|
lstSettings.addItem(new CustomSelectSetting(FPref.UI_ALT_PLAYERZONETABS,
|
||||||
Forge.getLocalizer().getMessage("lblAltZoneTabs"),
|
Forge.getLocalizer().getMessage("lblAltZoneTabs"),
|
||||||
Forge.getLocalizer().getMessage("nlAltZoneTabs")) {
|
Forge.getLocalizer().getMessage("nlAltZoneTabs"),
|
||||||
|
Lists.newArrayList("Off", "Vertical", "Horizontal")) {
|
||||||
@Override
|
@Override
|
||||||
public void select() {
|
public void valueChanged(String newValue) {
|
||||||
super.select();
|
super.valueChanged(newValue);
|
||||||
//update
|
//update
|
||||||
Forge.altZoneTabs = FModel.getPreferences().getPrefBoolean(FPref.UI_ALT_PLAYERZONETABS);
|
Forge.setAltZoneTabMode(FModel.getPreferences().getPref(FPref.UI_ALT_PLAYERZONETABS));
|
||||||
if (MatchController.instance != null)
|
if (MatchController.instance != null)
|
||||||
MatchController.instance.resetPlayerPanels();
|
MatchController.instance.resetPlayerPanels();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>${revision}</version>
|
<version>2.0.06</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-gui</artifactId>
|
<artifactId>forge-gui</artifactId>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -0,0 +1,11 @@
|
|||||||
|
Name:Emrakul's Awakening
|
||||||
|
ManaCost:no cost
|
||||||
|
Types:Artifact
|
||||||
|
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Command | CheckSVar$ X | SVarCompare$ LT7 | Execute$ TrigFlip | TriggerDescription$ At the beginning of your end step, if you have fewer than seven cards in hand, flip a coin. If you win the flip, draw cards equal to the difference.
|
||||||
|
SVar:TrigFlip:DB$ FlipACoin | Defined$ You | WinSubAbility$ TrigDraw
|
||||||
|
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Difference
|
||||||
|
SVar:X:Count$ValidHand Card.YouOwn
|
||||||
|
SVar:Difference:Number$7/Minus.X
|
||||||
|
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigWish | TriggerDescription$ At the beginning of your upkeep, put a land card from your library onto the battlefield at random.
|
||||||
|
SVar:TrigWish:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.YouCtrl | Hidden$ True | AtRandom$ True
|
||||||
|
Oracle:At the beginning of your upkeep, put a land card from your library onto the battlefield at random.\nAt the beginning of your end step, if you have fewer than seven cards in hand, flip a coin. If you win the flip, draw cards equal to the difference.
|
||||||
@@ -3,10 +3,6 @@
|
|||||||
<editorsettings>
|
<editorsettings>
|
||||||
<export target="wastetown..tmx" format="tmx"/>
|
<export target="wastetown..tmx" format="tmx"/>
|
||||||
</editorsettings>
|
</editorsettings>
|
||||||
<properties>
|
|
||||||
<property name="dungeonEffect">{"startBattleWithCardInCommandZone": [ "Emrakul's Presence" ]
|
|
||||||
}</property>
|
|
||||||
</properties>
|
|
||||||
<tileset firstgid="1" source="../../tileset/main.tsx"/>
|
<tileset firstgid="1" source="../../tileset/main.tsx"/>
|
||||||
<tileset firstgid="10113" source="../../tileset/buildings.tsx"/>
|
<tileset firstgid="10113" source="../../tileset/buildings.tsx"/>
|
||||||
<layer id="1" name="Background" width="30" height="30">
|
<layer id="1" name="Background" width="30" height="30">
|
||||||
@@ -42,7 +38,12 @@
|
|||||||
<object id="49" template="../../obj/gold.tx" x="106" y="216"/>
|
<object id="49" template="../../obj/gold.tx" x="106" y="216"/>
|
||||||
<object id="50" template="../../obj/enemy.tx" x="182.772" y="284.992" width="64" height="64">
|
<object id="50" template="../../obj/enemy.tx" x="182.772" y="284.992" width="64" height="64">
|
||||||
<properties>
|
<properties>
|
||||||
|
<property name="effect" value="{ "startBattleWithCardInCommandZone": [ "Emrakul's Presence"]}"/>
|
||||||
<property name="enemy" value="Emrakul"/>
|
<property name="enemy" value="Emrakul"/>
|
||||||
|
<property name="spawn.Easy" type="bool" value="false"/>
|
||||||
|
<property name="spawn.Hard" type="bool" value="true"/>
|
||||||
|
<property name="spawn.Insane" type="bool" value="true"/>
|
||||||
|
<property name="spawn.Normal" type="bool" value="false"/>
|
||||||
</properties>
|
</properties>
|
||||||
</object>
|
</object>
|
||||||
<object id="51" template="../../obj/entry_up.tx" x="209" y="480">
|
<object id="51" template="../../obj/entry_up.tx" x="209" y="480">
|
||||||
@@ -50,6 +51,16 @@
|
|||||||
<property name="teleport" value=""/>
|
<property name="teleport" value=""/>
|
||||||
</properties>
|
</properties>
|
||||||
</object>
|
</object>
|
||||||
|
<object id="52" template="../../obj/enemy.tx" x="182.772" y="284.992" width="64" height="64">
|
||||||
|
<properties>
|
||||||
|
<property name="effect" value="{ "startBattleWithCardInCommandZone": [ "Emrakul's Awakening"]}"/>
|
||||||
|
<property name="enemy" value="Emrakul"/>
|
||||||
|
<property name="spawn.Easy" type="bool" value="true"/>
|
||||||
|
<property name="spawn.Hard" type="bool" value="false"/>
|
||||||
|
<property name="spawn.Insane" type="bool" value="false"/>
|
||||||
|
<property name="spawn.Normal" type="bool" value="true"/>
|
||||||
|
</properties>
|
||||||
|
</object>
|
||||||
<object id="69" template="../../obj/enemy.tx" x="132.844" y="401.619">
|
<object id="69" template="../../obj/enemy.tx" x="132.844" y="401.619">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="dialog">[{
|
<property name="dialog">[{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Name:Airbending Lesson
|
Name:Airbending Lesson
|
||||||
ManaCost:2 W
|
ManaCost:2 W
|
||||||
Types:Instant Lesson
|
Types:Instant Lesson
|
||||||
A:SP$ Airbend | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | SubAbility$ DBDraw | SpellDescription$ Airbend target nonland permanent. (Exile it. While it’s exiled, its owner may cast it for {2} rather than its mana cost.)
|
A:SP$ Airbend | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | SubAbility$ DBDraw | SpellDescription$ Airbend target nonland permanent. (Exile it. While it's exiled, its owner may cast it for {2} rather than its mana cost.)
|
||||||
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SpellDescription$ Draw a card.
|
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SpellDescription$ Draw a card.
|
||||||
Oracle:Airbend target nonland permanent. (Exile it. While it’s exiled, its owner may cast it for {2} rather than its mana cost.)\nDraw a card.
|
Oracle:Airbend target nonland permanent. (Exile it. While it's exiled, its owner may cast it for {2} rather than its mana cost.)\nDraw a card.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:3 W
|
|||||||
Types:Legendary Creature Bison Ally
|
Types:Legendary Creature Bison Ally
|
||||||
PT:2/4
|
PT:2/4
|
||||||
K:Flying
|
K:Flying
|
||||||
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever NICKNAME attacks, another target attacking creature without flying gains flying until until end of turn.
|
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever NICKNAME attacks, another target attacking creature without flying gains flying until end of turn.
|
||||||
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.Other+attacking+withoutFlying | TgtPrompt$ Select another target attacking creature without flying | KW$ Flying
|
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.Other+attacking+withoutFlying | TgtPrompt$ Select another target attacking creature without flying | KW$ Flying
|
||||||
SVar:HasAttackEffect:TRUE
|
SVar:HasAttackEffect:TRUE
|
||||||
Oracle:Flying (This creature can't be blocked except by creatures with flying or reach.)\nWhenever Appa attacks, another target attacking creature without flying gains flying until until end of turn.
|
Oracle:Flying (This creature can't be blocked except by creatures with flying or reach.)\nWhenever Appa attacks, another target attacking creature without flying gains flying until end of turn.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:3 W
|
|||||||
Types:Creature Human Warrior
|
Types:Creature Human Warrior
|
||||||
PT:3/4
|
PT:3/4
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When this creature enters, exile up to two target cards from a single graveyard.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When this creature enters, exile up to two target cards from a single graveyard.
|
||||||
SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 2 | TargetsFromSingleZone$ True | ValidTgts$ Card | TgtPrompt$ Select up to two target cards from a single graveyard
|
SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 2 | TargetsWithSameController$ True | ValidTgts$ Card | TgtPrompt$ Select up to two target cards from a single graveyard
|
||||||
A:AB$ Tap | Cost$ W T | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Tap target creature.
|
A:AB$ Tap | Cost$ W T | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Tap target creature.
|
||||||
DeckHas:Ability$Graveyard
|
DeckHas:Ability$Graveyard
|
||||||
Oracle:When this creature enters, exile up to two target cards from a single graveyard.\n{W}, {T}: Tap target creature.
|
Oracle:When this creature enters, exile up to two target cards from a single graveyard.\n{W}, {T}: Tap target creature.
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ Name:Carrion Beetles
|
|||||||
ManaCost:B
|
ManaCost:B
|
||||||
Types:Creature Insect
|
Types:Creature Insect
|
||||||
PT:1/1
|
PT:1/1
|
||||||
A:AB$ ChangeZone | Cost$ 2 B T | TargetMin$ 0 | TargetMax$ 3 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
A:AB$ ChangeZone | Cost$ 2 B T | TargetMin$ 0 | TargetMax$ 3 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
||||||
Oracle:{2}{B}, {T}: Exile up to three target cards from a single graveyard.
|
Oracle:{2}{B}, {T}: Exile up to three target cards from a single graveyard.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Cease
|
Name:Cease
|
||||||
ManaCost:1 BG
|
ManaCost:1 BG
|
||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ ChangeZone | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Select up to two target cards from a single graveyard | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card | SubAbility$ DBGainLife | SpellDescription$ Exile up to two target cards from a single graveyard.
|
A:SP$ ChangeZone | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Select up to two target cards from a single graveyard | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card | SubAbility$ DBGainLife | SpellDescription$ Exile up to two target cards from a single graveyard.
|
||||||
SVar:DBGainLife:DB$ GainLife | ValidTgts$ Player | LifeAmount$ 2 | SubAbility$ DBDraw | SpellDescription$ Target player gains 2 life and draws a card.
|
SVar:DBGainLife:DB$ GainLife | ValidTgts$ Player | LifeAmount$ 2 | SubAbility$ DBDraw | SpellDescription$ Target player gains 2 life and draws a card.
|
||||||
SVar:DBDraw:DB$ Draw | Defined$ TargetedPlayer
|
SVar:DBDraw:DB$ Draw | Defined$ TargetedPlayer
|
||||||
DeckHas:Ability$Graveyard|LifeGain
|
DeckHas:Ability$Graveyard|LifeGain
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | O
|
|||||||
S:Mode$ Continuous | Affected$ Card.Self | AddTrigger$ TrigCounter | Condition$ Threshold | Description$ Threshold — As long as there are seven or more cards in your graveyard, CARDNAME has "At the beginning of your upkeep, you may put another +1/+1 counter on CARDNAME."
|
S:Mode$ Continuous | Affected$ Card.Self | AddTrigger$ TrigCounter | Condition$ Threshold | Description$ Threshold — As long as there are seven or more cards in your graveyard, CARDNAME has "At the beginning of your upkeep, you may put another +1/+1 counter on CARDNAME."
|
||||||
SVar:TrigCounter:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ GimmeSome | TriggerDescription$ At the beginning of your upkeep, you may put another +1/+1 counter on CARDNAME.
|
SVar:TrigCounter:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ GimmeSome | TriggerDescription$ At the beginning of your upkeep, you may put another +1/+1 counter on CARDNAME.
|
||||||
SVar:GimmeSome:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
|
SVar:GimmeSome:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
|
||||||
Oracle:At the beginning of your upkeep, you may put a +1/+1 counter on Chlorophant.\nThreshold — As long as there are seven or more cards in your graveyard,, Chlorophant has "At the beginning of your upkeep, you may put another +1/+1 counter on Chlorophant."
|
Oracle:At the beginning of your upkeep, you may put a +1/+1 counter on Chlorophant.\nThreshold — As long as there are seven or more cards in your graveyard, Chlorophant has "At the beginning of your upkeep, you may put another +1/+1 counter on Chlorophant."
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Creature Human Citizen
|
|||||||
PT:3/3
|
PT:3/3
|
||||||
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigCharm | TriggerDescription$ When this creature enters, ABILITY
|
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigCharm | TriggerDescription$ When this creature enters, ABILITY
|
||||||
SVar:TrigCharm:DB$ Charm | Choices$ DBRepair,DBImpound | CharmNum$ 1
|
SVar:TrigCharm:DB$ Charm | Choices$ DBRepair,DBImpound | CharmNum$ 1
|
||||||
SVar:DBRepair:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Card.cmcGE4 | SpellDescription$ Repair — Return target card with mana value 4 or greater from your graveyard to your hand.
|
SVar:DBRepair:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Card.YouCtrl+cmcGE4 | SpellDescription$ Repair — Return target card with mana value 4 or greater from your graveyard to your hand.
|
||||||
SVar:DBImpound:DB$ ChangeZone | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Impound — Exile target artifact or enchantment.
|
SVar:DBImpound:DB$ ChangeZone | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Impound — Exile target artifact or enchantment.
|
||||||
DeckHas:Ability$Graveyard
|
DeckHas:Ability$Graveyard
|
||||||
Oracle:When this creature enters, choose one —\n• Repair — Return target card with mana value 4 or greater from your graveyard to your hand.\n• Impound — Exile target artifact or enchantment.
|
Oracle:When this creature enters, choose one —\n• Repair — Return target card with mana value 4 or greater from your graveyard to your hand.\n• Impound — Exile target artifact or enchantment.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Name:Decompose
|
Name:Decompose
|
||||||
ManaCost:1 B
|
ManaCost:1 B
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ ChangeZone | TargetMin$ 0 | TargetMax$ 3 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
A:SP$ ChangeZone | TargetMin$ 0 | TargetMax$ 3 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
||||||
AI:RemoveDeck:Random
|
AI:RemoveDeck:Random
|
||||||
Oracle:Exile up to three target cards from a single graveyard.
|
Oracle:Exile up to three target cards from a single graveyard.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name:Digsite Conservator
|
|||||||
ManaCost:2
|
ManaCost:2
|
||||||
Types:Artifact Creature Gnome
|
Types:Artifact Creature Gnome
|
||||||
PT:2/1
|
PT:2/1
|
||||||
A:AB$ ChangeZone | Cost$ Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 4 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Select up to four target cards from a single graveyard | ValidTgts$ Card | SorcerySpeed$ True | SpellDescription$ Exile up to four target cards from a single graveyard. Activate only as a sorcery.
|
A:AB$ ChangeZone | Cost$ Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 4 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Select up to four target cards from a single graveyard | ValidTgts$ Card | SorcerySpeed$ True | SpellDescription$ Exile up to four target cards from a single graveyard. Activate only as a sorcery.
|
||||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDiscover | TriggerDescription$ When CARDNAME dies, you may pay {4}. If you do, discover 4. (Exile cards from the top of your library until you exile a nonland card with mana value 4 or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.)
|
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDiscover | TriggerDescription$ When CARDNAME dies, you may pay {4}. If you do, discover 4. (Exile cards from the top of your library until you exile a nonland card with mana value 4 or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.)
|
||||||
SVar:TrigDiscover:AB$ Discover | Cost$ 4 | Num$ 4
|
SVar:TrigDiscover:AB$ Discover | Cost$ 4 | Num$ 4
|
||||||
DeckHas:Ability$Sacrifice
|
DeckHas:Ability$Sacrifice
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ K:Visit:TrigPump
|
|||||||
SVar:TrigPump:DB$ Effect | StaticAbilities$ Pump | ValidTgts$ Creature | Triggers$ ExileSelf | RememberObjects$ Targeted | SpellDescription$ Target creature gains flying until end of turn, or until any player rolls a 1, whichever comes first.
|
SVar:TrigPump:DB$ Effect | StaticAbilities$ Pump | ValidTgts$ Creature | Triggers$ ExileSelf | RememberObjects$ Targeted | SpellDescription$ Target creature gains flying until end of turn, or until any player rolls a 1, whichever comes first.
|
||||||
SVar:Pump:Mode$ Continuous | Affected$ Card.IsRemembered | AddKeyword$ Flying | Description$ This creature gains flying until end of turn, or until any player rolls a 1.
|
SVar:Pump:Mode$ Continuous | Affected$ Card.IsRemembered | AddKeyword$ Flying | Description$ This creature gains flying until end of turn, or until any player rolls a 1.
|
||||||
SVar:ExileSelf:Mode$ RolledDie | Execute$ TrigRemove | ValidResult$ EQ1 | Static$ True
|
SVar:ExileSelf:Mode$ RolledDie | Execute$ TrigRemove | ValidResult$ EQ1 | Static$ True
|
||||||
SVar:TrigRemove:DB$ ChangeZone | Origin$ Command | Defined$ Self | Destination$ Exiled
|
SVar:TrigRemove:DB$ ChangeZone | Origin$ Command | Defined$ Self | Destination$ Exile
|
||||||
Oracle:Visit — Target creature gains flying until end of turn, or until any player rolls a 1, whichever comes first.
|
Oracle:Visit — Target creature gains flying until end of turn, or until any player rolls a 1, whichever comes first.
|
||||||
|
|
||||||
# --- VARIANTS ---
|
# --- VARIANTS ---
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Instant
|
|||||||
A:SP$ Charm | Choices$ EbonyDrain,EbonyExile,EbonyFear | Defined$ You
|
A:SP$ Charm | Choices$ EbonyDrain,EbonyExile,EbonyFear | Defined$ You
|
||||||
SVar:EbonyDrain:DB$ LoseLife | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | LifeAmount$ 1 | SubAbility$ EbonyGain | SpellDescription$ Target opponent loses 1 life and you gain 1 life.
|
SVar:EbonyDrain:DB$ LoseLife | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | LifeAmount$ 1 | SubAbility$ EbonyGain | SpellDescription$ Target opponent loses 1 life and you gain 1 life.
|
||||||
SVar:EbonyGain:DB$ GainLife | Defined$ You | LifeAmount$ 1
|
SVar:EbonyGain:DB$ GainLife | Defined$ You | LifeAmount$ 1
|
||||||
SVar:EbonyExile:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 3 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
SVar:EbonyExile:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 3 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
||||||
SVar:EbonyFear:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Fear | SpellDescription$ Target creature gains fear until end of turn.
|
SVar:EbonyFear:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Fear | SpellDescription$ Target creature gains fear until end of turn.
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
Oracle:Choose one —\n• Target opponent loses 1 life and you gain 1 life.\n• Exile up to three target cards from a single graveyard.\n• Target creature gains fear until end of turn. (It can't be blocked except by artifact creatures and/or black creatures.)
|
Oracle:Choose one —\n• Target opponent loses 1 life and you gain 1 life.\n• Exile up to three target cards from a single graveyard.\n• Target creature gains fear until end of turn. (It can't be blocked except by artifact creatures and/or black creatures.)
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ ManaCost:2 R
|
|||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ DealDamage | ValidTgts$ Creature | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature.
|
A:SP$ DealDamage | ValidTgts$ Creature | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature.
|
||||||
K:Mayhem:1 R
|
K:Mayhem:1 R
|
||||||
Oracle:Electro’s Bolt deals 4 damage to target creature.\nMayhem {1}{R} (You may cast this card from your graveyard for {1}{R} if you discarded it this turn. Timing rules still apply.)
|
Oracle:Electro's Bolt deals 4 damage to target creature.\nMayhem {1}{R} (You may cast this card from your graveyard for {1}{R} if you discarded it this turn. Timing rules still apply.)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ Name:Famished Ghoul
|
|||||||
ManaCost:3 B
|
ManaCost:3 B
|
||||||
Types:Creature Zombie
|
Types:Creature Zombie
|
||||||
PT:3/2
|
PT:3/2
|
||||||
A:AB$ ChangeZone | Cost$ 1 B Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 2 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to two target cards from a single graveyard.
|
A:AB$ ChangeZone | Cost$ 1 B Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 2 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to two target cards from a single graveyard.
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
Oracle:{1}{B}, Sacrifice Famished Ghoul: Exile up to two target cards from a single graveyard.
|
Oracle:{1}{B}, Sacrifice Famished Ghoul: Exile up to two target cards from a single graveyard.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ PT:3/5
|
|||||||
K:Flying
|
K:Flying
|
||||||
K:Deathtouch
|
K:Deathtouch
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When this creature enters, exile up to two target cards from a single graveyard.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When this creature enters, exile up to two target cards from a single graveyard.
|
||||||
SVar:TrigExile:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 2 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card
|
SVar:TrigExile:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 2 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card
|
||||||
AlternateMode:Omen
|
AlternateMode:Omen
|
||||||
Oracle:Flying, deathtouch\nWhen this creature enters, exile up to two target cards from a single graveyard.
|
Oracle:Flying, deathtouch\nWhen this creature enters, exile up to two target cards from a single graveyard.
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ SVar:DBDraw:DB$ Draw | NumCards$ X | SubAbility$ DBUntap | AILogic$ ConsiderPrim
|
|||||||
SVar:DBUntap:DB$ Untap | UntapUpTo$ True | UntapType$ Land | Amount$ 5 | SubAbility$ DBEffect | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10
|
SVar:DBUntap:DB$ Untap | UntapUpTo$ True | UntapType$ Land | Amount$ 5 | SubAbility$ DBEffect | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10
|
||||||
SVar:DBEffect:DB$ Effect | StaticAbilities$ STHandSize | Duration$ Permanent | SubAbility$ DBChange | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10
|
SVar:DBEffect:DB$ Effect | StaticAbilities$ STHandSize | Duration$ Permanent | SubAbility$ DBChange | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10
|
||||||
SVar:STHandSize:Mode$ Continuous | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size.
|
SVar:STHandSize:Mode$ Continuous | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size.
|
||||||
SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ Exile CARDNAME
|
SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ Exile CARDNAME.
|
||||||
SVar:X:Count$xPaid
|
SVar:X:Count$xPaid
|
||||||
Oracle:Draw X cards. If X is 10 or more, instead shuffle your graveyard into your library, draw X cards, untap up to five lands, and you have no maximum hand size for the rest of the game.\nExile Finale of Revelation.
|
Oracle:Draw X cards. If X is 10 or more, instead shuffle your graveyard into your library, draw X cards, untap up to five lands, and you have no maximum hand size for the rest of the game.\nExile Finale of Revelation.
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ ManaCost:4 R
|
|||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ Token | TokenAmount$ 2 | TokenScript$ r_2_2_soldier_firebending_1 | TokenOwner$ You | SpellDescription$ Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.)
|
A:SP$ Token | TokenAmount$ 2 | TokenScript$ r_2_2_soldier_firebending_1 | TokenOwner$ You | SpellDescription$ Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.)
|
||||||
K:Flashback:8 R
|
K:Flashback:8 R
|
||||||
Oracle:Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.)\nFlashback {8}{R} (You may may cast this card from your graveyard for its flashback cost. Then exile it.)
|
Oracle:Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.)\nFlashback {8}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Legendary Artifact Creature Robot
|
|||||||
PT:3/2
|
PT:3/2
|
||||||
K:More Than Meets the Eye:B R
|
K:More Than Meets the Eye:B R
|
||||||
A:AB$ PutCounter | Cost$ Sac<1/Artifact.Other/another artifact> | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | SorcerySpeed$ True | SubAbility$ DBConvert | AILogic$ AristocratCounters | SpellDescription$ Put a +1/+1 counter on NICKNAME and convert it. Activate only as a sorcery.
|
A:AB$ PutCounter | Cost$ Sac<1/Artifact.Other/another artifact> | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | SorcerySpeed$ True | SubAbility$ DBConvert | AILogic$ AristocratCounters | SpellDescription$ Put a +1/+1 counter on NICKNAME and convert it. Activate only as a sorcery.
|
||||||
SVar:DBConvert:DB$ SetState | Mode$ Transform | StackDescription$ Convert NICKNAME
|
SVar:DBConvert:DB$ SetState | Mode$ Transform | StackDescription$ Convert NICKNAME.
|
||||||
A:AB$ ChangeZoneAll | Cost$ 1 Discard<1/Hand> | ChangeType$ Card.YouOwn+counters_GE1_INTEL | Origin$ Exile | Destination$ Hand | SpellDescription$ Put all exiled cards you own with intel counters on them into your hand.
|
A:AB$ ChangeZoneAll | Cost$ 1 Discard<1/Hand> | ChangeType$ Card.YouOwn+counters_GE1_INTEL | Origin$ Exile | Destination$ Hand | SpellDescription$ Put all exiled cards you own with intel counters on them into your hand.
|
||||||
DeckHints:Ability$Counters
|
DeckHints:Ability$Counters
|
||||||
DeckHas:Ability$Sacrifice|Discard|Counters
|
DeckHas:Ability$Sacrifice|Discard|Counters
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
Name:Footsteps of the Goryo
|
Name:Footsteps of the Goryo
|
||||||
ManaCost:2 B
|
ManaCost:2 B
|
||||||
Types:Sorcery Arcane
|
Types:Sorcery Arcane
|
||||||
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature in your graveyard | GainControl$ True | SubAbility$ DBPump | AILogic$ BeforeCombat | SpellDescription$ Return target creature card from your graveyard to the battlefield. Sacrifice that creature at the beginning of the next end step.
|
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature in your graveyard | GainControl$ True | AtEOT$ Sacrifice | AILogic$ BeforeCombat | SpellDescription$ Return target creature card from your graveyard to the battlefield. Sacrifice that creature at the beginning of the next end step.
|
||||||
SVar:DBPump:DB$ Pump | Defined$ Targeted | AtEOT$ Sacrifice
|
|
||||||
AI:RemoveDeck:Random
|
AI:RemoveDeck:Random
|
||||||
Oracle:Return target creature card from your graveyard to the battlefield. Sacrifice that creature at the beginning of the next end step.
|
Oracle:Return target creature card from your graveyard to the battlefield. Sacrifice that creature at the beginning of the next end step.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ Types:Creature Nightmare Horror
|
|||||||
PT:2/2
|
PT:2/2
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters, exile up to two target cards from a single graveyard.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters, exile up to two target cards from a single graveyard.
|
||||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, return the exiled cards to their owner's graveyard.
|
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, return the exiled cards to their owner's graveyard.
|
||||||
SVar:TrigExile:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 2 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card | RememberTargets$ True | ForgetOtherTargets$ True
|
SVar:TrigExile:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 2 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card | RememberTargets$ True | ForgetOtherTargets$ True
|
||||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Graveyard
|
SVar:TrigReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Graveyard
|
||||||
Oracle:When Gravegouger enters, exile up to two target cards from a single graveyard.\nWhen Gravegouger leaves the battlefield, return the exiled cards to their owner's graveyard.
|
Oracle:When Gravegouger enters, exile up to two target cards from a single graveyard.\nWhen Gravegouger leaves the battlefield, return the exiled cards to their owner's graveyard.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ Types:Creature Human Detective
|
|||||||
PT:3/2
|
PT:3/2
|
||||||
K:Flying
|
K:Flying
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, exile up to two target cards from a single graveyard.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, exile up to two target cards from a single graveyard.
|
||||||
SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 2 | TargetsFromSingleZone$ True | ValidTgts$ Card | TgtPrompt$ Select up to two target cards from a single graveyard
|
SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 2 | TargetsWithSameController$ True | ValidTgts$ Card | TgtPrompt$ Select up to two target cards from a single graveyard
|
||||||
DeckHas:Ability$Graveyard
|
DeckHas:Ability$Graveyard
|
||||||
Oracle:Flying\nWhen Griffnaut Tracker enters, exile up to two target cards from a single graveyard.
|
Oracle:Flying\nWhen Griffnaut Tracker enters, exile up to two target cards from a single graveyard.
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ Name:Inner Demons Gangsters
|
|||||||
ManaCost:3 B
|
ManaCost:3 B
|
||||||
Types:Creature Human Rogue Villain
|
Types:Creature Human Rogue Villain
|
||||||
PT:3/4
|
PT:3/4
|
||||||
A:AB$ Pump | Cost$ Discard<1/Card> | Defined$ Self | NumAtt$ +1 | KW$ Menace | SorcerySpeed$ True | SpellDescription$ This creature gets +1/+0 and gains menace until end of turn. Activate only as a sorcery. (It can't be blocked except by two or more creatures.)
|
A:AB$ Pump | Cost$ Discard<1/Card> | Defined$ Self | NumAtt$ +1 | KW$ Menace | SorcerySpeed$ True | SpellDescription$ This creature gets +1/+0 and gains menace until end of turn. Activate only as a sorcery. (It can't be blocked except by two or more creatures.)
|
||||||
Oracle:Discard a card: This creature gets +1/+0 and gains menace until end of turn. Activate only as a sorcery. (It can't be blocked except by two or more creatures.)
|
Oracle:Discard a card: This creature gets +1/+0 and gains menace until end of turn. Activate only as a sorcery. (It can't be blocked except by two or more creatures.)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name:Kaya, Orzhov Usurper
|
|||||||
ManaCost:1 W B
|
ManaCost:1 W B
|
||||||
Types:Legendary Planeswalker Kaya
|
Types:Legendary Planeswalker Kaya
|
||||||
Loyalty:3
|
Loyalty:3
|
||||||
A:AB$ ChangeZone | Cost$ AddCounter<1/LOYALTY> | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 2 | TargetsFromSingleZone$ True | ValidTgts$ Card | TgtPrompt$ Select target card from a graveyard | Planeswalker$ True | SubAbility$ DBGainLife | RememberChanged$ True | SpellDescription$ Exile up to two target cards from a single graveyard. You gain 2 life if at least one creature card was exiled this way.
|
A:AB$ ChangeZone | Cost$ AddCounter<1/LOYALTY> | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 2 | TargetsWithSameController$ True | ValidTgts$ Card | TgtPrompt$ Select target card from a graveyard | Planeswalker$ True | SubAbility$ DBGainLife | RememberChanged$ True | SpellDescription$ Exile up to two target cards from a single graveyard. You gain 2 life if at least one creature card was exiled this way.
|
||||||
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 | ConditionDefined$ Remembered | ConditionPresent$ Creature | ConditionCompare$ GE1 | SubAbility$ DBCleanup
|
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 | ConditionDefined$ Remembered | ConditionPresent$ Creature | ConditionCompare$ GE1 | SubAbility$ DBCleanup
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
A:AB$ ChangeZone | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Choose target nonland permanent with mana value 1 or less | ValidTgts$ Permanent.nonLand+cmcLE1 | SpellDescription$ Exile target nonland permanent with mana value 1 or less.
|
A:AB$ ChangeZone | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Choose target nonland permanent with mana value 1 or less | ValidTgts$ Permanent.nonLand+cmcLE1 | SpellDescription$ Exile target nonland permanent with mana value 1 or less.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:6 G G
|
|||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
R:Event$ Counter | ValidCard$ Card.Self | ValidSA$ Spell | Layer$ CantHappen | Description$ This spell can't be countered.
|
R:Event$ Counter | ValidCard$ Card.Self | ValidSA$ Spell | Layer$ CantHappen | Description$ This spell can't be countered.
|
||||||
A:SP$ Draw | Defined$ You | NumCards$ X | SubAbility$ CheatBattlefield | SpellDescription$ Draw cards equal to the greatest toughness among creatures you control, then put any number of creature cards from your hand onto the battlefield.
|
A:SP$ Draw | Defined$ You | NumCards$ X | SubAbility$ CheatBattlefield | SpellDescription$ Draw cards equal to the greatest toughness among creatures you control, then put any number of creature cards from your hand onto the battlefield.
|
||||||
SVar:CheatBattlefield:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature | ChangeNum$ Y | StackDescription$ {p:You} puts any number of creature cards from their hand onto the battlefield
|
SVar:CheatBattlefield:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature | ChangeNum$ Y | StackDescription$ {p:You} puts any number of creature cards from their hand onto the battlefield.
|
||||||
SVar:X:Count$Valid Creature.YouCtrl$GreatestToughness
|
SVar:X:Count$Valid Creature.YouCtrl$GreatestToughness
|
||||||
SVar:Y:Count$ValidHand Creature.YouCtrl
|
SVar:Y:Count$ValidHand Creature.YouCtrl
|
||||||
DeckHints:Type$Wall|Plant|Treefolk
|
DeckHints:Type$Wall|Plant|Treefolk
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Lodestone Bauble
|
Name:Lodestone Bauble
|
||||||
ManaCost:0
|
ManaCost:0
|
||||||
Types:Artifact
|
Types:Artifact
|
||||||
A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 4 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 0 | TgtPrompt$ Choose target basic land card in a graveyard | ValidTgts$ Land.Basic | Chooser$ You | SubAbility$ ChooseZero | SpellDescription$ Put up to four target basic land cards from a player's graveyard on top of their library in any order. That player draws a card at the beginning of the next turn's upkeep.
|
A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 4 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 0 | TgtPrompt$ Choose target basic land card in a graveyard | ValidTgts$ Land.Basic | Chooser$ You | SubAbility$ ChooseZero | SpellDescription$ Put up to four target basic land cards from a player's graveyard on top of their library in any order. That player draws a card at the beginning of the next turn's upkeep.
|
||||||
SVar:ChooseZero:DB$ ChoosePlayer | Choices$ Player | ConditionDefined$ Targeted | ConditionNotPresent$ Card | SubAbility$ DelTrigSlowtrip
|
SVar:ChooseZero:DB$ ChoosePlayer | Choices$ Player | ConditionDefined$ Targeted | ConditionNotPresent$ Card | SubAbility$ DelTrigSlowtrip
|
||||||
SVar:DelTrigSlowtrip:DB$ DelayedTrigger | NextTurn$ True | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ DrawSlowtrip | RememberObjects$ TargetedOwner,ChosenPlayer | TriggerDescription$ Draw a card.
|
SVar:DelTrigSlowtrip:DB$ DelayedTrigger | NextTurn$ True | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ DrawSlowtrip | RememberObjects$ TargetedOwner,ChosenPlayer | TriggerDescription$ Draw a card.
|
||||||
SVar:DrawSlowtrip:DB$ Draw | NumCards$ 1 | Defined$ DelayTriggerRemembered
|
SVar:DrawSlowtrip:DB$ Draw | NumCards$ 1 | Defined$ DelayTriggerRemembered
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name:Martyr of Bones
|
|||||||
ManaCost:B
|
ManaCost:B
|
||||||
Types:Creature Human Wizard
|
Types:Creature Human Wizard
|
||||||
PT:1/1
|
PT:1/1
|
||||||
A:AB$ ChangeZone | Cost$ 1 Reveal<X/Card.Black> Sac<1/CARDNAME> | CostDesc$ {1}, Reveal X black cards from your hand, Sacrifice CARDNAME: | Origin$ Graveyard | TargetsFromSingleZone$ True | Destination$ Exile | TargetMin$ 0 | TargetMax$ Y | ValidTgts$ Card | SpellDescription$ Exile up to X target cards from a single graveyard.
|
A:AB$ ChangeZone | Cost$ 1 Reveal<X/Card.Black> Sac<1/CARDNAME> | CostDesc$ {1}, Reveal X black cards from your hand, Sacrifice CARDNAME: | Origin$ Graveyard | TargetsWithSameController$ True | Destination$ Exile | TargetMin$ 0 | TargetMax$ Y | ValidTgts$ Card | SpellDescription$ Exile up to X target cards from a single graveyard.
|
||||||
SVar:X:TargetedObjects$Amount
|
SVar:X:TargetedObjects$Amount
|
||||||
SVar:Y:Count$ValidHand Card.Black+YouOwn
|
SVar:Y:Count$ValidHand Card.Black+YouOwn
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ SVar:TrigRollDice:DB$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-6:DBMana1,7-1
|
|||||||
SVar:DBMana1:DB$ Mana | Produced$ R | Amount$ 4 | SpellDescription$ 1-6 VERT Add {R}{R}{R}{R}.
|
SVar:DBMana1:DB$ Mana | Produced$ R | Amount$ 4 | SpellDescription$ 1-6 VERT Add {R}{R}{R}{R}.
|
||||||
SVar:DBMana2:DB$ Mana | Produced$ R | Amount$ 5 | SpellDescription$ 7-14 VERT Add {R}{R}{R}{R}{R}.
|
SVar:DBMana2:DB$ Mana | Produced$ R | Amount$ 5 | SpellDescription$ 7-14 VERT Add {R}{R}{R}{R}{R}.
|
||||||
SVar:DBMana3:DB$ Mana | Produced$ R | Amount$ 6 | SpellDescription$ 15-20 VERT Add {R}{R}{R}{R}{R}{R}.
|
SVar:DBMana3:DB$ Mana | Produced$ R | Amount$ 6 | SpellDescription$ 15-20 VERT Add {R}{R}{R}{R}{R}{R}.
|
||||||
Oracle:When this creature enters from anywhere other than a graveyard or exile, if it’s on the battlefield and you control 9 or fewer creatures named “Name Sticker” Goblin, roll a 20-sided die.\n1-6 | Add {R}{R}{R}{R}.\n7-14 | Add {R}{R}{R}{R}{R}.\n15-20 | Add {R}{R}{R}{R}{R}{R}.
|
Oracle:When this creature enters from anywhere other than a graveyard or exile, if it's on the battlefield and you control 9 or fewer creatures named "Name Sticker" Goblin, roll a 20-sided die.\n1-6 | Add {R}{R}{R}{R}.\n7-14 | Add {R}{R}{R}{R}{R}.\n15-20 | Add {R}{R}{R}{R}{R}{R}.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ T:Mode$ CounterAddedOnce | ValidCard$ Creature.Other+inZoneBattlefield+Colorless
|
|||||||
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1
|
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1
|
||||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigManifest | TriggerDescription$ When NICKNAME dies, manifest a number of cards from the top of your library equal to the number of counters on it.
|
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigManifest | TriggerDescription$ When NICKNAME dies, manifest a number of cards from the top of your library equal to the number of counters on it.
|
||||||
SVar:TrigManifest:DB$ Manifest | Amount$ Y
|
SVar:TrigManifest:DB$ Manifest | Amount$ Y
|
||||||
SVar:Y:TriggeredCard$CardCounters.P1P1
|
SVar:Y:TriggeredCard$CardCounters.ALL
|
||||||
DeckHas:Ability$Counters
|
DeckHas:Ability$Counters
|
||||||
DeckHints:Ability$Counters
|
DeckHints:Ability$Counters
|
||||||
Oracle:Omarthis, Ghostfire Initiate enters with X +1/+1 counters on it.\nWhenever you put one or more +1/+1 counters on another colorless creature, you may put a +1/+1 counter on Omarthis.\nWhen Omarthis dies, manifest a number of cards from the top of your library equal to the number of counters on it.
|
Oracle:Omarthis, Ghostfire Initiate enters with X +1/+1 counters on it.\nWhenever you put one or more +1/+1 counters on another colorless creature, you may put a +1/+1 counter on Omarthis.\nWhen Omarthis dies, manifest a number of cards from the top of your library equal to the number of counters on it.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:2 B
|
|||||||
Types:Artifact
|
Types:Artifact
|
||||||
A:AB$ Token | Cost$ T Discard<1/Card> | TokenScript$ bg_1_1_pest_lifegain | StackDescription$ SpellDescription | SpellDescription$ Create a 1/1 black and green Pest creature token with "When this creature dies, you gain 1 life."
|
A:AB$ Token | Cost$ T Discard<1/Card> | TokenScript$ bg_1_1_pest_lifegain | StackDescription$ SpellDescription | SpellDescription$ Create a 1/1 black and green Pest creature token with "When this creature dies, you gain 1 life."
|
||||||
A:AB$ Mill | Cost$ 1 T | Defined$ Opponent | NumCards$ X | SpellDescription$ Each opponent mills cards equal to the amount of life you gained this turn.
|
A:AB$ Mill | Cost$ 1 T | Defined$ Opponent | NumCards$ X | SpellDescription$ Each opponent mills cards equal to the amount of life you gained this turn.
|
||||||
A:AB$ ChangeZone | Cost$ 4 T | TargetMin$ 4 | TargetMax$ 4 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose four target cards from a single graveyard | ValidTgts$ Card | SubAbility$ DBDraw | StackDescription$ Exile four target cards from a single graveyard. ({c:Targeted}) | SpellDescription$ Exile four target cards from a single graveyard.
|
A:AB$ ChangeZone | Cost$ 4 T | TargetMin$ 4 | TargetMax$ 4 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose four target cards from a single graveyard | ValidTgts$ Card | SubAbility$ DBDraw | StackDescription$ Exile four target cards from a single graveyard. ({c:Targeted}) | SpellDescription$ Exile four target cards from a single graveyard.
|
||||||
SVar:DBDraw:DB$ Draw | SpellDescription$ Draw a card.
|
SVar:DBDraw:DB$ Draw | SpellDescription$ Draw a card.
|
||||||
SVar:X:Count$LifeYouGainedThisTurn
|
SVar:X:Count$LifeYouGainedThisTurn
|
||||||
DeckHas:Ability$Discard|Token|LifeGain|Mill|Graveyard
|
DeckHas:Ability$Discard|Token|LifeGain|Mill|Graveyard
|
||||||
|
|||||||
@@ -17,5 +17,4 @@ PT:4/4
|
|||||||
K:Vigilance
|
K:Vigilance
|
||||||
K:Reach
|
K:Reach
|
||||||
S:Mode$ Continuous | Affected$ Legendary.YouCtrl+wasCast+nonColorless | AffectedZone$ Stack | AddKeyword$ Web-slinging:G W U:Spell.Legendary+nonColorless | Description$ Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}. (You may cast a spell for its web-slinging cost if you also return a tapped creature you control to its owner's hand.)
|
S:Mode$ Continuous | Affected$ Legendary.YouCtrl+wasCast+nonColorless | AffectedZone$ Stack | AddKeyword$ Web-slinging:G W U:Spell.Legendary+nonColorless | Description$ Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}. (You may cast a spell for its web-slinging cost if you also return a tapped creature you control to its owner's hand.)
|
||||||
Oracle:Vigilance, reach\nEach legendary spell you cast that’s one or more colors has web-slinging {G}{W}{U}. (You may cast a spell for its web-slinging cost if you also return a tapped creature you control to its owner's hand.)
|
Oracle:Vigilance, reach\nEach legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}. (You may cast a spell for its web-slinging cost if you also return a tapped creature you control to its owner's hand.)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ ALTERNATE
|
|||||||
Name:Pull
|
Name:Pull
|
||||||
ManaCost:4 BR BR
|
ManaCost:4 BR BR
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TargetMin$ 0 | TargetMax$ 2 | TargetsFromSingleZone$ True | ValidTgts$ Creature | TgtPrompt$ Select up to two target creature cards from a single graveyard | GainControl$ True | SubAbility$ DBPump | SpellDescription$ Put up to two target creature cards from a single graveyard onto the battlefield under your control. They gain haste until end of turn. Sacrifice them at the beginning of the next end step.
|
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TargetMin$ 0 | TargetMax$ 2 | TargetsWithSameController$ True | ValidTgts$ Creature | TgtPrompt$ Select up to two target creature cards from a single graveyard | GainControl$ True | SubAbility$ DBPump | SpellDescription$ Put up to two target creature cards from a single graveyard onto the battlefield under your control. They gain haste until end of turn. Sacrifice them at the beginning of the next end step.
|
||||||
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Haste | AtEOT$ Sacrifice
|
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Haste | AtEOT$ Sacrifice
|
||||||
Oracle:Put up to two target creature cards from a single graveyard onto the battlefield under your control. They gain haste until end of turn. Sacrifice them at the beginning of the next end step.
|
Oracle:Put up to two target creature cards from a single graveyard onto the battlefield under your control. They gain haste until end of turn. Sacrifice them at the beginning of the next end step.
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
Name:Push the Limit
|
Name:Push the Limit
|
||||||
ManaCost:5 R R
|
ManaCost:5 R R
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ ChangeZoneAll | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Mount.YouOwn,Vehicle.YouOwn | RememberChanged$ True | SubAbility$ DBPump | SpellDescription$ Return all Mount and Vehicle cards from your graveyard to the battlefield. Sacrifice them at the beginning of the next end step. Vehicles you control become artifact creatures until end of turn. Creatures you control gain haste until end of turn.
|
A:SP$ ChangeZoneAll | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Mount.YouOwn,Vehicle.YouOwn | AtEOT$ Sacrifice | SubAbility$ DBAnimateAll | SpellDescription$ Return all Mount and Vehicle cards from your graveyard to the battlefield. Sacrifice them at the beginning of the next end step. Vehicles you control become artifact creatures until end of turn. Creatures you control gain haste until end of turn.
|
||||||
SVar:DBPump:DB$ Pump | Defined$ Remembered | AtEOT$ Sacrifice | SubAbility$ DBAnimateAll
|
|
||||||
SVar:DBAnimateAll:DB$ AnimateAll | Types$ Artifact,Creature | ValidCards$ Vehicle.YouCtrl | SubAbility$ DBPumpAll
|
SVar:DBAnimateAll:DB$ AnimateAll | Types$ Artifact,Creature | ValidCards$ Vehicle.YouCtrl | SubAbility$ DBPumpAll
|
||||||
SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Haste | SubAbility$ DBCleanup
|
SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Haste
|
||||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
|
||||||
SVar:PlayMain1:TRUE
|
SVar:PlayMain1:TRUE
|
||||||
DeckHas:Ability$Graveyard
|
DeckHas:Ability$Graveyard
|
||||||
DeckHints:Ability$Discard|Sacrifice
|
DeckHints:Ability$Discard|Sacrifice
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ PT:3/2
|
|||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When this creature enters, ABILITY
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When this creature enters, ABILITY
|
||||||
SVar:TrigCharm:DB$ Charm | Choices$ DBDestroy,DBExile
|
SVar:TrigCharm:DB$ Charm | Choices$ DBDestroy,DBExile
|
||||||
SVar:DBDestroy:DB$ Destroy | ValidTgts$ Creature.wasDealtDamageThisTurn | TgtPrompt$ Select target creature that was dealt damage this turn | SpellDescription$ Destroy target creature that was dealt damage this turn.
|
SVar:DBDestroy:DB$ Destroy | ValidTgts$ Creature.wasDealtDamageThisTurn | TgtPrompt$ Select target creature that was dealt damage this turn | SpellDescription$ Destroy target creature that was dealt damage this turn.
|
||||||
SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 2 | TargetsFromSingleZone$ True | ValidTgts$ Card | TgtPrompt$ Select up to two target cards from a single graveyard | SpellDescription$ Exile up to two target cards from a single graveyard.
|
SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ 2 | TargetsWithSameController$ True | ValidTgts$ Card | TgtPrompt$ Select up to two target cards from a single graveyard | SpellDescription$ Exile up to two target cards from a single graveyard.
|
||||||
Oracle:When this creature enters, choose one —\n• Destroy target creature that was dealt damage this turn.\n• Exile up to two target cards from a single graveyard.
|
Oracle:When this creature enters, choose one —\n• Destroy target creature that was dealt damage this turn.\n• Exile up to two target cards from a single graveyard.
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ Name:Rag Dealer
|
|||||||
ManaCost:B
|
ManaCost:B
|
||||||
Types:Creature Human Rogue
|
Types:Creature Human Rogue
|
||||||
PT:1/1
|
PT:1/1
|
||||||
A:AB$ ChangeZone | Cost$ 2 B T | TargetMin$ 0 | TargetMax$ 3 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
A:AB$ ChangeZone | Cost$ 2 B T | TargetMin$ 0 | TargetMax$ 3 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
||||||
Oracle:{2}{B}, {T}: Exile up to three target cards from a single graveyard.
|
Oracle:{2}{B}, {T}: Exile up to three target cards from a single graveyard.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Rapid Decay
|
Name:Rapid Decay
|
||||||
ManaCost:1 B
|
ManaCost:1 B
|
||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ ChangeZone | TargetMin$ 0 | TargetMax$ 3 | TargetsFromSingleZone$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
A:SP$ ChangeZone | TargetMin$ 0 | TargetMax$ 3 | TargetsWithSameController$ True | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile up to three target cards from a single graveyard.
|
||||||
K:Cycling:2
|
K:Cycling:2
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
Oracle:Exile up to three target cards from a single graveyard.\nCycling {2} ({2}, Discard this card: Draw a card.)
|
Oracle:Exile up to three target cards from a single graveyard.\nCycling {2} ({2}, Discard this card: Draw a card.)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user