mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
Compare commits
118 Commits
cantTarget
...
spm-releas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
432d152d9a | ||
|
|
f7c0ef9428 | ||
|
|
5f6934782c | ||
|
|
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 | ||
|
|
2925e94cec | ||
|
|
02b7e408dc | ||
|
|
b61044abb5 | ||
|
|
f1114eeada | ||
|
|
b131e4eb04 | ||
|
|
8ba2ae4a24 | ||
|
|
ac93233e19 | ||
|
|
6b29e18a58 | ||
|
|
fd2d3c7bb9 | ||
|
|
a1d67cf844 | ||
|
|
2b64f82f98 | ||
|
|
43a1570601 | ||
|
|
db8e084332 | ||
|
|
c2fc877201 | ||
|
|
50f34442f6 | ||
|
|
1dbd358c19 | ||
|
|
bc3faa88c4 | ||
|
|
10618d4f31 | ||
|
|
47e873b4e9 | ||
|
|
acc9ead5b2 | ||
|
|
6125022c9e | ||
|
|
a0be5e25cb | ||
|
|
f2e3cdc111 | ||
|
|
7384aada40 | ||
|
|
1a8a4f63ae | ||
|
|
530d1efcd8 | ||
|
|
d8b1d76f42 | ||
|
|
830b145e88 | ||
|
|
51ed06d60a | ||
|
|
ac9b73935f | ||
|
|
fe2f49fbcc | ||
|
|
3d31494a1b | ||
|
|
ff42e730a2 | ||
|
|
a66fb8791c | ||
|
|
2ad5d20e83 | ||
|
|
4790394698 | ||
|
|
8b1c427809 | ||
|
|
c0b667c373 | ||
|
|
ca61627f5b | ||
|
|
c3e2a5b5ea | ||
|
|
0d26b499d3 | ||
|
|
bbbc5e0ee6 | ||
|
|
714d9442f1 | ||
|
|
d199765e6d | ||
|
|
e5156d8999 | ||
|
|
c0d5397541 | ||
|
|
444897d0f9 | ||
|
|
f2cb7956d3 | ||
|
|
e0c2a49c6b | ||
|
|
d64e5ebc13 | ||
|
|
9bea1bc717 | ||
|
|
bdf9573467 | ||
|
|
042eb4bf79 |
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -442,11 +442,9 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
sa.addDividedAllocation(c, amount);
|
||||
return decision;
|
||||
} else {
|
||||
if (!hasSacCost) {
|
||||
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||
return decision;
|
||||
}
|
||||
} else if (!hasSacCost) {
|
||||
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||
return decision;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -684,14 +682,12 @@ public class CountersPutAi extends CountersAi {
|
||||
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
CardCollection list = null;
|
||||
|
||||
CardCollection list;
|
||||
if (sa.isCurse()) {
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (list.isEmpty() && isMandatoryTrigger) {
|
||||
@@ -707,9 +703,8 @@ public class CountersPutAi extends CountersAi {
|
||||
|| sa.getTargets().isEmpty()) {
|
||||
sa.resetTargets();
|
||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (sa.isCurse()) {
|
||||
@@ -752,8 +747,6 @@ public class CountersPutAi extends CountersAi {
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
final Card source = sa.getHostCard();
|
||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
boolean preferred = true;
|
||||
CardCollection list;
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final boolean divided = sa.isDividedAsYouChoose();
|
||||
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
@@ -775,11 +768,11 @@ public class CountersPutAi extends CountersAi {
|
||||
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
|
||||
if (decision.willingToPlay()) {
|
||||
return decision;
|
||||
} else if (mandatory) {
|
||||
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||
} else {
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
if (mandatory) {
|
||||
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||
}
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
@@ -830,19 +823,19 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.isCurse()) {
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||
|
||||
int totalTargets = list.size();
|
||||
|
||||
sa.resetTargets();
|
||||
|
||||
Iterable<Card> filteredField;
|
||||
if (sa.isCurse()) {
|
||||
filteredField = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
filteredField = ai.getCardsIn(ZoneType.Battlefield);
|
||||
}
|
||||
CardCollection list = CardLists.getTargetableCards(filteredField, sa);
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||
int totalTargets = list.size();
|
||||
boolean preferred = true;
|
||||
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (mandatory) {
|
||||
// When things are mandatory, gotta handle a little differently
|
||||
@@ -879,27 +872,21 @@ public class CountersPutAi extends CountersAi {
|
||||
if (choice == null && mandatory) {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
} else if (type.equals("M1M1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
if (type.equals("M1M1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
} else if (preferred) {
|
||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||
choice = chooseBoonTarget(list, type);
|
||||
if (choice == null && mandatory) {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
} else if (type.equals("P1P1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
if (preferred) {
|
||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||
choice = chooseBoonTarget(list, type);
|
||||
if (choice == null && mandatory) {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
} else {
|
||||
if (type.equals("P1P1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
}
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
if (choice != null && divided) {
|
||||
int alloc = Math.max(amount / totalTargets, 1);
|
||||
@@ -1094,8 +1081,7 @@ public class CountersPutAi extends CountersAi {
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
GameEntity e = (GameEntity) params.get("Target");
|
||||
// for Card try to select not useless counter
|
||||
if (e instanceof Card) {
|
||||
Card c = (Card) e;
|
||||
if (e instanceof Card c) {
|
||||
if (c.getController().isOpponentOf(ai)) {
|
||||
if (options.contains(CounterEnumType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterEnumType.M1M1;
|
||||
@@ -1112,8 +1098,7 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (e instanceof Player) {
|
||||
Player p = (Player) e;
|
||||
} else if (e instanceof Player p) {
|
||||
if (p.isOpponentOf(ai)) {
|
||||
if (options.contains(CounterEnumType.POISON)) {
|
||||
return CounterEnumType.POISON;
|
||||
@@ -1247,9 +1232,8 @@ public class CountersPutAi extends CountersAi {
|
||||
if (numCtrs < optimalCMC) {
|
||||
// If the AI has less counters than the optimal CMC, it should play the ability.
|
||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||
} else {
|
||||
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -878,7 +878,7 @@ public class StaticData {
|
||||
}
|
||||
}
|
||||
}
|
||||
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
||||
List<String> NIF = new ArrayList<>(NIF_Q).stream().sorted().collect(Collectors.toList());
|
||||
List<String> CNI = new ArrayList<>(CNI_Q).stream().sorted().collect(Collectors.toList());
|
||||
List<String> TOK = new ArrayList<>(TOKEN_Q).stream().sorted().collect(Collectors.toList());
|
||||
|
||||
@@ -639,7 +639,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
* name - grouping #3
|
||||
* artist name - grouping #5
|
||||
*/
|
||||
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
|
||||
"(^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]*)( @(.*))?$"
|
||||
);
|
||||
|
||||
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
|
||||
@@ -1018,16 +1018,13 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
public static final Predicate<CardEdition> HAS_BOOSTER_BOX = edition -> edition.getBoosterBoxCount() > 0;
|
||||
|
||||
@Deprecated //Use CardEdition::hasBasicLands and a nonnull test.
|
||||
public static final Predicate<CardEdition> hasBasicLands = ed -> {
|
||||
if (ed == null) {
|
||||
// Happens for new sets with "???" code
|
||||
return false;
|
||||
}
|
||||
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||
if (null == StaticData.instance().getCommonCards().getCard(landName, ed.getCode(), 0))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return ed.hasBasicLands();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1048,7 +1045,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
public boolean hasBasicLands() {
|
||||
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||
if (null == StaticData.instance().getCommonCards().getCard(landName, this.getCode(), 0))
|
||||
if (this.getCardInSet(landName).isEmpty())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,16 @@ public class DeckRecognizer {
|
||||
LIMITED_CARD,
|
||||
CARD_FROM_NOT_ALLOWED_SET,
|
||||
CARD_FROM_INVALID_SET,
|
||||
/**
|
||||
* Valid card request, but can't be imported because the player does not have enough copies.
|
||||
* Should be replaced with a different printing if possible.
|
||||
*/
|
||||
CARD_NOT_IN_INVENTORY,
|
||||
/**
|
||||
* Valid card request for a card that isn't in the player's inventory, but new copies can be acquired freely.
|
||||
* Usually used for basic lands. Should be supplied to the import controller by the editor.
|
||||
*/
|
||||
FREE_CARD_NOT_IN_INVENTORY,
|
||||
// Warning messages
|
||||
WARNING_MESSAGE,
|
||||
UNKNOWN_CARD,
|
||||
@@ -63,10 +73,14 @@ public class DeckRecognizer {
|
||||
CARD_TYPE,
|
||||
CARD_RARITY,
|
||||
CARD_CMC,
|
||||
MANA_COLOUR
|
||||
MANA_COLOUR;
|
||||
|
||||
public static final EnumSet<TokenType> CARD_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET, CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY);
|
||||
public static final EnumSet<TokenType> IN_DECK_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, DECK_NAME, FREE_CARD_NOT_IN_INVENTORY);
|
||||
public static final EnumSet<TokenType> CARD_PLACEHOLDER_TOKEN_TYPES = EnumSet.of(CARD_TYPE, CARD_RARITY, CARD_CMC, MANA_COLOUR);
|
||||
}
|
||||
|
||||
public enum LimitedCardType{
|
||||
public enum LimitedCardType {
|
||||
BANNED,
|
||||
RESTRICTED,
|
||||
}
|
||||
@@ -108,6 +122,10 @@ public class DeckRecognizer {
|
||||
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card, cardRequestHasSetCode);
|
||||
}
|
||||
|
||||
public static Token NotInInventoryFree(final PaperCard card, final int count, final DeckSection section) {
|
||||
return new Token(TokenType.FREE_CARD_NOT_IN_INVENTORY, count, card, section, true);
|
||||
}
|
||||
|
||||
// WARNING MESSAGES
|
||||
// ================
|
||||
public static Token UnknownCard(final String cardName, final String setCode, final int count) {
|
||||
@@ -126,6 +144,10 @@ public class DeckRecognizer {
|
||||
return new Token(TokenType.WARNING_MESSAGE, msg);
|
||||
}
|
||||
|
||||
public static Token NotInInventory(final PaperCard card, final int count, final DeckSection section) {
|
||||
return new Token(TokenType.CARD_NOT_IN_INVENTORY, count, card, section, false);
|
||||
}
|
||||
|
||||
/* =================================
|
||||
* DECK SECTIONS
|
||||
* ================================= */
|
||||
@@ -239,14 +261,11 @@ public class DeckRecognizer {
|
||||
/**
|
||||
* Filters all token types that have a PaperCard instance set (not null)
|
||||
* @return true for tokens of type:
|
||||
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET.
|
||||
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY.
|
||||
* False otherwise.
|
||||
*/
|
||||
public boolean isCardToken() {
|
||||
return (this.type == TokenType.LEGAL_CARD ||
|
||||
this.type == TokenType.LIMITED_CARD ||
|
||||
this.type == TokenType.CARD_FROM_NOT_ALLOWED_SET ||
|
||||
this.type == TokenType.CARD_FROM_INVALID_SET);
|
||||
return TokenType.CARD_TOKEN_TYPES.contains(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,9 +274,7 @@ public class DeckRecognizer {
|
||||
* LEGAL_CARD, LIMITED_CARD, DECK_NAME; false otherwise.
|
||||
*/
|
||||
public boolean isTokenForDeck() {
|
||||
return (this.type == TokenType.LEGAL_CARD ||
|
||||
this.type == TokenType.LIMITED_CARD ||
|
||||
this.type == TokenType.DECK_NAME);
|
||||
return TokenType.IN_DECK_TOKEN_TYPES.contains(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +283,7 @@ public class DeckRecognizer {
|
||||
* False otherwise.
|
||||
*/
|
||||
public boolean isCardTokenForDeck() {
|
||||
return (this.type == TokenType.LEGAL_CARD || this.type == TokenType.LIMITED_CARD);
|
||||
return isCardToken() && isTokenForDeck();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,10 +293,7 @@ public class DeckRecognizer {
|
||||
* CARD_RARITY, CARD_CMC, CARD_TYPE, MANA_COLOUR
|
||||
*/
|
||||
public boolean isCardPlaceholder(){
|
||||
return (this.type == TokenType.CARD_RARITY ||
|
||||
this.type == TokenType.CARD_CMC ||
|
||||
this.type == TokenType.MANA_COLOUR ||
|
||||
this.type == TokenType.CARD_TYPE);
|
||||
return TokenType.CARD_PLACEHOLDER_TOKEN_TYPES.contains(this.type);
|
||||
}
|
||||
|
||||
/** Determines if current token is a Deck Section token
|
||||
@@ -536,7 +550,7 @@ public class DeckRecognizer {
|
||||
PaperCard tokenCard = token.getCard();
|
||||
|
||||
if (isAllowed(tokenSection)) {
|
||||
if (!tokenSection.equals(referenceDeckSectionInParsing)) {
|
||||
if (tokenSection != referenceDeckSectionInParsing) {
|
||||
Token sectionToken = Token.DeckSection(tokenSection.name(), this.allowedDeckSections);
|
||||
// just check that last token is stack is a card placeholder.
|
||||
// In that case, add the new section token before the placeholder
|
||||
@@ -575,7 +589,7 @@ public class DeckRecognizer {
|
||||
refLine = purgeAllLinks(refLine);
|
||||
|
||||
String line;
|
||||
if (StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
||||
if (refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
||||
line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, "");
|
||||
else
|
||||
line = refLine.trim(); // Remove any trailing formatting
|
||||
@@ -584,7 +598,7 @@ public class DeckRecognizer {
|
||||
// Final fantasy cards like Summon: Choco/Mog should be ommited to be recognized. TODO: fix maybe for future cards
|
||||
if (!line.contains("Summon:"))
|
||||
line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // ");
|
||||
if (StringUtils.startsWith(line, ASTERISK)) // markdown lists (tappedout md export)
|
||||
if (line.startsWith(ASTERISK)) // Markdown lists (tappedout md export)
|
||||
line = line.substring(2);
|
||||
|
||||
// == Patches to Corner Cases
|
||||
@@ -600,8 +614,8 @@ public class DeckRecognizer {
|
||||
Token result = recogniseCardToken(line, referenceSection);
|
||||
if (result == null)
|
||||
result = recogniseNonCardToken(line);
|
||||
return result != null ? result : StringUtils.startsWith(refLine, DOUBLE_SLASH) ||
|
||||
StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
|
||||
return result != null ? result : refLine.startsWith(DOUBLE_SLASH) ||
|
||||
refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
|
||||
new Token(TokenType.COMMENT, 0, refLine) : new Token(TokenType.UNKNOWN_TEXT, 0, refLine);
|
||||
}
|
||||
|
||||
@@ -613,7 +627,7 @@ public class DeckRecognizer {
|
||||
while (m.find()) {
|
||||
line = line.replaceAll(m.group(), "").trim();
|
||||
}
|
||||
if (StringUtils.endsWith(line, "()"))
|
||||
if (line.endsWith("()"))
|
||||
return line.substring(0, line.length()-2);
|
||||
return line;
|
||||
}
|
||||
@@ -741,21 +755,12 @@ public class DeckRecognizer {
|
||||
// This would save tons of time in parsing Input + would also allow to return UnsupportedCardTokens beforehand
|
||||
private DeckSection getTokenSection(String deckSec, DeckSection currentDeckSection, PaperCard card){
|
||||
if (deckSec != null) {
|
||||
DeckSection cardSection;
|
||||
switch (deckSec.toUpperCase().trim()) {
|
||||
case "MB":
|
||||
cardSection = DeckSection.Main;
|
||||
break;
|
||||
case "SB":
|
||||
cardSection = DeckSection.Sideboard;
|
||||
break;
|
||||
case "CM":
|
||||
cardSection = DeckSection.Commander;
|
||||
break;
|
||||
default:
|
||||
cardSection = DeckSection.matchingSection(card);
|
||||
break;
|
||||
}
|
||||
DeckSection cardSection = switch (deckSec.toUpperCase().trim()) {
|
||||
case "MB" -> DeckSection.Main;
|
||||
case "SB" -> DeckSection.Sideboard;
|
||||
case "CM" -> DeckSection.Commander;
|
||||
default -> DeckSection.matchingSection(card);
|
||||
};
|
||||
if (cardSection.validate(card))
|
||||
return cardSection;
|
||||
}
|
||||
@@ -1010,58 +1015,28 @@ 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);
|
||||
}
|
||||
|
||||
private static MagicColor.Color getMagicColor(String colorName){
|
||||
if (colorName.toLowerCase().startsWith("multi") || colorName.equalsIgnoreCase("m"))
|
||||
return null; // will be handled separately
|
||||
|
||||
byte color = MagicColor.fromName(colorName.toLowerCase());
|
||||
switch (color) {
|
||||
case MagicColor.WHITE:
|
||||
return MagicColor.Color.WHITE;
|
||||
case MagicColor.BLUE:
|
||||
return MagicColor.Color.BLUE;
|
||||
case MagicColor.BLACK:
|
||||
return MagicColor.Color.BLACK;
|
||||
case MagicColor.RED:
|
||||
return MagicColor.Color.RED;
|
||||
case MagicColor.GREEN:
|
||||
return MagicColor.Color.GREEN;
|
||||
default:
|
||||
return MagicColor.Color.COLORLESS;
|
||||
|
||||
}
|
||||
return MagicColor.Color.fromByte(MagicColor.fromName(colorName.toLowerCase()));
|
||||
}
|
||||
|
||||
public static String getLocalisedMagicColorName(String colorName){
|
||||
Localizer localizer = Localizer.getInstance();
|
||||
switch(colorName.toLowerCase()){
|
||||
case MagicColor.Constant.WHITE:
|
||||
return localizer.getMessage("lblWhite");
|
||||
|
||||
case MagicColor.Constant.BLUE:
|
||||
return localizer.getMessage("lblBlue");
|
||||
|
||||
case MagicColor.Constant.BLACK:
|
||||
return localizer.getMessage("lblBlack");
|
||||
|
||||
case MagicColor.Constant.RED:
|
||||
return localizer.getMessage("lblRed");
|
||||
|
||||
case MagicColor.Constant.GREEN:
|
||||
return localizer.getMessage("lblGreen");
|
||||
|
||||
case MagicColor.Constant.COLORLESS:
|
||||
return localizer.getMessage("lblColorless");
|
||||
case "multicolour":
|
||||
case "multicolor":
|
||||
return localizer.getMessage("lblMulticolor");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
return switch (colorName.toLowerCase()) {
|
||||
case MagicColor.Constant.WHITE -> localizer.getMessage("lblWhite");
|
||||
case MagicColor.Constant.BLUE -> localizer.getMessage("lblBlue");
|
||||
case MagicColor.Constant.BLACK -> localizer.getMessage("lblBlack");
|
||||
case MagicColor.Constant.RED -> localizer.getMessage("lblRed");
|
||||
case MagicColor.Constant.GREEN -> localizer.getMessage("lblGreen");
|
||||
case MagicColor.Constant.COLORLESS -> localizer.getMessage("lblColorless");
|
||||
case "multicolour", "multicolor" -> localizer.getMessage("lblMulticolor");
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1080,37 +1055,6 @@ public class DeckRecognizer {
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
private static Pair<String, String> getManaNameAndSymbol(String matchedMana) {
|
||||
if (matchedMana == null)
|
||||
return null;
|
||||
|
||||
Localizer localizer = Localizer.getInstance();
|
||||
switch (matchedMana.toLowerCase()) {
|
||||
case MagicColor.Constant.WHITE:
|
||||
case "w":
|
||||
return Pair.of(localizer.getMessage("lblWhite"), MagicColor.Color.WHITE.getSymbol());
|
||||
case MagicColor.Constant.BLUE:
|
||||
case "u":
|
||||
return Pair.of(localizer.getMessage("lblBlue"), MagicColor.Color.BLUE.getSymbol());
|
||||
case MagicColor.Constant.BLACK:
|
||||
case "b":
|
||||
return Pair.of(localizer.getMessage("lblBlack"), MagicColor.Color.BLACK.getSymbol());
|
||||
case MagicColor.Constant.RED:
|
||||
case "r":
|
||||
return Pair.of(localizer.getMessage("lblRed"), MagicColor.Color.RED.getSymbol());
|
||||
case MagicColor.Constant.GREEN:
|
||||
case "g":
|
||||
return Pair.of(localizer.getMessage("lblGreen"), MagicColor.Color.GREEN.getSymbol());
|
||||
case MagicColor.Constant.COLORLESS:
|
||||
case "c":
|
||||
return Pair.of(localizer.getMessage("lblColorless"), MagicColor.Color.COLORLESS.getSymbol());
|
||||
default: // Multicolour
|
||||
return Pair.of(localizer.getMessage("lblMulticolor"), "");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDeckName(final String lineAsIs) {
|
||||
if (lineAsIs == null)
|
||||
return false;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -367,7 +367,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
public final FCollectionView<SpellAbility> getManaAbilities() {
|
||||
FCollection<SpellAbility> newCol = new FCollection<>();
|
||||
updateSpellAbilities(newCol, true);
|
||||
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
||||
newCol.addAll(abilities.stream().filter(SpellAbility::isManaAbility).collect(Collectors.toList()));
|
||||
card.updateSpellAbilities(newCol, this, true);
|
||||
return newCol;
|
||||
@@ -375,7 +375,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
public final FCollectionView<SpellAbility> getNonManaAbilities() {
|
||||
FCollection<SpellAbility> newCol = new FCollection<>();
|
||||
updateSpellAbilities(newCol, false);
|
||||
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
||||
newCol.addAll(abilities.stream().filter(Predicate.not(SpellAbility::isManaAbility)).collect(Collectors.toList()));
|
||||
card.updateSpellAbilities(newCol, this, false);
|
||||
return newCol;
|
||||
@@ -390,7 +390,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
if (null != mana) {
|
||||
leftAbilities = leftAbilities.stream()
|
||||
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
|
||||
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
newCol.addAll(leftAbilities);
|
||||
@@ -402,7 +402,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
if (null != mana) {
|
||||
rightAbilities = rightAbilities.stream()
|
||||
.filter(mana ? SpellAbility::isManaAbility : Predicate.not(SpellAbility::isManaAbility))
|
||||
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
newCol.addAll(rightAbilities);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package forge.game.keyword;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.game.card.Card;
|
||||
import forge.card.CardSplitType;
|
||||
import forge.item.PaperCard;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -223,7 +223,7 @@ public enum Keyword {
|
||||
displayName = displayName0;
|
||||
}
|
||||
|
||||
public static KeywordInterface getInstance(String k) {
|
||||
private static Pair<Keyword, String> getKeywordDetails(String k) {
|
||||
Keyword keyword = Keyword.UNDEFINED;
|
||||
String details = k;
|
||||
// try to get real part
|
||||
@@ -255,15 +255,20 @@ public enum Keyword {
|
||||
keyword = smartValueOf(k);
|
||||
details = "";
|
||||
}
|
||||
return Pair.of(keyword, details);
|
||||
}
|
||||
|
||||
public static KeywordInterface getInstance(String k) {
|
||||
Pair<Keyword, String> p = getKeywordDetails(k);
|
||||
|
||||
KeywordInstance<?> inst;
|
||||
try {
|
||||
inst = keyword.type.getConstructor().newInstance();
|
||||
inst = p.getKey().type.getConstructor().newInstance();
|
||||
}
|
||||
catch (Exception e) {
|
||||
inst = new UndefinedKeyword();
|
||||
}
|
||||
inst.initialize(k, keyword, details);
|
||||
inst.initialize(k, p.getKey(), p.getValue());
|
||||
return inst;
|
||||
}
|
||||
|
||||
@@ -278,36 +283,44 @@ public enum Keyword {
|
||||
return keywords;
|
||||
}
|
||||
|
||||
private static Keyword get(String k) {
|
||||
if (k == null || k.isEmpty())
|
||||
return Keyword.UNDEFINED;
|
||||
|
||||
return getKeywordDetails(k).getKey();
|
||||
}
|
||||
|
||||
private static final Map<String, Set<Keyword>> cardKeywordSetLookup = new HashMap<>();
|
||||
|
||||
public static Set<Keyword> getKeywordSet(PaperCard card) {
|
||||
String key = card.getName();
|
||||
Set<Keyword> keywordSet = cardKeywordSetLookup.get(key);
|
||||
String name = card.getName();
|
||||
Set<Keyword> keywordSet = cardKeywordSetLookup.get(name);
|
||||
if (keywordSet == null) {
|
||||
keywordSet = new HashSet<>();
|
||||
for (KeywordInterface inst : Card.getCardForUi(card).getKeywords()) {
|
||||
final Keyword keyword = inst.getKeyword();
|
||||
if (keyword != Keyword.UNDEFINED) {
|
||||
keywordSet.add(keyword);
|
||||
CardSplitType cardSplitType = card.getRules().getSplitType();
|
||||
keywordSet = EnumSet.noneOf(Keyword.class);
|
||||
if (cardSplitType != CardSplitType.None && cardSplitType != CardSplitType.Split) {
|
||||
if (card.getRules().getOtherPart() != null) {
|
||||
if (card.getRules().getOtherPart().getKeywords() != null) {
|
||||
for (String key : card.getRules().getOtherPart().getKeywords()) {
|
||||
Keyword keyword = get(key);
|
||||
if (!Keyword.UNDEFINED.equals(keyword))
|
||||
keywordSet.add(keyword);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cardKeywordSetLookup.put(card.getName(), keywordSet);
|
||||
if (card.getRules().getMainPart().getKeywords() != null) {
|
||||
for (String key : card.getRules().getMainPart().getKeywords()) {
|
||||
Keyword keyword = get(key);
|
||||
if (!Keyword.UNDEFINED.equals(keyword))
|
||||
keywordSet.add(keyword);
|
||||
}
|
||||
}
|
||||
cardKeywordSetLookup.put(name, keywordSet);
|
||||
}
|
||||
return keywordSet;
|
||||
}
|
||||
|
||||
public static Runnable getPreloadTask() {
|
||||
if (cardKeywordSetLookup.size() < 10000) { //allow preloading even if some but not all cards loaded
|
||||
return () -> {
|
||||
final Collection<PaperCard> cards = StaticData.instance().getCommonCards().getUniqueCards();
|
||||
for (PaperCard card : cards) {
|
||||
getKeywordSet(card);
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Keyword smartValueOf(String value) {
|
||||
for (final Keyword v : Keyword.values()) {
|
||||
if (v.displayName.equalsIgnoreCase(value)) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -577,9 +577,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
public final boolean payLife(final int lifePayment, final SpellAbility cause, final boolean effect) {
|
||||
return payLife(lifePayment, cause, effect, null);
|
||||
}
|
||||
public final boolean payLife(final int lifePayment, final SpellAbility cause, final boolean effect, Map<AbilityKey, Object> params) {
|
||||
// fast check for pay zero life
|
||||
if (lifePayment <= 0) {
|
||||
cause.setPaidLife(0);
|
||||
@@ -599,9 +596,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
if (cause.isReplacementAbility() && effect) {
|
||||
replaceParams.putAll(cause.getReplacingObjects());
|
||||
}
|
||||
if (params != null) {
|
||||
replaceParams.putAll(params);
|
||||
}
|
||||
switch (getGame().getReplacementHandler().run(ReplacementType.PayLife, replaceParams)) {
|
||||
case Replaced:
|
||||
return true;
|
||||
@@ -1126,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;
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ public class StaticAbilityCantTarget {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (stAb.hasParam("AffectedZone")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card source = spellAbility.getHostCard();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.Map;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Localizer;
|
||||
|
||||
@@ -60,17 +59,8 @@ public class TriggerAttackerBlocked extends Trigger {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasParam("ValidBlocker")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
int count = CardLists.getValidCardCount(
|
||||
(Iterable<Card>) runParams.get(AbilityKey.Blockers),
|
||||
getParam("ValidBlocker"),
|
||||
getHostCard().getController(), getHostCard(), this
|
||||
);
|
||||
|
||||
if (count == 0) {
|
||||
return false;
|
||||
}
|
||||
if (!matchesValidParam("ValidBlocker", runParams.get(AbilityKey.Blockers))) {
|
||||
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
|
||||
|
||||
770
forge-gui-android/assets/database.json
Normal file
770
forge-gui-android/assets/database.json
Normal file
@@ -0,0 +1,770 @@
|
||||
{
|
||||
"a32x":{
|
||||
"CPU":"2x Cortex-A76 @ 2GHz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"MediaTek Dimensity 720 (MT6853)"
|
||||
},
|
||||
"angler":{
|
||||
"CPU":"4x Cortex-A57 @ 1.95GHz 4x Cortex-A53 @ 1.55GHz",
|
||||
"SoC":"Snapdragon 810 MSM8994"
|
||||
},
|
||||
"ane":{
|
||||
"CPU":"4x Cortex-A54 @ 2.3GHz 4x Cortex-A54 @ 1.7GHz",
|
||||
"SoC":"HiSilicon Kirin 659"
|
||||
},
|
||||
"amar_row_wifi":{
|
||||
"CPU":"8x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"Mediatek MT8768"
|
||||
},
|
||||
"atlas":{
|
||||
"CPU":"-",
|
||||
"SoC":"Intel(R) Core(TM) i5-8200Y @ 1.3GHz"
|
||||
},
|
||||
"apq8084":{
|
||||
"CPU":"4x Krait 450 @ 2.65GHz",
|
||||
"SoC":"Snapdragon 805 APQ8084"
|
||||
},
|
||||
"atoll":{
|
||||
"CPU":"2x Kryo 465 Gold @ 2.3GHz 6x Kryo 465 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 720G (SM7125)"
|
||||
},
|
||||
"art-l29":{
|
||||
"CPU":"4x Cortex-A73 @ 2.2GHz 4x Cortex-A53 @ 1.7GHz",
|
||||
"SoC":"HiSilicon Kirin 710F"
|
||||
},
|
||||
"baylake":{
|
||||
"CPU":"Atom Z3745 @ 1.3GHz",
|
||||
"SoC":"Intel Atom Z3745"
|
||||
},
|
||||
"begonia":{
|
||||
"CPU":"2x Cortex-176 @ 2GHz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"MediaTek Helios G90T MT6785T"
|
||||
},
|
||||
"blueline":{
|
||||
"CPU":"4x Kryo 385 Gold @ 2.8GHz 4x Kryo 385 Silver @ 1.75GHz",
|
||||
"SoC":"Snapdragon 845"
|
||||
},
|
||||
"bullhead":{
|
||||
"CPU":"4x Cortex-A57 @ 1.8GHz 4x Cortex-A53 @ 1.44GHz",
|
||||
"SoC":"Snapdragon 808"
|
||||
},
|
||||
"capri":{
|
||||
"CPU":"2x Cortex-A9 @ 1.2GHz",
|
||||
"SoC":"Broadcom BCM28155"
|
||||
},
|
||||
"cepheus":{
|
||||
"CPU":"1x Kryo 485 Gold @ 2.8GHz 3x Kryo 485 Gold @ 2.4GHz 4x Kryo 485 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 855 SM8150"
|
||||
},
|
||||
"cheryl":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"cheryl2":{
|
||||
"CPU":"4x Kryo 385 Gold @ 2.8GHz 4x Kryo 385 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 845 SDM845"
|
||||
},
|
||||
"cheetah":{
|
||||
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor G2 (GS201)"
|
||||
},
|
||||
"comet":{
|
||||
"CPU":"1x Cortex-X4 @ 31.GHz 3x Cortex-A720 @ 2.6GHz 4x Cortex-A520 @ 1.95GHz",
|
||||
"SoC":"Google Tensor G4 (GS401)"
|
||||
},
|
||||
"eureka":{
|
||||
"CPU":"6x Cortex-A78C @ 2.3 GHz",
|
||||
"SoC":"Snapdragon XR2 Gen 2 (SM8550)"
|
||||
},
|
||||
"felix":{
|
||||
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor G2 (GS201)"
|
||||
},
|
||||
"tangorpro":{
|
||||
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor G2 (GS201)"
|
||||
},
|
||||
"codina":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"NovaThor U8500"
|
||||
},
|
||||
"clovertrail":{
|
||||
"CPU":"2x Atom Z2560 @ 1.6GHz",
|
||||
"SoC":"Intel Atom Z2560"
|
||||
},
|
||||
"clt":{
|
||||
"CPU":"4x Cortex-A73 @ 2.36GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 970"
|
||||
},
|
||||
"els":{
|
||||
"CPU":"4x Cortex-A76 @ 2.8GHz 4x Cortex-A55 @ 1.9GHz",
|
||||
"SoC":"HiSilicon Kirin 990 5G"
|
||||
},
|
||||
"dandelion":{
|
||||
"CPU":"8x Cortex-A54 @ 1.5GHz",
|
||||
"SoC":"MediaTek Helio G25 (MT6762G)"
|
||||
},
|
||||
"darcy":{
|
||||
"CPU":"4x ARM Cortex-A53 4x ARM Cortex-A57",
|
||||
"SoC":"nVIDIA Tegra X1 T210"
|
||||
},
|
||||
"db8520h":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"NovaThor U8500"
|
||||
},
|
||||
"dragon":{
|
||||
"CPU":"4x Cortex-A57 @ 1.9GHz 4x Cortex-A53 @ 1.3GHz",
|
||||
"SoC":"nVIDIA Tegra X1 T210"
|
||||
},
|
||||
"douglas":{
|
||||
"CPU":"4x Cortex-A53 @ 1.3GHz",
|
||||
"SoC":"MediaTek MT8163"
|
||||
},
|
||||
"eeepad":{
|
||||
"CPU":"4x Atom Z2520 @ 1.2GHz",
|
||||
"SoC":"Intel Atom Z2520"
|
||||
},
|
||||
"endeavoru":{
|
||||
"CPU":"4x Cortex-A9 @ 1.5GHz 1x Cortex-A9 @ 0.5GHz",
|
||||
"SoC":"nVIDIA Tegra 3 AP33"
|
||||
},
|
||||
"eml":{
|
||||
"CPU":"4x Cortex-A73 @ 2.36GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 970"
|
||||
},
|
||||
"eve":{
|
||||
"CPU":"i5-7Y56 @ 1.2GHz",
|
||||
"SoC":"Ambel Lake-Y / Kaby Lake-U/Y"
|
||||
},
|
||||
"eva-l19":{
|
||||
"CPU":"4x Cortex-A72 @ 2.5GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 955"
|
||||
},
|
||||
"exynos990":{
|
||||
"CPU":"4x Cortex-A55 @ 2GHz 2x Cortex-A76 @ 2.5GHz 2x Exynos M5 @ 2.7GHz",
|
||||
"SoC":"Exynos 990"
|
||||
},
|
||||
"exynos9611":{
|
||||
"CPU":"4x Cortex-A73 @ 2.3GHz 4x Cortex-A53 @ 1.7GHz",
|
||||
"SoC":"Exynos 7 Octa (9611)"
|
||||
},
|
||||
"exynos2100":{
|
||||
"CPU":"1x Cortex-X1 @ 2.9GHz 3x Cortex-A78 @ 2.8GHz 4x Cortex-A55 @ 2.2GHz",
|
||||
"SoC":"Exynos 2100"
|
||||
},
|
||||
"exynos9810":{
|
||||
"CPU":"4x Exynos M3 @ 2.7GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Exynos 9 (9810)"
|
||||
},
|
||||
"exynos9820":{
|
||||
"CPU":"2 Exynos M4 @ 2.7GHz 2x Cortex-A75 @ 2.3GHz 4x Cortex-A55 @ 1.9GHz",
|
||||
"SoC":"Exynos 9 (9820)"
|
||||
},
|
||||
"ford":{
|
||||
"CPU":"4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"MediaTek MT8127"
|
||||
},
|
||||
"flo":{
|
||||
"CPU":"4x Krait 300 @ 1.5GHz",
|
||||
"SoC":"Snapdragon 600 APQ8064-FLO"
|
||||
},
|
||||
"flame":{
|
||||
"CPU":"1x Kryo 485 Gold @ 2.8GHz 3x Kryo 485 Gold @ 2.4GHz 4x Kryo 485 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 855 SM8150"
|
||||
},
|
||||
"fleur":{
|
||||
"CPU":"2x Cortex-A76 @ 2GHz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"MediaTek Helio G96 (MT6781)"
|
||||
},
|
||||
"flounder":{
|
||||
"CPU":"2x nVidia Denver @ 2.5GHz",
|
||||
"SoC":"nVIDIA Tegra K1 T132"
|
||||
},
|
||||
"g3u":{
|
||||
"CPU":"2x Cortex-A5 @ 1.0GHz",
|
||||
"SoC":"Snapdragon S4 Play MSM8225"
|
||||
},
|
||||
"gee":{
|
||||
"CPU":"4x Krait 300 @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Pro APQ8064"
|
||||
},
|
||||
"grouper":{
|
||||
"CPU":"4x Cortex-A9 @ 1.3GHz 1x Cortex-A9 @ 0.5GHz",
|
||||
"SoC":"nVIDIA Tegra 3 T30L"
|
||||
},
|
||||
"hammerhead":{
|
||||
"CPU":"4x Krait 400 @ 2.26GHz",
|
||||
"SoC":"Snapdragon 800 MSM8974"
|
||||
},
|
||||
"hawaii_ss_kylepro":{
|
||||
"CPU":"2x Cortex-A9 @ 1.2GHz",
|
||||
"SoC":"Broadcom BCM21664T"
|
||||
},
|
||||
"herring":{
|
||||
"CPU":"1x Cortex-A8 @ 1.0GHz",
|
||||
"SoC":"Exynos 3 Single 3110"
|
||||
},
|
||||
"hollywood":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.4GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon XR2"
|
||||
},
|
||||
"k6853v1_64_titan":{
|
||||
"CPU":"2x Cortex-A76 @ 2Ghz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"MediaTek Dimensity 720 (MT6853)"
|
||||
},
|
||||
"kalama":{
|
||||
"CPU":"1x Cortex-X3 @ 3.3GHz 2x Cortex-A710 @ 2.8GHz 2x Cortex-A715 @ 2.8GHz 3x Cortex-A510 @ 2.0GHz",
|
||||
"SoC":"Snapdragon 8 Gen 2 (SM8550)"
|
||||
},
|
||||
"kona":{
|
||||
"CPU":"1x Cortex-A77 @ 3.1GHz 3x Cortex-A77 @ 2.4GHz 4x Kryo 585 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 865 SM8250"
|
||||
},
|
||||
"kohaku":{
|
||||
"CPU":"i5-10210U @ 1.6GHz",
|
||||
"SoC":"Comet Lake-U"
|
||||
},
|
||||
"lahaina":{
|
||||
"CPU":"1x Cortex-X1 @ 2.8GHz 3x Cortex-A78 @ 2.4GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Snapdragon 888"
|
||||
},
|
||||
"liara":{
|
||||
"CPU":"5 Compute cores 2C+3G",
|
||||
"SoC":"AMD A4-9120C Radeon R4"
|
||||
},
|
||||
"lito":{
|
||||
"CPU":"2x Cortex-A77 @ 2.1GHz 6x Kryo 560 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 690 SM6350"
|
||||
},
|
||||
"lya":{
|
||||
"CPU":"2x Cortex-A76 @ 2.6GHz 2x Cortex-A76 @ 1.9GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 980"
|
||||
},
|
||||
"lyo-l01":{
|
||||
"CPU":"4x Cortex-153 @ 1.3GHz",
|
||||
"SoC":"MediaTek MT6735"
|
||||
},
|
||||
"mako":{
|
||||
"CPU":"4x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Pro APQ8064"
|
||||
},
|
||||
"marlin":{
|
||||
"CPU":"2x Kryo HP @ 2.15GHz 2x Kryo @ 1.6GHz",
|
||||
"SoC":"Snapdragon 821 MSM8996 Pro"
|
||||
},
|
||||
"med":{
|
||||
"CPU":"8x Cortex-A54 @ 1.5GHz",
|
||||
"SoC":"MediaTek MT6762R"
|
||||
},
|
||||
"mocha":{
|
||||
"CPU":"4x Cortex-A15 @2.2GHz",
|
||||
"SoC":"nVIDIA Tegra K1 T124"
|
||||
},
|
||||
"msm8225":{
|
||||
"CPU":"2x Cortex-A5 @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 MSM8225"
|
||||
},
|
||||
"msm8226":{
|
||||
"CPU":"4x Cortex-A7 @ 1.19GHz",
|
||||
"SoC":"Snapdragon 400 MSM8226"
|
||||
},
|
||||
"msm8625":{
|
||||
"CPU":"2x Cortex-A5 @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 MSM8625"
|
||||
},
|
||||
"MSM8227":{
|
||||
"CPU":"2x Krait @ 1GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8227"
|
||||
},
|
||||
"msm8627":{
|
||||
"CPU":"2x Krait @ 1GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8627"
|
||||
},
|
||||
"apq8030":{
|
||||
"CPU":"2x Krait @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 Plus APQ8030"
|
||||
},
|
||||
"msm8230":{
|
||||
"CPU":"2x Krait @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8230"
|
||||
},
|
||||
"msm8660_surf":{
|
||||
"CPU":"2x Scorpion @ 1.5GHz",
|
||||
"SoC":"Snapdragon S3 MSM8260"
|
||||
},
|
||||
"msm8630":{
|
||||
"CPU":"2x Krait @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8630"
|
||||
},
|
||||
"msm8930":{
|
||||
"CPU":"2x Krait @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8930"
|
||||
},
|
||||
"msm8937":{
|
||||
"CPU":"4x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"Snapdragon 425 MSM8917"
|
||||
},
|
||||
"apq8060a":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus APQ8060A"
|
||||
},
|
||||
"msm8260a":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8260A"
|
||||
},
|
||||
"msm8660a":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8660A"
|
||||
},
|
||||
"msm8960":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8960"
|
||||
},
|
||||
"msm8260a-pro":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8260A Pro"
|
||||
},
|
||||
"msm8960t":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8960T"
|
||||
},
|
||||
"msm8960t-pro":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8960T Pro"
|
||||
},
|
||||
"msm8960ab":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8960AB"
|
||||
},
|
||||
"msm8960dt":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8960DT"
|
||||
},
|
||||
"apq8064":{
|
||||
"CPU":"4x Krait 300 @ 1.5GHz",
|
||||
"SoC":"Snapdragon 600 APQ8064"
|
||||
},
|
||||
"msm8916":{
|
||||
"CPU":"4x Cortex-A53 @ 1.2GHz",
|
||||
"SoC":"Snapdragon 410 MSM8916"
|
||||
},
|
||||
"msm8953":{
|
||||
"CPU":"8x Cortex-A53 @ 2.0GHz",
|
||||
"SoC":"Snapdragon 625 MSM8953"
|
||||
},
|
||||
"msm8952":{
|
||||
"CPU":"8x Cortex-A53 @ 1.2GHz",
|
||||
"SoC":"Snapdragon 617 MSM8952"
|
||||
},
|
||||
"msm8956":{
|
||||
"CPU":"2x Cortex-A72 @ 1.8GHz 4x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"Snapdragon 650 MSM8956"
|
||||
},
|
||||
"msm8974":{
|
||||
"CPU":"4x Krait 400 @ 2.15GHz",
|
||||
"SoC":"Snapdragon 800 MSM8974"
|
||||
},
|
||||
"msm8974pro-ab":{
|
||||
"CPU":"4x Krait 400 @ 2.26GHz",
|
||||
"SoC":"Snapdragon 801 MSM8974PRO-AB"
|
||||
},
|
||||
"msm8974pro-ac":{
|
||||
"CPU":"4x Krait 400 @ 2.45GHz",
|
||||
"SoC":"Snapdragon 801 MSM8974AC"
|
||||
},
|
||||
"msm8976":{
|
||||
"CPU":"4x Cortex-A53 @ 1.4GHz 4x Cortex-A72 @ 1.8GHz",
|
||||
"SoC":"Snapdragon 652 MSM8976"
|
||||
},
|
||||
"msm8976pro":{
|
||||
"CPU":"4x Cortex-A53 @ 1.4GHz 4x Cortex-A72 @ 1.9GHz",
|
||||
"SoC":"Snapdragon 653 MSM8976 Pro"
|
||||
},
|
||||
"msm8992":{
|
||||
"CPU":"4x Cortex-A57 @ 1.8GHz 4x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"Snapdragon 808 MSM8992"
|
||||
},
|
||||
"msm8994":{
|
||||
"CPU":"4x Cortex-A57 @ 1.95GHz 4x Cortex-A53 @ 1.5GHz",
|
||||
"SoC":"Snapdragon 810 MSM8994"
|
||||
},
|
||||
"msm8996":{
|
||||
"CPU":"2x Kryo HP @ 1.8GHz 2x Kryo LP @ 1.36GHz",
|
||||
"SoC":"Snapdragon 820 MSM8996"
|
||||
},
|
||||
"msm8996pro":{
|
||||
"CPU":"2x Kryo HP @ 2.34GHz 2x Kryo LP @ 2.18GHz",
|
||||
"SoC":"Snapdragon 821 MSM8996 Pro"
|
||||
},
|
||||
"msm8998":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.4GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"msmnile":{
|
||||
"CPU":"1x Kryo 485 Gold @ 2.8GHz 3x Kryo 485 Gold @ 2.4GHz 4x Kryo 485 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 855 SM8150"
|
||||
},
|
||||
"mt6795t":{
|
||||
"CPU":"8x Cortex-A53 @ 2.1GHz",
|
||||
"SoC":"MediaTek Helio X10 MT6795T"
|
||||
},
|
||||
"mt6797m":{
|
||||
"CPU":"2x Cortex-A72 @ 2.1GHz 4x Cortex-A53 @ 1.85GHz 4x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"MediaTek Helio X20 MT6797M"
|
||||
},
|
||||
"mt6750t":{
|
||||
"CPU":"8x Cortex-A53 @ 1.5GHz",
|
||||
"SoC":"MediaTek MT6750T"
|
||||
},
|
||||
"mx5":{
|
||||
"CPU":"8x Cortex-A53 @ 2.1GHz",
|
||||
"SoC":"MediaTek Helio X10 MT6795T"
|
||||
},
|
||||
"mtk6575":{
|
||||
"CPU":"1x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"MediaTek MT6575"
|
||||
},
|
||||
"noh":{
|
||||
"CPU":"4x Cortex-A77 @ 3.1GHz 4x Cortex-A55 @ 2.0GHz",
|
||||
"SoC":"HiSilicon Kirin 9000"
|
||||
},
|
||||
"sdm710":{
|
||||
"CPU":"2x Kryo 385 Gold @ 2.2GHz 6x Kryo 385 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 710 SDM710"
|
||||
},
|
||||
"sdm845":{
|
||||
"CPU":"4x Kryo 385 Gold @ 2.8GHz 4x Kryo 385 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 845 SDM845"
|
||||
},
|
||||
"monterey":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"oriole":{
|
||||
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A76 @ 2.2GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor (Whitechapel)"
|
||||
},
|
||||
"oppo6779_18073":{
|
||||
"CPU":"2x Cortex-A75 @ 2.2GHz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"Mediatek MT6779 Helio P90"
|
||||
},
|
||||
"gt-p7510":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"nVIDIA Tegra 2 T20"
|
||||
},
|
||||
"pacific":{
|
||||
"CPU":"2x @ 2.1GHz 2x @ 1.6GHz",
|
||||
"SoC":"Snapdragon 820E Embedded"
|
||||
},
|
||||
"piranha":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"Texas Instruments OMAP4430"
|
||||
},
|
||||
"pro5":{
|
||||
"CPU":"4xCortex-56 @ 2.1GHz 4x Cortex-53 @ 1.5GHz",
|
||||
"SoC":"Exynos 7 Octa 7420"
|
||||
},
|
||||
"pro7plus":{
|
||||
"CPU":"2x Cortex-A73 @ 2.6GHz 4x Cortex-A53 @ 2.2GHz 4x Cortex-A35 @ 1.9GHz",
|
||||
"SoC":"MediaTek Helio X30 MT6799"
|
||||
},
|
||||
"pxa986":{
|
||||
"CPU":"2x Cortex-A9 @ 1.2GHz",
|
||||
"SoC":"Marvell PXA988"
|
||||
},
|
||||
"pxa19xx":{
|
||||
"CPU":"4x Cortex-A53 @ 1.25GHz",
|
||||
"SoC":"Maxvell Armada PXA1908"
|
||||
},
|
||||
"rhea_ss_corsicass":{
|
||||
"CPU":"1x Cortex-A9 @ 0.9GHz",
|
||||
"SoC":"Broadcom BCM21654"
|
||||
},
|
||||
"panther":{
|
||||
"CPU":"1x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor G2"
|
||||
},
|
||||
"prada":{
|
||||
"CPU":"4x Cortex-A53 @ 1.4 GHz 4x Cortex-A53 @ 1.1GHz",
|
||||
"SoC":"Snapdragon 430 MSM8937"
|
||||
},
|
||||
"pro6plus":{
|
||||
"CPU":"4x Exynos M1 @ 1.97GHz 4x Cortex-A53 @ 1.48GHz",
|
||||
"SoC":"Exynos 8 Octa 8890"
|
||||
},
|
||||
"universal3110":{
|
||||
"CPU":"1x Cortex-A8 @ 1.2GHz",
|
||||
"SoC":"Exynos 3 Single 3110"
|
||||
},
|
||||
"universal9820":{
|
||||
"CPU":"2x Exynos M4 @ 2.7GHz 2x Cortex-A75 @ 2.3GHz 4x Cortex-A55 @ 1.95GHz",
|
||||
"SoC":"Exynos 9 9820"
|
||||
},
|
||||
"redfin":{
|
||||
"CPU":"2x Kryo 475 Gold @ 2.4GHz 2x Kryo 475 Gold 2.2GHz 6x Kryo 475 Silver @ 1.8GHz ",
|
||||
"SoC":"Snapdragon 765/765G"
|
||||
},
|
||||
"s5e9925":{
|
||||
"CPU":"1x Cortex-X2 @ 2.8GHZ 3x Cortex-A710 @ 2.5GHz 4x Cortex-A510 @ 1.8GHz",
|
||||
"SoC":"Exynos 2200"
|
||||
},
|
||||
"saturn":{
|
||||
"CPU":"4x Krait 450 @ 2.45GHz",
|
||||
"SoC":"Snapdragon 805 APQ8084"
|
||||
},
|
||||
"sailfish":{
|
||||
"CPU":"2x Kryo HP @ 2.15GHz 2x Kryo @ 1.6GHz",
|
||||
"SoC":"Snapdragon 821 MSM8996 Pro"
|
||||
},
|
||||
"sdm660":{
|
||||
"CPU":"4x Kryo 260 HP @ 2.2GHz 4x Kryo 260 LP @ 1.8GHz",
|
||||
"SoC":"Snapdragon 660"
|
||||
},
|
||||
"sc-02b":{
|
||||
"CPU":"1x Cortex-A8 @ 1.2GHz",
|
||||
"SoC":"Exynos 3 Single 3110"
|
||||
},
|
||||
"sch-i905":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"nVIDIA Tegra 2 T20"
|
||||
},
|
||||
"sc7730s":{
|
||||
"CPU":"4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Spreadtrum SC7730S"
|
||||
},
|
||||
"shamu":{
|
||||
"CPU":"4x Krai 450 @ 2.65GHz",
|
||||
"SoC":"Snapdragon 805 APQ8084AB"
|
||||
},
|
||||
"shiba":{
|
||||
"CPU":"1x Cortex-X3 @ 2.9GHz 4x Cortex-A715 @ 2.3GHz 4x Cortex-A510 @ 1.7GHz",
|
||||
"SoC":"Google Tensor G3 (GS301)"
|
||||
},
|
||||
"sm6150":{
|
||||
"CPU":"2x Kryo 460 Gold @ 2GHz 6x 460 Kryo Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 675 SM6150"
|
||||
},
|
||||
"smdkc110":{
|
||||
"CPU":"1x Cortex-A8 @ 1.2GHz",
|
||||
"SoC":"Exynos 3 Single 3110"
|
||||
},
|
||||
"sun":{
|
||||
"CPU":"2x Oryon @ 4.5GHz 6x Oryon @ 3.5GHz",
|
||||
"SoC":"Snapdragon 9 Elite (SM8750)"
|
||||
},
|
||||
"ums512_25c10": {
|
||||
"CPU":"2 Cortex-A75 @2GHz 6x Cortex-A55 @ 2 GHz",
|
||||
"SoC":"Unisoc Tiger T618"
|
||||
},
|
||||
"universal3250":{
|
||||
"CPU":"2x Cortex-A7 @ 1.0GHz",
|
||||
"SoC":"Exynos 2 Dual 3250"
|
||||
},
|
||||
"universal3470":{
|
||||
"CPU":"4x Cortex-A7 @ 1.4GHz",
|
||||
"SoC":"Exynos 3 Quad 3470"
|
||||
},
|
||||
"universal3475":{
|
||||
"CPU":"4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 3 Quad 3475"
|
||||
},
|
||||
"universal4210":{
|
||||
"CPU":"2x Cortex-A9 @ 1.4GHz",
|
||||
"SoC":"Exynos 4 Dual 4210"
|
||||
},
|
||||
"universal4212":{
|
||||
"CPU":"2x Cortex-A9 @ 1.5GHz",
|
||||
"SoC":"Exynos 4 Dual 4212"
|
||||
},
|
||||
"universal4412":{
|
||||
"CPU":"4x Cortex-A9 @ 1.4GHz",
|
||||
"SoC":"Exynos 4 Quad 4412"
|
||||
},
|
||||
"smdk4x12":{
|
||||
"CPU":"4x Cortex-A9 @ 1.4GHz",
|
||||
"SoC":"Exynos 4 Quad 4412"
|
||||
},
|
||||
"universal4415":{
|
||||
"CPU":"4x Cortex-A9 @ 1.5GHz",
|
||||
"SoC":"Exynos 4 Quad 4415"
|
||||
},
|
||||
"universal5250":{
|
||||
"CPU":"2x Cortex-A15 @ 1.7GHz",
|
||||
"SoC":"Exynos 5 Dual 5250"
|
||||
},
|
||||
"universal5260":{
|
||||
"CPU":"2x Cortex-A15 @ 1.7GHz 4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 5 Hexa 5260"
|
||||
},
|
||||
"universal5410":{
|
||||
"CPU":"4x Cortex-A15 @ 1.6GHz 4x Cortex-A7 @ 1.2GHz",
|
||||
"SoC":"Exynos 5 Octa 5410"
|
||||
},
|
||||
"universal5420":{
|
||||
"CPU":"4x Cortex-A15 @ 1.9GHz 4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 5 Octa 5420"
|
||||
},
|
||||
"universal5422":{
|
||||
"CPU":"4x Cortex-A15 @ 2.1GHz 4x Cortex-A7 @ 1.5GHz",
|
||||
"SoC":"Exynos 5 Octa 5422"
|
||||
},
|
||||
"universal5430":{
|
||||
"CPU":"4x Cortex-A15 @ 1.8GHz 4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 5 Octa 5430"
|
||||
},
|
||||
"universal5800":{
|
||||
"CPU":"4x Cortex-A15 @ 2.0GHz 4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 5 Octa 5800"
|
||||
},
|
||||
"universal5433":{
|
||||
"CPU":"4x Cortex-A57 @ 1.9GHz 4x Cortex-A53 @ 1.3GHz",
|
||||
"SoC":"Exynos 7 Octa 5433"
|
||||
},
|
||||
"universal7420":{
|
||||
"CPU":"4x Cortex-A57 @ 2.1GHz 4x Cortex-A53 @ 1.5GHz",
|
||||
"SoC":"Exynos 7 Octa 7420"
|
||||
},
|
||||
"universal7570":{
|
||||
"CPU":"8x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"Exynos 7 Quad 7570"
|
||||
},
|
||||
"universal7580":{
|
||||
"CPU":"8x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 7 Octa 7580"
|
||||
},
|
||||
"universal7870":{
|
||||
"CPU":"8x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 7 Octa 7870"
|
||||
},
|
||||
"universal7880":{
|
||||
"CPU":"8x Cortex-A53 @ 1.9GHz",
|
||||
"SoC":"Exynos 7 Octa 7880"
|
||||
},
|
||||
"universal7872":{
|
||||
"CPU":"2x Cortex-A73 @ 2.0GHz 4x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 7 Hexa 7872"
|
||||
},
|
||||
"universal7885":{
|
||||
"CPU":"2x Cortex-A73 @ 2.2GHz 6x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 7 Octa 7885"
|
||||
},
|
||||
"universal8890":{
|
||||
"CPU":"4x Cortex-A53 @ 1.6GHz 4x Samsung Exynos M1 @ 2.6GHz",
|
||||
"SoC":"Exynos 8 Octa 8890"
|
||||
},
|
||||
"universal8895":{
|
||||
"CPU":"4x Samsung Exynos M1 @ 2.3GHz 4x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 9 Octa 8895"
|
||||
},
|
||||
"universal9810":{
|
||||
"CPU":"4x Samsung Exynos M3 @ 2.8GHz 4x Cortex-A55 @ 1.7GHz",
|
||||
"SoC":"Exynos 9 Series 9810"
|
||||
},
|
||||
"universal9825":{
|
||||
"CPU":"2x Samsung Exynos M4 @ 2.7GHz 2x Cortex-A75 @ 2.4GHz 4x Cortex-A55 @ 1.9GHz",
|
||||
"SoC":"Exynos 9 Series 9825"
|
||||
},
|
||||
"ville":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 MSM8290"
|
||||
},
|
||||
"vog":{
|
||||
"CPU":"2x Cortex-A76 @ 2.6GHz 2x Cortex-A76 @ 1.9GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 980"
|
||||
},
|
||||
"venus":{
|
||||
"CPU":"1x Cortex-X1 @ 2.8GHz 3x Cortex-A78 @ 2.4GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Snapdragon 888 SM8350"
|
||||
},
|
||||
"vtr":{
|
||||
"CPU":"4x Cortex-A73 @ 2.4GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 960"
|
||||
},
|
||||
"taimen":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"tn8":{
|
||||
"CPU":"4x Cortex-A15 @2.2GHz",
|
||||
"SoC":"nVIDIA Tegra K1 T124"
|
||||
},
|
||||
"taro":{
|
||||
"CPU":"1x Cortex-X2 @ 3GHz 3x Cortex-A710 @ 2.5GHz 4x Cortex-A510 @ 1.8GHz",
|
||||
"SoC":"Snapdragon 8 Gen 1 (SM8450)"
|
||||
},
|
||||
"thebes":{
|
||||
"CPU":"2x Cortex-A15 @ 1.5GHz 2x Cortex-A7 @ 1.2GHz",
|
||||
"SoC":"MediaTek MT8135"
|
||||
},
|
||||
"trinket":{
|
||||
"CPU":"4x Kryo 260 HP @ 2GHz 4x Kryo 260 LP @ 1.8GHz",
|
||||
"SoC":"Snapdragon 665 SM6125"
|
||||
},
|
||||
"tuna":{
|
||||
"CPU":"2x Cortex-A9 @ 1.2GHz",
|
||||
"SoC":"TI OMAP 4460"
|
||||
},
|
||||
"vu2kt":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8960"
|
||||
},
|
||||
"walleye":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"wikipad":{
|
||||
"CPU":"4x Cortex-A9 @ 1.3GHz 1x Cortex-A9 @ 0.5GHz",
|
||||
"SoC":"nVIDIA Tegra 3 T30L"
|
||||
},
|
||||
"qc_reference_phone":{
|
||||
"CPU":"2x Kryo HP @ 2.15GHz 2x Kryo LP @ 1.36GHz",
|
||||
"SoC":"Snapdragon 820 MSM8996"
|
||||
},
|
||||
"z4u":{
|
||||
"CPU":"4x Cortex-A5 @ 1.2GHz",
|
||||
"SoC":"Snapdragon 200 MSM8225Q"
|
||||
},
|
||||
"zs600kl":{
|
||||
"CPU":"4x Kryo 385 Gold @ 3GHz 4x Kryo 385 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 845"
|
||||
},
|
||||
"mt6765v":{
|
||||
"CPU":"4x Cortex-A53 @ 2.3GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"Mediatek MT6765G Helio G35 (12 nm)"
|
||||
},
|
||||
"k65v1_64_bsp_titan_rat":{
|
||||
"CPU":"4x Cortex-A53 @ 2.3GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"Mediatek MT6765 Helio P35 (12nm)"
|
||||
},
|
||||
"careena":{
|
||||
"CPU":"AMD 670F00h",
|
||||
"SoC":"AMD A4-9120C RADEON R4, 5 COMPUTE CORES 2C+3G"
|
||||
},
|
||||
"sion":{
|
||||
"CPU":"i3-8130U CPU 2.2GHz SoC",
|
||||
"SoC":"i3-8130U CPU 2,2 GHz soc"
|
||||
},
|
||||
"biloba":{
|
||||
"CPU":"2x Cortex-A75 @ 2.0GHz 6x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Mediatek MT6769Z Helio G85 (12nm)"
|
||||
},
|
||||
"ele":{
|
||||
"CPU":"2x Cortex-A76 @ 2.6GHz 2x Cortex-A76 @ 1.9GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Kirin 980 (7 nm)"
|
||||
},
|
||||
"krane":{
|
||||
"CPU":"4x Cortex-A73 @ 2.0GHz + 4x Cortex-A53 @ 2.0GHz",
|
||||
"SoC":"MediaTekHelio P60T (12nm)"
|
||||
},
|
||||
"pineapple":{
|
||||
"CPU":"1x Cortex-X4 @ 3.3GHz 3x Cortex-A720 @ 3.2GHz 2x Cortex-A720 @ 2.6GHz 4x Cortex-A520 @ 2.3GHz",
|
||||
"SoC":"Qualcomm SM8650-AB Snapdragon 8 Gen 3"
|
||||
},
|
||||
"s5e9945":{
|
||||
"CPU":"1x Cortex-X4 @ 3.2GHz 2x Cortex-A720 @ 2.9GHz 3x Cortex-A720 @ 2.6GHz 4x Cortex-A520 @ 2.0GHz",
|
||||
"SoC":"Exynos 2400"
|
||||
},
|
||||
"a12":{
|
||||
"CPU":"4x Cortex-A53 @ 2.35GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"MediaTek Helio P35 (MT6765)"
|
||||
}
|
||||
}
|
||||
@@ -67,10 +67,12 @@ import forge.util.ThreadUtil;
|
||||
import io.sentry.protocol.Device;
|
||||
import io.sentry.protocol.OperatingSystem;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.json.JSONObject;
|
||||
import org.jupnp.DefaultUpnpServiceConfiguration;
|
||||
import org.jupnp.android.AndroidUpnpServiceConfiguration;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
@@ -188,7 +190,26 @@ public class Main extends AndroidApplication {
|
||||
|
||||
boolean permissiongranted = checkPermission();
|
||||
Gadapter = new AndroidAdapter(getContext());
|
||||
|
||||
String cpu = "";
|
||||
String soc = "";
|
||||
boolean getChipset = false;
|
||||
// database.json source: https://github.com/xTheEc0/Android-Device-Hardware-Specs-Database
|
||||
try {
|
||||
InputStream is = getAssets().open("database.json");
|
||||
int size = is.available();
|
||||
byte[] buffer = new byte[size];
|
||||
is.read(buffer);
|
||||
is.close();
|
||||
JSONObject db = new JSONObject(new String(buffer, StandardCharsets.UTF_8));
|
||||
JSONObject board = db.getJSONObject(Build.BOARD);
|
||||
cpu = board.get("CPU").toString();
|
||||
soc = board.get("SoC").toString();
|
||||
getChipset = true;
|
||||
} catch (Exception e) {
|
||||
cpu = getCpuName();
|
||||
soc = Build.BOARD;
|
||||
getChipset = false;
|
||||
}
|
||||
// Device Info
|
||||
Device device = new Device();
|
||||
device.setId(Build.ID);
|
||||
@@ -197,8 +218,8 @@ public class Main extends AndroidApplication {
|
||||
device.setBrand(Build.BRAND);
|
||||
device.setManufacturer(Build.MANUFACTURER);
|
||||
device.setMemorySize(memInfo.totalMem);
|
||||
device.setCpuDescription(getCpuName());
|
||||
device.setChipset(Build.HARDWARE + " " + Build.BOARD);
|
||||
device.setCpuDescription(cpu);
|
||||
device.setChipset(soc);
|
||||
// OS Info
|
||||
OperatingSystem os = new OperatingSystem();
|
||||
os.setName("Android");
|
||||
@@ -206,7 +227,7 @@ public class Main extends AndroidApplication {
|
||||
os.setBuild(Build.DISPLAY);
|
||||
os.setRawDescription(getAndroidOSName());
|
||||
|
||||
initForge(Gadapter, new HWInfo(device, os), permissiongranted, totalMemory, isTabletDevice(getContext()));
|
||||
initForge(Gadapter, new HWInfo(device, os, getChipset), permissiongranted, totalMemory, isTabletDevice(getContext()));
|
||||
}
|
||||
|
||||
private void crossfade(View contentView, View previousView) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class AddBasicLandsDialog {
|
||||
private static final int LAND_PANEL_PADDING = 3;
|
||||
|
||||
private final FComboBoxPanel<CardEdition> cbLandSet = new FComboBoxPanel<>(Localizer.getInstance().getMessage("lblLandSet") + ":", FlowLayout.CENTER,
|
||||
IterableUtil.filter(StaticData.instance().getSortedEditions(), CardEdition.Predicates.hasBasicLands));
|
||||
IterableUtil.filter(StaticData.instance().getSortedEditions(), CardEdition::hasBasicLands));
|
||||
|
||||
private final MainPanel panel = new MainPanel();
|
||||
private final LandPanel pnlPlains = new LandPanel("Plains");
|
||||
|
||||
@@ -46,7 +46,6 @@ import forge.toolbox.*;
|
||||
import forge.util.Localizer;
|
||||
import forge.view.FDialog;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import static forge.deck.DeckRecognizer.TokenType.*;
|
||||
|
||||
@@ -523,7 +522,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
else
|
||||
deck.setName(currentDeckName);
|
||||
}
|
||||
host.getDeckController().loadDeck(deck, controller.getCreateNewDeck());
|
||||
host.getDeckController().loadDeck(deck, controller.getImportBehavior() != DeckImportController.ImportBehavior.MERGE);
|
||||
processWindowEvent(new WindowEvent(DeckImport.this, WindowEvent.WINDOW_CLOSING));
|
||||
});
|
||||
|
||||
@@ -531,7 +530,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
this.createNewDeckCheckbox.setSelected(false);
|
||||
this.createNewDeckCheckbox.addActionListener(e -> {
|
||||
boolean createNewDeck = createNewDeckCheckbox.isSelected();
|
||||
controller.setCreateNewDeck(createNewDeck);
|
||||
controller.setImportBehavior(createNewDeck ? DeckImportController.ImportBehavior.CREATE_NEW : DeckImportController.ImportBehavior.MERGE);
|
||||
String cmdAcceptLabel = createNewDeck ? CREATE_NEW_DECK_CMD_LABEL : IMPORT_CARDS_CMD_LABEL;
|
||||
cmdAcceptButton.setText(cmdAcceptLabel);
|
||||
String smartCardArtChboxTooltip = createNewDeck ? SMART_CARDART_TT_NO_DECK : SMART_CARDART_TT_WITH_DECK;
|
||||
@@ -600,7 +599,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
if (token.getType() == LIMITED_CARD)
|
||||
cssClass = WARN_MSG_CLASS;
|
||||
String statusMsg = String.format("<span class=\"%s\" style=\"font-size: 9px;\">%s</span>", cssClass,
|
||||
getTokenStatusMessage(token));
|
||||
controller.getTokenStatusMessage(token));
|
||||
statusLbl.append(statusMsg);
|
||||
}
|
||||
|
||||
@@ -740,12 +739,12 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
private String toHTML(final DeckRecognizer.Token token) {
|
||||
if (token == null)
|
||||
return "";
|
||||
String tokenMsg = getTokenMessage(token);
|
||||
String tokenMsg = controller.getTokenMessage(token);
|
||||
if (tokenMsg == null)
|
||||
return "";
|
||||
String tokenStatus = getTokenStatusMessage(token);
|
||||
String tokenStatus = controller.getTokenStatusMessage(token);
|
||||
String cssClass = getTokenCSSClass(token.getType());
|
||||
if (tokenStatus.length() == 0)
|
||||
if (tokenStatus.isEmpty())
|
||||
tokenMsg = padEndWithHTMLSpaces(tokenMsg, 2*PADDING_TOKEN_MSG_LENGTH+10);
|
||||
else {
|
||||
tokenMsg = padEndWithHTMLSpaces(tokenMsg, PADDING_TOKEN_MSG_LENGTH);
|
||||
@@ -755,11 +754,6 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
tokenMsg = String.format("<a class=\"%s\" href=\"%s\">%s</a>", cssClass,
|
||||
token.getKey().toString(), tokenMsg);
|
||||
|
||||
if (tokenStatus == null) {
|
||||
String tokenTag = String.format("<td colspan=\"2\" class=\"%s\">%s</td>", cssClass, tokenMsg);
|
||||
return String.format("<tr>%s</tr>", tokenTag);
|
||||
}
|
||||
|
||||
String tokenTag = "<td class=\"%s\">%s</td>";
|
||||
String tokenMsgTag = String.format(tokenTag, cssClass, tokenMsg);
|
||||
String tokenStatusTag;
|
||||
@@ -776,97 +770,6 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
return String.format("%s%s", targetMsg, spacer);
|
||||
}
|
||||
|
||||
private String getTokenMessage(DeckRecognizer.Token token) {
|
||||
switch (token.getType()) {
|
||||
case LEGAL_CARD:
|
||||
case LIMITED_CARD:
|
||||
case CARD_FROM_NOT_ALLOWED_SET:
|
||||
case CARD_FROM_INVALID_SET:
|
||||
return String.format("%s x %s %s", token.getQuantity(), token.getText(), getTokenFoilLabel(token));
|
||||
// Card Warning Msgs
|
||||
case UNKNOWN_CARD:
|
||||
case UNSUPPORTED_CARD:
|
||||
return token.getQuantity() > 0 ? String.format("%s x %s", token.getQuantity(), token.getText())
|
||||
: token.getText();
|
||||
|
||||
case UNSUPPORTED_DECK_SECTION:
|
||||
return String.format("%s: %s", Localizer.getInstance().getMessage("lblWarningMsgPrefix"),
|
||||
Localizer.getInstance()
|
||||
.getMessage("lblWarnDeckSectionNotAllowedInEditor", token.getText(),
|
||||
this.currentGameType));
|
||||
|
||||
// Special Case of Card moved into another section (e.g. Commander from Sideboard)
|
||||
case WARNING_MESSAGE:
|
||||
return String.format("%s: %s", Localizer.getInstance()
|
||||
.getMessage("lblWarningMsgPrefix"), token.getText());
|
||||
|
||||
// Placeholders
|
||||
case DECK_SECTION_NAME:
|
||||
return String.format("%s: %s", Localizer.getInstance().getMessage("lblDeckSection"),
|
||||
token.getText());
|
||||
|
||||
case CARD_RARITY:
|
||||
return String.format("%s: %s", Localizer.getInstance().getMessage("lblRarity"),
|
||||
token.getText());
|
||||
|
||||
case CARD_TYPE:
|
||||
case CARD_CMC:
|
||||
case MANA_COLOUR:
|
||||
case COMMENT:
|
||||
return token.getText();
|
||||
|
||||
case DECK_NAME:
|
||||
return String.format("%s: %s", Localizer.getInstance().getMessage("lblDeckName"),
|
||||
token.getText());
|
||||
|
||||
case UNKNOWN_TEXT:
|
||||
default:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private String getTokenStatusMessage(DeckRecognizer.Token token){
|
||||
if (token == null)
|
||||
return "";
|
||||
|
||||
switch (token.getType()) {
|
||||
case LIMITED_CARD:
|
||||
return String.format("%s: %s", Localizer.getInstance().getMessage("lblWarningMsgPrefix"),
|
||||
Localizer.getInstance().getMessage("lblWarnLimitedCard",
|
||||
StringUtils.capitalize(token.getLimitedCardType().name()), getGameFormatLabel()));
|
||||
|
||||
case CARD_FROM_NOT_ALLOWED_SET:
|
||||
return Localizer.getInstance().getMessage("lblErrNotAllowedCard", getGameFormatLabel());
|
||||
|
||||
case CARD_FROM_INVALID_SET:
|
||||
return Localizer.getInstance().getMessage("lblErrCardEditionDate");
|
||||
|
||||
case UNSUPPORTED_CARD:
|
||||
return Localizer.getInstance().getMessage("lblErrUnsupportedCard", this.currentGameType);
|
||||
|
||||
case UNKNOWN_CARD:
|
||||
return String.format("%s: %s", Localizer.getInstance().getMessage("lblWarningMsgPrefix"),
|
||||
Localizer.getInstance().getMessage("lblWarnUnknownCardMsg"));
|
||||
|
||||
case UNSUPPORTED_DECK_SECTION:
|
||||
case WARNING_MESSAGE:
|
||||
case COMMENT:
|
||||
case CARD_CMC:
|
||||
case MANA_COLOUR:
|
||||
case CARD_TYPE:
|
||||
case DECK_SECTION_NAME:
|
||||
case CARD_RARITY:
|
||||
case DECK_NAME:
|
||||
case LEGAL_CARD:
|
||||
case UNKNOWN_TEXT:
|
||||
default:
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getTokenCSSClass(DeckRecognizer.TokenType tokenType){
|
||||
switch (tokenType){
|
||||
case LEGAL_CARD:
|
||||
@@ -899,17 +802,6 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getTokenFoilLabel(DeckRecognizer.Token token) {
|
||||
if (!token.isCardToken())
|
||||
return "";
|
||||
final String foilMarker = "- (Foil)";
|
||||
return token.getCard().isFoil() ? foilMarker : "";
|
||||
}
|
||||
|
||||
private String getGameFormatLabel() {
|
||||
return String.format("\"%s\"", this.controller.getCurrentGameFormatName());
|
||||
}
|
||||
}
|
||||
|
||||
class GameFormatDropdownRenderer extends JLabel implements ListCellRenderer<GameFormat> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public class GameLauncher {
|
||||
os.setBuild(si.getOperatingSystem().getVersionInfo().getBuildNumber());
|
||||
os.setRawDescription(si.getOperatingSystem() + " x" + si.getOperatingSystem().getBitness());
|
||||
totalRAM = Math.round(si.getHardware().getMemory().getTotal() / 1024f / 1024f);
|
||||
hw = new HWInfo(device, os);
|
||||
hw = new HWInfo(device, os, false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class EntryActor extends MapActor{
|
||||
{
|
||||
if(targetMap==null||targetMap.isEmpty())
|
||||
{
|
||||
stage.exitDungeon(false);
|
||||
stage.exitDungeon(false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ public class PortalActor extends EntryActor {
|
||||
}
|
||||
if (currentAnimationType == PortalAnimationTypes.Active) {
|
||||
if (targetMap == null || targetMap.isEmpty()) {
|
||||
stage.exitDungeon(false);
|
||||
stage.exitDungeon(false, false);
|
||||
} else {
|
||||
if (targetMap.equals(currentMap)) {
|
||||
stage.spawn(entryTargetObject);
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -1077,6 +1077,25 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
return items.random();
|
||||
}
|
||||
|
||||
public boolean hasEquippedItem() {
|
||||
for (Long id : equippedItems.values()) {
|
||||
ItemData item = getEquippedItem(id);
|
||||
if (item == null)
|
||||
continue;
|
||||
if (isHardorInsaneDifficulty()) {
|
||||
return true;
|
||||
} else {
|
||||
switch (item.equipmentSlot) {
|
||||
// limit to these for easy and normal
|
||||
case "Boots", "Body", "Neck" -> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ItemData getEquippedAbility1() {
|
||||
for (Long id : equippedItems.values()) {
|
||||
ItemData data = getEquippedItem(id);
|
||||
|
||||
@@ -27,8 +27,6 @@ import forge.item.PaperCard;
|
||||
import forge.itemmanager.*;
|
||||
import forge.itemmanager.filters.CardColorFilter;
|
||||
import forge.itemmanager.filters.CardTypeFilter;
|
||||
import forge.localinstance.properties.ForgePreferences;
|
||||
import forge.menu.FCheckBoxMenuItem;
|
||||
import forge.menu.FDropDownMenu;
|
||||
import forge.menu.FMenuItem;
|
||||
import forge.menu.FPopupMenu;
|
||||
@@ -41,6 +39,7 @@ import forge.util.Utils;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class AdventureDeckEditor extends FDeckEditor {
|
||||
protected static class AdventureEditorConfig extends DeckEditorConfig {
|
||||
@@ -146,7 +145,8 @@ public class AdventureDeckEditor extends FDeckEditor {
|
||||
if(event.cardBlock != null) {
|
||||
if(event.cardBlock.getLandSet() != null)
|
||||
return List.of(event.cardBlock.getLandSet());
|
||||
List<CardEdition> eventSets = event.cardBlock.getSets();
|
||||
List<CardEdition> eventSets = new ArrayList<>(event.cardBlock.getSets());
|
||||
eventSets.removeIf(Predicate.not(CardEdition::hasBasicLands));
|
||||
if(!eventSets.isEmpty())
|
||||
return eventSets;
|
||||
}
|
||||
@@ -558,7 +558,7 @@ public class AdventureDeckEditor extends FDeckEditor {
|
||||
currentEvent.participants[i].setDeck(opponentDecks[i]);
|
||||
}
|
||||
currentEvent.draftedDeck = (Deck) currentEvent.registeredDeck.copyTo("Draft Deck");
|
||||
if (allowsAddBasic()) {
|
||||
if (allowAddBasic()) {
|
||||
showAddBasicLandsDialog();
|
||||
//Might be annoying if you haven't pruned your deck yet, but best to remind player that
|
||||
//this probably needs to be done since it's there since it's not normally part of Adventure
|
||||
@@ -713,27 +713,6 @@ public class AdventureDeckEditor extends FDeckEditor {
|
||||
return this.deckHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FPopupMenu createMoreOptionsMenu() {
|
||||
return new FPopupMenu() {
|
||||
@Override
|
||||
protected void buildMenu() {
|
||||
Localizer localizer = Forge.getLocalizer();
|
||||
addItem(new FMenuItem(localizer.getMessage("btnCopyToClipboard"), Forge.hdbuttons ? FSkinImage.HDEXPORT : FSkinImage.BLANK, e1 -> FDeckViewer.copyDeckToClipboard(getDeck())));
|
||||
if (allowsAddBasic()) {
|
||||
FMenuItem addBasic = new FMenuItem(localizer.getMessage("lblAddBasicLands"), FSkinImage.LANDLOGO, e1 -> showAddBasicLandsDialog());
|
||||
addItem(addBasic);
|
||||
}
|
||||
if(FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.DEV_MODE_ENABLED)) {
|
||||
addItem(new FCheckBoxMenuItem(localizer.getMessage("cbEnforceDeckLegality"), shouldEnforceConformity(), e -> toggleConformity()));
|
||||
String devSuffix = " (" + localizer.getMessage("lblDev") + ")";
|
||||
addItem(new FMenuItem(localizer.getMessage("lblAddcard") + devSuffix, FSkinImage.HDPLUS, e -> showDevAddCardDialog()));
|
||||
}
|
||||
((DeckEditorPage) getSelectedPage()).buildDeckMenu(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addChosenBasicLands(CardPool landsToAdd) {
|
||||
if(isLimitedEditor())
|
||||
@@ -765,6 +744,12 @@ public class AdventureDeckEditor extends FDeckEditor {
|
||||
catalog.moveCards(landsToMove, getMainDeckPage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PaperCard supplyPrintForImporter(PaperCard missingCard) {
|
||||
PaperCard out = super.supplyPrintForImporter(missingCard);
|
||||
return out == null ? null : out.getNoSellVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cacheTabPages() {
|
||||
super.cacheTabPages();
|
||||
@@ -775,7 +760,9 @@ public class AdventureDeckEditor extends FDeckEditor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean allowsAddBasic() {
|
||||
protected boolean allowAddBasic() {
|
||||
if(getEditorConfig() instanceof DeckPreviewConfig)
|
||||
return false;
|
||||
AdventureEventData currentEvent = getCurrentEvent();
|
||||
if (currentEvent == null)
|
||||
return true;
|
||||
|
||||
@@ -157,7 +157,7 @@ public class ArenaScene extends UIScene implements IAfterMatch {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWinner(boolean winner) {
|
||||
public void setWinner(boolean winner, boolean isArena) {
|
||||
enable = false;
|
||||
Array<ArenaRecord> winners = new Array<>();
|
||||
Array<EnemySprite> winnersEnemies = new Array<>();
|
||||
|
||||
@@ -142,7 +142,7 @@ public class DuelScene extends ForgeScene {
|
||||
Current.player().getStatistic().setResult(enemyName, winner);
|
||||
|
||||
if (last instanceof IAfterMatch) {
|
||||
((IAfterMatch) last).setWinner(winner);
|
||||
((IAfterMatch) last).setWinner(winner, isArena);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -541,7 +541,7 @@ public class EventScene extends MenuScene implements IAfterMatch {
|
||||
|
||||
AdventureEventData.AdventureEventMatch humanMatch = null;
|
||||
|
||||
public void setWinner(boolean winner) {
|
||||
public void setWinner(boolean winner, boolean isArena) {
|
||||
if (winner) {
|
||||
humanMatch.winner = humanMatch.p1;
|
||||
humanMatch.p1.wins++;
|
||||
|
||||
@@ -181,8 +181,8 @@ public abstract class HudScene extends Scene implements InputProcessor, IAfterMa
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWinner(boolean winner) {
|
||||
stage.setWinner(winner);
|
||||
public void setWinner(boolean winner, boolean isArena) {
|
||||
stage.setWinner(winner, isArena);
|
||||
}
|
||||
|
||||
public boolean isInHudOnlyMode() {
|
||||
|
||||
@@ -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") + ")",
|
||||
|
||||
@@ -203,7 +203,7 @@ public class ConsoleCommandInterpreter {
|
||||
});
|
||||
registerCommand(new String[]{"leave"}, s -> {
|
||||
if (!MapStage.getInstance().isInMap()) return "not on a map";
|
||||
MapStage.getInstance().exitDungeon(false);
|
||||
MapStage.getInstance().exitDungeon(false, false);
|
||||
return "Got out";
|
||||
});
|
||||
registerCommand(new String[]{"debug", "collision"}, s -> {
|
||||
|
||||
@@ -893,7 +893,7 @@ public class GameHUD extends Stage {
|
||||
@Override
|
||||
public boolean act(float v) {
|
||||
if (exitDungeon) {
|
||||
MapStage.getInstance().exitDungeon(false);
|
||||
MapStage.getInstance().exitDungeon(false, false);
|
||||
setDisabled(exitToWorldMapActor, true, "[%120][+ExitToWorldMap]", "\u2613");
|
||||
setDisabled(bookmarkActor, true, "[%120][+Bookmark]", "\u2613");
|
||||
}
|
||||
|
||||
@@ -74,8 +74,8 @@ public abstract class GameStage extends Stage {
|
||||
private float touchY = -1;
|
||||
private final float timer = 0;
|
||||
private float animationTimeout = 0;
|
||||
public static float maximumScrollDistance=1.5f;
|
||||
public static float minimumScrollDistance=0.3f;
|
||||
public static float maximumScrollDistance = 1.5f;
|
||||
public static float minimumScrollDistance = 0.3f;
|
||||
|
||||
private String extraAnnouncement = "";
|
||||
|
||||
@@ -97,8 +97,9 @@ public abstract class GameStage extends Stage {
|
||||
public void setDialogStage(Stage dialogStage) {
|
||||
this.dialogStage = dialogStage;
|
||||
}
|
||||
|
||||
public void showDialog() {
|
||||
if (dialogStage == null){
|
||||
if (dialogStage == null) {
|
||||
setDialogStage(GameHUD.getInstance());
|
||||
}
|
||||
GameHUD.getInstance().playerIdle();
|
||||
@@ -123,6 +124,7 @@ public abstract class GameStage extends Stage {
|
||||
|
||||
/**
|
||||
* Triggered when the hud is showing a dialog, which is tracked separately
|
||||
*
|
||||
* @param isShowing Whether a dialog is currently showing
|
||||
*/
|
||||
public void hudIsShowingDialog(boolean isShowing) {
|
||||
@@ -248,24 +250,21 @@ public abstract class GameStage extends Stage {
|
||||
showDialog();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean axisMoved(Controller controller, int axisIndex, float value) {
|
||||
|
||||
if (MapStage.getInstance().isDialogOnlyInput()||isPaused()) {
|
||||
if (MapStage.getInstance().isDialogOnlyInput() || isPaused()) {
|
||||
return true;
|
||||
}
|
||||
player.getMovementDirection().x = controller.getAxis(0);
|
||||
player.getMovementDirection().y = -controller.getAxis(1);
|
||||
if(player.getMovementDirection().len()<0.2)
|
||||
{
|
||||
if (player.getMovementDirection().len() < 0.2) {
|
||||
player.stop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum PlayerModification
|
||||
{
|
||||
enum PlayerModification {
|
||||
Sprint,
|
||||
Hide,
|
||||
Fly
|
||||
@@ -273,29 +272,32 @@ public abstract class GameStage extends Stage {
|
||||
}
|
||||
|
||||
|
||||
HashMap<PlayerModification,Float> currentModifications=new HashMap<>();
|
||||
public void modifyPlayer(PlayerModification mod,float value) {
|
||||
float currentValue=0;
|
||||
if(currentModifications.containsKey(mod))
|
||||
{
|
||||
currentValue=currentModifications.get(mod);
|
||||
HashMap<PlayerModification, Float> currentModifications = new HashMap<>();
|
||||
|
||||
public void modifyPlayer(PlayerModification mod, float value) {
|
||||
float currentValue = 0;
|
||||
if (currentModifications.containsKey(mod)) {
|
||||
currentValue = currentModifications.get(mod);
|
||||
}
|
||||
currentModifications.put(mod,currentValue+value);
|
||||
currentModifications.put(mod, currentValue + value);
|
||||
}
|
||||
|
||||
public void flyFor(float value) {
|
||||
modifyPlayer(PlayerModification.Fly,value);
|
||||
modifyPlayer(PlayerModification.Fly, value);
|
||||
player.playEffect(Paths.EFFECT_FLY);
|
||||
}
|
||||
|
||||
public void hideFor(float value) {
|
||||
modifyPlayer(PlayerModification.Hide,value);
|
||||
player.setColor(player.getColor().r,player.getColor().g,player.getColor().b,0.5f);
|
||||
modifyPlayer(PlayerModification.Hide, value);
|
||||
player.setColor(player.getColor().r, player.getColor().g, player.getColor().b, 0.5f);
|
||||
player.playEffect(Paths.EFFECT_HIDE);
|
||||
}
|
||||
|
||||
public void sprintFor(float value) {
|
||||
modifyPlayer(PlayerModification.Sprint,value);
|
||||
modifyPlayer(PlayerModification.Sprint, value);
|
||||
player.playEffect(Paths.EFFECT_SPRINT);
|
||||
}
|
||||
|
||||
public void startPause(float i) {
|
||||
startPause(i, null);
|
||||
}
|
||||
@@ -305,6 +307,7 @@ public abstract class GameStage extends Stage {
|
||||
animationTimeout = i;
|
||||
player.setMovementDirection(Vector2.Zero);
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return animationTimeout > 0;
|
||||
}
|
||||
@@ -330,7 +333,7 @@ public abstract class GameStage extends Stage {
|
||||
dialog = Controls.newDialog("");
|
||||
}
|
||||
|
||||
public void setWinner(boolean b) {
|
||||
public void setWinner(boolean b, boolean a) {
|
||||
}
|
||||
|
||||
public void setBounds(float width, float height) {
|
||||
@@ -365,15 +368,13 @@ public abstract class GameStage extends Stage {
|
||||
animationTimeout -= delta;
|
||||
return;
|
||||
}
|
||||
Array<PlayerModification> modsToRemove=new Array<>();
|
||||
for(Map.Entry<PlayerModification, Float> mod:currentModifications.entrySet())
|
||||
{
|
||||
mod.setValue(mod.getValue()-delta);
|
||||
if(mod.getValue()<0)
|
||||
Array<PlayerModification> modsToRemove = new Array<>();
|
||||
for (Map.Entry<PlayerModification, Float> mod : currentModifications.entrySet()) {
|
||||
mod.setValue(mod.getValue() - delta);
|
||||
if (mod.getValue() < 0)
|
||||
modsToRemove.add(mod.getKey());
|
||||
}
|
||||
for(PlayerModification mod:modsToRemove)
|
||||
{
|
||||
for (PlayerModification mod : modsToRemove) {
|
||||
currentModifications.remove(mod);
|
||||
onRemoveEffect(mod);
|
||||
}
|
||||
@@ -408,10 +409,9 @@ public abstract class GameStage extends Stage {
|
||||
}
|
||||
|
||||
private void onRemoveEffect(PlayerModification mod) {
|
||||
switch (mod)
|
||||
{
|
||||
switch (mod) {
|
||||
case Hide:
|
||||
player.setColor(player.getColor().r,player.getColor().g,player.getColor().b,1f);
|
||||
player.setColor(player.getColor().r, player.getColor().g, player.getColor().b, 1f);
|
||||
break;
|
||||
case Fly:
|
||||
player.removeEffect(Paths.EFFECT_FLY);
|
||||
@@ -430,20 +430,16 @@ public abstract class GameStage extends Stage {
|
||||
super.keyDown(keycode);
|
||||
if (isPaused())
|
||||
return true;
|
||||
if (KeyBinding.Left.isPressed(keycode))
|
||||
{
|
||||
if (KeyBinding.Left.isPressed(keycode)) {
|
||||
player.getMovementDirection().x = -1;
|
||||
}
|
||||
if (KeyBinding.Right.isPressed(keycode) )
|
||||
{
|
||||
if (KeyBinding.Right.isPressed(keycode)) {
|
||||
player.getMovementDirection().x = +1;
|
||||
}
|
||||
if (KeyBinding.Up.isPressed(keycode))
|
||||
{
|
||||
if (KeyBinding.Up.isPressed(keycode)) {
|
||||
player.getMovementDirection().y = +1;
|
||||
}
|
||||
if (KeyBinding.Down.isPressed(keycode))
|
||||
{
|
||||
if (KeyBinding.Down.isPressed(keycode)) {
|
||||
player.getMovementDirection().y = -1;
|
||||
}
|
||||
if (keycode == Input.Keys.F5)//todo config
|
||||
@@ -483,9 +479,8 @@ public abstract class GameStage extends Stage {
|
||||
if (GameHUD.getInstance().isDebugMap()) {
|
||||
TileMapScene S = TileMapScene.instance();
|
||||
PointOfInterestData P = PointOfInterestData.getPointOfInterest("DEBUGZONE");
|
||||
if( P != null)
|
||||
{
|
||||
PointOfInterest PoI = new PointOfInterest(P,new Vector2(0,0), MyRandom.getRandom());
|
||||
if (P != null) {
|
||||
PointOfInterest PoI = new PointOfInterest(P, new Vector2(0, 0), MyRandom.getRandom());
|
||||
S.load(PoI);
|
||||
Forge.switchScene(S);
|
||||
}
|
||||
@@ -571,14 +566,12 @@ public abstract class GameStage extends Stage {
|
||||
public boolean keyUp(int keycode) {
|
||||
if (isPaused())
|
||||
return true;
|
||||
if (KeyBinding.Left.isPressed(keycode)||KeyBinding.Right.isPressed(keycode))
|
||||
{
|
||||
if (KeyBinding.Left.isPressed(keycode) || KeyBinding.Right.isPressed(keycode)) {
|
||||
player.getMovementDirection().x = 0;
|
||||
if (!player.isMoving())
|
||||
stop();
|
||||
}
|
||||
if (KeyBinding.Down.isPressed(keycode)||KeyBinding.Up.isPressed(keycode))
|
||||
{
|
||||
if (KeyBinding.Down.isPressed(keycode) || KeyBinding.Up.isPressed(keycode)) {
|
||||
player.getMovementDirection().y = 0;
|
||||
if (!player.isMoving())
|
||||
stop();
|
||||
@@ -658,17 +651,16 @@ public abstract class GameStage extends Stage {
|
||||
return Vector2.Zero.cpy();
|
||||
}
|
||||
|
||||
protected void teleported(Vector2 position)
|
||||
{
|
||||
protected void teleported(Vector2 position) {
|
||||
|
||||
}
|
||||
|
||||
public void setPosition(Vector2 position) {
|
||||
getPlayerSprite().setPosition(position);
|
||||
teleported(position);
|
||||
}
|
||||
|
||||
public void resetPlayerLocation()
|
||||
{
|
||||
public void resetPlayerLocation() {
|
||||
PointOfInterest poi = Current.world().findPointsOfInterest("Spawn");
|
||||
if (poi != null) {
|
||||
Forge.advFreezePlayerControls = true;
|
||||
@@ -677,7 +669,7 @@ public abstract class GameStage extends Stage {
|
||||
Timer.schedule(new Timer.Task() {
|
||||
@Override
|
||||
public void run() {
|
||||
showImageDialog(Current.generateDefeatMessage(), getDefeatBadge(),
|
||||
showImageDialog(Current.generateDefeatMessage(true), getDefeatBadge(),
|
||||
() -> FThreads.invokeInEdtNowOrLater(() -> Forge.setTransitionScreen(new CoverScreen(() -> {
|
||||
Forge.advFreezePlayerControls = false;
|
||||
WorldStage.getInstance().setPosition(new Vector2(poi.getPosition().x - 16f, poi.getPosition().y + 16f));
|
||||
@@ -689,6 +681,21 @@ public abstract class GameStage extends Stage {
|
||||
}, 1f);
|
||||
}//Spawn shouldn't be null
|
||||
}
|
||||
|
||||
public void defeatedFromBoss() {
|
||||
if (!Current.player().hasEquippedItem())
|
||||
return;
|
||||
Forge.advFreezePlayerControls = true;
|
||||
getPlayerSprite().setAnimation(CharacterSprite.AnimationTypes.Hit);
|
||||
getPlayerSprite().playEffect(Paths.EFFECT_BLOOD, 0.5f);
|
||||
Timer.schedule(new Timer.Task() {
|
||||
@Override
|
||||
public void run() {
|
||||
showImageDialog(Current.generateDefeatMessage(false), getDefeatBadge(), () -> Forge.advFreezePlayerControls = false);
|
||||
}
|
||||
}, 1f);
|
||||
}
|
||||
|
||||
private FBufferedImage getDefeatBadge() {
|
||||
FileHandle defeat = Config.instance().getFile("ui/defeat.png");
|
||||
if (defeat.exists()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package forge.adventure.stage;
|
||||
|
||||
public interface IAfterMatch {
|
||||
void setWinner(boolean winner);
|
||||
void setWinner(boolean winner, boolean isArena);
|
||||
}
|
||||
|
||||
@@ -606,7 +606,7 @@ public class MapStage extends GameStage {
|
||||
}));
|
||||
break;
|
||||
case "exit":
|
||||
addMapActor(obj, new OnCollide(() -> MapStage.this.exitDungeon(false)));
|
||||
addMapActor(obj, new OnCollide(() -> MapStage.this.exitDungeon(false, false)));
|
||||
break;
|
||||
case "dialog":
|
||||
if (obj instanceof TiledMapTileMapObject) {
|
||||
@@ -750,7 +750,7 @@ public class MapStage extends GameStage {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean exitDungeon(boolean defeated) {
|
||||
public boolean exitDungeon(boolean defeated, boolean defeatedByBoss) {
|
||||
AdventureQuestController.instance().updateQuestsLeave();
|
||||
clearIsInMap();
|
||||
AdventureQuestController.instance().showQuestDialogs(this);
|
||||
@@ -758,6 +758,8 @@ public class MapStage extends GameStage {
|
||||
effect = null; //Reset dungeon effects.
|
||||
if (defeated)
|
||||
WorldStage.getInstance().resetPlayerLocation();
|
||||
else if (defeatedByBoss)
|
||||
WorldStage.getInstance().defeatedFromBoss();
|
||||
Forge.switchScene(GameScene.instance());
|
||||
isPlayerLeavingDungeon = false;
|
||||
dialogOnlyInput = false;
|
||||
@@ -766,7 +768,7 @@ public class MapStage extends GameStage {
|
||||
|
||||
|
||||
@Override
|
||||
public void setWinner(boolean playerWins) {
|
||||
public void setWinner(boolean playerWins, boolean isArena) {
|
||||
isLoadingMatch = false;
|
||||
freezeAllEnemyBehaviors = true;
|
||||
if (playerWins) {
|
||||
@@ -805,16 +807,16 @@ public class MapStage extends GameStage {
|
||||
boolean defeated = Current.player().defeated();
|
||||
//If hardcore mode is added, check and redirect to game over screen here
|
||||
if (canFailDungeon && !defeated)
|
||||
dungeonFailedDialog(true);
|
||||
dungeonFailedDialog(true, currentMob.getData().boss && !isArena);
|
||||
else
|
||||
exitDungeon(defeated);
|
||||
exitDungeon(defeated, currentMob.getData().boss && !isArena);
|
||||
MapStage.this.stop();
|
||||
currentMob = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void dungeonFailedDialog(boolean exit) {
|
||||
private void dungeonFailedDialog(boolean exit, boolean defeatedByBoss) {
|
||||
dialog.getButtonTable().clear();
|
||||
dialog.getContentTable().clear();
|
||||
dialog.clearListeners();
|
||||
@@ -834,7 +836,7 @@ public class MapStage extends GameStage {
|
||||
L.skipToTheEnd();
|
||||
super.clicked(event, x, y);
|
||||
if (exit)
|
||||
exitDungeon(false);
|
||||
exitDungeon(false, defeatedByBoss);
|
||||
}
|
||||
});
|
||||
dialog.getButtonTable().add(ok).width(240f);
|
||||
|
||||
@@ -162,7 +162,7 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWinner(boolean playerIsWinner) {
|
||||
public void setWinner(boolean playerIsWinner, boolean isArena) {
|
||||
if (playerIsWinner) {
|
||||
currentMob.clearCollisionHeight();
|
||||
Current.player().win();
|
||||
@@ -192,10 +192,13 @@ public class WorldStage extends GameStage implements SaveFileContent {
|
||||
boolean defeated = Current.player().defeated();
|
||||
AdventureQuestController.instance().updateQuestsLose(currentMob);
|
||||
AdventureQuestController.instance().showQuestDialogs(MapStage.getInstance());
|
||||
boolean defeatedFromBoss = currentMob.getData().boss && !isArena;
|
||||
WorldStage.this.removeEnemy(currentMob);
|
||||
currentMob = null;
|
||||
if (defeated) {
|
||||
WorldStage.getInstance().resetPlayerLocation();
|
||||
} else if (defeatedFromBoss) {
|
||||
WorldStage.getInstance().defeatedFromBoss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -809,6 +809,11 @@ public class CardUtil {
|
||||
return generateBoosterPackAsDeck(edition);
|
||||
}
|
||||
|
||||
private static PaperCard getReplacement(String missingCard, String replacementCard) {
|
||||
System.err.println(missingCard + " : Not found in the database.\nReplacement card: " + replacementCard);
|
||||
return FModel.getMagicDb().getCommonCards().getCard(replacementCard);
|
||||
}
|
||||
|
||||
public static PaperCard getCardByName(String cardName) {
|
||||
List<PaperCard> validCards;
|
||||
//Faster to ask the CardDB for a card name than it is to search the pool.
|
||||
@@ -817,6 +822,10 @@ public class CardUtil {
|
||||
else
|
||||
validCards = FModel.getMagicDb().getCommonCards().getUniqueCardsNoAlt(cardName);
|
||||
|
||||
if (validCards.isEmpty()) {
|
||||
return getReplacement(cardName, "Wastes");
|
||||
}
|
||||
|
||||
return validCards.get(Current.world().getRandom().nextInt(validCards.size()));
|
||||
}
|
||||
|
||||
@@ -828,7 +837,7 @@ public class CardUtil {
|
||||
.filter(input -> input.getEdition().equals(edition)).collect(Collectors.toList());
|
||||
|
||||
if (validCards.isEmpty()) {
|
||||
System.err.println("Unexpected behavior: tried to call getCardByNameAndEdition for card " + cardName + " from the edition " + edition + ", but didn't find it in the DB. A random existing instance will be returned.");
|
||||
System.err.println("Unexpected behavior: tried to call getCardByNameAndEdition for card " + cardName + " from the edition " + edition + ", but didn't find it in the DB. A random existing instance will be returned if found.");
|
||||
return getCardByName(cardName);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,9 @@ public class Current {
|
||||
public static void setLatestDeck(Deck generateDeck) {
|
||||
deck=generateDeck;
|
||||
}
|
||||
public static String generateDefeatMessage() {
|
||||
String message = Forge.getLocalizer().getMessage("lblYouDied", player().getName());
|
||||
public static String generateDefeatMessage(boolean hasDied) {
|
||||
String key = hasDied ? "lblYouDied" : "lblYouLostTheLastGame";
|
||||
String message = Forge.getLocalizer().getMessage(key, player().getName());
|
||||
ItemData itemData = player().getRandomEquippedItem();
|
||||
if (itemData != null) {
|
||||
itemData.isCracked = true;
|
||||
|
||||
@@ -20,6 +20,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
captureException(e, key, subData);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +34,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -45,6 +47,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -57,6 +60,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -69,6 +73,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -81,6 +86,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -95,6 +101,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
stream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -107,6 +114,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
captureException(e, key, subData);
|
||||
}
|
||||
}
|
||||
@@ -119,6 +127,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
captureException(e, key, subData);
|
||||
}
|
||||
}
|
||||
@@ -132,6 +141,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -147,6 +157,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,53 +26,50 @@ import java.util.zip.InflaterInputStream;
|
||||
/**
|
||||
* Represents everything that will be saved, like the player and the world.
|
||||
*/
|
||||
public class WorldSave {
|
||||
public class WorldSave {
|
||||
|
||||
static final public int AUTO_SAVE_SLOT =-1;
|
||||
static final public int QUICK_SAVE_SLOT =-2;
|
||||
static final public int INVALID_SAVE_SLOT =-3;
|
||||
static final WorldSave currentSave=new WorldSave();
|
||||
static final public int AUTO_SAVE_SLOT = -1;
|
||||
static final public int QUICK_SAVE_SLOT = -2;
|
||||
static final public int INVALID_SAVE_SLOT = -3;
|
||||
static final WorldSave currentSave = new WorldSave();
|
||||
public WorldSaveHeader header = new WorldSaveHeader();
|
||||
private final AdventurePlayer player=new AdventurePlayer();
|
||||
private final World world=new World();
|
||||
private final PointOfInterestChanges.Map pointOfInterestChanges= new PointOfInterestChanges.Map();
|
||||
private final AdventurePlayer player = new AdventurePlayer();
|
||||
private final World world = new World();
|
||||
private final PointOfInterestChanges.Map pointOfInterestChanges = new PointOfInterestChanges.Map();
|
||||
|
||||
|
||||
private final SignalList onLoadList=new SignalList();
|
||||
private final SignalList onLoadList = new SignalList();
|
||||
|
||||
public final World getWorld()
|
||||
{
|
||||
public final World getWorld() {
|
||||
return world;
|
||||
}
|
||||
public AdventurePlayer getPlayer()
|
||||
{
|
||||
|
||||
public AdventurePlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public void onLoad(Runnable run)
|
||||
{
|
||||
public void onLoad(Runnable run) {
|
||||
onLoadList.add(run);
|
||||
}
|
||||
public PointOfInterestChanges getPointOfInterestChanges(String id)
|
||||
{
|
||||
if(!pointOfInterestChanges.containsKey(id))
|
||||
pointOfInterestChanges.put(id,new PointOfInterestChanges());
|
||||
|
||||
public PointOfInterestChanges getPointOfInterestChanges(String id) {
|
||||
if (!pointOfInterestChanges.containsKey(id))
|
||||
pointOfInterestChanges.put(id, new PointOfInterestChanges());
|
||||
return pointOfInterestChanges.get(id);
|
||||
}
|
||||
|
||||
static public boolean load(int currentSlot) {
|
||||
|
||||
String fileName = WorldSave.getSaveFile(currentSlot);
|
||||
if(!new File(fileName).exists())
|
||||
if (!new File(fileName).exists())
|
||||
return false;
|
||||
new File(getSaveDir()).mkdirs();
|
||||
try {
|
||||
try(FileInputStream fos = new FileInputStream(fileName);
|
||||
InflaterInputStream inf = new InflaterInputStream(fos);
|
||||
ObjectInputStream oos = new ObjectInputStream(inf))
|
||||
{
|
||||
try (FileInputStream fos = new FileInputStream(fileName);
|
||||
InflaterInputStream inf = new InflaterInputStream(fos);
|
||||
ObjectInputStream oos = new ObjectInputStream(inf)) {
|
||||
currentSave.header = (WorldSaveHeader) oos.readObject();
|
||||
SaveFileData mainData=(SaveFileData)oos.readObject();
|
||||
SaveFileData mainData = (SaveFileData) oos.readObject();
|
||||
currentSave.player.load(mainData.readSubData("player"));
|
||||
GamePlayerUtil.getGuiPlayer().setName(currentSave.player.getName());
|
||||
try {
|
||||
@@ -95,9 +92,11 @@ public class WorldSave {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isSafeFile(String name) {
|
||||
return filenameToSlot(name)!= INVALID_SAVE_SLOT;
|
||||
return filenameToSlot(name) != INVALID_SAVE_SLOT;
|
||||
}
|
||||
|
||||
static public int filenameToSlot(String name) {
|
||||
if (name.equals("auto_save.sav"))
|
||||
return AUTO_SAVE_SLOT;
|
||||
@@ -131,10 +130,10 @@ public class WorldSave {
|
||||
public static WorldSave generateNewWorld(String name, boolean male, int race, int avatarIndex, ColorSet startingColorIdentity, DifficultyData diff, AdventureModes mode, int customDeckIndex, CardEdition starterEdition, long seed) {
|
||||
currentSave.world.generateNew(seed);
|
||||
currentSave.pointOfInterestChanges.clear();
|
||||
boolean chaos=mode==AdventureModes.Chaos;
|
||||
boolean custom=mode==AdventureModes.Custom;
|
||||
Deck starterDeck = Config.instance().starterDeck(startingColorIdentity,diff,mode,customDeckIndex,starterEdition);
|
||||
currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff);
|
||||
boolean chaos = mode == AdventureModes.Chaos;
|
||||
boolean custom = mode == AdventureModes.Custom;
|
||||
Deck starterDeck = Config.instance().starterDeck(startingColorIdentity, diff, mode, customDeckIndex, starterEdition);
|
||||
currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff);
|
||||
currentSave.player.setWorldPosY((int) (currentSave.world.getData().playerStartPosY * currentSave.world.getData().height * currentSave.world.getTileSize()));
|
||||
currentSave.player.setWorldPosX((int) (currentSave.world.getData().playerStartPosX * currentSave.world.getData().width * currentSave.world.getTileSize()));
|
||||
currentSave.onLoadList.emit();
|
||||
@@ -142,46 +141,103 @@ public class WorldSave {
|
||||
}
|
||||
|
||||
public boolean autoSave() {
|
||||
return save("auto save"+ SaveLoadScene.instance().getSaveFileSuffix(),AUTO_SAVE_SLOT);
|
||||
return save("auto save" + SaveLoadScene.instance().getSaveFileSuffix(), AUTO_SAVE_SLOT);
|
||||
}
|
||||
|
||||
public boolean quickSave() {
|
||||
return save("quick save"+ SaveLoadScene.instance().getSaveFileSuffix(),QUICK_SAVE_SLOT);
|
||||
return save("quick save" + SaveLoadScene.instance().getSaveFileSuffix(), QUICK_SAVE_SLOT);
|
||||
}
|
||||
|
||||
public boolean quickLoad() {
|
||||
return load(QUICK_SAVE_SLOT);
|
||||
}
|
||||
|
||||
public boolean save(String text, int currentSlot) {
|
||||
header.name = text;
|
||||
|
||||
String fileName = WorldSave.getSaveFile(currentSlot);
|
||||
String oldFileName = fileName.replace(".sav", ".old");
|
||||
new File(getSaveDir()).mkdirs();
|
||||
File currentFile = new File(fileName);
|
||||
File backupFile = new File(oldFileName);
|
||||
if (currentFile.exists())
|
||||
currentFile.renameTo(backupFile);
|
||||
|
||||
try {
|
||||
try(FileOutputStream fos = new FileOutputStream(fileName);
|
||||
DeflaterOutputStream def= new DeflaterOutputStream(fos);
|
||||
ObjectOutputStream oos = new ObjectOutputStream(def))
|
||||
{
|
||||
header.saveDate= new Date();
|
||||
oos.writeObject(header);
|
||||
SaveFileData mainData=new SaveFileData();
|
||||
mainData.store("player",currentSave.player.save());
|
||||
mainData.store("world",currentSave.world.save());
|
||||
mainData.store("worldStage", WorldStage.getInstance().save());
|
||||
mainData.store("pointOfInterestChanges",currentSave.pointOfInterestChanges.save());
|
||||
try (FileOutputStream fos = new FileOutputStream(fileName);
|
||||
DeflaterOutputStream def = new DeflaterOutputStream(fos);
|
||||
ObjectOutputStream oos = new ObjectOutputStream(def)) {
|
||||
SaveFileData player = currentSave.player.save();
|
||||
SaveFileData world = currentSave.world.save();
|
||||
SaveFileData worldStage = WorldStage.getInstance().save();
|
||||
SaveFileData poiChanges = currentSave.pointOfInterestChanges.save();
|
||||
|
||||
String message = getExceptionMessage(player, world, worldStage, poiChanges);
|
||||
if (!message.isEmpty()) {
|
||||
oos.close();
|
||||
fos.close();
|
||||
restoreBackup(oldFileName, fileName);
|
||||
announceError(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
SaveFileData mainData = new SaveFileData();
|
||||
mainData.store("player", player);
|
||||
mainData.store("world", world);
|
||||
mainData.store("worldStage", worldStage);
|
||||
mainData.store("pointOfInterestChanges", poiChanges);
|
||||
|
||||
if (mainData.readString("IOException") != null) {
|
||||
oos.close();
|
||||
fos.close();
|
||||
restoreBackup(oldFileName, fileName);
|
||||
announceError("Please check forge.log for errors.");
|
||||
return true;
|
||||
}
|
||||
|
||||
header.saveDate = new Date();
|
||||
oos.writeObject(header);
|
||||
oos.writeObject(mainData);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
restoreBackup(oldFileName, fileName);
|
||||
announceError("Please check forge.log for errors.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Config.instance().getSettingData().lastActiveSave = WorldSave.filename(currentSlot);
|
||||
Config.instance().saveSettings();
|
||||
if (backupFile.exists())
|
||||
backupFile.delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void restoreBackup(String oldFilename, String currentFilename) {
|
||||
File f = new File(currentFilename);
|
||||
if (f.exists())
|
||||
f.delete();
|
||||
File b = new File(oldFilename);
|
||||
if (b.exists())
|
||||
b.renameTo(new File(currentFilename));
|
||||
}
|
||||
|
||||
public String getExceptionMessage(SaveFileData... datas) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
|
||||
for (SaveFileData data : datas) {
|
||||
String s = data.readString("IOException");
|
||||
if (s != null)
|
||||
message.append(s).append("\n");
|
||||
}
|
||||
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
private void announceError(String message) {
|
||||
currentSave.player.getCurrentGameStage().setExtraAnnouncement("Error Saving File!\n" + message);
|
||||
}
|
||||
|
||||
public void clearChanges() {
|
||||
pointOfInterestChanges.clear();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -59,7 +59,7 @@ public class AddBasicLandsDialog extends FDialog {
|
||||
private final Consumer<CardPool> callback;
|
||||
|
||||
private final FLabel lblLandSet = add(new FLabel.Builder().text(Forge.getLocalizer().getMessage("lblLandSet") + ":").font(FSkinFont.get(12)).textColor(FLabel.getInlineLabelColor()).build());
|
||||
private final FComboBox<CardEdition> cbLandSet = add(new FComboBox<>(IterableUtil.filter(StaticData.instance().getEditions(), CardEdition.Predicates.hasBasicLands)));
|
||||
private final FComboBox<CardEdition> cbLandSet = add(new FComboBox<>(IterableUtil.filter(StaticData.instance().getSortedEditions(), CardEdition::hasBasicLands)));
|
||||
|
||||
private final FScrollPane scroller = add(new FScrollPane() {
|
||||
@Override
|
||||
|
||||
@@ -81,13 +81,21 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
public boolean allowsCardReplacement() { return hasInfiniteCardPool() || usePlayerInventory(); }
|
||||
|
||||
public List<CardEdition> getBasicLandSets(Deck currentDeck) {
|
||||
if(hasInfiniteCardPool())
|
||||
return FModel.getMagicDb().getSortedEditions().stream().filter(CardEdition::hasBasicLands).collect(Collectors.toList());
|
||||
return List.of(DeckProxy.getDefaultLandSet(currentDeck));
|
||||
}
|
||||
|
||||
protected abstract IDeckController getController();
|
||||
protected abstract DeckEditorPage[] getInitialPages();
|
||||
|
||||
protected DeckSection[] getExtraSections() {
|
||||
public DeckSection[] getPrimarySections() {
|
||||
if(getGameType() != null)
|
||||
return getGameType().getPrimaryDeckSections().toArray(new DeckSection[0]);
|
||||
return new DeckSection[]{DeckSection.Main, DeckSection.Sideboard};
|
||||
}
|
||||
|
||||
public DeckSection[] getExtraSections() {
|
||||
if(getGameType() != null)
|
||||
return getGameType().getSupplimentalDeckSections().toArray(new DeckSection[0]);
|
||||
return new DeckSection[]{DeckSection.Attractions, DeckSection.Contraptions};
|
||||
@@ -144,7 +152,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
ItemManagerConfig catalogConfig = null;
|
||||
ItemManagerConfig mainSectionConfig = null;
|
||||
ItemManagerConfig sideboardConfig = null;
|
||||
Function<Deck, CardEdition> fnGetBasicLandSet = null;
|
||||
Function<Deck, Collection<CardEdition>> fnGetBasicLandSet = null;
|
||||
Supplier<ItemPool<PaperCard>> itemPoolSupplier = null;
|
||||
String catalogCaption = null;
|
||||
|
||||
@@ -196,7 +204,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
this.sideboardConfig = sideboardConfig;
|
||||
return this;
|
||||
}
|
||||
public GameTypeDeckEditorConfig setBasicLandSetFunction(Function<Deck, CardEdition> fnGetBasicLandSet) {
|
||||
public GameTypeDeckEditorConfig setBasicLandSetFunction(Function<Deck, Collection<CardEdition>> fnGetBasicLandSet) {
|
||||
this.fnGetBasicLandSet = fnGetBasicLandSet;
|
||||
return this;
|
||||
}
|
||||
@@ -296,9 +304,21 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DeckSection[] getExtraSections() {
|
||||
public DeckSection[] getPrimarySections() {
|
||||
return gameType.getPrimaryDeckSections().toArray(new DeckSection[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeckSection[] getExtraSections() {
|
||||
return gameType.getSupplimentalDeckSections().toArray(new DeckSection[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CardEdition> getBasicLandSets(Deck currentDeck) {
|
||||
if(this.fnGetBasicLandSet != null)
|
||||
return List.copyOf(fnGetBasicLandSet.apply(currentDeck));
|
||||
return super.getBasicLandSets(currentDeck);
|
||||
}
|
||||
}
|
||||
|
||||
public static DeckEditorConfig EditorConfigConstructed = new GameTypeDeckEditorConfig(GameType.Constructed,
|
||||
@@ -348,18 +368,19 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
.setMainSectionConfig(ItemManagerConfig.QUEST_DECK_EDITOR)
|
||||
.setSideboardConfig(ItemManagerConfig.QUEST_DECK_EDITOR)
|
||||
.setPlayerInventorySupplier(() -> FModel.getQuest().getCards().getCardpool())
|
||||
.setBasicLandSetFunction(d -> FModel.getQuest().getDefaultLandSet());
|
||||
.setBasicLandSetFunction(d -> FModel.getQuest().getAvailableLandSets());
|
||||
public static DeckEditorConfig EditorConfigQuestCommander = new GameTypeDeckEditorConfig(GameType.QuestCommander, DECK_CONTROLLER_QUEST)
|
||||
.setCatalogConfig(ItemManagerConfig.QUEST_EDITOR_POOL)
|
||||
.setMainSectionConfig(ItemManagerConfig.QUEST_DECK_EDITOR)
|
||||
.setSideboardConfig(ItemManagerConfig.QUEST_DECK_EDITOR)
|
||||
.setPlayerInventorySupplier(() -> FModel.getQuest().getCards().getCardpool())
|
||||
.setBasicLandSetFunction(d -> FModel.getQuest().getDefaultLandSet());
|
||||
.setBasicLandSetFunction(d -> FModel.getQuest().getAvailableLandSets());
|
||||
public static DeckEditorConfig EditorConfigQuestDraft = new GameTypeDeckEditorConfig(GameType.QuestDraft, DECK_CONTROLLER_QUEST_DRAFT);
|
||||
public static DeckEditorConfig EditorConfigPlanarConquest = new GameTypeDeckEditorConfig(GameType.PlanarConquest, DECK_CONTROLLER_PLANAR_CONQUEST)
|
||||
.setCatalogConfig(ItemManagerConfig.CONQUEST_COLLECTION)
|
||||
.setMainSectionConfig(ItemManagerConfig.CONQUEST_DECK_EDITOR)
|
||||
.setPlayerInventorySupplier(ConquestUtil::getAvailablePool);
|
||||
.setPlayerInventorySupplier(ConquestUtil::getAvailablePool)
|
||||
.setBasicLandSetFunction(ConquestUtil::getBasicLandSets);
|
||||
|
||||
protected static DeckSectionPage createPageForExtraSection(DeckSection deckSection, DeckEditorConfig editorConfig) {
|
||||
CardManager cm = new CardManager(false);
|
||||
@@ -542,7 +563,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
@Override
|
||||
protected void buildMenu() {
|
||||
final Localizer localizer = Forge.getLocalizer();
|
||||
if (allowsAddBasic())
|
||||
if (allowAddBasic())
|
||||
addItem(new FMenuItem(localizer.getMessage("lblAddBasicLands"), FSkinImage.LANDLOGO, e -> showAddBasicLandsDialog()));
|
||||
if (showAddExtraSectionOption()) {
|
||||
addItem(new FMenuItem(localizer.getMessage("lblAddDeckSection"), FSkinImage.CHAOS, e -> {
|
||||
@@ -558,28 +579,41 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
});
|
||||
}));
|
||||
}
|
||||
if (editorConfig.getGameType() != null && editorConfig.hasInfiniteCardPool()) {
|
||||
if (editorConfig.hasInfiniteCardPool() || editorConfig.usePlayerInventory()) {
|
||||
addItem(new FMenuItem(localizer.getMessage("lblImportFromClipboard"), Forge.hdbuttons ? FSkinImage.HDIMPORT : FSkinImage.OPEN, e -> {
|
||||
FDeckImportDialog dialog = new FDeckImportDialog(!deck.isEmpty(), FDeckEditor.this.editorConfig);
|
||||
FDeckImportDialog dialog = new FDeckImportDialog(deck, FDeckEditor.this.editorConfig);
|
||||
if(editorConfig.usePlayerInventory())
|
||||
dialog.setFreePrintConverter(FDeckEditor.this::supplyPrintForImporter);
|
||||
dialog.setImportBannedCards(!FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY));
|
||||
dialog.setCallback(importedDeck -> {
|
||||
if (deck != null && importedDeck.hasName()) {
|
||||
deck.setName(importedDeck.getName());
|
||||
setHeaderText(importedDeck.getName());
|
||||
}
|
||||
if (dialog.createNewDeck()) {
|
||||
for (Entry<DeckSection, CardPool> section : importedDeck) {
|
||||
DeckSectionPage page = getPageForSection(section.getKey());
|
||||
if (page != null)
|
||||
page.setCards(section.getValue());
|
||||
}
|
||||
} else {
|
||||
for (Entry<DeckSection, CardPool> section : importedDeck) {
|
||||
DeckSectionPage page = getPageForSection(section.getKey());
|
||||
if (page != null)
|
||||
page.addCards(section.getValue());
|
||||
}
|
||||
switch (dialog.getImportBehavior()) {
|
||||
case REPLACE_CURRENT:
|
||||
for(DeckSectionPage page : pagesBySection.values()) {
|
||||
if(importedDeck.has(page.deckSection)) {
|
||||
page.setCards(importedDeck.get(page.deckSection));
|
||||
if(hiddenExtraSections.contains(page.deckSection))
|
||||
showExtraSectionTab(page.deckSection);
|
||||
}
|
||||
else
|
||||
page.setCards(new CardPool());
|
||||
}
|
||||
break;
|
||||
case CREATE_NEW:
|
||||
deckController.setDeck(importedDeck);
|
||||
break;
|
||||
case MERGE:
|
||||
for (Entry<DeckSection, CardPool> section : importedDeck) {
|
||||
DeckSectionPage page = getPageForSection(section.getKey());
|
||||
if (page != null)
|
||||
page.addCards(section.getValue());
|
||||
}
|
||||
}
|
||||
});
|
||||
dialog.initParse();
|
||||
dialog.show();
|
||||
setSelectedPage(getMainDeckPage()); //select main deck page if needed so main deck if visible below dialog
|
||||
}));
|
||||
@@ -643,6 +677,20 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
getMainDeckPage().addCards(landsToAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a card is missing from a player's inventory while importing a deck, it gets run through here.
|
||||
* Returning a PaperCard will let unlimited copies of that card be used as a substitute. Returning null
|
||||
* will leave the card missing from the import.
|
||||
*/
|
||||
protected PaperCard supplyPrintForImporter(PaperCard missingCard) {
|
||||
//Could support dungeons here too? Not that we really use them in the editor...
|
||||
if(!missingCard.isVeryBasicLand())
|
||||
return null;
|
||||
List<CardEdition> basicSets = editorConfig.getBasicLandSets(deck);
|
||||
String setCode = basicSets.isEmpty() ? "JMP" : basicSets.get(0).getCode();
|
||||
return FModel.getMagicDb().fetchCard(missingCard.getCardName(), setCode, null);
|
||||
}
|
||||
|
||||
protected boolean shouldEnforceConformity() {
|
||||
if(FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY))
|
||||
return true;
|
||||
@@ -695,6 +743,9 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
showExtraSectionTab(section);
|
||||
if(pagesBySection.containsKey(section))
|
||||
setSelectedPage(pagesBySection.get(section));
|
||||
else if(section == DeckSection.Main && pagesBySection.containsKey(mainDeckPage.deckSection))
|
||||
//Tried to switch to the Main page in a Planar or Scheme deck.
|
||||
setSelectedPage(pagesBySection.get(mainDeckPage.deckSection));
|
||||
}
|
||||
|
||||
public void notifyNewControllerModel() {
|
||||
@@ -1027,7 +1078,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
protected boolean allowSaveAs() {
|
||||
return allowSave() && allowRename();
|
||||
}
|
||||
protected boolean allowsAddBasic() {
|
||||
protected boolean allowAddBasic() {
|
||||
return !isDrafting();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
package forge.deck;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.Forge;
|
||||
import forge.Graphics;
|
||||
@@ -31,11 +32,15 @@ import forge.deck.DeckRecognizer.TokenType;
|
||||
import forge.game.GameType;
|
||||
import forge.gui.FThreads;
|
||||
import forge.gui.util.SOptionPane;
|
||||
import forge.item.PaperCard;
|
||||
import forge.toolbox.FCheckBox;
|
||||
import forge.toolbox.FComboBox;
|
||||
import forge.toolbox.FDialog;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.toolbox.FTextArea;
|
||||
import forge.util.ItemPool;
|
||||
import forge.util.Localizer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
|
||||
public class FDeckImportDialog extends FDialog {
|
||||
@@ -45,7 +50,7 @@ public class FDeckImportDialog extends FDialog {
|
||||
private final FCheckBox newEditionCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblImportLatestVersionCard"), false));
|
||||
private final FCheckBox dateTimeCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblUseOnlySetsReleasedBefore"), false));
|
||||
private final FCheckBox smartCardArtCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblUseSmartCardArt"), false));
|
||||
private final FCheckBox createNewDeckCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblNewDeckCheckbox"), false));
|
||||
private final FCheckBox createNewDeckCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblReplaceDeckCheckbox"), false));
|
||||
// private final FCheckBox importInDeck = add(new FCheckBox()
|
||||
/*setting onlyCoreExpCheck to false allow the copied cards to pass the check of deck contents
|
||||
forge-core\src\main\java\forge\deck\Deck.javaDeck.java starting @ Line 320 which is called by
|
||||
@@ -57,100 +62,60 @@ public class FDeckImportDialog extends FDialog {
|
||||
private final FComboBox<String> monthDropdown = add(new FComboBox<>()); //don't need wrappers since skin can't change while this dialog is open
|
||||
private final FComboBox<Integer> yearDropdown = add(new FComboBox<>());
|
||||
|
||||
private final boolean showOptions;
|
||||
private final boolean currentDeckIsEmpty;
|
||||
private boolean showOptions;
|
||||
private final Deck currentDeck;
|
||||
private boolean createNewDeckControl;
|
||||
private final DeckImportController controller;
|
||||
private final FDeckEditor.DeckEditorConfig editorConfig;
|
||||
|
||||
private final static ImmutableList<String> importOrCancel = ImmutableList.of(Forge.getLocalizer().getMessage("lblImport"), Forge.getLocalizer().getMessage("lblCancel"));
|
||||
|
||||
public FDeckImportDialog(final boolean replacingDeck, final FDeckEditor.DeckEditorConfig editorConfig) {
|
||||
public FDeckImportDialog(final Deck currentDeck, final FDeckEditor.DeckEditorConfig editorConfig) {
|
||||
super(Forge.getLocalizer().getMessage("lblImportFromClipboard"), 2);
|
||||
boolean usingInventory = editorConfig.usePlayerInventory();
|
||||
boolean replacingDeck = !currentDeck.isEmpty() || usingInventory;
|
||||
this.currentDeck = currentDeck;
|
||||
this.editorConfig = editorConfig;
|
||||
ItemPool<PaperCard> cardPool = editorConfig.getCardPool(false);
|
||||
controller = new DeckImportController(dateTimeCheck, monthDropdown, yearDropdown, replacingDeck);
|
||||
String contents = Forge.getClipboard().getContents();
|
||||
if (contents == null)
|
||||
contents = ""; //prevent NPE
|
||||
txtInput.setText(contents);
|
||||
|
||||
if (editorConfig.allowsCardReplacement()) {
|
||||
GameType gameType = editorConfig.getGameType();
|
||||
controller.setGameFormat(gameType);
|
||||
List<DeckSection> supportedSections = new ArrayList<>();
|
||||
supportedSections.add(DeckSection.Main);
|
||||
supportedSections.add(DeckSection.Sideboard);
|
||||
if (editorConfig.hasCommander())
|
||||
supportedSections.add(DeckSection.Commander);
|
||||
supportedSections.addAll(Lists.newArrayList(editorConfig.getExtraSections()));
|
||||
controller.setAllowedSections(supportedSections);
|
||||
}
|
||||
GameType gameType = editorConfig.getGameType();
|
||||
controller.setGameFormat(gameType);
|
||||
List<DeckSection> supportedSections = new ArrayList<>();
|
||||
supportedSections.addAll(List.of(editorConfig.getPrimarySections()));
|
||||
supportedSections.addAll(List.of(editorConfig.getExtraSections()));
|
||||
controller.setAllowedSections(supportedSections);
|
||||
controller.setCurrentDeckInEditor(currentDeck);
|
||||
if(usingInventory)
|
||||
controller.setPlayerInventory(cardPool);
|
||||
|
||||
onlyCoreExpCheck.setSelected(StaticData.instance().isCoreExpansionOnlyFilterSet());
|
||||
newEditionCheck.setSelected(StaticData.instance().cardArtPreferenceIsLatest());
|
||||
smartCardArtCheck.setSelected(StaticData.instance().isEnabledCardArtSmartSelection());
|
||||
createNewDeckCheck.setSelected(replacingDeck);
|
||||
this.currentDeckIsEmpty = !replacingDeck;
|
||||
this.createNewDeckControl = replacingDeck;
|
||||
|
||||
initButton(0, Forge.getLocalizer().getMessage("lblImport"), e -> FThreads.invokeInBackgroundThread(() -> {
|
||||
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); //ensure deck updated based on any changes to options
|
||||
if(usingInventory)
|
||||
controller.setImportBehavior(DeckImportController.ImportBehavior.REPLACE_CURRENT);
|
||||
else
|
||||
controller.setImportBehavior(createNewDeckControl ? DeckImportController.ImportBehavior.CREATE_NEW : DeckImportController.ImportBehavior.MERGE);
|
||||
|
||||
if (controller.isSmartCardArtEnabled())
|
||||
tokens = controller.optimiseCardArtInTokens();
|
||||
|
||||
//if there are any cards that cannot be imported, let user know this and give them the option to cancel
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (DeckRecognizer.Token token : tokens) {
|
||||
if (token.getType() == TokenType.CARD_FROM_NOT_ALLOWED_SET
|
||||
|| token.getType() == TokenType.CARD_FROM_INVALID_SET
|
||||
|| token.getType() == TokenType.UNKNOWN_CARD
|
||||
|| token.getType() == TokenType.UNSUPPORTED_CARD) {
|
||||
if (sb.length() > 0)
|
||||
sb.append("\n");
|
||||
sb.append(token.getQuantity()).append(" ").append(token.getText());
|
||||
}
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
if (SOptionPane.showOptionDialog(Forge.getLocalizer().getMessage("lblFollowingCardsCannotBeImported") + "\n\n" + sb, Forge.getLocalizer().getMessage("lblImportRemainingCards"), SOptionPane.INFORMATION_ICON, importOrCancel) == 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Deck deck = controller.accept(); //must accept in background thread in case a dialog is shown
|
||||
if (deck == null) { return; }
|
||||
|
||||
FThreads.invokeInEdtLater(() -> {
|
||||
hide();
|
||||
if (callback != null)
|
||||
callback.accept(deck);
|
||||
});
|
||||
}));
|
||||
initButton(0, Forge.getLocalizer().getMessage("lblImport"), e -> FThreads.invokeInBackgroundThread(this::performImport));
|
||||
initButton(1, Forge.getLocalizer().getMessage("lblCancel"), e -> hide());
|
||||
|
||||
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText());
|
||||
if (controller.isSmartCardArtEnabled())
|
||||
tokens = controller.optimiseCardArtInTokens();
|
||||
//ensure at least one known card found on clipboard
|
||||
for (DeckRecognizer.Token token : tokens) {
|
||||
if (token.getType() == TokenType.LEGAL_CARD) {
|
||||
showOptions = true;
|
||||
|
||||
dateTimeCheck.setCommand(e -> updateDropDownEnabled());
|
||||
newEditionCheck.setCommand(e -> setArtPreferenceInController());
|
||||
onlyCoreExpCheck.setCommand(e -> setArtPreferenceInController());
|
||||
smartCardArtCheck.setCommand(e -> controller.setSmartCardArtOptimisation(smartCardArtCheck.isSelected()));
|
||||
createNewDeckCheck.setCommand(e -> {
|
||||
createNewDeckControl = createNewDeckCheck.isSelected();
|
||||
controller.setCreateNewDeck(createNewDeckControl);
|
||||
});
|
||||
updateDropDownEnabled();
|
||||
setArtPreferenceInController();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
showOptions = false;
|
||||
setButtonEnabled(0, false);
|
||||
txtInput.setText(Forge.getLocalizer().getMessage("lblNoKnownCardsOnClipboard"));
|
||||
dateTimeCheck.setCommand(e -> updateDropDownEnabled());
|
||||
newEditionCheck.setCommand(e -> setArtPreferenceInController());
|
||||
onlyCoreExpCheck.setCommand(e -> setArtPreferenceInController());
|
||||
smartCardArtCheck.setCommand(e -> controller.setSmartCardArtOptimisation(smartCardArtCheck.isSelected()));
|
||||
createNewDeckCheck.setCommand(e -> {
|
||||
createNewDeckControl = createNewDeckCheck.isSelected();
|
||||
controller.setImportBehavior(createNewDeckControl ? DeckImportController.ImportBehavior.CREATE_NEW : DeckImportController.ImportBehavior.MERGE);
|
||||
});
|
||||
setShowOptions(false);
|
||||
}
|
||||
|
||||
private void setArtPreferenceInController() {
|
||||
@@ -160,16 +125,66 @@ public class FDeckImportDialog extends FDialog {
|
||||
}
|
||||
|
||||
private void updateDropDownEnabled() {
|
||||
boolean enabled = dateTimeCheck.isSelected();
|
||||
boolean enabled = dateTimeCheck.isSelected() && this.showOptions;
|
||||
monthDropdown.setEnabled(enabled);
|
||||
yearDropdown.setEnabled(enabled);
|
||||
}
|
||||
|
||||
private void setShowOptions(boolean showOptions) {
|
||||
this.showOptions = showOptions;
|
||||
dateTimeCheck.setEnabled(showOptions);
|
||||
newEditionCheck.setEnabled(showOptions);
|
||||
onlyCoreExpCheck.setEnabled(showOptions);
|
||||
newEditionCheck.setEnabled(showOptions);
|
||||
smartCardArtCheck.setEnabled(showOptions);
|
||||
createNewDeckCheck.setEnabled(showOptions);
|
||||
updateDropDownEnabled();
|
||||
}
|
||||
|
||||
public void setCallback(Consumer<Deck> callback0){
|
||||
callback = callback0;
|
||||
}
|
||||
|
||||
public boolean createNewDeck(){ return this.createNewDeckControl; }
|
||||
public void setFreePrintConverter(Function<PaperCard, PaperCard> freePrintConverter) {
|
||||
this.controller.setFreePrintConverter(freePrintConverter);
|
||||
}
|
||||
|
||||
public DeckImportController.ImportBehavior getImportBehavior() {
|
||||
return controller.getImportBehavior();
|
||||
}
|
||||
|
||||
public void setImportBannedCards(boolean importBannedCards) {
|
||||
controller.importBannedAndRestrictedCards(importBannedCards);
|
||||
}
|
||||
|
||||
public void initParse() {
|
||||
boolean usingInventory = editorConfig.usePlayerInventory();
|
||||
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText());
|
||||
if (usingInventory)
|
||||
tokens = controller.constrainTokensToInventory();
|
||||
else if (controller.isSmartCardArtEnabled())
|
||||
tokens = controller.optimiseCardArtInTokens();
|
||||
//ensure at least one known card found on clipboard
|
||||
for (DeckRecognizer.Token token : tokens) {
|
||||
if (token.getType() == TokenType.LEGAL_CARD || token.getType() == TokenType.FREE_CARD_NOT_IN_INVENTORY) {
|
||||
|
||||
if(usingInventory) {
|
||||
//Settings aren't compatible with player inventories.
|
||||
setShowOptions(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setShowOptions(true);
|
||||
|
||||
updateDropDownEnabled();
|
||||
setArtPreferenceInController();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setButtonEnabled(0, false);
|
||||
txtInput.setText(Forge.getLocalizer().getMessage("lblNoKnownCardsOnClipboard"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOverlay(Graphics g) {
|
||||
@@ -202,7 +217,7 @@ public class FDeckImportDialog extends FDialog {
|
||||
yearDropdown.setBounds(x + dropDownWidth + fieldPadding, y, dropDownWidth, h);
|
||||
y += h + fieldPadding;
|
||||
|
||||
if (!this.currentDeckIsEmpty){
|
||||
if (!this.currentDeck.isEmpty()){
|
||||
smartCardArtCheck.setBounds(x, y, w/2, h);
|
||||
createNewDeckCheck.setBounds(x + w/2, y, w/2, h);
|
||||
} else
|
||||
@@ -222,4 +237,50 @@ public class FDeckImportDialog extends FDialog {
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
private static final EnumSet<TokenType> MISSING_TOKENS = EnumSet.of(TokenType.CARD_FROM_NOT_ALLOWED_SET,
|
||||
TokenType.CARD_FROM_INVALID_SET, TokenType.UNKNOWN_CARD, TokenType.UNSUPPORTED_CARD,
|
||||
TokenType.WARNING_MESSAGE, TokenType.CARD_NOT_IN_INVENTORY);
|
||||
|
||||
private void performImport() {
|
||||
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); //ensure deck updated based on any changes to options
|
||||
|
||||
if (editorConfig.usePlayerInventory())
|
||||
tokens = controller.constrainTokensToInventory();
|
||||
else if (controller.isSmartCardArtEnabled())
|
||||
tokens = controller.optimiseCardArtInTokens();
|
||||
|
||||
//if there are any cards that cannot be imported, let user know this and give them the option to cancel
|
||||
// Android API StringBuilder isEmpty() is unavailable. https://developer.android.com/reference/java/lang/StringBuilder
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (DeckRecognizer.Token token : tokens) {
|
||||
if (MISSING_TOKENS.contains(token.getType())) {
|
||||
if (sb.length() != 0)
|
||||
sb.append("\n");
|
||||
String message = controller.getTokenMessage(token);
|
||||
String statusMessage = controller.getTokenStatusMessage(token);
|
||||
if(!StringUtils.isBlank(statusMessage))
|
||||
sb.append(String.format("%s - (%s)", message, statusMessage));
|
||||
else
|
||||
sb.append(statusMessage);
|
||||
}
|
||||
}
|
||||
if (sb.length() != 0) {
|
||||
Localizer localizer = Forge.getLocalizer();
|
||||
if (SOptionPane.showOptionDialog(localizer.getMessage("lblFollowingCardsCannotBeImported") + "\n\n" + sb, localizer.getMessage("lblImportRemainingCards"), SOptionPane.WARNING_ICON, importOrCancel) == 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Deck deck = controller.accept(currentDeck.getName()); //must accept in background thread in case a dialog is shown
|
||||
if (deck == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
FThreads.invokeInEdtLater(() -> {
|
||||
hide();
|
||||
if (callback != null)
|
||||
callback.accept(deck);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 105 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.
|
||||
@@ -0,0 +1,10 @@
|
||||
Name:Emrakul's Presence
|
||||
ManaCost:no cost
|
||||
Types:Artifact
|
||||
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Command | CheckSVar$ X | SVarCompare$ LT8 | Execute$ TrigDraw | TriggerDescription$ At the beginning of your end step, if you have fewer than eight cards in hand, draw cards equal to the difference.
|
||||
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Difference
|
||||
SVar:X:Count$ValidHand Card.YouOwn
|
||||
SVar:Difference:Number$8/Minus.X
|
||||
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigWish | TriggerDescription$ At the beginning of your upkeep, put a random permanent card from your graveyard or exile onto the battlefield.
|
||||
SVar:TrigWish:DB$ ChangeZone | Origin$ Graveyard,Exile | Destination$ Battlefield | ChangeType$ Permanent.YouCtrl | Hidden$ True | AtRandom$ True
|
||||
Oracle:At the beginning of your upkeep, put a random permanent card from your graveyard or exile onto the battlefield.\nAt the beginning of your end step, if you have fewer than eight cards in hand, draw cards equal to the difference.
|
||||
@@ -1,19 +1,25 @@
|
||||
[metadata]
|
||||
Name=Emrakul
|
||||
[Main]
|
||||
4 Black Lotus
|
||||
4 Crystal Vein
|
||||
1 Black Lotus
|
||||
2 Vesuva
|
||||
4 Eldrazi Temple
|
||||
3 Emrakul, the Aeons Torn
|
||||
3 Emrakul, the Promised End
|
||||
3 Forsaken Monument
|
||||
2 It That Betrays
|
||||
2 Pathrazer of Ulamog
|
||||
2 Void Winnower
|
||||
2 Emrakul, the Aeons Torn
|
||||
2 Emrakul, the Promised End
|
||||
4 Expedition Map
|
||||
2 Eye of Ugin
|
||||
4 Mana Vault
|
||||
4 Ancient Tomb
|
||||
1 Strip Mine
|
||||
4 All is Dust
|
||||
2 Kozilek, Butcher of Truth
|
||||
2 Kozilek, the Great Distortion
|
||||
4 Mana Crypt
|
||||
4 Sol Ring
|
||||
4 Titan's Presence
|
||||
1 Mana Crypt
|
||||
1 Sol Ring
|
||||
3 Titan's Presence
|
||||
2 Ulamog, the Ceaseless Hunger
|
||||
2 Ulamog, the Infinite Gyre
|
||||
4 Urza's Mine
|
||||
|
||||
@@ -38,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">
|
||||
@@ -46,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">[{
|
||||
|
||||
@@ -144,3 +144,4 @@ Tarkir Dragonstorm, 3/6/TDM, TDM
|
||||
Final Fantasy, 3/6/FIN, FIN
|
||||
Alchemy: Innistrad, 3/6/ISD, YMID
|
||||
Edge of Eternities, 3/6/EOE, EOE
|
||||
Marvel's Spider-Man, 3/6/SPM, SPM
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user