mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-13 01:08:06 +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
|
||||
|
||||
- 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
|
||||
id: bump_version
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -97,6 +97,7 @@ public class AiController {
|
||||
private int lastAttackAggression;
|
||||
private boolean useLivingEnd;
|
||||
private List<SpellAbility> skipped;
|
||||
private boolean timeoutReached;
|
||||
|
||||
public AiController(final Player computerPlayer, final Game game0) {
|
||||
player = computerPlayer;
|
||||
@@ -1664,6 +1665,9 @@ public class AiController {
|
||||
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<>(() -> {
|
||||
//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);
|
||||
@@ -1673,6 +1677,11 @@ public class AiController {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timeoutReached) {
|
||||
timeoutReached = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sa.getHostCard().hasKeyword(Keyword.STORM)
|
||||
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
|
||||
&& player.getZone(ZoneType.Hand).contains(
|
||||
@@ -1752,7 +1761,10 @@ public class AiController {
|
||||
t.stop();
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
// Android and Java 20 dropped support to stop so sadly thread will keep running
|
||||
timeoutReached = 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;
|
||||
}
|
||||
|
||||
@@ -298,13 +298,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
|
||||
if (sa.hasParam("Origin")) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||
}
|
||||
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 = CardLists.canSubsequentlyTarget(list, sa);
|
||||
|
||||
if (sa.hasParam("AttachedTo")) {
|
||||
list = CardLists.filter(list, c -> {
|
||||
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);
|
||||
if (sa.canTarget(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -168,21 +168,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public boolean isTransformable() {
|
||||
if (CardSplitType.Transform == 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;
|
||||
return CardSplitType.Transform == getSplitType() || CardSplitType.Modal == getSplitType();
|
||||
}
|
||||
|
||||
public ICardFace getWSpecialize() {
|
||||
|
||||
@@ -20,7 +20,6 @@ package forge.card;
|
||||
import com.google.common.collect.UnmodifiableIterator;
|
||||
import forge.card.MagicColor.Color;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.util.BinaryUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -41,27 +40,97 @@ import java.util.stream.Stream;
|
||||
public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Serializable {
|
||||
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 float orderWeight;
|
||||
|
||||
private static final ColorSet[] cache = new ColorSet[32];
|
||||
private final Set<Color> enumSet;
|
||||
private final String desc;
|
||||
|
||||
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) {
|
||||
this.myColor = mask;
|
||||
private ColorSet(final Color... ordered) {
|
||||
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.enumSet = EnumSet.copyOf(orderedShards);
|
||||
this.desc = orderedShards.stream().map(Color::getShortName).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
public static ColorSet fromMask(final int mask) {
|
||||
final int mask32 = mask & MagicColor.ALL_COLORS;
|
||||
if (cache[mask32] == null) {
|
||||
cache[mask32] = new ColorSet((byte) 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) {
|
||||
byte mask = 0;
|
||||
for (final String s : colors) {
|
||||
@@ -293,17 +362,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final ManaCostShard[] orderedShards = getOrderedShards();
|
||||
return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the null color.
|
||||
*
|
||||
* @return the nullColor
|
||||
*/
|
||||
public static ColorSet getNullColor() {
|
||||
return NO_COLORS;
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,16 +384,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
||||
}
|
||||
|
||||
public Set<Color> toEnumSet() {
|
||||
if (isColorless()) {
|
||||
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);
|
||||
return EnumSet.copyOf(enumSet);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
//Get array of mana cost shards for color set in the proper order
|
||||
public ManaCostShard[] getOrderedShards() {
|
||||
return shardOrderLookup[myColor];
|
||||
}
|
||||
|
||||
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 };
|
||||
public Collection<Color> getOrderedColors() {
|
||||
return orderedShards;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package forge.card;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import forge.deck.DeckRecognizer;
|
||||
import forge.util.Localizer;
|
||||
|
||||
/**
|
||||
* Holds byte values for each color magic has.
|
||||
@@ -158,20 +158,23 @@ public final class MagicColor {
|
||||
}
|
||||
|
||||
public enum Color {
|
||||
WHITE(Constant.WHITE, MagicColor.WHITE, "{W}"),
|
||||
BLUE(Constant.BLUE, MagicColor.BLUE, "{U}"),
|
||||
BLACK(Constant.BLACK, MagicColor.BLACK, "{B}"),
|
||||
RED(Constant.RED, MagicColor.RED, "{R}"),
|
||||
GREEN(Constant.GREEN, MagicColor.GREEN, "{G}"),
|
||||
COLORLESS(Constant.COLORLESS, MagicColor.COLORLESS, "{C}");
|
||||
WHITE(Constant.WHITE, MagicColor.WHITE, "W", "lblWhite"),
|
||||
BLUE(Constant.BLUE, MagicColor.BLUE, "U", "lblBlue"),
|
||||
BLACK(Constant.BLACK, MagicColor.BLACK, "B", "lblBlack"),
|
||||
RED(Constant.RED, MagicColor.RED, "R", "lblRed"),
|
||||
GREEN(Constant.GREEN, MagicColor.GREEN, "G", "lblGreen"),
|
||||
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;
|
||||
|
||||
Color(String name0, byte colormask0, String symbol0) {
|
||||
Color(String name0, byte colormask0, String shortName, String label) {
|
||||
name = name0;
|
||||
colormask = colormask0;
|
||||
symbol = symbol0;
|
||||
this.shortName = shortName;
|
||||
symbol = "{" + shortName + "}";
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public static Color fromByte(final byte color) {
|
||||
@@ -188,13 +191,15 @@ public final class MagicColor {
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getLocalizedName() {
|
||||
//Should probably move some of this logic back here, or at least to a more general location.
|
||||
return DeckRecognizer.getLocalisedMagicColorName(getName());
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public byte getColormask() {
|
||||
public String getLocalizedName() {
|
||||
return Localizer.getInstance().getMessage(label);
|
||||
}
|
||||
|
||||
public byte getColorMask() {
|
||||
return colormask;
|
||||
}
|
||||
public String getSymbol() {
|
||||
@@ -202,7 +207,7 @@ public final class MagicColor {
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
return getLocalizedName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1015,7 +1015,7 @@ public class DeckRecognizer {
|
||||
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
||||
String localisedName1 = magicColor1.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);
|
||||
}
|
||||
|
||||
|
||||
@@ -593,7 +593,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
||||
|
||||
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
|
||||
if(markedColors == null)
|
||||
markedColors = ColorSet.getNullColor();
|
||||
markedColors = ColorSet.NO_COLORS;
|
||||
return new PaperCardFlags(this, markedColors, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
return false;
|
||||
CardSplitType cst = this.cardRules.getSplitType();
|
||||
//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
|
||||
|
||||
@@ -633,7 +633,10 @@ public class BoosterGenerator {
|
||||
System.out.println("Parsing from main code: " + mainCode);
|
||||
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
|
||||
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;
|
||||
|
||||
} 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>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ForgeScript {
|
||||
boolean withSource = property.endsWith("Source");
|
||||
final ColorSet colors;
|
||||
if (withSource && StaticAbilityColorlessDamageSource.colorlessDamageSource(cardState)) {
|
||||
colors = ColorSet.getNullColor();
|
||||
colors = ColorSet.NO_COLORS;
|
||||
} else {
|
||||
colors = cardState.getCard().getColor(cardState);
|
||||
}
|
||||
@@ -412,6 +412,8 @@ public class ForgeScript {
|
||||
return !sa.isPwAbility() && !sa.getRestrictions().isSorcerySpeed();
|
||||
}
|
||||
return true;
|
||||
} else if(property.startsWith("NamedAbility")) {
|
||||
return sa.getName().equals(property.substring(12));
|
||||
} else if (sa.getHostCard() != null) {
|
||||
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
||||
}
|
||||
|
||||
@@ -845,6 +845,8 @@ public class Game {
|
||||
p.revealFaceDownCards();
|
||||
}
|
||||
|
||||
// TODO free any mindslaves
|
||||
|
||||
for (Card c : cards) {
|
||||
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
||||
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
||||
@@ -880,8 +882,6 @@ public class Game {
|
||||
}
|
||||
triggerList.put(c.getZone().getZoneType(), null, c);
|
||||
getAction().ceaseToExist(c, false);
|
||||
// CR 603.2f owner of trigger source lost game
|
||||
getTriggerHandler().clearDelayedTrigger(c);
|
||||
}
|
||||
} else {
|
||||
// return stolen permanents
|
||||
|
||||
@@ -220,10 +220,6 @@ public class GameAction {
|
||||
//copied.setGamePieceType(GamePieceType.COPIED_SPELL);
|
||||
}
|
||||
|
||||
if (c.isTransformed()) {
|
||||
copied.incrementTransformedTimestamp();
|
||||
}
|
||||
|
||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) {
|
||||
copied.setCastSA(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)
|
||||
if (z != null) {
|
||||
z.remove(c);
|
||||
c.setZone(c.getOwner().getZone(ZoneType.None));
|
||||
if (z.is(ZoneType.Battlefield)) {
|
||||
c.runLeavesPlayCommands();
|
||||
}
|
||||
|
||||
@@ -993,9 +993,6 @@ public final class GameActionUtil {
|
||||
oldCard.setBackSide(false);
|
||||
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
||||
oldCard.unanimateBestow();
|
||||
if (ability.isDisturb() || ability.hasParam("CastTransformed")) {
|
||||
oldCard.undoIncrementTransformedTimestamp();
|
||||
}
|
||||
|
||||
if (ability.hasParam("Prototype")) {
|
||||
oldCard.removeCloneState(oldCard.getPrototypeTimestamp());
|
||||
|
||||
@@ -239,6 +239,10 @@ public final class AbilityFactory {
|
||||
spellAbility.putParam("PrecostDesc", "Exhaust — ");
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("Named")) {
|
||||
spellAbility.setName(mapParams.get("Named"));
|
||||
}
|
||||
|
||||
// *********************************************
|
||||
// set universal properties of the SpellAbility
|
||||
|
||||
@@ -359,9 +363,6 @@ public final class AbilityFactory {
|
||||
if (mapParams.containsKey("TargetUnique")) {
|
||||
abTgt.setUniqueTargets(true);
|
||||
}
|
||||
if (mapParams.containsKey("TargetsFromSingleZone")) {
|
||||
abTgt.setSingleZone(true);
|
||||
}
|
||||
if (mapParams.containsKey("TargetsWithoutSameCreatureType")) {
|
||||
abTgt.setWithoutSameCreatureType(true);
|
||||
}
|
||||
|
||||
@@ -1870,6 +1870,14 @@ public class AbilityUtils {
|
||||
}
|
||||
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 {
|
||||
// fallback if ctb isn't a spellability
|
||||
if (sq[0].startsWith("LastStateBattlefield")) {
|
||||
|
||||
@@ -20,14 +20,14 @@ public class AirbendEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder("Airbend ");
|
||||
|
||||
|
||||
Iterable<Card> tgts;
|
||||
if (sa.usesTargeting()) {
|
||||
tgts = getCardsfromTargets(sa);
|
||||
} else { // otherwise add self to list and go from there
|
||||
tgts = sa.knownDetermineDefined(sa.getParam("Defined"));
|
||||
}
|
||||
|
||||
|
||||
sb.append(sa.getParamOrDefault("DefinedDesc", Lang.joinHomogenous(tgts)));
|
||||
sb.append(".");
|
||||
if (Iterables.size(tgts) > 1) {
|
||||
@@ -46,7 +46,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
||||
final Player pl = sa.getActivatingPlayer();
|
||||
|
||||
final CardZoneTable triggerList = CardZoneTable.getSimultaneousInstance(sa);
|
||||
|
||||
|
||||
for (Card c : getTargetCards(sa)) {
|
||||
final Card gameCard = game.getCardState(c, null);
|
||||
// gameCard is LKI in that case, the card is not in game anymore
|
||||
@@ -55,7 +55,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
||||
if (gameCard == null || !c.equalsWithGameTimestamp(gameCard) || gameCard.isPhasedOut()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!gameCard.canExiledBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class AirbendEffect extends SpellAbilityEffect {
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
handleExiledWith(triggerList.allCards(), sa);
|
||||
|
||||
|
||||
pl.triggerElementalBend(TriggerType.Airbend);
|
||||
}
|
||||
|
||||
|
||||
@@ -928,7 +928,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
List<ZoneType> origin = Lists.newArrayList();
|
||||
if (sa.hasParam("Origin")) {
|
||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||
origin.addAll(ZoneType.listValueOf(sa.getParam("Origin")));
|
||||
}
|
||||
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
|
||||
@@ -1474,7 +1474,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (ZoneType.Exile.equals(destination) && sa.hasParam("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();
|
||||
movedCard.addCounter(cType, cAmount, player, table);
|
||||
table.replaceCounterEffect(game, sa, true);
|
||||
|
||||
@@ -287,22 +287,17 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
||||
// need to create a physical card first, i need the original card faces
|
||||
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()) {
|
||||
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,
|
||||
// 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.
|
||||
// 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.
|
||||
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 {
|
||||
copy.setState(copy.getCurrentStateName(), true, true);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ public class DamageResolveEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
CardDamageMap damageMap = sa.getDamageMap();
|
||||
if (damageMap == null) {
|
||||
// this can happen if damagesource was missing
|
||||
return;
|
||||
}
|
||||
CardDamageMap preventMap = sa.getPreventMap();
|
||||
GameEntityCounterTable counterTable = sa.getCounterTable();
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ public class EarthbendEffect extends SpellAbilityEffect {
|
||||
final Game game = source.getGame();
|
||||
final Player pl = sa.getActivatingPlayer();
|
||||
int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("Num", "1"), sa);
|
||||
|
||||
|
||||
long ts = game.getNextTimestamp();
|
||||
|
||||
String desc = "When it dies or is exiled, return it to the battlefield tapped.";
|
||||
@@ -59,17 +59,17 @@ public class EarthbendEffect extends SpellAbilityEffect {
|
||||
c.addNewPT(0, 0, ts, 0);
|
||||
c.addChangedCardTypes(Arrays.asList("Creature"), null, false, EnumSet.noneOf(RemoveType.class), ts, 0, true, false);
|
||||
c.addChangedCardKeywords(Arrays.asList("Haste"), null, false, ts, null);
|
||||
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
c.addCounter(CounterEnumType.P1P1, num, pl, table);
|
||||
table.replaceCounterEffect(game, sa, true);
|
||||
|
||||
|
||||
buildTrigger(sa, c, sbTrigA, "Graveyard");
|
||||
buildTrigger(sa, c, sbTrigB, "Exile");
|
||||
}
|
||||
pl.triggerElementalBend(TriggerType.Earthbend);
|
||||
}
|
||||
|
||||
|
||||
protected void buildTrigger(SpellAbility sa, Card c, String sbTrig, String zone) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
|
||||
@@ -428,6 +428,10 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
tgtSA.getTargetRestrictions().setMandatory(true);
|
||||
}
|
||||
|
||||
if (sa.hasParam("Named")) {
|
||||
tgtSA.setName(sa.getName());
|
||||
}
|
||||
|
||||
// can't be done later
|
||||
if (sa.hasParam("ReplaceGraveyard")) {
|
||||
if (!sa.hasParam("ReplaceGraveyardValid")
|
||||
|
||||
@@ -92,12 +92,11 @@ public class SacrificeEffect extends SpellAbilityEffect {
|
||||
CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa);
|
||||
|
||||
if (valid.equals("Self") && game.getZoneOf(host) != null) {
|
||||
if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield)) {
|
||||
if (!optional || activator.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null)) {
|
||||
if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) {
|
||||
host.addRemembered(host);
|
||||
}
|
||||
if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield) &&
|
||||
(!optional || activator.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null))) {
|
||||
if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) {
|
||||
host.addRemembered(host);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -9,6 +9,7 @@ import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterType;
|
||||
@@ -20,7 +21,6 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.CardTranslation;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
public class TimeTravelEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -41,10 +41,8 @@ public class TimeTravelEffect extends SpellAbilityEffect {
|
||||
final CounterType counterType = CounterEnumType.TIME;
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
FCollection<Card> list = new FCollection<>();
|
||||
|
||||
// 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
|
||||
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 bestowTimestamp = -1;
|
||||
private long transformedTimestamp = 0;
|
||||
private long transformedTimestamp = -1;
|
||||
private long prototypeTimestamp = -1;
|
||||
private long mutatedTimestamp = -1;
|
||||
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 getTransformedTimestamp() { return transformedTimestamp; }
|
||||
public void incrementTransformedTimestamp() { this.transformedTimestamp++; }
|
||||
public void undoIncrementTransformedTimestamp() { this.transformedTimestamp--; }
|
||||
public void setTransformedTimestamp(long ts) { this.transformedTimestamp = ts; }
|
||||
|
||||
// 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
|
||||
@@ -696,7 +695,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false);
|
||||
}
|
||||
incrementTransformedTimestamp();
|
||||
setTransformedTimestamp(ts);
|
||||
|
||||
return retResult;
|
||||
} else if (mode.equals("Flip")) {
|
||||
@@ -1070,7 +1069,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public final boolean isDoubleFaced() {
|
||||
return isTransformable() || isMeldable() || isModal();
|
||||
return isTransformable() || isMeldable();
|
||||
}
|
||||
|
||||
public final boolean isFlipCard() {
|
||||
@@ -1132,7 +1131,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public final boolean isTransformed() {
|
||||
return getTransformedTimestamp() != 0;
|
||||
if (isMeldable() || hasMergedCard()) {
|
||||
return false;
|
||||
}
|
||||
return this.isTransformable() && isBackSide();
|
||||
}
|
||||
|
||||
public final boolean isFlipped() {
|
||||
@@ -2263,7 +2265,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
public final ColorSet getMarkedColors() {
|
||||
if (markedColor == null) {
|
||||
return ColorSet.getNullColor();
|
||||
return ColorSet.NO_COLORS;
|
||||
}
|
||||
return markedColor;
|
||||
}
|
||||
@@ -7638,9 +7640,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
if (sa.isBestow()) {
|
||||
animateBestow();
|
||||
}
|
||||
if (sa.isDisturb() || sa.hasParam("CastTransformed")) {
|
||||
incrementTransformedTimestamp();
|
||||
}
|
||||
if (sa.hasParam("Prototype") && prototypeTimestamp == -1) {
|
||||
long next = game.getNextTimestamp();
|
||||
addCloneState(CardFactory.getCloneStates(this, this, sa), next);
|
||||
|
||||
@@ -131,9 +131,7 @@ public class CardCopyService {
|
||||
|
||||
c.setState(in.getCurrentStateName(), false);
|
||||
c.setRules(in.getRules());
|
||||
if (in.isTransformed()) {
|
||||
c.incrementTransformedTimestamp();
|
||||
}
|
||||
c.setBackSide(in.isBackSide());
|
||||
|
||||
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.
|
||||
// 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());
|
||||
if (copyFrom.isTransformed()) {
|
||||
to.incrementTransformedTimestamp();
|
||||
}
|
||||
} else if (fromIsTransformedCard) {
|
||||
copyState(copyFrom, copyFrom.getCurrentStateName(), to, CardStateName.Original);
|
||||
} else {
|
||||
@@ -274,9 +269,6 @@ public class CardCopyService {
|
||||
}
|
||||
newCopy.setFlipped(copyFrom.isFlipped());
|
||||
newCopy.setBackSide(copyFrom.isBackSide());
|
||||
if (copyFrom.isTransformed()) {
|
||||
newCopy.incrementTransformedTimestamp();
|
||||
}
|
||||
if (newCopy.hasAlternateState()) {
|
||||
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
|
||||
final Card copy = getCard(original.getPaperCard(), controller, id, game);
|
||||
|
||||
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);
|
||||
// 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 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.
|
||||
// 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());
|
||||
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 {
|
||||
copy.setState(copy.getCurrentStateName(), true, true);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ import java.util.*;
|
||||
public class PhaseHandler implements java.io.Serializable {
|
||||
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
|
||||
private PhaseType phase = null;
|
||||
private int turn = 0;
|
||||
@@ -92,6 +95,7 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
|
||||
private final transient Game game;
|
||||
|
||||
|
||||
public PhaseHandler(final Game game0) {
|
||||
game = game0;
|
||||
}
|
||||
@@ -1003,12 +1007,7 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
|
||||
private final static boolean DEBUG_PHASES = false;
|
||||
|
||||
public void startFirstTurn(Player goesFirst) {
|
||||
startFirstTurn(goesFirst, null);
|
||||
}
|
||||
public void startFirstTurn(Player goesFirst, Runnable startGameHook) {
|
||||
StopWatch sw = new StopWatch();
|
||||
|
||||
public void setupFirstTurn(Player goesFirst, Runnable startGameHook) {
|
||||
if (phase != null) {
|
||||
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();
|
||||
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
|
||||
while (!game.isGameOver()) {
|
||||
if (givePriorityToPlayer) {
|
||||
if (DEBUG_PHASES) {
|
||||
sw.start();
|
||||
}
|
||||
while (!game.isGameOver() && !(game.getAge() == GameStage.RestartedByKarn)) {
|
||||
mainLoopStep();
|
||||
}
|
||||
}
|
||||
|
||||
game.fireEvent(new GameEventPlayerPriority(playerTurn, phase, getPriorityPlayer()));
|
||||
List<SpellAbility> chosenSa = null;
|
||||
|
||||
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) {
|
||||
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");
|
||||
public void mainLoopStep() {
|
||||
if (givePriorityToPlayer) {
|
||||
if (DEBUG_PHASES) {
|
||||
sw.start();
|
||||
}
|
||||
|
||||
// actingPlayer is the player who may act
|
||||
// the firstAction is the player who gained Priority First in this segment
|
||||
// of Priority
|
||||
Player nextPlayer = game.getNextPlayerAfter(getPriorityPlayer());
|
||||
game.fireEvent(new GameEventPlayerPriority(playerTurn, phase, getPriorityPlayer()));
|
||||
List<SpellAbility> chosenSa = null;
|
||||
|
||||
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) {
|
||||
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()) {
|
||||
if (playerTurn.hasLost()) {
|
||||
setPriority(game.getNextPlayerAfter(playerTurn));
|
||||
} else {
|
||||
setPriority(playerTurn);
|
||||
}
|
||||
}
|
||||
else if (DEBUG_PHASES) {
|
||||
System.out.print(" >> (no priority given to " + getPriorityPlayer() + ")\n");
|
||||
}
|
||||
|
||||
// end phase
|
||||
givePriorityToPlayer = true;
|
||||
onPhaseEnd();
|
||||
advanceToNextPhase();
|
||||
onPhaseBegin();
|
||||
// actingPlayer is the player who may act
|
||||
// the firstAction is the player who gained Priority First in this segment
|
||||
// of Priority
|
||||
Player nextPlayer = game.getNextPlayerAfter(getPriorityPlayer());
|
||||
|
||||
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
|
||||
if (game.getAge() == GameStage.RestartedByKarn) {
|
||||
setPhase(null);
|
||||
game.updatePhaseForView();
|
||||
game.fireEvent(new GameEventGameRestarted(playerTurn));
|
||||
return;
|
||||
// end phase
|
||||
givePriorityToPlayer = true;
|
||||
onPhaseEnd();
|
||||
advanceToNextPhase();
|
||||
onPhaseBegin();
|
||||
}
|
||||
else if (!game.getStack().hasSimultaneousStackEntries()) {
|
||||
game.getStack().resolveStack();
|
||||
}
|
||||
} else {
|
||||
// pass the priority to other player
|
||||
pPlayerPriority = nextPlayer;
|
||||
}
|
||||
|
||||
// update Priority for all players
|
||||
for (final Player p : game.getPlayers()) {
|
||||
p.setHasPriority(getPriorityPlayer() == p);
|
||||
}
|
||||
// If ever the karn's ultimate resolved
|
||||
if (game.getAge() == GameStage.RestartedByKarn) {
|
||||
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));
|
||||
}
|
||||
|
||||
surveilThisTurn++;
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
runParams.put(AbilityKey.FirstTime, surveilThisTurn == 1);
|
||||
runParams.put(AbilityKey.FirstTime, surveilThisTurn == 0);
|
||||
if (params != null) {
|
||||
runParams.putAll(params);
|
||||
}
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false);
|
||||
|
||||
surveilThisTurn++;
|
||||
}
|
||||
|
||||
public int getSurveilThisTurn() {
|
||||
|
||||
@@ -280,15 +280,8 @@ public class ReplacementHandler {
|
||||
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
|
||||
effectSA = AbilityFactory.getAbility(host, replacementEffect.getParam("ReplaceWith"), replacementEffect);
|
||||
//replacementEffect.setOverridingAbility(effectSA);
|
||||
//effectSA.setTrigger(true);
|
||||
} else if (replacementEffect.getOverridingAbility() != null) {
|
||||
effectSA = replacementEffect.getOverridingAbility();
|
||||
}
|
||||
|
||||
// TODO: the source of replacement effect should be the source of the original effect
|
||||
effectSA = replacementEffect.ensureAbility();
|
||||
if (effectSA != null) {
|
||||
SpellAbility tailend = effectSA;
|
||||
do {
|
||||
|
||||
@@ -174,6 +174,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
private CardZoneTable changeZoneTable;
|
||||
private Map<Player, Integer> loseLifeMap;
|
||||
|
||||
private String name = "";
|
||||
|
||||
public CardCollection getLastStateBattlefield() {
|
||||
return lastStateBattlefield;
|
||||
}
|
||||
@@ -1246,6 +1248,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
clone.mayChooseNewTargets = false;
|
||||
|
||||
clone.triggeringObjects = AbilityKey.newMap(this.triggeringObjects);
|
||||
if (!lki) {
|
||||
clone.replacingObjects = AbilityKey.newMap();
|
||||
}
|
||||
|
||||
clone.setPayCosts(getPayCosts().copy());
|
||||
if (manaPart != null) {
|
||||
@@ -2675,4 +2680,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
public void clearOptionalKeywordAmount() {
|
||||
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
|
||||
private boolean uniqueTargets = false;
|
||||
private boolean singleZone = false;
|
||||
private boolean forEachPlayer = false;
|
||||
private boolean differentControllers = false;
|
||||
private boolean differentCMC = false;
|
||||
@@ -100,7 +99,6 @@ public class TargetRestrictions {
|
||||
this.tgtZone = target.getZone();
|
||||
this.saValidTargeting = target.getSAValidTargeting();
|
||||
this.uniqueTargets = target.isUniqueTargets();
|
||||
this.singleZone = target.isSingleZone();
|
||||
this.forEachPlayer = target.isForEachPlayer();
|
||||
this.differentControllers = target.isDifferentControllers();
|
||||
this.differentCMC = target.isDifferentCMC();
|
||||
@@ -538,12 +536,6 @@ public class TargetRestrictions {
|
||||
public final void setUniqueTargets(final boolean unique) {
|
||||
this.uniqueTargets = unique;
|
||||
}
|
||||
public final boolean isSingleZone() {
|
||||
return this.singleZone;
|
||||
}
|
||||
public final void setSingleZone(final boolean single) {
|
||||
this.singleZone = single;
|
||||
}
|
||||
public boolean isWithoutSameCreatureType() {
|
||||
return withoutSameCreatureType;
|
||||
}
|
||||
|
||||
@@ -392,6 +392,10 @@ public abstract class Trigger extends TriggerReplacementBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (condition == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("LifePaid".equals(condition)) {
|
||||
final SpellAbility trigSA = (SpellAbility) runParams.get(AbilityKey.SpellAbility);
|
||||
if (trigSA != null && trigSA.getAmountLifePaid() <= 0) {
|
||||
@@ -442,7 +446,15 @@ public abstract class Trigger extends TriggerReplacementBase {
|
||||
if (game.getCombat().getAttackersAndDefenders().values().containsAll(attacker.getOpponents())) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -464,7 +464,7 @@ public class TrackableTypes {
|
||||
public static final TrackableType<ColorSet> ColorSetType = new TrackableType<ColorSet>() {
|
||||
@Override
|
||||
public ColorSet getDefaultValue() {
|
||||
return ColorSet.getNullColor();
|
||||
return ColorSet.NO_COLORS;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-android</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-desktop</artifactId>
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.awt.Graphics;
|
||||
import javax.swing.JTable;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.card.MagicColor;
|
||||
import forge.toolbox.CardFaceSymbols;
|
||||
|
||||
public class ColorSetRenderer extends ItemCellRenderer {
|
||||
@@ -33,7 +33,7 @@ public class ColorSetRenderer extends ItemCellRenderer {
|
||||
this.cs = (ColorSet) value;
|
||||
}
|
||||
else {
|
||||
this.cs = ColorSet.getNullColor();
|
||||
this.cs = ColorSet.NO_COLORS;
|
||||
}
|
||||
this.setToolTipText(cs.toString());
|
||||
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 dx = Math.min(elemtWidth + elemtGap, offsetIfNoSpace);
|
||||
|
||||
for (final ManaCostShard s : cs.getOrderedShards()) {
|
||||
CardFaceSymbols.drawManaSymbol(s.getImageKey(), g, x, y);
|
||||
for (final MagicColor.Color s : cs.getOrderedColors()) {
|
||||
CardFaceSymbols.drawManaSymbol(s.getShortName(), g, x, y);
|
||||
x += dx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.StringTokenizer;
|
||||
import com.esotericsoftware.minlog.Log;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
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) {
|
||||
for (final ManaCostShard s : colorSet.getOrderedShards()) {
|
||||
if (DECK_COLORSET.get(s.getImageKey())!=null)
|
||||
FSkin.drawImage(g, DECK_COLORSET.get(s.getImageKey()), x, y, imageSize, imageSize);
|
||||
for (final MagicColor.Color s : colorSet.getOrderedColors()) {
|
||||
if (DECK_COLORSET.get(s.getShortName())!=null)
|
||||
FSkin.drawImage(g, DECK_COLORSET.get(s.getShortName()), x, y, imageSize, imageSize);
|
||||
if (!vertical)
|
||||
x += imageSize;
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.GuiDesktop;
|
||||
import forge.StaticData;
|
||||
import forge.ai.AIOption;
|
||||
import forge.ai.AITest;
|
||||
import forge.ai.LobbyPlayerAi;
|
||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||
import forge.deck.Deck;
|
||||
@@ -18,21 +16,12 @@ 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.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 SimulationTest {
|
||||
private static boolean initialized = false;
|
||||
public class SimulationTest extends AITest {
|
||||
|
||||
public Game resetGame() {
|
||||
// need to be done after FModel.initialize, or the Localizer isn't loaded yet
|
||||
@@ -53,19 +42,6 @@ public class SimulationTest {
|
||||
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) {
|
||||
return new GameSimulator(new SimulationController(new Score(0)) {
|
||||
@@ -75,106 +51,4 @@ public class SimulationTest {
|
||||
}
|
||||
}, 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>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-ios</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile-dev</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-gui-mobile</artifactId>
|
||||
|
||||
@@ -101,6 +101,7 @@ public class Forge implements ApplicationListener {
|
||||
public static boolean allowCardBG = false;
|
||||
public static boolean altPlayerLayout = false;
|
||||
public static boolean altZoneTabs = false;
|
||||
public static String altZoneTabMode = "Off";
|
||||
public static boolean animatedCardTapUntap = false;
|
||||
public static String enableUIMask = "Crop";
|
||||
public static String selector = "Default";
|
||||
@@ -222,7 +223,7 @@ public class Forge implements ApplicationListener {
|
||||
reversedPrompt = getForgePreferences().getPrefBoolean(FPref.UI_REVERSE_PROMPT_BUTTON);
|
||||
autoAIDeckSelection = getForgePreferences().getPrefBoolean(FPref.UI_AUTO_AIDECK_SELECTION);
|
||||
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);
|
||||
enableUIMask = getForgePreferences().getPref(FPref.UI_ENABLE_BORDER_MASKING);
|
||||
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));
|
||||
}
|
||||
}
|
||||
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() {
|
||||
//Classic Mode Various Screen GUI are not yet supported, needs control mapping for each screens
|
||||
if (isMobileAdventureMode) {
|
||||
@@ -337,8 +349,11 @@ public class Forge implements ApplicationListener {
|
||||
GuiBase.setIsAdventureMode(true);
|
||||
advStartup = false;
|
||||
isMobileAdventureMode = true;
|
||||
if (GuiBase.isAndroid()) //force it for adventure mode
|
||||
altZoneTabs = true;
|
||||
//force it for adventure mode if the prefs is not updated from boolean value to string value
|
||||
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
|
||||
setCursor(null, "0");
|
||||
if (!GuiBase.isAndroid() || !getDeviceAdapter().getGamepads().isEmpty())
|
||||
@@ -755,7 +770,7 @@ public class Forge implements ApplicationListener {
|
||||
isMobileAdventureMode = false;
|
||||
GuiBase.setIsAdventureMode(false);
|
||||
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());
|
||||
clearTransitionScreen();
|
||||
openHomeDefault();
|
||||
@@ -1468,7 +1483,7 @@ public class Forge implements ApplicationListener {
|
||||
|
||||
boolean handled;
|
||||
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 {
|
||||
handled = pan(mouseMovedX, mouseMovedY, 0, -Utils.AVG_FINGER_HEIGHT * amountY, true);
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ public class AdventureEventData implements Serializable {
|
||||
Random placeholder = MyRandom.getRandom();
|
||||
MyRandom.setRandom(getEventRandom());
|
||||
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();
|
||||
assignPlayerNames(draft);
|
||||
}
|
||||
@@ -607,15 +607,18 @@ public class AdventureEventData implements Serializable {
|
||||
description += "Block: " + getCardBlock() + "\n";
|
||||
description += "Boosters: " + String.join(", ", packConfiguration) + "\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) {
|
||||
description += "- Bronze Challenge Coin [][+BronzeChallengeCoin][BLACK]\n\n";
|
||||
} else if (eventRules.acceptsSilverChallengeCoin) {
|
||||
description += "- Silver Challenge Coin [][+SilverChallengeCoin][BLACK]\n\n";
|
||||
} else if (eventRules.acceptsChallengeCoin) {
|
||||
description += "- Gold Challenge Coin [][+ChallengeCoin][BLACK]\n\n";
|
||||
} else {
|
||||
description += "\n";
|
||||
|
||||
if (eventStatus == AdventureEventController.EventStatus.Available) {
|
||||
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) {
|
||||
description += "- Bronze Challenge Coin [][+BronzeChallengeCoin][BLACK]\n\n";
|
||||
} else if (eventRules.acceptsSilverChallengeCoin) {
|
||||
description += "- Silver Challenge Coin [][+SilverChallengeCoin][BLACK]\n\n";
|
||||
} else if (eventRules.acceptsChallengeCoin) {
|
||||
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());
|
||||
} else if (format.equals(AdventureEventController.EventFormat.Jumpstart)) {
|
||||
|
||||
@@ -193,6 +193,8 @@ public class DuelScene extends ForgeScene {
|
||||
public void enter() {
|
||||
GameHUD.getInstance().unloadAudio();
|
||||
GameType mainGameType;
|
||||
boolean isDeckMissing = false;
|
||||
String isDeckMissingMsg = "";
|
||||
if (eventData != null && eventData.eventRules != null) {
|
||||
mainGameType = eventData.eventRules.gameType;
|
||||
} else {
|
||||
@@ -295,6 +297,12 @@ public class DuelScene extends ForgeScene {
|
||||
} else {
|
||||
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);
|
||||
|
||||
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);
|
||||
MatchController.instance.setGameView(hostedMatch.getGameView());
|
||||
boolean showMessages = enemy.getData().boss || (enemy.getData().copyPlayerDeck && Current.player().isUsingCustomDeck());
|
||||
if (chaosBattle || showMessages) {
|
||||
if (chaosBattle || showMessages || isDeckMissing) {
|
||||
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);
|
||||
matchOverlay = new LoadingOverlay(() -> FThreads.delayInEDT(300, () -> FThreads.invokeInEdtNowOrLater(() ->
|
||||
bossDialogue.show())), false, true);
|
||||
|
||||
@@ -259,7 +259,7 @@ public class SettingsScene extends UIScene {
|
||||
|
||||
if (!GuiBase.isAndroid()) {
|
||||
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 {
|
||||
addCheckBox(Forge.getLocalizer().getMessage("lblLandscapeMode") + " (" +
|
||||
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) {
|
||||
final float dx = imageSize;
|
||||
|
||||
for (final ManaCostShard s : colorSet.getOrderedShards()) {
|
||||
drawSymbol(s.getImageKey(), g, x, y, imageSize, imageSize);
|
||||
for (final MagicColor.Color s : colorSet.getOrderedColors()) {
|
||||
drawSymbol(s.getShortName(), g, x, y, imageSize, imageSize);
|
||||
if (!vertical)
|
||||
x += dx;
|
||||
else
|
||||
|
||||
@@ -49,6 +49,24 @@ public class CardImageRenderer {
|
||||
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() {
|
||||
//force static fields to be updated the next time a card image is rendered
|
||||
prevImageWidth = 0;
|
||||
@@ -143,7 +161,7 @@ public class CardImageRenderer {
|
||||
x += outerBorderThickness;
|
||||
y += 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
|
||||
Color[] headerColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.NAME_BOX_TINT);
|
||||
@@ -158,15 +176,15 @@ public class CardImageRenderer {
|
||||
|
||||
float artWidth = w - 2 * artInset;
|
||||
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 textBoxHeight = h - headerHeight - artHeight - typeBoxHeight - outerBorderThickness - artInset;
|
||||
|
||||
if (state.isCreature() || state.isPlaneswalker() || state.hasPrintedPT() || state.isBattle()) {
|
||||
ptBoxHeight = 2 * PT_FONT.getCapHeight();
|
||||
ptBoxHeight = 2 * getCapHeight(PT_FONT);
|
||||
}
|
||||
//space for artist
|
||||
textBoxHeight -= 2 * PT_FONT.getCapHeight();
|
||||
textBoxHeight -= 2 * getCapHeight(PT_FONT);
|
||||
PaperCard paperCard = null;
|
||||
try {
|
||||
paperCard = ImageUtil.getPaperCardFromImageKey(state.getImageKey());
|
||||
@@ -246,7 +264,7 @@ public class CardImageRenderer {
|
||||
}
|
||||
//draw artist
|
||||
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) {
|
||||
if (colors == null)
|
||||
@@ -296,7 +314,7 @@ public class CardImageRenderer {
|
||||
manaCostWidth = CardFaceSymbols.getWidth(otherManaCost, manaSymbolSize) + HEADER_PADDING;
|
||||
CardFaceSymbols.drawManaCost(g, otherManaCost, x + w - manaCostWidth, y + (h - manaSymbolSize) / 2, manaSymbolSize);
|
||||
//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);
|
||||
}
|
||||
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, manaSymbolSize) + HEADER_PADDING;
|
||||
@@ -517,7 +535,7 @@ public class CardImageRenderer {
|
||||
} else {
|
||||
//left
|
||||
//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);
|
||||
drawTypeLine(g, card.getState(true), canShow, altcolors, x, y + typeBoxHeight, w - (w / 2), typeBoxHeight, noText, true, true);
|
||||
float mod = (typeBoxHeight + typeBoxHeight);
|
||||
@@ -714,7 +732,7 @@ public class CardImageRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
float padding = TEXT_FONT.getCapHeight() * 0.75f;
|
||||
float padding = getCapHeight(TEXT_FONT) * 0.75f;
|
||||
x += padding;
|
||||
y += padding;
|
||||
w -= 2 * padding;
|
||||
@@ -747,15 +765,15 @@ public class CardImageRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
float padding = Math.round(PT_FONT.getCapHeight() / 4);
|
||||
float padding = Math.round(getCapHeight(PT_FONT) / 4);
|
||||
float totalPieceWidth = -padding;
|
||||
float[] pieceWidths = new float[pieces.size()];
|
||||
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;
|
||||
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);
|
||||
x += w - boxWidth;
|
||||
@@ -951,14 +969,14 @@ public class CardImageRenderer {
|
||||
x += outerBorderThickness;
|
||||
y += 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
|
||||
Color[] nameBoxColors = FSkinColor.tintColors(Color.WHITE, colors, CardRenderer.NAME_BOX_TINT);
|
||||
drawDetailsNameBox(g, card, state, canShow, nameBoxColors, x, y, w, cardNameBoxHeight);
|
||||
|
||||
float innerBorderThickness = outerBorderThickness / 2;
|
||||
float ptBoxHeight = 2 * PT_FONT.getCapHeight();
|
||||
float ptBoxHeight = 2 * getCapHeight(PT_FONT);
|
||||
float textBoxHeight = h - cardNameBoxHeight - ptBoxHeight - outerBorderThickness - 3 * innerBorderThickness;
|
||||
|
||||
y += cardNameBoxHeight + innerBorderThickness;
|
||||
@@ -1141,7 +1159,7 @@ public class CardImageRenderer {
|
||||
float padding = h / 8;
|
||||
|
||||
//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
|
||||
float manaCostWidth = 0;
|
||||
@@ -1154,7 +1172,7 @@ public class CardImageRenderer {
|
||||
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);
|
||||
//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);
|
||||
}
|
||||
manaCostWidth += CardFaceSymbols.getWidth(mainManaCost, MANA_SYMBOL_SIZE) + HEADER_PADDING;
|
||||
@@ -1168,7 +1186,7 @@ public class CardImageRenderer {
|
||||
|
||||
//draw type and set label for card
|
||||
y += h;
|
||||
h = 2 * TYPE_FONT.getCapHeight();
|
||||
h = 2 * getCapHeight(TYPE_FONT);
|
||||
|
||||
String set = state.getSetCode();
|
||||
CardRarity rarity = state.getRarity();
|
||||
@@ -1189,7 +1207,7 @@ public class CardImageRenderer {
|
||||
fillColorBackground(g, colors, 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
|
||||
x += padX;
|
||||
y += padY;
|
||||
@@ -1202,8 +1220,8 @@ public class CardImageRenderer {
|
||||
float idWidth = 0;
|
||||
if (canShow) {
|
||||
String idText = CardDetailUtil.formatCardId(state);
|
||||
g.drawText(idText, TYPE_FONT, idForeColor, x, y + TYPE_FONT.getCapHeight() / 2, w, h, false, Align.left, false);
|
||||
idWidth = TYPE_FONT.getBounds(idText).width;
|
||||
g.drawText(idText, TYPE_FONT, idForeColor, x, y + getCapHeight(TYPE_FONT) / 2, w, h, false, Align.left, false);
|
||||
idWidth = getBoundsWidth(idText, TYPE_FONT);
|
||||
}
|
||||
|
||||
String ptText = CardDetailUtil.formatPrimaryCharacteristic(state, canShow);
|
||||
@@ -1212,7 +1230,7 @@ public class CardImageRenderer {
|
||||
}
|
||||
|
||||
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,
|
||||
w - idWidth - padding); //prevent box overlapping ID
|
||||
x += w - boxWidth;
|
||||
|
||||
@@ -11,7 +11,7 @@ public class ColorSetImage implements FImage {
|
||||
|
||||
public ColorSetImage(ColorSet colorSet0) {
|
||||
colorSet = colorSet0;
|
||||
shardCount = colorSet.getOrderedShards().length;
|
||||
shardCount = colorSet.getOrderedColors().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,7 +16,6 @@ import forge.assets.*;
|
||||
import forge.assets.FSkinColor.Colors;
|
||||
import forge.card.*;
|
||||
import forge.card.CardRenderer.CardStackPosition;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.deck.*;
|
||||
import forge.deck.io.DeckPreferences;
|
||||
import forge.game.card.CardView;
|
||||
@@ -37,7 +36,6 @@ import forge.util.Utils;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static forge.assets.FSkin.getDefaultSkinFile;
|
||||
@@ -1100,10 +1098,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
deckSelectMode = true;
|
||||
deckProxy = (DeckProxy) item;
|
||||
}
|
||||
if (item instanceof PaperCard) {
|
||||
if (item instanceof PaperCard pc) {
|
||||
showRanking = itemManager.getShowRanking() && FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_OVERLAY_DRAFT_RANKING);
|
||||
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));
|
||||
if (draftRank >= 90) {
|
||||
draftRankImage = FSkinImage.DRAFTRANK_S;
|
||||
@@ -1115,10 +1113,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
draftRankImage = FSkinImage.DRAFTRANK_C;
|
||||
}
|
||||
}
|
||||
if (((PaperCard) item).getMarkedColors() != null) {
|
||||
markedColors = Arrays.stream(((PaperCard) item).getMarkedColors().getOrderedShards())
|
||||
.map(ManaCostShard::toString)
|
||||
.collect(Collectors.joining());
|
||||
if (pc.getMarkedColors() != null) {
|
||||
markedColors = pc.getMarkedColors().toString();
|
||||
}
|
||||
}
|
||||
if(fnPrice != null) {
|
||||
|
||||
@@ -174,10 +174,13 @@ public class MatchController extends AbstractGuiGame {
|
||||
final VPlayerPanel playerPanel = new VPlayerPanel(p, isLocal || noHumans, players.size());
|
||||
if (isLocal && !init) {
|
||||
playerPanels.add(0, playerPanel); //ensure local player always first among player panels
|
||||
playerPanel.setBottomPlayer(true);
|
||||
init = true;
|
||||
}
|
||||
else {
|
||||
playerPanels.add(playerPanel);
|
||||
if (playerPanel.equals(playerPanels.get(0)))
|
||||
playerPanel.setBottomPlayer(true);
|
||||
}
|
||||
}
|
||||
view = new MatchScreen(playerPanels);
|
||||
|
||||
@@ -53,6 +53,12 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
rotateCards180 = b0;
|
||||
}
|
||||
|
||||
private float getCardStackOffset() {
|
||||
if (Forge.isHorizontalTabLayout())
|
||||
return 0.125f;
|
||||
return CARD_STACK_OFFSET;
|
||||
}
|
||||
|
||||
protected void refreshCardPanels(Iterable<CardView> model) {
|
||||
clear();
|
||||
|
||||
@@ -78,7 +84,9 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean b0) {
|
||||
if (isVisible() == b0) { return; }
|
||||
if (isVisible() == b0) {
|
||||
return;
|
||||
}
|
||||
super.setVisible(b0);
|
||||
if (b0) { //when zone becomes visible, ensure display area of panels is updated and panels layed out
|
||||
for (CardAreaPanel pnl : cardPanels.get()) {
|
||||
@@ -146,7 +154,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
CardAreaPanel attachedPanel = attachedPanels.get(i);
|
||||
if (attachedPanel != null) {
|
||||
int count = addCards(attachedPanel, x, y, cardWidth, cardHeight);
|
||||
x += count * cardWidth * CARD_STACK_OFFSET;
|
||||
x += count * cardWidth * getCardStackOffset();
|
||||
totalCount += count;
|
||||
}
|
||||
}
|
||||
@@ -156,7 +164,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
cardPanel.setBounds(x, y, cardWidth, cardHeight);
|
||||
|
||||
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);
|
||||
}
|
||||
return totalCount + 1;
|
||||
@@ -165,6 +173,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
protected float getCardWidth(float cardHeight) {
|
||||
return (cardHeight - 2 * FCardPanel.PADDING) / FCardPanel.ASPECT_RATIO + 2 * FCardPanel.PADDING; //ensure aspect ratio maintained after padding applied
|
||||
}
|
||||
|
||||
protected float getCardHeight(float cardWidth) {
|
||||
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())) {
|
||||
if (cardPanel != null) {
|
||||
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
|
||||
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
|
||||
if (index >= 0 && index < orderedCards.get().size())
|
||||
return MatchController.instance.getGameController().getActivateDescription(orderedCards.get().get(index));
|
||||
@@ -266,9 +275,11 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
public CardAreaPanel getAttachedToPanel() {
|
||||
return attachedToPanel;
|
||||
}
|
||||
|
||||
public void setAttachedToPanel(final CardAreaPanel attachedToPanel0) {
|
||||
attachedToPanel = attachedToPanel0;
|
||||
}
|
||||
|
||||
public List<CardAreaPanel> getAttachedPanels() {
|
||||
if (attachedPanels == null) {
|
||||
attachedPanels = new ArrayList<>();
|
||||
@@ -278,15 +289,19 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
}
|
||||
return attachedPanels;
|
||||
}
|
||||
|
||||
public CardAreaPanel getNextPanelInStack() {
|
||||
return nextPanelInStack;
|
||||
}
|
||||
|
||||
public void setNextPanelInStack(CardAreaPanel nextPanelInStack0) {
|
||||
nextPanelInStack = nextPanelInStack0;
|
||||
}
|
||||
|
||||
public CardAreaPanel getPrevPanelInStack() {
|
||||
return prevPanelInStack;
|
||||
}
|
||||
|
||||
public void setPrevPanelInStack(CardAreaPanel prevPanelInStack0) {
|
||||
prevPanelInStack = prevPanelInStack0;
|
||||
}
|
||||
@@ -324,8 +339,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
attachedPanels.remove(CardAreaPanel.get(getAttachedto));
|
||||
setAttachedToPanel(null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setAttachedToPanel(null);
|
||||
}
|
||||
}
|
||||
@@ -423,7 +437,9 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
}
|
||||
|
||||
public void showZoom() {
|
||||
if (displayArea == null) { return; }
|
||||
if (displayArea == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<CardView> cards = displayArea.orderedCards.get();
|
||||
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) {
|
||||
if (!selectOtherCardsInStack) { return null; }
|
||||
if (!selectOtherCardsInStack) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//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<>();
|
||||
|
||||
@@ -490,7 +510,9 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
|
||||
public Vector2 getTargetingArrowOrigin() {
|
||||
//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());
|
||||
}
|
||||
@@ -517,8 +539,7 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
g.startRotateTransform(x + w / 2, y + h / 2, 180);
|
||||
super.draw(g);
|
||||
g.endTransform();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
super.draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.screens.match.views;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import forge.Forge;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.card.CardView.CardStateView;
|
||||
import forge.game.player.PlayerView;
|
||||
@@ -33,6 +34,7 @@ public class VField extends FContainer {
|
||||
public boolean isFlipped() {
|
||||
return flipped;
|
||||
}
|
||||
|
||||
public void setFlipped(boolean flipped0) {
|
||||
flipped = flipped0;
|
||||
}
|
||||
@@ -61,7 +63,9 @@ public class VField extends FContainer {
|
||||
clear();
|
||||
|
||||
Iterable<CardView> model = player.getBattlefield();
|
||||
if (model == null) { return; }
|
||||
if (model == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (CardView card : model) {
|
||||
CardAreaPanel cardPanel = CardAreaPanel.get(card);
|
||||
@@ -84,18 +88,15 @@ public class VField extends FContainer {
|
||||
if (!tryStackCard(card, creatures)) {
|
||||
creatures.add(card);
|
||||
}
|
||||
}
|
||||
else if (details.isLand()) {
|
||||
} else if (details.isLand()) {
|
||||
if (!tryStackCard(card, lands)) {
|
||||
lands.add(card);
|
||||
}
|
||||
}
|
||||
else if (details.isArtifact() && (details.isContraption() || details.isAttraction())) {
|
||||
} else if (details.isArtifact() && (details.isContraption() || details.isAttraction())) {
|
||||
if (contraptions == null)
|
||||
contraptions = new ArrayList<>();
|
||||
contraptions.add(card); //Arrange these later.
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (!tryStackCard(card, otherPermanents)) {
|
||||
otherPermanents.add(card);
|
||||
}
|
||||
@@ -103,7 +104,7 @@ public class VField extends FContainer {
|
||||
}
|
||||
}
|
||||
|
||||
if(contraptions != null) {
|
||||
if (contraptions != null) {
|
||||
contraptions = arrangeContraptions(contraptions);
|
||||
otherPermanents.addAll(contraptions);
|
||||
}
|
||||
@@ -111,8 +112,7 @@ public class VField extends FContainer {
|
||||
if (creatures.isEmpty()) {
|
||||
row1.refreshCardPanels(otherPermanents);
|
||||
row2.refreshCardPanels(lands);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
row1.refreshCardPanels(creatures);
|
||||
lands.addAll(otherPermanents);
|
||||
row2.refreshCardPanels(lands);
|
||||
@@ -173,13 +173,14 @@ public class VField extends FContainer {
|
||||
TreeSet<CardView> row = new TreeSet<>((c1, c2) -> {
|
||||
//Order is sprocket-less cards, then sprocket 1, sprocket 2, sprocket 3, and finally attractions.
|
||||
int sprocket1 = c1.getSprocket(), sprocket2 = c2.getSprocket();
|
||||
if(sprocket1 == 0 && c1.getCurrentState().isAttraction())
|
||||
if (sprocket1 == 0 && c1.getCurrentState().isAttraction())
|
||||
sprocket1 = 4;
|
||||
if(sprocket2 == 0 && c2.getCurrentState().isAttraction())
|
||||
if (sprocket2 == 0 && c2.getCurrentState().isAttraction())
|
||||
sprocket2 = 4;
|
||||
return sprocket1 - sprocket2;
|
||||
});
|
||||
outer: for (CardView card : contraptions) {
|
||||
outer:
|
||||
for (CardView card : contraptions) {
|
||||
if (card.hasCardAttachments()) {
|
||||
row.add(card); //Don't stack contraptions or attractions with attachments.
|
||||
continue;
|
||||
@@ -187,7 +188,7 @@ public class VField extends FContainer {
|
||||
if (card.getCurrentState().isAttraction()) {
|
||||
//Stack attractions with other attractions.
|
||||
for (CardView c : row) {
|
||||
if(c.getCurrentState().isAttraction() && !c.hasCardAttachments()) {
|
||||
if (c.getCurrentState().isAttraction() && !c.hasCardAttachments()) {
|
||||
stackOnto(card, c);
|
||||
continue outer;
|
||||
}
|
||||
@@ -240,13 +241,17 @@ public class VField extends FContainer {
|
||||
if (flipped) {
|
||||
y1 = cardSize;
|
||||
y2 = 0;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
y1 = 0;
|
||||
y2 = cardSize;
|
||||
}
|
||||
row1.setBounds(0, y1, width-fieldModifier, cardSize);
|
||||
row2.setBounds(0, y2, (width - commandZoneWidth)-fieldModifier, cardSize);
|
||||
if (Forge.isHorizontalTabLayout()) {
|
||||
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 {
|
||||
@@ -270,13 +275,14 @@ public class VField extends FContainer {
|
||||
public void setNextSelected(int val) {
|
||||
this.selected++;
|
||||
if (this.selected >= this.getChildCount())
|
||||
this.selected = this.getChildCount()-1;
|
||||
this.selected = this.getChildCount() - 1;
|
||||
if (this.selectedChild != null)
|
||||
this.selectedChild.setHovered(false);
|
||||
this.selectedChild = getChildAt(this.selected);
|
||||
this.selectedChild.setHovered(true);
|
||||
MatchScreen.setPotentialListener(Arrays.asList(this.selectedChild));
|
||||
}
|
||||
|
||||
public void selectCurrent() {
|
||||
if (this.selectedChild != null) {
|
||||
this.selectedChild.setHovered(true);
|
||||
@@ -285,6 +291,7 @@ public class VField extends FContainer {
|
||||
this.setNextSelected(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void unselectCurrent() {
|
||||
if (this.selectedChild != null) {
|
||||
this.selectedChild.setHovered(false);
|
||||
|
||||
@@ -28,6 +28,7 @@ public class VManaPool extends VDisplayArea {
|
||||
return FSkinColor.get(Colors.ADV_CLR_TEXT);
|
||||
return FSkinColor.get(Colors.CLR_TEXT);
|
||||
}
|
||||
|
||||
private static final FSkinFont FONT = FSkinFont.get(16);
|
||||
|
||||
private final PlayerView player;
|
||||
@@ -37,7 +38,7 @@ public class VManaPool extends VDisplayArea {
|
||||
public VManaPool(PlayerView 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_U, MagicColor.BLUE);
|
||||
addManaLabel(FSkinProp.IMG_MANA_B, MagicColor.BLACK);
|
||||
@@ -69,7 +70,7 @@ public class VManaPool extends VDisplayArea {
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
|
||||
if (Forge.isLandscapeMode()) {
|
||||
if (Forge.isLandscapeMode() && !Forge.altZoneTabs) {
|
||||
float labelWidth = visibleWidth / 2;
|
||||
float labelHeight = visibleHeight / 3;
|
||||
|
||||
@@ -79,13 +80,11 @@ public class VManaPool extends VDisplayArea {
|
||||
if (++count % 2 == 0) {
|
||||
x = 0;
|
||||
y += labelHeight;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
x += labelWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
float labelWidth = visibleWidth / manaLabels.size();
|
||||
float labelHeight = visibleHeight;
|
||||
|
||||
@@ -113,14 +112,16 @@ public class VManaPool extends VDisplayArea {
|
||||
activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
if(!(MatchController.instance.getGameController() instanceof PlayerControllerHuman))
|
||||
if (!(MatchController.instance.getGameController() instanceof PlayerControllerHuman))
|
||||
return;
|
||||
PlayerControllerHuman controller = (PlayerControllerHuman) MatchController.instance.getGameController();
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean flick(float x, float y) {
|
||||
if (player.isLobbyPlayer(GamePlayerUtil.getGuiPlayer())) {
|
||||
@@ -146,17 +147,18 @@ public class VManaPool extends VDisplayArea {
|
||||
if (h > maxImageHeight) {
|
||||
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()) {
|
||||
h /= 2;
|
||||
w = image.getWidth() * h / image.getHeight();
|
||||
w = image.getWidth() * h * modifier / image.getHeight();
|
||||
}
|
||||
float x = (getWidth() - w) / 2;
|
||||
float y = gapY + (maxImageHeight - h) / 2;
|
||||
|
||||
if (isHovered())
|
||||
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;
|
||||
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 INFO_FONT = FSkinFont.get(12);
|
||||
private static final FSkinFont INFO2_FONT = FSkinFont.get(14);
|
||||
|
||||
private static FSkinColor getInfoForeColor() {
|
||||
if (Forge.isMobileAdventureMode)
|
||||
return FSkinColor.get(Colors.ADV_CLR_TEXT);
|
||||
return FSkinColor.get(Colors.CLR_TEXT);
|
||||
}
|
||||
|
||||
private static FSkinColor getDisplayAreaBackColor() {
|
||||
if (Forge.isMobileAdventureMode)
|
||||
return FSkinColor.get(Colors.ADV_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() {
|
||||
if (Forge.isMobileAdventureMode)
|
||||
return FSkinColor.get(Colors.ADV_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_Y = Utils.scale(2);
|
||||
|
||||
@@ -79,11 +89,13 @@ public class VPlayerPanel extends FContainer {
|
||||
private boolean forMultiPlayer = false;
|
||||
public int adjustHeight = 1;
|
||||
private int selected = 0;
|
||||
private boolean isBottomPlayer = false;
|
||||
|
||||
public VPlayerPanel(PlayerView player0, boolean showHand, int playerCount) {
|
||||
player = player0;
|
||||
phaseIndicator = add(new VPhaseIndicator());
|
||||
|
||||
if(playerCount > 2){
|
||||
if (playerCount > 2) {
|
||||
forMultiPlayer = true;
|
||||
avatarHeight *= 0.5f;
|
||||
//displayAreaHeightFactor *= 0.7f;
|
||||
@@ -116,6 +128,10 @@ public class VPlayerPanel extends FContainer {
|
||||
return player;
|
||||
}
|
||||
|
||||
public void setBottomPlayer(boolean val) {
|
||||
isBottomPlayer = val;
|
||||
}
|
||||
|
||||
public void addZoneDisplay(ZoneType zoneType) {
|
||||
VZoneDisplay zoneDisplay = add(new VZoneDisplay(player, zoneType));
|
||||
InfoTabZone zoneTab = add(new InfoTabZone(zoneDisplay, zoneType));
|
||||
@@ -124,22 +140,22 @@ public class VPlayerPanel extends FContainer {
|
||||
}
|
||||
|
||||
public static FSkinImage iconFromZone(ZoneType zoneType) {
|
||||
switch (zoneType) {
|
||||
case Hand: return Forge.hdbuttons ? FSkinImage.HDHAND : FSkinImage.HAND;
|
||||
case Library: return Forge.hdbuttons ? FSkinImage.HDLIBRARY : FSkinImage.LIBRARY;
|
||||
case Graveyard: return Forge.hdbuttons ? FSkinImage.HDGRAVEYARD : FSkinImage.GRAVEYARD;
|
||||
case Exile: return Forge.hdbuttons ? FSkinImage.HDEXILE : FSkinImage.EXILE;
|
||||
case Sideboard: return Forge.hdbuttons ? FSkinImage.HDSIDEBOARD :FSkinImage.SIDEBOARD;
|
||||
case Flashback: return Forge.hdbuttons ? FSkinImage.HDFLASHBACK :FSkinImage.FLASHBACK;
|
||||
case Command: return FSkinImage.COMMAND;
|
||||
case PlanarDeck: return FSkinImage.PLANAR;
|
||||
case SchemeDeck: return FSkinImage.SCHEME;
|
||||
case AttractionDeck: return FSkinImage.ATTRACTION;
|
||||
case ContraptionDeck: return FSkinImage.CONTRAPTION;
|
||||
case Ante: return FSkinImage.ANTE;
|
||||
case Junkyard: return FSkinImage.JUNKYARD;
|
||||
default: return FSkinImage.HDLIBRARY;
|
||||
}
|
||||
return switch (zoneType) {
|
||||
case Hand -> Forge.hdbuttons ? FSkinImage.HDHAND : FSkinImage.HAND;
|
||||
case Library -> Forge.hdbuttons ? FSkinImage.HDLIBRARY : FSkinImage.LIBRARY;
|
||||
case Graveyard -> Forge.hdbuttons ? FSkinImage.HDGRAVEYARD : FSkinImage.GRAVEYARD;
|
||||
case Exile -> Forge.hdbuttons ? FSkinImage.HDEXILE : FSkinImage.EXILE;
|
||||
case Sideboard -> Forge.hdbuttons ? FSkinImage.HDSIDEBOARD : FSkinImage.SIDEBOARD;
|
||||
case Flashback -> Forge.hdbuttons ? FSkinImage.HDFLASHBACK : FSkinImage.FLASHBACK;
|
||||
case Command -> FSkinImage.COMMAND;
|
||||
case PlanarDeck -> FSkinImage.PLANAR;
|
||||
case SchemeDeck -> FSkinImage.SCHEME;
|
||||
case AttractionDeck -> FSkinImage.ATTRACTION;
|
||||
case ContraptionDeck -> FSkinImage.CONTRAPTION;
|
||||
case Ante -> FSkinImage.ANTE;
|
||||
case Junkyard -> FSkinImage.JUNKYARD;
|
||||
default -> FSkinImage.HDLIBRARY;
|
||||
};
|
||||
}
|
||||
|
||||
public Iterable<InfoTab> getTabs() {
|
||||
@@ -151,12 +167,12 @@ public class VPlayerPanel extends FContainer {
|
||||
}
|
||||
|
||||
public void resetZoneTabs() {
|
||||
for(InfoTab tab : tabs)
|
||||
for (InfoTab tab : tabs)
|
||||
tab.reset();
|
||||
}
|
||||
|
||||
public void setSelectedZone(ZoneType zoneType) {
|
||||
if(zoneTabs.containsKey(zoneType))
|
||||
if (zoneTabs.containsKey(zoneType))
|
||||
setSelectedTab(zoneTabs.get(zoneType));
|
||||
else {
|
||||
extraTab.setActiveZone(zoneType);
|
||||
@@ -189,6 +205,7 @@ public class VPlayerPanel extends FContainer {
|
||||
MatchController.getView().revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setNextSelectedTab(boolean change) {
|
||||
if (change) {
|
||||
if (selectedTab != null) {
|
||||
@@ -207,13 +224,13 @@ public class VPlayerPanel extends FContainer {
|
||||
int numExtraTabs = extraTab.displayAreas.size();
|
||||
if (selected < 0 || selected >= numTabs + numExtraTabs)
|
||||
selected = 0;
|
||||
if(selected >= numTabs) {
|
||||
if (selected >= numTabs) {
|
||||
extraTab.setActiveZoneByIndex(selected - numTabs);
|
||||
setSelectedTab(extraTab);
|
||||
}
|
||||
else
|
||||
} else
|
||||
setSelectedTab(tabs.get(selected));
|
||||
}
|
||||
|
||||
public void closeSelectedTab() {
|
||||
if (selectedTab != null) {
|
||||
selectedTab.setDisplayVisible(false);
|
||||
@@ -234,6 +251,7 @@ public class VPlayerPanel extends FContainer {
|
||||
public boolean isFlipped() {
|
||||
return field.isFlipped();
|
||||
}
|
||||
|
||||
public void setFlipped(boolean flipped0) {
|
||||
field.setFlipped(flipped0);
|
||||
}
|
||||
@@ -254,9 +272,11 @@ public class VPlayerPanel extends FContainer {
|
||||
public VField getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public VField.FieldRow getSelectedRow() {
|
||||
return selectedRow;
|
||||
}
|
||||
|
||||
public void switchRow() {
|
||||
if (selectedRow == field.getRow1())
|
||||
selectedRow = field.getRow2();
|
||||
@@ -289,29 +309,22 @@ public class VPlayerPanel extends FContainer {
|
||||
}
|
||||
|
||||
public void updateZone(ZoneType zoneType) {
|
||||
if (zoneType == ZoneType.Battlefield ) {
|
||||
if (zoneType == ZoneType.Battlefield) {
|
||||
field.update(true);
|
||||
}
|
||||
else if (zoneType == ZoneType.Command) {
|
||||
} else if (zoneType == ZoneType.Command) {
|
||||
commandZone.update();
|
||||
}
|
||||
else {
|
||||
if(zoneTabs.containsKey(zoneType))
|
||||
if (selectedTab != null && Forge.isHorizontalTabLayout())
|
||||
updateTabLayout(initW, initH);
|
||||
} else {
|
||||
if (zoneTabs.containsKey(zoneType))
|
||||
zoneTabs.get(zoneType).update();
|
||||
else if(EXTRA_ZONES.contains(zoneType)) {
|
||||
else if (EXTRA_ZONES.contains(zoneType)) {
|
||||
extraTab.update(zoneType);
|
||||
}
|
||||
|
||||
//update flashback zone when graveyard, library, exile, or stack zones updated
|
||||
switch (zoneType) {
|
||||
case Graveyard:
|
||||
case Library:
|
||||
case Exile:
|
||||
case Stack:
|
||||
zoneTabs.get(ZoneType.Flashback).update();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case Graveyard, Library, Exile, Stack -> zoneTabs.get(ZoneType.Flashback).update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,7 +340,7 @@ public class VPlayerPanel extends FContainer {
|
||||
float x = avatarHeight;
|
||||
float w = width - avatarHeight;
|
||||
float indicatorScale = 1f;
|
||||
if(avatarHeight<VAvatar.HEIGHT){
|
||||
if (avatarHeight < VAvatar.HEIGHT) {
|
||||
indicatorScale = 0.6f;
|
||||
}
|
||||
float h = phaseIndicator.getPreferredHeight(w) * indicatorScale;
|
||||
@@ -366,8 +379,7 @@ public class VPlayerPanel extends FContainer {
|
||||
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
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
field.setCommandZoneWidth(0);
|
||||
}
|
||||
|
||||
@@ -383,18 +395,23 @@ public class VPlayerPanel extends FContainer {
|
||||
field.setFieldModifier(0);
|
||||
}
|
||||
|
||||
private float initW, initH, commandZoneWidth, commandZoneCount, avatarWidth, prefWidth;
|
||||
private final float mod = 2.4f;
|
||||
|
||||
private void doLandscapeLayout(float width, float height) {
|
||||
initW = width;
|
||||
initH = height;
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float yAlt = 0;
|
||||
float avatarWidth = Forge.altZoneTabs ? avatar.getWidth() : 0;
|
||||
avatarWidth = Forge.altZoneTabs ? avatar.getWidth() : 0;
|
||||
avatar.setPosition(x, y);
|
||||
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());
|
||||
if (Forge.altPlayerLayout && !Forge.altZoneTabs) {
|
||||
if (adjustHeight > 2)
|
||||
y += INFO_FONT.getLineHeight()/2;
|
||||
y += INFO_FONT.getLineHeight() / 2;
|
||||
} else
|
||||
y += lblLife.getHeight();
|
||||
|
||||
@@ -412,12 +429,16 @@ public class VPlayerPanel extends FContainer {
|
||||
tab.setBounds(x, y, infoTabWidth, infoTabHeight);
|
||||
y += infoTabHeight;
|
||||
} else {
|
||||
tab.setBounds(x+width-avatarWidth, yAlt, avatarWidth, infoTabHeightAlt);
|
||||
tab.setBounds(x + width - avatarWidth, yAlt, avatarWidth, infoTabHeightAlt);
|
||||
yAlt += infoTabHeightAlt;
|
||||
}
|
||||
}
|
||||
}
|
||||
x = avatar.getRight();
|
||||
updateTabLayout(width, height);
|
||||
}
|
||||
|
||||
private void updateTabLayout(float width, float height) {
|
||||
float x = avatar.getRight();
|
||||
phaseIndicator.resetFont();
|
||||
phaseIndicator.setBounds(x, 0, avatar.getWidth() * 0.6f, height);
|
||||
x += phaseIndicator.getWidth();
|
||||
@@ -429,38 +450,77 @@ public class VPlayerPanel extends FContainer {
|
||||
}
|
||||
|
||||
//account for command zone if needed
|
||||
int commandZoneCount = commandZone.getCount();
|
||||
commandZoneWidth = 0f;
|
||||
commandZoneCount = commandZone.getCount();
|
||||
if (commandZoneCount > 0) {
|
||||
float commandZoneHeight = height / 2;
|
||||
float commandZoneWidth = Math.min(commandZoneCount, 2) * commandZone.getCardWidth(commandZoneHeight);
|
||||
commandZone.setBounds(x + fieldWidth - commandZoneWidth, height - commandZoneHeight, commandZoneWidth, commandZoneHeight);
|
||||
float minCommandCards = Forge.isHorizontalTabLayout() ? 5 : 2;
|
||||
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
|
||||
commandZone.setTop(height - commandZone.getBottom());
|
||||
}
|
||||
|
||||
field.setCommandZoneWidth(commandZoneWidth + 1); //ensure second row of field accounts for width of command zone and its border
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
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) {
|
||||
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);
|
||||
else
|
||||
field.setFieldModifier(avatarWidth/16);
|
||||
} else {
|
||||
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
|
||||
public void drawBackground(Graphics g) {
|
||||
float y;
|
||||
InfoTab infoTab = selectedTab;
|
||||
float pad = Forge.isHorizontalTabLayout() ? avatarWidth / 16f : 0f;
|
||||
if (infoTab != null) { //draw background and border for selected zone if needed
|
||||
VDisplayArea selectedDisplayArea = infoTab.getDisplayArea();
|
||||
float x = selectedDisplayArea == null ? 0 : selectedDisplayArea.getLeft();
|
||||
@@ -468,12 +528,13 @@ public class VPlayerPanel extends FContainer {
|
||||
float top = selectedDisplayArea == null ? 0 : selectedDisplayArea.getTop();
|
||||
float h = selectedDisplayArea == null ? 0 : selectedDisplayArea.getHeight();
|
||||
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()) {
|
||||
g.drawLine(1, MatchScreen.getBorderColor(), x, top, x, bottom);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
y = isFlipped() ? top + 1 : bottom;
|
||||
//don't know why infotab gets null here, either way don't crash the gui..
|
||||
float left = infoTab == null ? 0 : infoTab.getLeft();
|
||||
@@ -487,6 +548,8 @@ public class VPlayerPanel extends FContainer {
|
||||
float x = commandZone.getLeft();
|
||||
y = commandZone.getTop();
|
||||
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()) {
|
||||
y += commandZone.getHeight();
|
||||
}
|
||||
@@ -499,7 +562,7 @@ public class VPlayerPanel extends FContainer {
|
||||
ArrayList<FScrollPane> out = new ArrayList<>();
|
||||
out.add(field.getRow1());
|
||||
out.add(field.getRow2());
|
||||
for(InfoTabZone tab : zoneTabs.values())
|
||||
for (InfoTabZone tab : zoneTabs.values())
|
||||
out.add(tab.displayArea);
|
||||
out.add(commandZone);
|
||||
out.addAll(extraTab.displayAreas.values());
|
||||
@@ -522,7 +585,7 @@ public class VPlayerPanel extends FContainer {
|
||||
private void update() {
|
||||
int vibrateDuration = 0;
|
||||
int delta = player.getLife() - life;
|
||||
player.setAvatarLifeDifference(player.getAvatarLifeDifference()+delta);
|
||||
player.setAvatarLifeDifference(player.getAvatarLifeDifference() + delta);
|
||||
if (delta != 0) {
|
||||
if (delta < 0) {
|
||||
vibrateDuration += delta * -100;
|
||||
@@ -553,6 +616,7 @@ public class VPlayerPanel extends FContainer {
|
||||
Gdx.input.vibrate(Math.min(vibrateDuration, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateShards() {
|
||||
manaShards = player.getNumManaShards();
|
||||
}
|
||||
@@ -567,55 +631,55 @@ public class VPlayerPanel extends FContainer {
|
||||
public void draw(Graphics g) {
|
||||
adjustHeight = 1;
|
||||
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) {
|
||||
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);
|
||||
} else {
|
||||
float halfHeight = getHeight() / divider;
|
||||
float textStart = halfHeight + Utils.scale(1);
|
||||
float textWidth = getWidth() - textStart;
|
||||
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.drawText(lifeStr, INFO_FONT, getInfoForeColor().getColor(), textStart, 0, textWidth, halfHeight, false, Align.left, false);
|
||||
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.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);
|
||||
mod+=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.drawText(String.valueOf(poisonCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, halfHeight + 2, textWidth, halfHeight, false, Align.left, false);
|
||||
mod += 1;
|
||||
}
|
||||
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.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);
|
||||
mod+=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.drawText(String.valueOf(energyCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||
mod += 1;
|
||||
}
|
||||
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.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);
|
||||
mod+=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.drawText(String.valueOf(experienceCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||
mod += 1;
|
||||
}
|
||||
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.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);
|
||||
mod+=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.drawText(String.valueOf(radCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||
mod += 1;
|
||||
}
|
||||
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.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);
|
||||
mod+=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.drawText(String.valueOf(ticketCounters), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||
mod += 1;
|
||||
}
|
||||
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.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);
|
||||
mod+=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.drawText(String.valueOf(manaShards), INFO_FONT, getInfoForeColor().getColor(), textStart, (halfHeight * mod) + 2, textWidth, halfHeight, false, Align.left, false);
|
||||
mod += 1;
|
||||
}
|
||||
adjustHeight = (mod > 2) && (avatar.getHeight() < halfHeight*mod)? mod : 1;
|
||||
adjustHeight = (mod > 2) && (avatar.getHeight() < halfHeight * mod) ? mod : 1;
|
||||
}
|
||||
} else {
|
||||
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
|
||||
g.drawImage(FSkinImage.ENERGY, 0, halfHeight, halfHeight, halfHeight);
|
||||
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.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 void setDisplayVisible(boolean visible);
|
||||
|
||||
public abstract void setDisplayBounds(float x, float y, float width, float height);
|
||||
|
||||
public abstract void setRotate180(boolean rotate180);
|
||||
|
||||
public abstract void update();
|
||||
|
||||
public abstract void reset();
|
||||
|
||||
public abstract float getIdealWidth(float pref);
|
||||
|
||||
protected boolean isSelected() {
|
||||
return selectedTab == this;
|
||||
}
|
||||
|
||||
protected FSkinColor getSelectedBackgroundColor() {
|
||||
return getDisplayAreaBackColor();
|
||||
}
|
||||
|
||||
protected boolean isAlignedRightForAltDisplay() {
|
||||
return false;
|
||||
}
|
||||
@@ -745,8 +817,8 @@ public class VPlayerPanel extends FContainer {
|
||||
if (lblLife.getRotate180()) {
|
||||
g.startRotateTransform(x + w / 2, y + h / 2, 180);
|
||||
}
|
||||
float mod = isHovered() ? w/8f:0;
|
||||
g.drawImage(icon, x-mod/2, y-mod/2, w+mod, h+mod);
|
||||
float mod = isHovered() ? w / 8f : 0;
|
||||
g.drawImage(icon, x - mod / 2, y - mod / 2, w + mod, h + mod);
|
||||
if (lblLife.getRotate180()) {
|
||||
g.endTransform();
|
||||
}
|
||||
@@ -773,8 +845,8 @@ public class VPlayerPanel extends FContainer {
|
||||
h = icon.getHeight() * w / icon.getWidth();
|
||||
x = (getWidth() - w) / 2;
|
||||
y = INFO_TAB_PADDING_Y;
|
||||
float mod = isHovered() ? w/8f:0;
|
||||
g.drawImage(icon, x-mod/2, y-mod/2, w+mod, h+mod);
|
||||
float mod = isHovered() ? w / 8f : 0;
|
||||
g.drawImage(icon, x - mod / 2, y - mod / 2, w + mod, h + mod);
|
||||
|
||||
y += h + INFO_TAB_PADDING_Y;
|
||||
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
|
||||
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);
|
||||
|
||||
public boolean isAlignedRightForAltDisplay() {
|
||||
return altDisplayZones.contains(this.zoneType);
|
||||
}
|
||||
@@ -857,6 +936,23 @@ public class VPlayerPanel extends FContainer {
|
||||
public void reset() {
|
||||
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() {
|
||||
super(DEFAULT_ICON);
|
||||
this.displayAreas = new EnumMap<>(ZoneType.class);
|
||||
for(ZoneType zoneType : EXTRA_ZONES) {
|
||||
for (ZoneType zoneType : EXTRA_ZONES) {
|
||||
FCollectionView<CardView> cards = player.getCards(zoneType);
|
||||
if(cards == null || cards.isEmpty())
|
||||
if (cards == null || cards.isEmpty())
|
||||
continue;
|
||||
createZoneIfMissing(zoneType);
|
||||
hasCardsInExtraZone = true;
|
||||
@@ -887,38 +983,39 @@ public class VPlayerPanel extends FContainer {
|
||||
}
|
||||
|
||||
public void createZoneIfMissing(ZoneType zone) {
|
||||
if(this.displayAreas.containsKey(zone))
|
||||
if (this.displayAreas.containsKey(zone))
|
||||
return;
|
||||
VZoneDisplay display = VPlayerPanel.this.add(new VZoneDisplay(player, zone));
|
||||
this.displayAreas.put(zone, display);
|
||||
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.
|
||||
}
|
||||
|
||||
public void setActiveZone(ZoneType zone) {
|
||||
if(this.activeZone == zone)
|
||||
if (this.activeZone == zone)
|
||||
return;
|
||||
createZoneIfMissing(zone);
|
||||
getDisplayArea().setVisible(false);
|
||||
this.activeZone = zone;
|
||||
if(isSelected())
|
||||
if (isSelected())
|
||||
getDisplayArea().setVisible(true);
|
||||
updateTab();
|
||||
}
|
||||
|
||||
public void setActiveZoneByIndex(int index) {
|
||||
List<ZoneType> keyList = List.copyOf(displayAreas.keySet());
|
||||
setActiveZone(keyList.get(index % keyList.size()));
|
||||
}
|
||||
|
||||
private void updateTab() {
|
||||
if(!hasCardsInExtraZone)
|
||||
if (!hasCardsInExtraZone)
|
||||
this.value = "";
|
||||
else if(!getDisplayArea().isVisible())
|
||||
else if (!getDisplayArea().isVisible())
|
||||
this.value = "+";
|
||||
else
|
||||
this.value = String.valueOf(displayAreas.get(this.activeZone).getCount());
|
||||
if(getDisplayArea().isVisible())
|
||||
if (getDisplayArea().isVisible())
|
||||
this.icon = iconFromZone(this.activeZone);
|
||||
else
|
||||
this.icon = DEFAULT_ICON;
|
||||
@@ -932,7 +1029,7 @@ public class VPlayerPanel extends FContainer {
|
||||
|
||||
@Override
|
||||
public void setDisplayVisible(boolean visible) {
|
||||
if(!visible)
|
||||
if (!visible)
|
||||
displayAreas.values().forEach(d -> d.setVisible(false));
|
||||
else
|
||||
getDisplayArea().setVisible(true);
|
||||
@@ -954,12 +1051,13 @@ public class VPlayerPanel extends FContainer {
|
||||
displayAreas.values().forEach(VDisplayArea::update);
|
||||
updateTab();
|
||||
}
|
||||
|
||||
public void update(ZoneType zoneType) {
|
||||
if(!displayAreas.containsKey(zoneType)) {
|
||||
if(!EXTRA_ZONES.contains(zoneType))
|
||||
if (!displayAreas.containsKey(zoneType)) {
|
||||
if (!EXTRA_ZONES.contains(zoneType))
|
||||
return;
|
||||
FCollectionView<CardView> cards = player.getCards(zoneType);
|
||||
if(cards == null || cards.isEmpty())
|
||||
if (cards == null || cards.isEmpty())
|
||||
return;
|
||||
createZoneIfMissing(zoneType);
|
||||
}
|
||||
@@ -987,18 +1085,28 @@ public class VPlayerPanel extends FContainer {
|
||||
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
|
||||
public boolean tap(float x, float y, int count) {
|
||||
if(this.displayAreas.isEmpty())
|
||||
if (this.displayAreas.isEmpty())
|
||||
return false;
|
||||
if(count >= 2) {
|
||||
if (count >= 2) {
|
||||
onClickZone(this.activeZone);
|
||||
return true;
|
||||
}
|
||||
FPopupMenu menu = new FPopupMenu() {
|
||||
@Override
|
||||
protected void buildMenu() {
|
||||
for(ZoneType zone : displayAreas.keySet()) {
|
||||
for (ZoneType zone : displayAreas.keySet()) {
|
||||
String label = WordUtils.capitalize(zone.getTranslatedName());
|
||||
addItem(new FMenuItem(label, iconFromZone(zone), (e) -> onClickZone(zone)));
|
||||
}
|
||||
@@ -1009,7 +1117,7 @@ public class VPlayerPanel extends FContainer {
|
||||
}
|
||||
|
||||
public void onClickZone(ZoneType zone) {
|
||||
if(activeZone == zone && this.isSelected()) {
|
||||
if (activeZone == zone && this.isSelected()) {
|
||||
setSelectedTab(null);
|
||||
return;
|
||||
}
|
||||
@@ -1042,7 +1150,7 @@ public class VPlayerPanel extends FContainer {
|
||||
|
||||
@Override
|
||||
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) {
|
||||
MatchScreen.nullPotentialListener();
|
||||
closeSelectedTab();
|
||||
|
||||
@@ -113,7 +113,7 @@ public class VZoneDisplay extends VCardDisplayArea {
|
||||
}
|
||||
|
||||
protected boolean layoutVerticallyForLandscapeMode() {
|
||||
return true;
|
||||
return !Forge.altZoneTabs || !"Horizontal".equalsIgnoreCase(Forge.altZoneTabMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -100,17 +100,17 @@ public class ConquestAEtherScreen extends FScreen {
|
||||
}
|
||||
|
||||
private void updateFilteredPool() {
|
||||
Predicate<PaperCard> predicate = btnColorFilter.buildFilterPredicate(null);
|
||||
predicate = btnTypeFilter.buildFilterPredicate(predicate);
|
||||
predicate = btnRarityFilter.buildFilterPredicate(predicate);
|
||||
predicate = btnCMCFilter.buildFilterPredicate(predicate);
|
||||
Predicate<PaperCard> predicate = btnColorFilter
|
||||
.and(btnTypeFilter)
|
||||
.and(btnRarityFilter)
|
||||
.and(btnCMCFilter);
|
||||
|
||||
final CardRarity selectedRarity = btnRarityFilter.selectedOption.getRarity();
|
||||
|
||||
filteredPool.clear();
|
||||
strictPool.clear();
|
||||
for (PaperCard card : pool) {
|
||||
if (predicate == null || predicate.test(card)) {
|
||||
if (predicate.test(card)) {
|
||||
filteredPool.add(card);
|
||||
if (selectedRarity == card.getRarity()) {
|
||||
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 List<AEtherFilter> options;
|
||||
private AEtherFilter selectedOption;
|
||||
@@ -383,11 +383,9 @@ public class ConquestAEtherScreen extends FScreen {
|
||||
}
|
||||
}
|
||||
|
||||
private Predicate<PaperCard> buildFilterPredicate(Predicate<PaperCard> predicate) {
|
||||
if (predicate == null) {
|
||||
return selectedOption.getPredicate();
|
||||
}
|
||||
return predicate.and(selectedOption.getPredicate());
|
||||
@Override
|
||||
public boolean test(PaperCard card) {
|
||||
return selectedOption.test(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -274,14 +274,15 @@ public class SettingsPage extends TabPage<SettingsScreen> {
|
||||
MatchController.instance.resetPlayerPanels();
|
||||
}
|
||||
}, 1);
|
||||
lstSettings.addItem(new BooleanSetting(FPref.UI_ALT_PLAYERZONETABS,
|
||||
lstSettings.addItem(new CustomSelectSetting(FPref.UI_ALT_PLAYERZONETABS,
|
||||
Forge.getLocalizer().getMessage("lblAltZoneTabs"),
|
||||
Forge.getLocalizer().getMessage("nlAltZoneTabs")) {
|
||||
Forge.getLocalizer().getMessage("nlAltZoneTabs"),
|
||||
Lists.newArrayList("Off", "Vertical", "Horizontal")) {
|
||||
@Override
|
||||
public void select() {
|
||||
super.select();
|
||||
public void valueChanged(String newValue) {
|
||||
super.valueChanged(newValue);
|
||||
//update
|
||||
Forge.altZoneTabs = FModel.getPreferences().getPrefBoolean(FPref.UI_ALT_PLAYERZONETABS);
|
||||
Forge.setAltZoneTabMode(FModel.getPreferences().getPref(FPref.UI_ALT_PLAYERZONETABS));
|
||||
if (MatchController.instance != null)
|
||||
MatchController.instance.resetPlayerPanels();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>${revision}</version>
|
||||
<version>2.0.06</version>
|
||||
</parent>
|
||||
|
||||
<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>
|
||||
<export target="wastetown..tmx" format="tmx"/>
|
||||
</editorsettings>
|
||||
<properties>
|
||||
<property name="dungeonEffect">{"startBattleWithCardInCommandZone": [ "Emrakul's Presence" ]
|
||||
}</property>
|
||||
</properties>
|
||||
<tileset firstgid="1" source="../../tileset/main.tsx"/>
|
||||
<tileset firstgid="10113" source="../../tileset/buildings.tsx"/>
|
||||
<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="50" template="../../obj/enemy.tx" x="182.772" y="284.992" width="64" height="64">
|
||||
<properties>
|
||||
<property name="effect" value="{ "startBattleWithCardInCommandZone": [ "Emrakul's Presence"]}"/>
|
||||
<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>
|
||||
</object>
|
||||
<object id="51" template="../../obj/entry_up.tx" x="209" y="480">
|
||||
@@ -50,6 +51,16 @@
|
||||
<property name="teleport" value=""/>
|
||||
</properties>
|
||||
</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">
|
||||
<properties>
|
||||
<property name="dialog">[{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Airbending Lesson
|
||||
ManaCost:2 W
|
||||
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.
|
||||
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
|
||||
PT:2/4
|
||||
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: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
|
||||
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.
|
||||
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.
|
||||
DeckHas:Ability$Graveyard
|
||||
Oracle:When this creature enters, exile up to two target cards from a single graveyard.\n{W}, {T}: Tap target creature.
|
||||
|
||||
@@ -6,4 +6,4 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S
|
||||
SVar:TrigEarthbend:DB$ Earthbend | Num$ 2
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Trample | Description$ Creatures you control with +1/+1 counters on them have trample.
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nCreatures you control with +1/+1 counters on them have trample.
|
||||
Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nCreatures you control with +1/+1 counters on them have trample.
|
||||
|
||||
@@ -3,4 +3,4 @@ ManaCost:6 B
|
||||
Types:Sorcery
|
||||
A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose up to six target creature cards with different names in your graveyard | ValidTgts$ Creature.YouOwn | TargetsWithDifferentNames$ True | TargetMin$ 0 | TargetMax$ 6 | SpellDescription$ Return up to six target creature cards with different names from your graveyard to the battlefield.
|
||||
DeckHas:Ability$Graveyard
|
||||
Oracle:Return up to six target creature cards with different names from your graveyard to the battlefield.
|
||||
Oracle:Return up to six target creature cards with different names from your graveyard to the battlefield.
|
||||
|
||||
@@ -7,4 +7,4 @@ SVar:TrigEarthbend:DB$ Earthbend | Num$ 1
|
||||
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPutCounterAll | TriggerDescription$ Whenever NICKNAME attacks, put two +1/+1 counters on each land creature you control.
|
||||
SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.Land+YouCtrl | CounterType$ P1P1 | CounterNum$ 2
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:When Bumi enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.)\nWhenever Bumi attacks, put two +1/+1 counters on each land creature you control.
|
||||
Oracle:When Bumi enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.)\nWhenever Bumi attacks, put two +1/+1 counters on each land creature you control.
|
||||
|
||||
@@ -2,5 +2,5 @@ Name:Carrion Beetles
|
||||
ManaCost:B
|
||||
Types:Creature Insect
|
||||
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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Cease
|
||||
ManaCost:1 BG
|
||||
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:DBDraw:DB$ Draw | Defined$ TargetedPlayer
|
||||
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."
|
||||
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
|
||||
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."
|
||||
|
||||
@@ -5,4 +5,4 @@ A:SP$ Charm | Choices$ DBDiscard,DBEarthbend
|
||||
SVar:DBDiscard:DB$ Discard | ValidTgts$ Opponent | Mode$ RevealYouChoose | DiscardValid$ Permanent.nonLand | NumCards$ 1 | SpellDescription$ Target opponent reveals their hand. You choose a nonland permanent card from it. That player discards that card.
|
||||
SVar:DBEarthbend:DB$ Earthbend | Num$ 2 | SpellDescription$ Earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)
|
||||
DeckHas:Ability$Discard|Counters
|
||||
Oracle:Choose one —\n• Target opponent reveals their hand. You choose a nonland permanent card from it. That player discards that card.\n• Earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)
|
||||
Oracle:Choose one —\n• Target opponent reveals their hand. You choose a nonland permanent card from it. That player discards that card.\n• Earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Creature Human Citizen
|
||||
PT:3/3
|
||||
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: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.
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Decompose
|
||||
ManaCost:1 B
|
||||
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
|
||||
Oracle:Exile up to three target cards from a single graveyard.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Digsite Conservator
|
||||
ManaCost:2
|
||||
Types:Artifact Creature Gnome
|
||||
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.)
|
||||
SVar:TrigDiscover:AB$ Discover | Cost$ 4 | Num$ 4
|
||||
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: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: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.
|
||||
|
||||
# --- VARIANTS ---
|
||||
|
||||
@@ -5,4 +5,4 @@ PT:3/1
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigEarthbend | TriggerDescription$ When this creature dies, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)
|
||||
SVar:TrigEarthbend:DB$ Earthbend | Num$ 2
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:When this creature dies, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)
|
||||
Oracle:When this creature dies, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)
|
||||
|
||||
@@ -6,4 +6,4 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S
|
||||
SVar:TrigEarthbend:DB$ Earthbend | Num$ 2
|
||||
S:Mode$ Continuous | Affected$ Creature.Land+YouCtrl | AddKeyword$ Vigilance | Description$ Land creatures you control have vigilance. (Attacking doesn't cause them to tap.)
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nLand creatures you control have vigilance. (Attacking doesn't cause them to tap.)
|
||||
Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nLand creatures you control have vigilance. (Attacking doesn't cause them to tap.)
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Instant
|
||||
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: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.
|
||||
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.)
|
||||
|
||||
@@ -3,5 +3,5 @@ ManaCost:2 R
|
||||
Types:Sorcery
|
||||
A:SP$ DealDamage | ValidTgts$ Creature | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature.
|
||||
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
|
||||
Types:Creature Zombie
|
||||
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
|
||||
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: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.
|
||||
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
|
||||
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: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: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
|
||||
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.
|
||||
|
||||
@@ -7,4 +7,4 @@ T:Mode$ SpellCast | ValidCard$ Card.wasCastFromExile | ValidActivatingPlayer$ Yo
|
||||
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Battlefield | ValidCard$ Permanent.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounterAll | Secondary$ True | TriggerDescription$ Whenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control.
|
||||
SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1
|
||||
SVar:X:Count$CardPower
|
||||
Oracle:Firebending X, where X is Fire Lord Zuko's power. (Whenever this creature attacks, add X {R}. This mana lasts until end of combat.)\nWhenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control.
|
||||
Oracle:Firebending X, where X is Fire Lord Zuko's power. (Whenever this creature attacks, add X {R}. This mana lasts until end of combat.)\nWhenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control.
|
||||
|
||||
@@ -3,4 +3,4 @@ ManaCost:4 R
|
||||
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.)
|
||||
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,4 +4,4 @@ Types:Creature Human Cleric
|
||||
PT:2/2
|
||||
K:Firebending:1
|
||||
A:AB$ PutCounter | Cost$ 1 R R | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on this creature.
|
||||
Oracle:Firebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\n{1}{R}{R}: Put a +1/+1 counter on this creature.
|
||||
Oracle:Firebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\n{1}{R}{R}: Put a +1/+1 counter on this creature.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Legendary Artifact Creature Robot
|
||||
PT:3/2
|
||||
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.
|
||||
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.
|
||||
DeckHints:Ability$Counters
|
||||
DeckHas:Ability$Sacrifice|Discard|Counters
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
Name:Footsteps of the Goryo
|
||||
ManaCost:2 B
|
||||
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.
|
||||
SVar:DBPump:DB$ Pump | Defined$ Targeted | AtEOT$ Sacrifice
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Creature Nightmare Horror
|
||||
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$ 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
|
||||
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
|
||||
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.
|
||||
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
|
||||
Oracle:Flying\nWhen Griffnaut Tracker enters, exile up to two target cards from a single graveyard.
|
||||
|
||||
@@ -6,4 +6,4 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Ally.O
|
||||
SVar:TrigEarthbend:DB$ Earthbend | Num$ 1
|
||||
DeckHas:Ability$Counters
|
||||
DeckNeeds:Type$Ally
|
||||
Oracle:Whenever another Ally you control enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.)
|
||||
Oracle:Whenever another Ally you control enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.)
|
||||
|
||||
@@ -2,5 +2,5 @@ Name:Inner Demons Gangsters
|
||||
ManaCost:3 B
|
||||
Types:Creature Human Rogue Villain
|
||||
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.)
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Kaya, Orzhov Usurper
|
||||
ManaCost:1 W B
|
||||
Types:Legendary Planeswalker Kaya
|
||||
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: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.
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:6 G G
|
||||
Types:Sorcery
|
||||
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.
|
||||
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:Y:Count$ValidHand Creature.YouCtrl
|
||||
DeckHints:Type$Wall|Plant|Treefolk
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user