mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
Compare commits
1 Commits
spm-releas
...
cardTraitC
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1efddc9a92 |
4
.github/workflows/maven-publish.yml
vendored
4
.github/workflows/maven-publish.yml
vendored
@@ -129,9 +129,7 @@ jobs:
|
||||
makeLatest: true
|
||||
|
||||
- name: 🔧 Install XML tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxml2-utils
|
||||
run: sudo apt-get install -y libxml2-utils
|
||||
|
||||
- name: 🔼 Bump versionCode in root POM
|
||||
id: bump_version
|
||||
|
||||
@@ -15,7 +15,7 @@ public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
GuiBase.setInterface(new GuiMobile(Files.exists(Paths.get("./res"))?"./":"../forge-gui/"));
|
||||
GuiBase.setDeviceInfo(null, 0, 0, System.getProperty("user.home") + "/Downloads/");
|
||||
GuiBase.setDeviceInfo(null, 0, 0);
|
||||
new EditorMainWindow(Config.instance());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@ 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;
|
||||
@@ -1665,9 +1664,6 @@ 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);
|
||||
@@ -1677,11 +1673,6 @@ 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(
|
||||
@@ -1761,10 +1752,7 @@ 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;
|
||||
}
|
||||
|
||||
@@ -974,13 +974,17 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
int pBonus = 0;
|
||||
if (ability.getApi() == ApiType.Pump) {
|
||||
if (!ability.hasParam("NumAtt")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||
if (pBonus > 0) {
|
||||
power += pBonus;
|
||||
}
|
||||
}
|
||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||
continue;
|
||||
@@ -994,11 +998,12 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
}
|
||||
|
||||
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
power += pBonus;
|
||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
if (pBonus > 0) {
|
||||
power += pBonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1102,13 +1107,17 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
int tBonus = 0;
|
||||
if (ability.getApi() == ApiType.Pump) {
|
||||
if (!ability.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||
if (tBonus > 0) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
}
|
||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||
continue;
|
||||
@@ -1122,11 +1131,12 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
}
|
||||
|
||||
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
toughness += tBonus;
|
||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
if (tBonus > 0) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return toughness;
|
||||
@@ -1295,7 +1305,6 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
int pBonus = 0;
|
||||
if (ability.getApi() == ApiType.Pump) {
|
||||
if (!ability.hasParam("NumAtt")) {
|
||||
continue;
|
||||
@@ -1305,8 +1314,11 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ability.getPayCosts().hasTapCost()) {
|
||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||
if (pBonus > 0) {
|
||||
power += pBonus;
|
||||
}
|
||||
}
|
||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||
@@ -1321,14 +1333,13 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ability.getPayCosts().hasTapCost()) {
|
||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
if (pBonus > 0) {
|
||||
power += pBonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||
power += pBonus;
|
||||
}
|
||||
}
|
||||
return power;
|
||||
}
|
||||
@@ -1519,14 +1530,16 @@ public class ComputerUtilCombat {
|
||||
if (ability.getPayCosts().hasTapCost() && !attacker.hasKeyword(Keyword.VIGILANCE)) {
|
||||
continue;
|
||||
}
|
||||
if (!ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int tBonus = 0;
|
||||
if (ability.getApi() == ApiType.Pump) {
|
||||
if (!ability.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
||||
toughness += AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||
continue;
|
||||
@@ -1540,11 +1553,10 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
}
|
||||
|
||||
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||
toughness += tBonus;
|
||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
if (tBonus > 0) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
return toughness;
|
||||
|
||||
@@ -291,12 +291,6 @@ public class ComputerUtilMana {
|
||||
continue;
|
||||
}
|
||||
|
||||
int amount = ma.hasParam("Amount") ? AbilityUtils.calculateAmount(ma.getHostCard(), ma.getParam("Amount"), ma) : 1;
|
||||
if (amount <= 0) {
|
||||
// wrong gamestate for variable amount
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sa.getApi() == ApiType.Animate) {
|
||||
// For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana
|
||||
if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined"))
|
||||
@@ -449,6 +443,7 @@ public class ComputerUtilMana {
|
||||
manaProduced = manaProduced.replace(s, color);
|
||||
}
|
||||
} else if (saMana.hasParam("ReplaceColor")) {
|
||||
// replace color
|
||||
String color = saMana.getParam("ReplaceColor");
|
||||
if ("Chosen".equals(color)) {
|
||||
if (card.hasChosenColor()) {
|
||||
@@ -740,8 +735,7 @@ public class ComputerUtilMana {
|
||||
|
||||
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
|
||||
if (sa.getTargets() != null && sa.getTargets().contains(saPayment.getHostCard())) {
|
||||
// not a good idea to sac a card that you're targeting with the SA you're paying for
|
||||
saExcludeList.add(saPayment);
|
||||
saExcludeList.add(saPayment); // not a good idea to sac a card that you're targeting with the SA you're paying for
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1502,7 +1496,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
if (!cost.isReusuableResource()) {
|
||||
for (CostPart part : cost.getCostParts()) {
|
||||
for(CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostSacrifice && !part.payCostFromSource()) {
|
||||
unpreferredCost = true;
|
||||
}
|
||||
@@ -1593,8 +1587,10 @@ public class ComputerUtilMana {
|
||||
|
||||
// don't use abilities with dangerous drawbacks
|
||||
AbilitySub sub = m.getSubAbility();
|
||||
if (sub != null && !SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
||||
continue;
|
||||
if (sub != null) {
|
||||
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
|
||||
|
||||
@@ -1347,11 +1347,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
// Ai won't understand that anyway
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
|
||||
// Ai won't understand that anyway
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<DeckSection, List<? extends PaperCard>> complainCardsCantPlayWell(Deck myDeck) {
|
||||
// TODO check if profile detection set to Auto
|
||||
|
||||
@@ -298,7 +298,13 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
|
||||
if (sa.hasParam("Origin")) {
|
||||
origin = ZoneType.listValueOf(sa.getParam("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);
|
||||
}
|
||||
}
|
||||
final String destination = sa.getParam("Destination");
|
||||
|
||||
@@ -902,6 +908,8 @@ 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)) {
|
||||
@@ -1244,12 +1252,53 @@ 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,9 +442,11 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -607,20 +609,6 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
|
||||
final int currCounters = cards.get(0).getCounters(CounterType.getType(type));
|
||||
|
||||
// adding counters would cause counter amount to overflow
|
||||
if (Integer.MAX_VALUE - currCounters <= amount) {
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
if (type.equals("P1P1")) {
|
||||
if (Integer.MAX_VALUE - cards.get(0).getNetPower() <= amount) {
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
if (Integer.MAX_VALUE - cards.get(0).getNetToughness() <= amount) {
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
}
|
||||
|
||||
// each non +1/+1 counter on the card is a 10% chance of not
|
||||
// activating this ability.
|
||||
|
||||
@@ -682,12 +670,14 @@ public class CountersPutAi extends CountersAi {
|
||||
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
CardCollection list;
|
||||
CardCollection list = null;
|
||||
|
||||
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) {
|
||||
@@ -703,8 +693,9 @@ public class CountersPutAi extends CountersAi {
|
||||
|| sa.getTargets().isEmpty()) {
|
||||
sa.resetTargets();
|
||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (sa.isCurse()) {
|
||||
@@ -747,6 +738,8 @@ 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);
|
||||
@@ -768,11 +761,11 @@ public class CountersPutAi extends CountersAi {
|
||||
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
|
||||
if (decision.willingToPlay()) {
|
||||
return decision;
|
||||
}
|
||||
if (mandatory) {
|
||||
} else if (mandatory) {
|
||||
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||
} else {
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
@@ -823,19 +816,19 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
|
||||
Iterable<Card> filteredField;
|
||||
if (sa.isCurse()) {
|
||||
filteredField = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
filteredField = ai.getCardsIn(ZoneType.Battlefield);
|
||||
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
CardCollection list = CardLists.getTargetableCards(filteredField, sa);
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||
int totalTargets = list.size();
|
||||
boolean preferred = true;
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||
|
||||
int totalTargets = list.size();
|
||||
|
||||
sa.resetTargets();
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (mandatory) {
|
||||
// When things are mandatory, gotta handle a little differently
|
||||
@@ -872,21 +865,27 @@ public class CountersPutAi extends CountersAi {
|
||||
if (choice == null && mandatory) {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
} else if (type.equals("M1M1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
choice = Aggregates.random(list);
|
||||
if (type.equals("M1M1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
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 {
|
||||
choice = Aggregates.random(list);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (choice != null && divided) {
|
||||
int alloc = Math.max(amount / totalTargets, 1);
|
||||
@@ -1081,7 +1080,8 @@ 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 c) {
|
||||
if (e instanceof Card) {
|
||||
Card c = (Card) e;
|
||||
if (c.getController().isOpponentOf(ai)) {
|
||||
if (options.contains(CounterEnumType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterEnumType.M1M1;
|
||||
@@ -1098,7 +1098,8 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (e instanceof Player p) {
|
||||
} else if (e instanceof Player) {
|
||||
Player p = (Player) e;
|
||||
if (p.isOpponentOf(ai)) {
|
||||
if (options.contains(CounterEnumType.POISON)) {
|
||||
return CounterEnumType.POISON;
|
||||
@@ -1232,8 +1233,9 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
||||
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& ai.getManaPool().totalMana() <= 0
|
||||
&& (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
|
||||
&& !card.hasETBTrigger(true) && !card.hasSVar("AmbushAI")
|
||||
&& (!card.hasETBTrigger(true) && !card.hasSVar("AmbushAI"))
|
||||
&& game.getStack().isEmpty()
|
||||
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
|
||||
// AiPlayDecision.AnotherTime;
|
||||
|
||||
@@ -31,8 +31,6 @@ public final class ImageKeys {
|
||||
public static final String MONARCH_IMAGE = "monarch";
|
||||
public static final String THE_RING_IMAGE = "the_ring";
|
||||
public static final String RADIATION_IMAGE = "radiation";
|
||||
public static final String SPEED_IMAGE = "speed";
|
||||
public static final String MAX_SPEED_IMAGE = "max_speed";
|
||||
|
||||
public static final String BACKFACE_POSTFIX = "$alt";
|
||||
public static final String SPECFACE_W = "$wspec";
|
||||
|
||||
@@ -878,7 +878,7 @@ public class StaticData {
|
||||
}
|
||||
}
|
||||
}
|
||||
// stream().toList() causes crash on Android 8-13, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android, 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());
|
||||
|
||||
@@ -45,6 +45,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
public final static char NameSetSeparator = '|';
|
||||
public final static String FlagPrefix = "#";
|
||||
public static final String FlagSeparator = "\t";
|
||||
private final String exlcudedCardName = "Concentrate";
|
||||
private final String exlcudedCardSet = "DS0";
|
||||
|
||||
// need this to obtain cardReference by name+set+artindex
|
||||
private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Lists::newArrayList);
|
||||
@@ -301,7 +303,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
|
||||
// create faces list from rules
|
||||
for (final CardRules rule : rules.values()) {
|
||||
if (filteredCards.contains(rule.getName()))
|
||||
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
|
||||
continue;
|
||||
for (ICardFace face : rule.getAllFaces()) {
|
||||
addFaceToDbNames(face);
|
||||
@@ -499,9 +501,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
public void addCard(PaperCard paperCard) {
|
||||
if (filtered.contains(paperCard.getName())) {
|
||||
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
|
||||
return;
|
||||
}
|
||||
|
||||
allCardsByName.put(paperCard.getName(), paperCard);
|
||||
|
||||
@@ -522,6 +523,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean excludeCard(String cardName, String cardEdition) {
|
||||
if (filtered.isEmpty())
|
||||
return false;
|
||||
if (filtered.contains(cardName)) {
|
||||
if (exlcudedCardSet.equalsIgnoreCase(cardEdition) && exlcudedCardName.equalsIgnoreCase(cardName))
|
||||
return true;
|
||||
else return !exlcudedCardName.equalsIgnoreCase(cardName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void reIndex() {
|
||||
uniqueCardsByName.clear();
|
||||
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
||||
|
||||
@@ -52,14 +52,6 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
public DraftOptions getDraftOptions() {
|
||||
return draftOptions;
|
||||
}
|
||||
|
||||
public void setDraftOptions(DraftOptions draftOptions) {
|
||||
this.draftOptions = draftOptions;
|
||||
}
|
||||
|
||||
// immutable
|
||||
public enum Type {
|
||||
UNKNOWN,
|
||||
@@ -283,22 +275,18 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
// Booster/draft info
|
||||
private List<BoosterSlot> boosterSlots = null;
|
||||
private boolean smallSetOverride = false;
|
||||
private String additionalUnlockSet = "";
|
||||
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
||||
|
||||
// Replace all of these things with booster slots
|
||||
private boolean foilAlwaysInCommonSlot = false;
|
||||
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
||||
private double foilChanceInBooster = 0;
|
||||
private double chanceReplaceCommonWith = 0;
|
||||
private String slotReplaceCommonWith = "Common";
|
||||
private String additionalSheetForFoils = "";
|
||||
private String additionalUnlockSet = "";
|
||||
private String boosterMustContain = "";
|
||||
private String boosterReplaceSlotFromPrintSheet = "";
|
||||
private String sheetReplaceCardFromSheet = "";
|
||||
private String sheetReplaceCardFromSheet2 = "";
|
||||
|
||||
// Draft options
|
||||
private DraftOptions draftOptions = null;
|
||||
private String doublePickDuringDraft = "";
|
||||
private String[] chaosDraftThemes = new String[0];
|
||||
|
||||
private final ListMultimap<String, EditionEntry> cardMap;
|
||||
@@ -385,6 +373,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
public String getSlotReplaceCommonWith() { return slotReplaceCommonWith; }
|
||||
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
||||
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
||||
public String getDoublePickDuringDraft() { return doublePickDuringDraft; }
|
||||
public String getBoosterMustContain() { return boosterMustContain; }
|
||||
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
|
||||
public String getSheetReplaceCardFromSheet() { return sheetReplaceCardFromSheet; }
|
||||
@@ -630,7 +619,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
* functional variant name - grouping #9
|
||||
*/
|
||||
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
|
||||
"(^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
|
||||
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
|
||||
);
|
||||
|
||||
final Pattern tokenPattern = Pattern.compile(
|
||||
@@ -639,7 +628,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();
|
||||
@@ -819,6 +808,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
res.additionalUnlockSet = metadata.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
|
||||
|
||||
res.smallSetOverride = metadata.getBoolean("TreatAsSmallSet", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
|
||||
res.doublePickDuringDraft = metadata.get("DoublePick", ""); // "FirstPick" or "Always"
|
||||
|
||||
res.boosterMustContain = metadata.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
|
||||
res.boosterReplaceSlotFromPrintSheet = metadata.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
|
||||
@@ -826,23 +816,6 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
res.sheetReplaceCardFromSheet2 = metadata.get("SheetReplaceCardFromSheet2", "");
|
||||
res.chaosDraftThemes = metadata.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
|
||||
|
||||
// Draft options
|
||||
String doublePick = metadata.get("DoublePick", "Never");
|
||||
int maxPodSize = metadata.getInt("MaxPodSize", 8);
|
||||
int recommendedPodSize = metadata.getInt("RecommendedPodSize", 8);
|
||||
int maxMatchPlayers = metadata.getInt("MaxMatchPlayers", 2);
|
||||
String deckType = metadata.get("DeckType", "Normal");
|
||||
String freeCommander = metadata.get("FreeCommander", "");
|
||||
|
||||
res.draftOptions = new DraftOptions(
|
||||
doublePick,
|
||||
maxPodSize,
|
||||
recommendedPodSize,
|
||||
maxMatchPlayers,
|
||||
deckType,
|
||||
freeCommander
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1018,13 +991,16 @@ 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;
|
||||
}
|
||||
return ed.hasBasicLands();
|
||||
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||
if (null == StaticData.instance().getCommonCards().getCard(landName, ed.getCode(), 0))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1045,7 +1021,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
|
||||
public boolean hasBasicLands() {
|
||||
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||
if (this.getCardInSet(landName).isEmpty())
|
||||
if (null == StaticData.instance().getCommonCards().getCard(landName, this.getCode(), 0))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -168,7 +168,21 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public boolean isTransformable() {
|
||||
return CardSplitType.Transform == getSplitType() || CardSplitType.Modal == getSplitType();
|
||||
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;
|
||||
}
|
||||
|
||||
public ICardFace getWSpecialize() {
|
||||
|
||||
@@ -20,6 +20,7 @@ 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;
|
||||
@@ -40,95 +41,25 @@ 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 final Set<Color> enumSet;
|
||||
private final String desc;
|
||||
|
||||
private static final ColorSet[] cache = new ColorSet[32];
|
||||
|
||||
public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS);
|
||||
public static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
|
||||
private static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS);
|
||||
|
||||
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));
|
||||
private ColorSet(final byte mask) {
|
||||
this.myColor = mask;
|
||||
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;
|
||||
return cache[mask32];
|
||||
}
|
||||
|
||||
public static ColorSet fromEnums(final Color... colors) {
|
||||
byte mask = 0;
|
||||
for (Color e : colors) {
|
||||
mask |= e.getColorMask();
|
||||
if (cache[mask32] == null) {
|
||||
cache[mask32] = new ColorSet((byte) mask32);
|
||||
}
|
||||
return fromMask(mask);
|
||||
return cache[mask32];
|
||||
}
|
||||
|
||||
public static ColorSet fromNames(final String... colors) {
|
||||
@@ -362,7 +293,17 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return desc;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,7 +325,16 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
||||
}
|
||||
|
||||
public Set<Color> toEnumSet() {
|
||||
return EnumSet.copyOf(enumSet);
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -422,12 +372,72 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<Color> stream() {
|
||||
public Stream<MagicColor.Color> stream() {
|
||||
return this.toEnumSet().stream();
|
||||
}
|
||||
|
||||
//Get array of mana cost shards for color set in the proper order
|
||||
public Collection<Color> getOrderedColors() {
|
||||
return orderedShards;
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package forge.card;
|
||||
|
||||
public class DraftOptions {
|
||||
public enum DoublePick {
|
||||
NEVER,
|
||||
FIRST_PICK, // only first pick each pack
|
||||
WHEN_POD_SIZE_IS_4, // only when pod size is 4, so you can pick two cards each time
|
||||
ALWAYS // each time you receive a pack, you can pick two cards
|
||||
};
|
||||
public enum DeckType {
|
||||
Normal, // Standard deck, usually 40 cards
|
||||
Commander // Special deck type for Commander format. Important for selection/construction
|
||||
}
|
||||
|
||||
private DoublePick doublePick = DoublePick.NEVER;
|
||||
private final int maxPodSize; // Usually 8, but could be smaller for cubes. I guess it could be larger too
|
||||
private final int recommendedPodSize; // Usually 8, but is 4 for new double pick
|
||||
private final int maxMatchPlayers; // Usually 2, but 4 for things like Commander or Conspiracy
|
||||
private final DeckType deckType; // Normal or Commander
|
||||
private final String freeCommander;
|
||||
|
||||
public DraftOptions(String doublePickOption, int maxPodSize, int recommendedPodSize, int maxMatchPlayers, String deckType, String freeCommander) {
|
||||
this.maxPodSize = maxPodSize;
|
||||
this.recommendedPodSize = recommendedPodSize;
|
||||
this.maxMatchPlayers = maxMatchPlayers;
|
||||
this.deckType = DeckType.valueOf(deckType);
|
||||
this.freeCommander = freeCommander;
|
||||
if (doublePickOption != null) {
|
||||
switch (doublePickOption.toLowerCase()) {
|
||||
case "firstpick":
|
||||
doublePick = DoublePick.FIRST_PICK;
|
||||
break;
|
||||
case "always":
|
||||
doublePick = DoublePick.ALWAYS;
|
||||
break;
|
||||
case "whenpodsizeis4":
|
||||
doublePick = DoublePick.WHEN_POD_SIZE_IS_4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public int getMaxPodSize() {
|
||||
return maxPodSize;
|
||||
}
|
||||
public int getRecommendedPodSize() {
|
||||
return recommendedPodSize;
|
||||
}
|
||||
public DoublePick getDoublePick() {
|
||||
return doublePick;
|
||||
}
|
||||
|
||||
public DoublePick isDoublePick(int podSize) {
|
||||
if (doublePick == DoublePick.WHEN_POD_SIZE_IS_4) {
|
||||
if (podSize != 4) {
|
||||
return DoublePick.NEVER;
|
||||
}
|
||||
// only when pod size is 4, so you can pick two cards each time
|
||||
return DoublePick.ALWAYS;
|
||||
}
|
||||
|
||||
return doublePick;
|
||||
}
|
||||
|
||||
|
||||
public int getMaxMatchPlayers() {
|
||||
return maxMatchPlayers;
|
||||
}
|
||||
public DeckType getDeckType() {
|
||||
return deckType;
|
||||
}
|
||||
public String getFreeCommander() {
|
||||
return freeCommander;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package forge.card;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import forge.util.Localizer;
|
||||
import forge.deck.DeckRecognizer;
|
||||
|
||||
/**
|
||||
* Holds byte values for each color magic has.
|
||||
@@ -158,23 +158,20 @@ public final class MagicColor {
|
||||
}
|
||||
|
||||
public enum Color {
|
||||
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");
|
||||
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}");
|
||||
|
||||
private final String name, shortName, symbol;
|
||||
private final String label;
|
||||
private final String name, symbol;
|
||||
private final byte colormask;
|
||||
|
||||
Color(String name0, byte colormask0, String shortName, String label) {
|
||||
Color(String name0, byte colormask0, String symbol0) {
|
||||
name = name0;
|
||||
colormask = colormask0;
|
||||
this.shortName = shortName;
|
||||
symbol = "{" + shortName + "}";
|
||||
this.label = label;
|
||||
symbol = symbol0;
|
||||
}
|
||||
|
||||
public static Color fromByte(final byte color) {
|
||||
@@ -191,15 +188,13 @@ public final class MagicColor {
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getLocalizedName() {
|
||||
return Localizer.getInstance().getMessage(label);
|
||||
//Should probably move some of this logic back here, or at least to a more general location.
|
||||
return DeckRecognizer.getLocalisedMagicColorName(getName());
|
||||
}
|
||||
|
||||
public byte getColorMask() {
|
||||
public byte getColormask() {
|
||||
return colormask;
|
||||
}
|
||||
public String getSymbol() {
|
||||
@@ -207,7 +202,7 @@ public final class MagicColor {
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return getLocalizedName();
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,20 +115,6 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
return parts.get(DeckSection.Main);
|
||||
}
|
||||
|
||||
public Pair<Deck, List<PaperCard>> getValid() {
|
||||
List<PaperCard> unsupported = new ArrayList<>();
|
||||
for (Entry<DeckSection, CardPool> kv : parts.entrySet()) {
|
||||
CardPool pool = kv.getValue();
|
||||
for (Entry<PaperCard, Integer> pc : pool) {
|
||||
if (pc.getKey().getRules() != null && pc.getKey().getRules().isUnsupported()) {
|
||||
unsupported.add(pc.getKey());
|
||||
pool.remove(pc.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair.of(this, unsupported);
|
||||
}
|
||||
|
||||
public List<PaperCard> getCommanders() {
|
||||
List<PaperCard> result = Lists.newArrayList();
|
||||
final CardPool cp = get(DeckSection.Commander);
|
||||
|
||||
@@ -49,16 +49,6 @@ 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,
|
||||
@@ -73,14 +63,10 @@ public class DeckRecognizer {
|
||||
CARD_TYPE,
|
||||
CARD_RARITY,
|
||||
CARD_CMC,
|
||||
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);
|
||||
MANA_COLOUR
|
||||
}
|
||||
|
||||
public enum LimitedCardType {
|
||||
public enum LimitedCardType{
|
||||
BANNED,
|
||||
RESTRICTED,
|
||||
}
|
||||
@@ -122,10 +108,6 @@ 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) {
|
||||
@@ -144,10 +126,6 @@ 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
|
||||
* ================================= */
|
||||
@@ -261,11 +239,14 @@ 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, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY.
|
||||
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET.
|
||||
* False otherwise.
|
||||
*/
|
||||
public boolean isCardToken() {
|
||||
return TokenType.CARD_TOKEN_TYPES.contains(this.type);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +255,9 @@ public class DeckRecognizer {
|
||||
* LEGAL_CARD, LIMITED_CARD, DECK_NAME; false otherwise.
|
||||
*/
|
||||
public boolean isTokenForDeck() {
|
||||
return TokenType.IN_DECK_TOKEN_TYPES.contains(this.type);
|
||||
return (this.type == TokenType.LEGAL_CARD ||
|
||||
this.type == TokenType.LIMITED_CARD ||
|
||||
this.type == TokenType.DECK_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,7 +266,7 @@ public class DeckRecognizer {
|
||||
* False otherwise.
|
||||
*/
|
||||
public boolean isCardTokenForDeck() {
|
||||
return isCardToken() && isTokenForDeck();
|
||||
return (this.type == TokenType.LEGAL_CARD || this.type == TokenType.LIMITED_CARD);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,7 +276,10 @@ public class DeckRecognizer {
|
||||
* CARD_RARITY, CARD_CMC, CARD_TYPE, MANA_COLOUR
|
||||
*/
|
||||
public boolean isCardPlaceholder(){
|
||||
return TokenType.CARD_PLACEHOLDER_TOKEN_TYPES.contains(this.type);
|
||||
return (this.type == TokenType.CARD_RARITY ||
|
||||
this.type == TokenType.CARD_CMC ||
|
||||
this.type == TokenType.MANA_COLOUR ||
|
||||
this.type == TokenType.CARD_TYPE);
|
||||
}
|
||||
|
||||
/** Determines if current token is a Deck Section token
|
||||
@@ -550,7 +536,7 @@ public class DeckRecognizer {
|
||||
PaperCard tokenCard = token.getCard();
|
||||
|
||||
if (isAllowed(tokenSection)) {
|
||||
if (tokenSection != referenceDeckSectionInParsing) {
|
||||
if (!tokenSection.equals(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
|
||||
@@ -589,7 +575,7 @@ public class DeckRecognizer {
|
||||
refLine = purgeAllLinks(refLine);
|
||||
|
||||
String line;
|
||||
if (refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
||||
if (StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER))
|
||||
line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, "");
|
||||
else
|
||||
line = refLine.trim(); // Remove any trailing formatting
|
||||
@@ -598,7 +584,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 (line.startsWith(ASTERISK)) // Markdown lists (tappedout md export)
|
||||
if (StringUtils.startsWith(line, ASTERISK)) // markdown lists (tappedout md export)
|
||||
line = line.substring(2);
|
||||
|
||||
// == Patches to Corner Cases
|
||||
@@ -614,8 +600,8 @@ public class DeckRecognizer {
|
||||
Token result = recogniseCardToken(line, referenceSection);
|
||||
if (result == null)
|
||||
result = recogniseNonCardToken(line);
|
||||
return result != null ? result : refLine.startsWith(DOUBLE_SLASH) ||
|
||||
refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
|
||||
return result != null ? result : StringUtils.startsWith(refLine, DOUBLE_SLASH) ||
|
||||
StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
|
||||
new Token(TokenType.COMMENT, 0, refLine) : new Token(TokenType.UNKNOWN_TEXT, 0, refLine);
|
||||
}
|
||||
|
||||
@@ -627,7 +613,7 @@ public class DeckRecognizer {
|
||||
while (m.find()) {
|
||||
line = line.replaceAll(m.group(), "").trim();
|
||||
}
|
||||
if (line.endsWith("()"))
|
||||
if (StringUtils.endsWith(line, "()"))
|
||||
return line.substring(0, line.length()-2);
|
||||
return line;
|
||||
}
|
||||
@@ -755,12 +741,21 @@ 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" -> DeckSection.Main;
|
||||
case "SB" -> DeckSection.Sideboard;
|
||||
case "CM" -> DeckSection.Commander;
|
||||
default -> DeckSection.matchingSection(card);
|
||||
};
|
||||
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;
|
||||
}
|
||||
if (cardSection.validate(card))
|
||||
return cardSection;
|
||||
}
|
||||
@@ -1015,28 +1010,58 @@ 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
|
||||
return MagicColor.Color.fromByte(MagicColor.fromName(colorName.toLowerCase()));
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static String getLocalisedMagicColorName(String colorName){
|
||||
Localizer localizer = Localizer.getInstance();
|
||||
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 -> "";
|
||||
};
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1055,6 +1080,37 @@ 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.NO_COLORS;
|
||||
markedColors = ColorSet.getNullColor();
|
||||
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 || cst == CardSplitType.Modal;
|
||||
return cst == CardSplitType.Transform;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -633,10 +633,7 @@ 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);
|
||||
PrintSheet fromSheet = tryGetStaticSheet(sheetName);
|
||||
if (fromSheet == null)
|
||||
throw new RuntimeException("PrintSheet Error: " + ps.getName() + " didn't find " + sheetName + " from " + mainCode);
|
||||
src = fromSheet.toFlatList();
|
||||
src = tryGetStaticSheet(sheetName).toFlatList();
|
||||
setPred = x -> true;
|
||||
|
||||
} else if (mainCode.startsWith("promo") || mainCode.startsWith("name")) { // get exactly the named cards, that's a tiny inlined print sheet
|
||||
|
||||
@@ -207,6 +207,8 @@ public class ImageUtil {
|
||||
else
|
||||
editionCode = cp.getEdition().toLowerCase();
|
||||
String cardCollectorNumber = cp.getCollectorNumber();
|
||||
// Hack to account for variations in Arabian Nights
|
||||
cardCollectorNumber = cardCollectorNumber.replace("+", "†");
|
||||
// override old planechase sets from their modified id since scryfall move the planechase cards outside their original setcode
|
||||
if (cardCollectorNumber.startsWith("OHOP")) {
|
||||
editionCode = "ohop";
|
||||
@@ -250,11 +252,6 @@ public class ImageUtil {
|
||||
: "&face=front");
|
||||
}
|
||||
|
||||
if (cardCollectorNumber.endsWith("☇")) {
|
||||
faceParam = "&face=back";
|
||||
cardCollectorNumber = cardCollectorNumber.substring(0, cardCollectorNumber.length() - 1);
|
||||
}
|
||||
|
||||
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, encodeUtf8(cardCollectorNumber),
|
||||
langCode, versionParam, faceParam);
|
||||
}
|
||||
@@ -264,10 +261,6 @@ public class ImageUtil {
|
||||
if (!faceParam.isEmpty()) {
|
||||
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
|
||||
}
|
||||
if (collectorNumber.endsWith("☇")) {
|
||||
faceParam = "&face=back";
|
||||
collectorNumber = collectorNumber.substring(0, collectorNumber.length() - 1);
|
||||
}
|
||||
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, encodeUtf8(collectorNumber),
|
||||
langCode, versionParam, faceParam);
|
||||
}
|
||||
@@ -288,7 +281,8 @@ public class ImageUtil {
|
||||
char c;
|
||||
for (int i = 0; i < in.length(); i++) {
|
||||
c = in.charAt(i);
|
||||
if ((c != '"') && (c != '/') && (c != ':') && (c != '?')) {
|
||||
if ((c == '"') || (c == '/') || (c == ':') || (c == '?')) {
|
||||
} else {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ForgeScript {
|
||||
boolean withSource = property.endsWith("Source");
|
||||
final ColorSet colors;
|
||||
if (withSource && StaticAbilityColorlessDamageSource.colorlessDamageSource(cardState)) {
|
||||
colors = ColorSet.NO_COLORS;
|
||||
colors = ColorSet.getNullColor();
|
||||
} else {
|
||||
colors = cardState.getCard().getColor(cardState);
|
||||
}
|
||||
@@ -195,7 +195,7 @@ public class ForgeScript {
|
||||
return sa.isKeyword(Keyword.SADDLE);
|
||||
} else if (property.equals("Station")) {
|
||||
return sa.isKeyword(Keyword.STATION);
|
||||
} else if (property.equals("Cycling")) {
|
||||
}else if (property.equals("Cycling")) {
|
||||
return sa.isCycling();
|
||||
} else if (property.equals("Dash")) {
|
||||
return sa.isDash();
|
||||
@@ -237,8 +237,6 @@ public class ForgeScript {
|
||||
return sa.isBoast();
|
||||
} else if (property.equals("Exhaust")) {
|
||||
return sa.isExhaust();
|
||||
} else if (property.equals("Mayhem")) {
|
||||
return sa.isMayhem();
|
||||
} else if (property.equals("Mutate")) {
|
||||
return sa.isMutate();
|
||||
} else if (property.equals("Ninjutsu")) {
|
||||
@@ -412,8 +410,6 @@ 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,8 +845,6 @@ 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)) {
|
||||
@@ -882,6 +880,8 @@ 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,6 +220,10 @@ 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);
|
||||
@@ -970,7 +974,6 @@ 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();
|
||||
}
|
||||
@@ -2219,13 +2222,6 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
|
||||
// Notify players
|
||||
for (Player p : game.getPlayers()) {
|
||||
p.getController().revealUnsupported(unsupported);
|
||||
}
|
||||
}
|
||||
|
||||
/** Delivers a message to all players. (use reveal to show Cards) */
|
||||
public void notifyOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) {
|
||||
if (saSource != null) {
|
||||
|
||||
@@ -125,22 +125,10 @@ public final class GameActionUtil {
|
||||
|
||||
// need to be done there before static abilities does reset the card
|
||||
// These Keywords depend on the Mana Cost of for Split Cards
|
||||
if (sa.isBasicSpell()) {
|
||||
if (sa.isBasicSpell() && !sa.isLandAbility()) {
|
||||
for (final KeywordInterface inst : source.getKeywords()) {
|
||||
final String keyword = inst.getOriginal();
|
||||
|
||||
if (keyword.startsWith("Mayhem")) {
|
||||
if (!source.isInZone(ZoneType.Graveyard) || !source.wasDiscarded() || !source.enteredThisTurn()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Mayhem));
|
||||
}
|
||||
|
||||
if (sa.isLandAbility()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (keyword.startsWith("Escape")) {
|
||||
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||
continue;
|
||||
@@ -178,6 +166,18 @@ public final class GameActionUtil {
|
||||
}
|
||||
|
||||
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Flashback));
|
||||
} else if (keyword.startsWith("Mayhem")) {
|
||||
if (!source.isInZone(ZoneType.Graveyard) || !source.wasDiscarded() || !source.enteredThisTurn()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if source has No Mana cost, and Mayhem doesn't have own one,
|
||||
// Mayhem can't work
|
||||
if (keyword.equals("Mayhem") && source.getManaCost().isNoCost()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Mayhem));
|
||||
} else if (keyword.startsWith("Harmonize")) {
|
||||
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||
continue;
|
||||
@@ -242,7 +242,6 @@ public final class GameActionUtil {
|
||||
}
|
||||
stackCopy.setLastKnownZone(game.getStackZone());
|
||||
stackCopy.setCastFrom(oldZone);
|
||||
stackCopy.setCastSA(sa);
|
||||
lkicheck = true;
|
||||
|
||||
stackCopy.clearStaticChangedCardKeywords(false);
|
||||
@@ -993,6 +992,9 @@ 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());
|
||||
|
||||
@@ -15,7 +15,6 @@ public class GameRules {
|
||||
private boolean AISideboardingEnabled = false;
|
||||
private boolean sideboardForAI = false;
|
||||
private final Set<GameType> appliedVariants = EnumSet.noneOf(GameType.class);
|
||||
private int simTimeout = 120;
|
||||
|
||||
// it's a preference, not rule... but I could hardly find a better place for it
|
||||
private boolean useGrayText;
|
||||
@@ -125,12 +124,4 @@ public class GameRules {
|
||||
public void setWarnAboutAICards(final boolean warnAboutAICards) {
|
||||
this.warnAboutAICards = warnAboutAICards;
|
||||
}
|
||||
|
||||
public int getSimTimeout() {
|
||||
return this.simTimeout;
|
||||
}
|
||||
|
||||
public void setSimTimeout(final int duration) {
|
||||
this.simTimeout = duration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import forge.item.PaperCard;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
@@ -225,7 +224,6 @@ public class Match {
|
||||
// friendliness
|
||||
Map<Player, Map<DeckSection, List<? extends PaperCard>>> rAICards = new HashMap<>();
|
||||
Multimap<Player, PaperCard> removedAnteCards = ArrayListMultimap.create();
|
||||
Map<Player, List<PaperCard>> unsupported = new HashMap<>();
|
||||
|
||||
final FCollectionView<Player> players = game.getPlayers();
|
||||
final List<RegisteredPlayer> playersConditions = game.getMatch().getPlayers();
|
||||
@@ -290,32 +288,22 @@ public class Match {
|
||||
}
|
||||
}
|
||||
|
||||
Deck toCheck = psc.getDeck();
|
||||
if (toCheck == null) {
|
||||
try {
|
||||
System.err.println(psc.getPlayer().getName() + " Deck is NULL...");
|
||||
int val = rules.getGameType().getDeckFormat().getMainRange().getMinimum();
|
||||
toCheck = new Deck("NULL");
|
||||
if (val > 0)
|
||||
toCheck.getMain().add("Wastes", val);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
Pair<Deck, List<PaperCard>> myDeck = toCheck.getValid();
|
||||
player.setDraftNotes(myDeck.getLeft().getDraftNotes());
|
||||
Deck myDeck = psc.getDeck();
|
||||
player.setDraftNotes(myDeck.getDraftNotes());
|
||||
|
||||
Set<PaperCard> myRemovedAnteCards = null;
|
||||
if (!rules.useAnte()) {
|
||||
myRemovedAnteCards = getRemovedAnteCards(myDeck.getLeft());
|
||||
myRemovedAnteCards = getRemovedAnteCards(myDeck);
|
||||
for (PaperCard cp: myRemovedAnteCards) {
|
||||
for (Entry<DeckSection, CardPool> ds : myDeck.getLeft()) {
|
||||
for (Entry<DeckSection, CardPool> ds : myDeck) {
|
||||
ds.getValue().removeAll(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preparePlayerZone(player, ZoneType.Library, myDeck.getLeft().getMain(), psc.useRandomFoil());
|
||||
if (myDeck.getLeft().has(DeckSection.Sideboard)) {
|
||||
preparePlayerZone(player, ZoneType.Sideboard, myDeck.getLeft().get(DeckSection.Sideboard), psc.useRandomFoil());
|
||||
preparePlayerZone(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
|
||||
if (myDeck.has(DeckSection.Sideboard)) {
|
||||
preparePlayerZone(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
|
||||
|
||||
// Assign Companion
|
||||
Card companion = player.assignCompanion(game, person);
|
||||
@@ -334,7 +322,7 @@ public class Match {
|
||||
player.shuffle(null);
|
||||
|
||||
if (isFirstGame) {
|
||||
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck.getLeft());
|
||||
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck);
|
||||
if (cardsComplained != null && !cardsComplained.isEmpty()) {
|
||||
rAICards.put(player, cardsComplained);
|
||||
}
|
||||
@@ -349,7 +337,6 @@ public class Match {
|
||||
if (myRemovedAnteCards != null && !myRemovedAnteCards.isEmpty()) {
|
||||
removedAnteCards.putAll(player, myRemovedAnteCards);
|
||||
}
|
||||
unsupported.put(player, myDeck.getRight());
|
||||
}
|
||||
|
||||
final Localizer localizer = Localizer.getInstance();
|
||||
@@ -360,10 +347,6 @@ public class Match {
|
||||
if (!removedAnteCards.isEmpty()) {
|
||||
game.getAction().revealAnte(localizer.getMessage("lblAnteCardsRemoved"), removedAnteCards);
|
||||
}
|
||||
|
||||
if (!unsupported.isEmpty()) {
|
||||
game.getAction().revealUnsupported(unsupported);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeAnte(Game lastGame) {
|
||||
|
||||
@@ -239,10 +239,6 @@ public final class AbilityFactory {
|
||||
spellAbility.putParam("PrecostDesc", "Exhaust — ");
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("Named")) {
|
||||
spellAbility.setName(mapParams.get("Named"));
|
||||
}
|
||||
|
||||
// *********************************************
|
||||
// set universal properties of the SpellAbility
|
||||
|
||||
@@ -363,6 +359,9 @@ 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);
|
||||
}
|
||||
@@ -384,9 +383,6 @@ public final class AbilityFactory {
|
||||
if (mapParams.containsKey("TargetsWithDifferentCMC")) {
|
||||
abTgt.setDifferentCMC(true);
|
||||
}
|
||||
if (mapParams.containsKey("TargetsWithDifferentNames")) {
|
||||
abTgt.setDifferentNames(true);
|
||||
}
|
||||
if (mapParams.containsKey("TargetsWithEqualToughness")) {
|
||||
abTgt.setEqualToughness(true);
|
||||
}
|
||||
|
||||
@@ -1870,14 +1870,6 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,6 @@ public class AlterAttributeEffect extends SpellAbilityEffect {
|
||||
boolean altered = false;
|
||||
|
||||
switch (attr.trim()) {
|
||||
case "Harnessed":
|
||||
altered = gameCard.setHarnessed(activate);
|
||||
break;
|
||||
case "Plotted":
|
||||
altered = gameCard.setPlotted(activate);
|
||||
|
||||
|
||||
@@ -928,7 +928,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
List<ZoneType> origin = Lists.newArrayList();
|
||||
if (sa.hasParam("Origin")) {
|
||||
origin.addAll(ZoneType.listValueOf(sa.getParam("Origin")));
|
||||
origin = ZoneType.listValueOf(sa.getParam("Origin"));
|
||||
}
|
||||
ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
|
||||
@@ -969,10 +969,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
String prompt;
|
||||
if (sa.hasParam("OptionalPrompt")) {
|
||||
prompt = sa.getParam("OptionalPrompt");
|
||||
} else if (defined) {
|
||||
prompt = Localizer.getInstance().getMessage("lblPutThatCardFromPlayerOriginToDestination", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase(), destination.getTranslatedName().toLowerCase());
|
||||
} else {
|
||||
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase());
|
||||
if (defined) {
|
||||
prompt = Localizer.getInstance().getMessage("lblPutThatCardFromPlayerOriginToDestination", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase(), destination.getTranslatedName().toLowerCase());
|
||||
} else {
|
||||
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase());
|
||||
}
|
||||
}
|
||||
String message = MessageUtil.formatMessage(prompt , decider, player);
|
||||
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {
|
||||
@@ -1474,7 +1476,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(source, sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
||||
int cAmount = AbilityUtils.calculateAmount(sa.getOriginalHost(), sa.getParamOrDefault("WithCountersAmount", "1"), sa);
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
movedCard.addCounter(cType, cAmount, player, table);
|
||||
table.replaceCounterEffect(game, sa, true);
|
||||
|
||||
@@ -287,17 +287,22 @@ 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,10 +18,6 @@ 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();
|
||||
|
||||
@@ -34,9 +34,6 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
|
||||
// CR 603.12a if the trigger event or events occur multiple times during the resolution of the spell or ability that created it,
|
||||
// the reflexive triggered ability will trigger once for each of those times
|
||||
int amt = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TriggerAmount", "1"), sa);
|
||||
if (amt <= 0) {
|
||||
return;
|
||||
|
||||
@@ -54,29 +54,33 @@ public class LifeExchangeEffect extends SpellAbilityEffect {
|
||||
|
||||
final int life1 = p1.getLife();
|
||||
final int life2 = p2.getLife();
|
||||
final int diff = Math.abs(life1 - life2);
|
||||
|
||||
if (life2 > life1) {
|
||||
// swap players
|
||||
Player tmp = p2;
|
||||
p2 = p1;
|
||||
p1 = tmp;
|
||||
if (sa.hasParam("RememberDifference")) {
|
||||
final int diff = life1 - life2;
|
||||
source.addRemembered(diff);
|
||||
}
|
||||
if (diff > 0 && p1.canLoseLife() && p2.canGainLife()) {
|
||||
|
||||
final Map<Player, Integer> lossMap = Maps.newHashMap();
|
||||
if ((life1 > life2) && p1.canLoseLife() && p2.canGainLife()) {
|
||||
final int diff = life1 - life2;
|
||||
final int lost = p1.loseLife(diff, false, false);
|
||||
p2.gainLife(diff, source, sa);
|
||||
if (lost > 0) {
|
||||
final Map<Player, Integer> lossMap = Maps.newHashMap();
|
||||
lossMap.put(p1, lost);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
|
||||
source.getGame().getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
|
||||
if (sa.hasParam("RememberOwnLoss") && p1.equals(sa.getActivatingPlayer())) {
|
||||
source.addRemembered(lost);
|
||||
}
|
||||
}
|
||||
} else if ((life2 > life1) && p2.canLoseLife() && p1.canGainLife()) {
|
||||
final int diff = life2 - life1;
|
||||
final int lost = p2.loseLife(diff, false, false);
|
||||
p1.gainLife(diff, source, sa);
|
||||
if (lost > 0) {
|
||||
lossMap.put(p2, lost);
|
||||
}
|
||||
} else {
|
||||
// they are equal or can't be exchanged, so nothing to do
|
||||
}
|
||||
if (sa.hasParam("RememberDifference")) {
|
||||
source.addRemembered(p1.getLife() - p2.getLife());
|
||||
if (!lossMap.isEmpty()) { // Run triggers if any player actually lost life
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
|
||||
source.getGame().getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,11 +271,10 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
producedMana.append(abMana.produceMana(mana, p, sa));
|
||||
}
|
||||
|
||||
// Only clear express choice after mana has been produced
|
||||
abMana.clearExpressChoice();
|
||||
|
||||
abMana.tapsForMana(sa.getRootAbility(), producedMana.toString());
|
||||
|
||||
// Only clear express choice after mana has been produced
|
||||
abMana.clearExpressChoice();
|
||||
if (sa.isKeyword(Keyword.FIREBENDING)) {
|
||||
activator.triggerElementalBend(TriggerType.Firebend);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,8 @@ public class PermanentCreatureEffect extends PermanentEffect {
|
||||
public String getStackDescription(final SpellAbility sa) {
|
||||
final CardState source = sa.getCardState();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(CardTranslation.getTranslatedName(source.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ");
|
||||
sb.append(sa.getParamOrDefault("SetPower", source.getBasePowerString()));
|
||||
sb.append(" / ").append(sa.getParamOrDefault("SetToughness", source.getBaseToughnessString()));
|
||||
sb.append(CardTranslation.getTranslatedName(source.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ").append(source.getBasePowerString());
|
||||
sb.append(" / ").append(source.getBaseToughnessString());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,10 +428,6 @@ 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")
|
||||
|
||||
@@ -17,6 +17,7 @@ import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.perpetual.PerpetualKeywords;
|
||||
@@ -281,17 +282,6 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
List<Card> tgtCards = getCardsfromTargets(sa);
|
||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
if (sa.hasParam("Optional")) {
|
||||
final String targets = Lang.joinHomogenous(tgtCards);
|
||||
final String message = sa.hasParam("OptionQuestion")
|
||||
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
|
||||
: Localizer.getInstance().getMessage("lblApplyPumpToTarget", targets);
|
||||
|
||||
if (!activator.getController().confirmAction(sa, null, message, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> keywords = Lists.newArrayList();
|
||||
if (sa.hasParam("KW")) {
|
||||
keywords.addAll(Arrays.asList(sa.getParam("KW").split(" & ")));
|
||||
@@ -317,6 +307,8 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, host, sa);
|
||||
}
|
||||
|
||||
final CardCollection untargetedCards = CardUtil.getRadiance(sa);
|
||||
|
||||
if (sa.hasParam("DefinedKW")) {
|
||||
String defined = sa.getParam("DefinedKW");
|
||||
if (defined.equals("ChosenType")) {
|
||||
@@ -402,6 +394,17 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
keywords = choice;
|
||||
}
|
||||
|
||||
if (sa.hasParam("Optional")) {
|
||||
final String targets = Lang.joinHomogenous(tgtCards);
|
||||
final String message = sa.hasParam("OptionQuestion")
|
||||
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
|
||||
: Localizer.getInstance().getMessage("lblApplyPumpToTarget", targets);
|
||||
|
||||
if (!activator.getController().confirmAction(sa, null, message, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberObjects")) {
|
||||
host.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("RememberObjects"), sa));
|
||||
}
|
||||
@@ -491,7 +494,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), tgtCards);
|
||||
}
|
||||
|
||||
for (final Card tgtC : CardUtil.getRadiance(sa)) {
|
||||
for (final Card tgtC : untargetedCards) {
|
||||
// only pump things in PumpZone
|
||||
if (!tgtC.isInZones(pumpZones)) {
|
||||
continue;
|
||||
|
||||
@@ -92,11 +92,12 @@ 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) &&
|
||||
(!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)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -9,7 +9,6 @@ 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;
|
||||
@@ -21,6 +20,7 @@ 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,8 +41,10 @@ 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
|
||||
CardCollection list = CardLists.filter(activator.getCardsIn(ZoneType.Exile), CardPredicates.hasSuspend());
|
||||
list.addAll(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)));
|
||||
|
||||
|
||||
@@ -203,7 +203,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
private boolean unearthed;
|
||||
private boolean ringbearer;
|
||||
private boolean monstrous;
|
||||
private boolean harnessed;
|
||||
private boolean renowned;
|
||||
private boolean solved;
|
||||
private boolean tributed;
|
||||
@@ -257,7 +256,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
private long worldTimestamp = -1;
|
||||
private long bestowTimestamp = -1;
|
||||
private long transformedTimestamp = -1;
|
||||
private long transformedTimestamp = 0;
|
||||
private long prototypeTimestamp = -1;
|
||||
private long mutatedTimestamp = -1;
|
||||
private int timesMutated = 0;
|
||||
@@ -425,7 +424,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
public long getPrototypeTimestamp() { return prototypeTimestamp; }
|
||||
|
||||
public long getTransformedTimestamp() { return transformedTimestamp; }
|
||||
public void setTransformedTimestamp(long ts) { this.transformedTimestamp = ts; }
|
||||
public void incrementTransformedTimestamp() { this.transformedTimestamp++; }
|
||||
public void undoIncrementTransformedTimestamp() { this.transformedTimestamp--; }
|
||||
|
||||
// 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
|
||||
@@ -695,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);
|
||||
}
|
||||
setTransformedTimestamp(ts);
|
||||
incrementTransformedTimestamp();
|
||||
|
||||
return retResult;
|
||||
} else if (mode.equals("Flip")) {
|
||||
@@ -1069,7 +1069,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public final boolean isDoubleFaced() {
|
||||
return isTransformable() || isMeldable();
|
||||
return isTransformable() || isMeldable() || isModal();
|
||||
}
|
||||
|
||||
public final boolean isFlipCard() {
|
||||
@@ -1131,10 +1131,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public final boolean isTransformed() {
|
||||
if (isMeldable() || hasMergedCard()) {
|
||||
return false;
|
||||
}
|
||||
return this.isTransformable() && isBackSide();
|
||||
return getTransformedTimestamp() != 0;
|
||||
}
|
||||
|
||||
public final boolean isFlipped() {
|
||||
@@ -2265,7 +2262,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
public final ColorSet getMarkedColors() {
|
||||
if (markedColor == null) {
|
||||
return ColorSet.NO_COLORS;
|
||||
return ColorSet.getNullColor();
|
||||
}
|
||||
return markedColor;
|
||||
}
|
||||
@@ -2465,8 +2462,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
}
|
||||
sbLong.append("Enchant ").append(desc).append("\r\n");
|
||||
} else if (keyword.startsWith("Ripple")) {
|
||||
sbLong.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
|
||||
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
|
||||
|| keyword.startsWith("Disguise") || keyword.startsWith("Reflect")
|
||||
|| keyword.startsWith("Disguise")
|
||||
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
||||
|| keyword.startsWith("Madness:")|| keyword.startsWith("Recover")
|
||||
|| keyword.startsWith("Reconfigure") || keyword.startsWith("Squad")
|
||||
@@ -2505,15 +2504,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
sbLong.append(".");
|
||||
}
|
||||
if (k.length > 3) {
|
||||
sbLong.append(". ").append(k[3]);
|
||||
sbLong.append(". " + k[3]);
|
||||
}
|
||||
}
|
||||
sbLong.append(" (").append(inst.getReminderText()).append(")");
|
||||
sbLong.append("\r\n");
|
||||
} else if (keyword.equals("Mayhem")) {
|
||||
sbLong.append(" (").append(inst.getReminderText()).append(")");
|
||||
sbLong.append("\r\n");
|
||||
}
|
||||
} else if (keyword.startsWith("Reflect")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1]));
|
||||
sbLong.append(" (").append(inst.getReminderText()).append(")");
|
||||
sbLong.append("\r\n");
|
||||
} else if (keyword.startsWith("Echo")) {
|
||||
sbLong.append("Echo ");
|
||||
final String[] upkeepCostParams = keyword.split(":");
|
||||
@@ -2652,7 +2653,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing:")
|
||||
|| keyword.startsWith("Afterlife") || keyword.startsWith("Hideaway") || keyword.startsWith("Toxic")
|
||||
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|
||||
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Ripple")) {
|
||||
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[0]).append(" ").append(k[1]).append(" (").append(inst.getReminderText()).append(")");
|
||||
} else if (keyword.startsWith("Crew")) {
|
||||
@@ -2968,9 +2969,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
if (monstrous) {
|
||||
sb.append("Monstrous\r\n");
|
||||
}
|
||||
if (harnessed) {
|
||||
sb.append("Harnessed\r\n");
|
||||
}
|
||||
if (renowned) {
|
||||
sb.append("Renowned\r\n");
|
||||
}
|
||||
@@ -6695,14 +6693,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
setRingBearer(false);
|
||||
}
|
||||
|
||||
public final boolean isHarnessed() {
|
||||
return harnessed;
|
||||
}
|
||||
public final boolean setHarnessed(final boolean harnessed0) {
|
||||
harnessed = harnessed0;
|
||||
return true;
|
||||
}
|
||||
|
||||
public final boolean isMonstrous() {
|
||||
return monstrous;
|
||||
}
|
||||
@@ -6860,10 +6850,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return exiledSA.isKeyword(Keyword.WARP);
|
||||
}
|
||||
|
||||
public boolean isWebSlinged() {
|
||||
return getCastSA() != null & getCastSA().isAlternativeCost(AlternativeCost.WebSlinging);
|
||||
}
|
||||
|
||||
public boolean isSpecialized() {
|
||||
return specialized;
|
||||
}
|
||||
@@ -7159,7 +7145,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return false;
|
||||
}
|
||||
|
||||
if (StaticAbilityCantTarget.cantTarget(this, sa) != null) {
|
||||
if (StaticAbilityCantTarget.cantTarget(this, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -7640,6 +7626,9 @@ 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,7 +131,9 @@ public class CardCopyService {
|
||||
|
||||
c.setState(in.getCurrentStateName(), false);
|
||||
c.setRules(in.getRules());
|
||||
c.setBackSide(in.isBackSide());
|
||||
if (in.isTransformed()) {
|
||||
c.incrementTransformedTimestamp();
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
@@ -166,6 +168,9 @@ 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 {
|
||||
@@ -269,6 +274,9 @@ 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,16 +87,22 @@ 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);
|
||||
}
|
||||
@@ -370,28 +376,22 @@ public class CardFactory {
|
||||
}
|
||||
}
|
||||
|
||||
// Negative card Id's are for view purposes only
|
||||
// Build English oracle and translated oracle mapping
|
||||
if (c.getId() >= 0) {
|
||||
// Build English oracle and translated oracle mapping
|
||||
CardTranslation.buildOracleMapping(face.getName(), face.getOracleText(), variantName);
|
||||
}
|
||||
|
||||
// Set name for Sentry reports to be identifiable
|
||||
// Name first so Senty has the Card name
|
||||
c.setName(face.getName());
|
||||
|
||||
if (c.getId() >= 0) { // Set Triggers & Abilities if not for view
|
||||
for (Entry<String, String> v : face.getVariables())
|
||||
c.setSVar(v.getKey(), v.getValue());
|
||||
for (String r : face.getReplacements())
|
||||
c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true, c.getCurrentState()));
|
||||
for (String s : face.getStaticAbilities())
|
||||
c.addStaticAbility(s);
|
||||
for (String t : face.getTriggers())
|
||||
c.addTrigger(TriggerHandler.parseTrigger(t, c, true, c.getCurrentState()));
|
||||
for (Entry<String, String> v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
|
||||
|
||||
// keywords not before variables
|
||||
c.addIntrinsicKeywords(face.getKeywords(), false);
|
||||
}
|
||||
for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true, c.getCurrentState()));
|
||||
for (String s : face.getStaticAbilities()) c.addStaticAbility(s);
|
||||
for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true, c.getCurrentState()));
|
||||
|
||||
// keywords not before variables
|
||||
c.addIntrinsicKeywords(face.getKeywords(), false);
|
||||
if (face.getDraftActions() != null) {
|
||||
face.getDraftActions().forEach(c::addDraftAction);
|
||||
}
|
||||
@@ -420,8 +420,7 @@ public class CardFactory {
|
||||
|
||||
c.setAttractionLights(face.getAttractionLights());
|
||||
|
||||
if (c.getId() > 0) // Set FactoryAbilities if not for view
|
||||
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
|
||||
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
|
||||
}
|
||||
|
||||
public static void copySpellAbility(SpellAbility from, SpellAbility to, final Card host, final Player p, final boolean lki, final boolean keepTextChanges) {
|
||||
|
||||
@@ -2776,9 +2776,10 @@ public class CardFactoryUtil {
|
||||
final String cost = params[1];
|
||||
|
||||
final StringBuilder sbAttach = new StringBuilder();
|
||||
sbAttach.append("SP$ Attach | ValidTgts$ Creature | Cost$ ");
|
||||
sbAttach.append("SP$ Attach | Cost$ ");
|
||||
sbAttach.append(cost);
|
||||
sbAttach.append(" | AILogic$ ").append(params.length > 2 ? params[2] : "Pump");
|
||||
sbAttach.append(" | Bestow$ True | ValidTgts$ Creature");
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(sbAttach.toString(), card);
|
||||
final StringBuilder sbDesc = new StringBuilder();
|
||||
@@ -4112,7 +4113,7 @@ public class CardFactoryUtil {
|
||||
sbValid.append("| ").append(param).append(k[1]);
|
||||
}
|
||||
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True"
|
||||
String effect = "Mode$ CantTarget | ValidCard$ Card.Self | Secondary$ True"
|
||||
+ sbValid.toString() + " | Activator$ Opponent | Description$ "
|
||||
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
@@ -4153,7 +4154,7 @@ public class CardFactoryUtil {
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
|
||||
// Target
|
||||
effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True ";
|
||||
effect = "Mode$ CantTarget | Protection$ True | ValidCard$ Card.Self | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidSource$ " + valid;
|
||||
}
|
||||
@@ -4161,7 +4162,7 @@ public class CardFactoryUtil {
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
|
||||
// Attach
|
||||
effect = "Mode$ CantAttach | Target$ Card.Self | Secondary$ True ";
|
||||
effect = "Mode$ CantAttach | Protection$ True | Target$ Card.Self | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidCard$ " + valid;
|
||||
}
|
||||
@@ -4182,7 +4183,7 @@ public class CardFactoryUtil {
|
||||
" | Description$ Chapter abilities of this Saga can't trigger the turn it entered the battlefield unless it has exactly the number of lore counters on it specified in the chapter symbol of that ability.";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Shroud")) {
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True"
|
||||
String effect = "Mode$ CantTarget | ValidCard$ Card.Self | Secondary$ True"
|
||||
+ " | Description$ Shroud (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Skulk")) {
|
||||
|
||||
@@ -1244,8 +1244,7 @@ public class CardProperty {
|
||||
if (property.contains("ControlledBy")) {
|
||||
FCollectionView<Player> p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], spellAbility);
|
||||
cards = CardLists.filterControlledBy(cards, p);
|
||||
// Kraven the Hunter LTB trigger
|
||||
if (!card.isLKI() && !cards.contains(card)) {
|
||||
if (!cards.contains(card)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1820,10 +1819,6 @@ public class CardProperty {
|
||||
if (!card.isWarped()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("webSlinged")) {
|
||||
if (!card.isWebSlinged()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("CrewedThisTurn")) {
|
||||
if (!hasTimestampMatch(card, source.getCrewedByThisTurn())) return false;
|
||||
} else if (property.equals("CrewedBySourceThisTurn")) {
|
||||
@@ -1832,10 +1827,6 @@ public class CardProperty {
|
||||
if (card.getDevouredCards().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("harnessed")) {
|
||||
if (!card.isHarnessed()) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("IsMonstrous")) {
|
||||
if (!card.isMonstrous()) {
|
||||
return false;
|
||||
|
||||
@@ -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 8-13, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android, 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 8-13, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android, 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 8-13, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android, 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 8-13, use Collectors.toList()
|
||||
// stream().toList() causes crash on Android, use Collectors.toList()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
newCol.addAll(rightAbilities);
|
||||
@@ -468,9 +468,6 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
|
||||
return Iterables.getFirst(getIntrinsicSpellAbilities(), null);
|
||||
}
|
||||
public final SpellAbility getFirstSpellAbility() {
|
||||
if (this.card.getCastSA() != null) {
|
||||
return this.card.getCastSA();
|
||||
}
|
||||
return Iterables.getFirst(getNonManaAbilities(), null);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package forge.game.card;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
@@ -8,74 +7,45 @@ import forge.game.trigger.Trigger;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CardTraitChanges implements Cloneable {
|
||||
|
||||
private List<Trigger> triggers = Lists.newArrayList();
|
||||
private List<ReplacementEffect> replacements = Lists.newArrayList();
|
||||
private List<SpellAbility> abilities = Lists.newArrayList();
|
||||
private List<StaticAbility> staticAbilities = Lists.newArrayList();
|
||||
|
||||
private List<SpellAbility> removedAbilities = Lists.newArrayList();
|
||||
|
||||
private boolean removeAll = false;
|
||||
private boolean removeNonMana = false;
|
||||
|
||||
public CardTraitChanges(Collection<SpellAbility> spells, Collection<SpellAbility> removedAbilities,
|
||||
Collection<Trigger> trigger, Collection<ReplacementEffect> res, Collection<StaticAbility> st,
|
||||
boolean removeAll, boolean removeNonMana) {
|
||||
if (spells != null) {
|
||||
this.abilities.addAll(spells);
|
||||
}
|
||||
if (removedAbilities != null) {
|
||||
this.removedAbilities.addAll(removedAbilities);
|
||||
}
|
||||
if (trigger != null) {
|
||||
this.triggers.addAll(trigger);
|
||||
}
|
||||
if (res != null) {
|
||||
this.replacements.addAll(res);
|
||||
}
|
||||
if (st != null) {
|
||||
this.staticAbilities.addAll(st);
|
||||
}
|
||||
|
||||
this.removeAll |= removeAll;
|
||||
this.removeNonMana |= removeNonMana;
|
||||
}
|
||||
public record CardTraitChanges(Collection<SpellAbility> abilities, Collection<SpellAbility> removedAbilities,
|
||||
Collection<Trigger> triggers, Collection<ReplacementEffect> replacements, Collection<StaticAbility> staticAbilities,
|
||||
boolean removeAll, boolean removeNonMana) {
|
||||
|
||||
/**
|
||||
* @return the triggers
|
||||
*/
|
||||
public Collection<Trigger> getTriggers() {
|
||||
return triggers;
|
||||
return Objects.requireNonNullElse(triggers, List.of());
|
||||
}
|
||||
/**
|
||||
* @return the replacements
|
||||
*/
|
||||
public Collection<ReplacementEffect> getReplacements() {
|
||||
return replacements;
|
||||
return Objects.requireNonNullElse(replacements, List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the abilities
|
||||
*/
|
||||
public Collection<SpellAbility> getAbilities() {
|
||||
return abilities;
|
||||
return Objects.requireNonNullElse(abilities, List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the abilities
|
||||
*/
|
||||
public Collection<SpellAbility> getRemovedAbilities() {
|
||||
return removedAbilities;
|
||||
return Objects.requireNonNullElse(removedAbilities, List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the staticAbilities
|
||||
*/
|
||||
public Collection<StaticAbility> getStaticAbilities() {
|
||||
return staticAbilities;
|
||||
return Objects.requireNonNullElse(staticAbilities, List.of());
|
||||
}
|
||||
|
||||
public boolean isRemoveAll() {
|
||||
@@ -87,53 +57,30 @@ public class CardTraitChanges implements Cloneable {
|
||||
}
|
||||
|
||||
public CardTraitChanges copy(Card host, boolean lki) {
|
||||
try {
|
||||
CardTraitChanges result = (CardTraitChanges) super.clone();
|
||||
|
||||
result.abilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
result.abilities.add(sa.copy(host, lki));
|
||||
}
|
||||
result.removedAbilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : this.removedAbilities) {
|
||||
result.removedAbilities.add(sa.copy(host, lki));
|
||||
}
|
||||
|
||||
result.triggers = Lists.newArrayList();
|
||||
for (Trigger tr : this.triggers) {
|
||||
result.triggers.add(tr.copy(host, lki));
|
||||
}
|
||||
|
||||
result.replacements = Lists.newArrayList();
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
result.replacements.add(re.copy(host, lki));
|
||||
}
|
||||
|
||||
result.staticAbilities = Lists.newArrayList();
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
result.staticAbilities.add(sa.copy(host, lki));
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("CardTraitChanges : clone() error", ex);
|
||||
}
|
||||
return new CardTraitChanges(
|
||||
this.getAbilities().stream().map(sa -> sa.copy(host, lki)).collect(Collectors.toList()),
|
||||
this.getRemovedAbilities().stream().map(sa -> sa.copy(host, lki)).collect(Collectors.toList()),
|
||||
this.getTriggers().stream().map(tr -> tr.copy(host, lki)).collect(Collectors.toList()),
|
||||
this.getReplacements().stream().map(tr -> tr.copy(host, lki)).collect(Collectors.toList()),
|
||||
this.getStaticAbilities().stream().map(st -> st.copy(host, lki)).collect(Collectors.toList()),
|
||||
removeAll, removeNonMana
|
||||
);
|
||||
}
|
||||
|
||||
public void changeText() {
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
for (SpellAbility sa : this.getAbilities()) {
|
||||
sa.changeText();
|
||||
}
|
||||
|
||||
for (Trigger tr : this.triggers) {
|
||||
for (Trigger tr : this.getTriggers()) {
|
||||
tr.changeText();
|
||||
}
|
||||
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
for (ReplacementEffect re : this.getReplacements()) {
|
||||
re.changeText();
|
||||
}
|
||||
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
for (StaticAbility sa : this.getStaticAbilities()) {
|
||||
sa.changeText();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +60,13 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
|
||||
public static TrackableCollection<CardView> getCollection(Iterable<Card> cards) {
|
||||
if (cards == null) {
|
||||
return null;
|
||||
}
|
||||
TrackableCollection<CardView> collection = new TrackableCollection<>();
|
||||
if (cards != null) {
|
||||
for (Card c : cards) {
|
||||
if (c != null && c.getRenderForUI()) { //only add cards that match their card for UI
|
||||
collection.add(c.getView());
|
||||
}
|
||||
for (Card c : cards) {
|
||||
if (c.getRenderForUI()) { //only add cards that match their card for UI
|
||||
collection.add(c.getView());
|
||||
}
|
||||
}
|
||||
return collection;
|
||||
|
||||
@@ -167,8 +167,6 @@ public enum CounterEnumType implements CounterType {
|
||||
|
||||
FILIBUSTER("FLBTR", 255, 179, 119),
|
||||
|
||||
FILM("FILM", 255, 255, 255),
|
||||
|
||||
FINALITY("FINAL", 255, 255, 255),
|
||||
|
||||
FIRE("FIRE", 240, 30, 35),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game.combat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,12 +39,10 @@ public class AttackRequirement {
|
||||
|
||||
//MustAttack static check
|
||||
final List<GameEntity> mustAttack = StaticAbilityMustAttack.entitiesMustAttack(attacker);
|
||||
nAttackAnything += Collections.frequency(mustAttack, attacker);
|
||||
for (GameEntity e : mustAttack) {
|
||||
if (e.equals(attacker)) {
|
||||
nAttackAnything++;
|
||||
} else {
|
||||
defenderSpecific.add(e);
|
||||
}
|
||||
if (e.equals(attacker)) continue;
|
||||
defenderSpecific.add(e);
|
||||
}
|
||||
|
||||
for (final GameEntity defender : possibleDefenders) {
|
||||
|
||||
@@ -225,7 +225,7 @@ public class CombatUtil {
|
||||
if (!ge.equals(defender) && ge instanceof Player) {
|
||||
// found a player which does not goad that creature
|
||||
// and creature can attack this player or planeswalker
|
||||
if (!attacker.isGoadedBy((Player) ge) && canAttack(attacker, ge)) {
|
||||
if (!attacker.isGoadedBy((Player) ge) && !ge.hasKeyword("Creatures your opponents control attack a player other than you if able.") && canAttack(attacker, ge)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -233,6 +233,17 @@ public class CombatUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// Quasi-goad logic for "Kardur, Doomscourge" etc. that isn't goad but behaves the same
|
||||
if (defender != null && defender.hasKeyword("Creatures your opponents control attack a player other than you if able.")) {
|
||||
for (GameEntity ge : getAllPossibleDefenders(attacker.getController())) {
|
||||
if (!ge.equals(defender) && ge instanceof Player) {
|
||||
if (!ge.hasKeyword("Creatures your opponents control attack a player other than you if able.") && canAttack(attacker, ge)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CantAttack static abilities
|
||||
if (StaticAbilityCantAttackBlock.cantAttack(attacker, defender)) {
|
||||
return false;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package forge.game.keyword;
|
||||
|
||||
import forge.card.CardSplitType;
|
||||
import forge.StaticData;
|
||||
import forge.game.card.Card;
|
||||
import forge.item.PaperCard;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -119,7 +119,7 @@ public enum Keyword {
|
||||
LIVING_METAL("Living metal", SimpleKeyword.class, true, "During your turn, this Vehicle is also a creature."),
|
||||
LIVING_WEAPON("Living Weapon", SimpleKeyword.class, true, "When this Equipment enters, create a 0/0 black Phyrexian Germ creature token, then attach this to it."),
|
||||
MADNESS("Madness", KeywordWithCost.class, false, "If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard."),
|
||||
MAYHEM("Mayhem", Mayhem.class, false, "You may cast this card from your graveyard for %s if you discarded it this turn. Timing rules still apply."),
|
||||
MAYHEM("Mayhem", KeywordWithCost.class, false, "You may cast this card from your graveyard for %s if you discarded it this turn. Timing rules still apply."),
|
||||
MELEE("Melee", SimpleKeyword.class, false, "Whenever this creature attacks, it gets +1/+1 until end of turn for each opponent you attacked this combat."),
|
||||
MENTOR("Mentor", SimpleKeyword.class, false, "Whenever this creature attacks, put a +1/+1 counter on target attacking creature with lesser power."),
|
||||
MENACE("Menace", SimpleKeyword.class, true, "This creature can't be blocked except by two or more creatures."),
|
||||
@@ -223,7 +223,7 @@ public enum Keyword {
|
||||
displayName = displayName0;
|
||||
}
|
||||
|
||||
private static Pair<Keyword, String> getKeywordDetails(String k) {
|
||||
public static KeywordInterface getInstance(String k) {
|
||||
Keyword keyword = Keyword.UNDEFINED;
|
||||
String details = k;
|
||||
// try to get real part
|
||||
@@ -255,20 +255,15 @@ 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 = p.getKey().type.getConstructor().newInstance();
|
||||
inst = keyword.type.getConstructor().newInstance();
|
||||
}
|
||||
catch (Exception e) {
|
||||
inst = new UndefinedKeyword();
|
||||
}
|
||||
inst.initialize(k, p.getKey(), p.getValue());
|
||||
inst.initialize(k, keyword, details);
|
||||
return inst;
|
||||
}
|
||||
|
||||
@@ -283,44 +278,36 @@ 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 name = card.getName();
|
||||
Set<Keyword> keywordSet = cardKeywordSetLookup.get(name);
|
||||
String key = card.getName();
|
||||
Set<Keyword> keywordSet = cardKeywordSetLookup.get(key);
|
||||
if (keywordSet == null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
keywordSet = new HashSet<>();
|
||||
for (KeywordInterface inst : Card.getCardForUi(card).getKeywords()) {
|
||||
final Keyword keyword = inst.getKeyword();
|
||||
if (keyword != Keyword.UNDEFINED) {
|
||||
keywordSet.add(keyword);
|
||||
}
|
||||
}
|
||||
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);
|
||||
cardKeywordSetLookup.put(card.getName(), 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)) {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package forge.game.keyword;
|
||||
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.cost.Cost;
|
||||
|
||||
public class Mayhem extends KeywordWithCost {
|
||||
|
||||
|
||||
@Override
|
||||
protected void parse(String details) {
|
||||
if (!details.isEmpty()) {
|
||||
super.parse(details);
|
||||
} else {
|
||||
this.cost = new Cost(ManaCost.NO_COST, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String formatReminderText(String reminderText) {
|
||||
if (this.cost.getTotalMana().isNoCost()) {
|
||||
return "You may play this card from your graveyard if you discarded it this turn. Timing rules still apply.";
|
||||
}
|
||||
return super.formatReminderText(reminderText);
|
||||
}
|
||||
}
|
||||
@@ -62,9 +62,6 @@ 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;
|
||||
@@ -95,7 +92,6 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
|
||||
private final transient Game game;
|
||||
|
||||
|
||||
public PhaseHandler(final Game game0) {
|
||||
game = game0;
|
||||
}
|
||||
@@ -1007,7 +1003,12 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
|
||||
private final static boolean DEBUG_PHASES = false;
|
||||
|
||||
public void setupFirstTurn(Player goesFirst, Runnable startGameHook) {
|
||||
public void startFirstTurn(Player goesFirst) {
|
||||
startFirstTurn(goesFirst, null);
|
||||
}
|
||||
public void startFirstTurn(Player goesFirst, Runnable startGameHook) {
|
||||
StopWatch sw = new StopWatch();
|
||||
|
||||
if (phase != null) {
|
||||
throw new IllegalStateException("Turns already started, call this only once per game");
|
||||
}
|
||||
@@ -1023,146 +1024,132 @@ 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() && !(game.getAge() == GameStage.RestartedByKarn)) {
|
||||
mainLoopStep();
|
||||
}
|
||||
}
|
||||
|
||||
public void mainLoopStep() {
|
||||
if (givePriorityToPlayer) {
|
||||
if (DEBUG_PHASES) {
|
||||
sw.start();
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
while (!game.isGameOver()) {
|
||||
if (givePriorityToPlayer) {
|
||||
if (DEBUG_PHASES) {
|
||||
System.out.print("... " + pPlayerPriority + " plays " + chosenSa);
|
||||
sw.start();
|
||||
}
|
||||
|
||||
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());
|
||||
game.fireEvent(new GameEventPlayerPriority(playerTurn, phase, getPriorityPlayer()));
|
||||
List<SpellAbility> chosenSa = null;
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
if (chosenSa == null) {
|
||||
break; // that means 'I pass'
|
||||
}
|
||||
if (DEBUG_PHASES) {
|
||||
System.out.print("... " + pPlayerPriority + " plays " + chosenSa);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
boolean rollback = false;
|
||||
for (SpellAbility sa : chosenSa) {
|
||||
Card saHost = sa.getHostCard();
|
||||
final Zone originZone = saHost.getZone();
|
||||
final CardZoneTable triggerList = new CardZoneTable(game.getLastStateBattlefield(), game.getLastStateGraveyard());
|
||||
|
||||
if (pPlayerPriority.getController().playChosenSpellAbility(sa)) {
|
||||
// 117.3c If a player has priority when they cast a spell, activate an ability, [play a land]
|
||||
// that player receives priority afterward.
|
||||
pFirstPriority = pPlayerPriority; // all opponents have to pass before stack is allowed to resolve
|
||||
} else if (game.EXPERIMENTAL_RESTORE_SNAPSHOT) {
|
||||
rollback = true;
|
||||
}
|
||||
|
||||
saHost = game.getCardState(saHost);
|
||||
final Zone currentZone = saHost.getZone();
|
||||
|
||||
// Need to check if Zone did change
|
||||
if (currentZone != null && originZone != null && !currentZone.equals(originZone) && (sa.isSpell() || sa.isLandAbility())) {
|
||||
// currently there can be only one Spell put on the Stack at once, or Land Abilities be played
|
||||
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), saHost);
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
}
|
||||
// Don't copy last state if we're in the middle of rolling back a spell...
|
||||
if (!rollback) {
|
||||
game.copyLastState();
|
||||
}
|
||||
loopCount++;
|
||||
} while (loopCount < 999 || !pPlayerPriority.getController().isAI());
|
||||
|
||||
if (loopCount >= 999 && pPlayerPriority.getController().isAI()) {
|
||||
System.out.print("AI looped too much with: " + chosenSa);
|
||||
}
|
||||
|
||||
if (DEBUG_PHASES) {
|
||||
sw.stop();
|
||||
System.out.print("... passed in " + sw.getTime()/1000f + " s\n");
|
||||
System.out.println("\t\tStack: " + game.getStack());
|
||||
sw.reset();
|
||||
}
|
||||
}
|
||||
else if (DEBUG_PHASES) {
|
||||
System.out.print(" >> (no priority given to " + getPriorityPlayer() + ")\n");
|
||||
}
|
||||
|
||||
// actingPlayer is the player who may act
|
||||
// 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) {
|
||||
sw.stop();
|
||||
System.out.print("... passed in " + sw.getTime()/1000f + " s\n");
|
||||
System.out.println("\t\tStack: " + game.getStack());
|
||||
sw.reset();
|
||||
System.out.println(TextUtil.concatWithSpace(playerTurn.toString(),TextUtil.addSuffix(phase.toString(),":"), pPlayerPriority.toString(),"is active, previous was", nextPlayer.toString()));
|
||||
}
|
||||
}
|
||||
else if (DEBUG_PHASES) {
|
||||
System.out.print(" >> (no priority given to " + getPriorityPlayer() + ")\n");
|
||||
}
|
||||
if (pFirstPriority == nextPlayer) {
|
||||
if (game.getStack().isEmpty()) {
|
||||
if (playerTurn.hasLost()) {
|
||||
setPriority(game.getNextPlayerAfter(playerTurn));
|
||||
} else {
|
||||
setPriority(playerTurn);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// end phase
|
||||
givePriorityToPlayer = true;
|
||||
onPhaseEnd();
|
||||
advanceToNextPhase();
|
||||
onPhaseBegin();
|
||||
}
|
||||
|
||||
// end phase
|
||||
givePriorityToPlayer = true;
|
||||
onPhaseEnd();
|
||||
advanceToNextPhase();
|
||||
onPhaseBegin();
|
||||
else if (!game.getStack().hasSimultaneousStackEntries()) {
|
||||
game.getStack().resolveStack();
|
||||
}
|
||||
} else {
|
||||
// pass the priority to other player
|
||||
pPlayerPriority = nextPlayer;
|
||||
}
|
||||
else if (!game.getStack().hasSimultaneousStackEntries()) {
|
||||
game.getStack().resolveStack();
|
||||
|
||||
// If ever the karn's ultimate resolved
|
||||
if (game.getAge() == GameStage.RestartedByKarn) {
|
||||
setPhase(null);
|
||||
game.updatePhaseForView();
|
||||
game.fireEvent(new GameEventGameRestarted(playerTurn));
|
||||
return;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
// update Priority for all players
|
||||
for (final Player p : game.getPlayers()) {
|
||||
p.setHasPriority(getPriorityPlayer() == p);
|
||||
// update Priority for all players
|
||||
for (final Player p : game.getPlayers()) {
|
||||
p.setHasPriority(getPriorityPlayer() == p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
|
||||
import forge.game.spellability.AlternativeCost;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.*;
|
||||
import forge.game.trigger.Trigger;
|
||||
@@ -455,6 +454,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run any applicable replacement effects.
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
||||
repParams.put(AbilityKey.LifeGained, lifeGain);
|
||||
repParams.put(AbilityKey.SourceSA, sa);
|
||||
@@ -520,7 +520,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return 0;
|
||||
}
|
||||
int oldLife = life;
|
||||
|
||||
// Run applicable replacement effects
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
||||
repParams.put(AbilityKey.Amount, toLose);
|
||||
repParams.put(AbilityKey.IsDamage, damage);
|
||||
@@ -553,6 +553,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
boolean firstLost = lifeLostThisTurn == 0;
|
||||
|
||||
lifeLostThisTurn += toLose;
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
@@ -577,6 +578,9 @@ 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);
|
||||
@@ -596,6 +600,9 @@ 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;
|
||||
@@ -1078,7 +1085,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
// CantTarget static abilities
|
||||
if (StaticAbilityCantTarget.cantTarget(this, sa) != null) {
|
||||
if (StaticAbilityCantTarget.cantTarget(this, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1120,14 +1127,13 @@ 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 == 0);
|
||||
runParams.put(AbilityKey.FirstTime, surveilThisTurn == 1);
|
||||
if (params != null) {
|
||||
runParams.putAll(params);
|
||||
}
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.Surveil, runParams, false);
|
||||
|
||||
surveilThisTurn++;
|
||||
}
|
||||
|
||||
public int getSurveilThisTurn() {
|
||||
@@ -1712,8 +1718,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
final Zone zone = game.getZoneOf(land);
|
||||
if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !mayPlay
|
||||
&& (landSa == null || !landSa.isAlternativeCost(AlternativeCost.Mayhem))))) {
|
||||
if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !mayPlay))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1961,11 +1966,10 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
CardState speedFront = speedEffect.getState(CardStateName.Original);
|
||||
CardState speedBack = speedEffect.getState(CardStateName.Backside);
|
||||
|
||||
|
||||
speedFront.setImageKey(StaticData.instance().getOtherImageKey(ImageKeys.SPEED_IMAGE, CardEdition.UNKNOWN_CODE));
|
||||
speedFront.setImageKey("t:speed");
|
||||
speedFront.setName("Start Your Engines!");
|
||||
|
||||
speedBack.setImageKey(StaticData.instance().getOtherImageKey(ImageKeys.MAX_SPEED_IMAGE, CardEdition.UNKNOWN_CODE));
|
||||
speedBack.setImageKey("t:max_speed");
|
||||
speedBack.setName("Max Speed!");
|
||||
|
||||
String label = Localizer.getInstance().getMessage("lblSpeed", this.speed);
|
||||
|
||||
@@ -287,8 +287,6 @@ public abstract class PlayerController {
|
||||
public abstract void revealAnte(String message, Multimap<Player, PaperCard> removedAnteCards);
|
||||
public abstract void revealAISkipCards(String message, Map<Player, Map<DeckSection, List<? extends PaperCard>>> deckCards);
|
||||
|
||||
public abstract void revealUnsupported(Map<Player, List<PaperCard>> unsupported);
|
||||
|
||||
// These 2 are for AI
|
||||
public CardCollectionView cheatShuffle(CardCollectionView list) { return list; }
|
||||
public Map<DeckSection, List<? extends PaperCard>> complainCardsCantPlayWell(Deck myDeck) { return null; }
|
||||
|
||||
@@ -23,21 +23,21 @@ public class PlayerFactoryUtil {
|
||||
sbValid.append("| ValidSource$ ").append(k[1]);
|
||||
}
|
||||
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ Player.You | Secondary$ True "
|
||||
String effect = "Mode$ CantTarget | ValidPlayer$ Player.You | Secondary$ True "
|
||||
+ sbValid.toString() + " | Activator$ Opponent | EffectZone$ Command | Description$ "
|
||||
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
|
||||
|
||||
final Card card = player.getKeywordCard();
|
||||
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
|
||||
} else if (keyword.equals("Shroud")) {
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ Player.You | Secondary$ True "
|
||||
String effect = "Mode$ CantTarget | ValidPlayer$ Player.You | Secondary$ True "
|
||||
+ "| EffectZone$ Command | Description$ Shroud (" + inst.getReminderText() + ")";
|
||||
|
||||
final Card card = player.getKeywordCard();
|
||||
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
|
||||
} else if (keyword.startsWith("Protection")) {
|
||||
String valid = CardFactoryUtil.getProtectionValid(keyword, false);
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ Player.You | EffectZone$ Command | Secondary$ True ";
|
||||
String effect = "Mode$ CantTarget | Protection$ True | ValidPlayer$ Player.You | EffectZone$ Command | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidSource$ " + valid;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public class PlayerFactoryUtil {
|
||||
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
|
||||
|
||||
// Attach
|
||||
effect = "Mode$ CantAttach | Target$ Player.You | EffectZone$ Command | Secondary$ True ";
|
||||
effect = "Mode$ CantAttach | Protection$ True | Target$ Player.You | EffectZone$ Command | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidCard$ " + valid;
|
||||
}
|
||||
|
||||
@@ -280,8 +280,15 @@ public class ReplacementHandler {
|
||||
host = game.getCardState(host);
|
||||
}
|
||||
|
||||
// TODO: the source of replacement effect should be the source of the original effect
|
||||
effectSA = replacementEffect.ensureAbility();
|
||||
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();
|
||||
}
|
||||
|
||||
if (effectSA != null) {
|
||||
SpellAbility tailend = effectSA;
|
||||
do {
|
||||
|
||||
@@ -174,8 +174,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
private CardZoneTable changeZoneTable;
|
||||
private Map<Player, Integer> loseLifeMap;
|
||||
|
||||
private String name = "";
|
||||
|
||||
public CardCollection getLastStateBattlefield() {
|
||||
return lastStateBattlefield;
|
||||
}
|
||||
@@ -356,7 +354,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
public int totalAmountOfManaGenerated(SpellAbility saPaidFor, boolean multiply) {
|
||||
int result = 0;
|
||||
AbilityManaPart mp = getManaPart();
|
||||
if (mp != null && mp.meetsManaRestrictions(saPaidFor)) {
|
||||
if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor)) {
|
||||
result += amountOfManaGenerated(multiply);
|
||||
}
|
||||
result += subAbility != null ? subAbility.totalAmountOfManaGenerated(saPaidFor, multiply) : 0;
|
||||
@@ -673,10 +671,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
public final boolean isMadness() {
|
||||
return isAlternativeCost(AlternativeCost.Madness);
|
||||
}
|
||||
|
||||
public final boolean isMayhem() {
|
||||
return isAlternativeCost(AlternativeCost.Mayhem);
|
||||
}
|
||||
|
||||
public final boolean isMutate() {
|
||||
return isAlternativeCost(AlternativeCost.Mutate);
|
||||
@@ -1248,9 +1242,6 @@ 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) {
|
||||
@@ -1509,22 +1500,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
}
|
||||
}
|
||||
|
||||
if (tr.isDifferentCMC() && entity instanceof Card) {
|
||||
for (final Card c : getTargets().getTargetCards()) {
|
||||
if (entity != c && c.getCMC() == (((Card) entity).getCMC())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tr.isDifferentNames() && entity instanceof Card) {
|
||||
for (final Card c : getTargets().getTargetCards()) {
|
||||
if (entity != c && c.sharesNameWith(((Card) entity).getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tr.isSameController() && entity instanceof Card) {
|
||||
Player newController;
|
||||
newController = ((Card) entity).getController();
|
||||
@@ -2680,12 +2655,4 @@ 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,10 +55,10 @@ 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;
|
||||
private boolean differentNames = false;
|
||||
private boolean equalToughness = false;
|
||||
private boolean sameController = false;
|
||||
private boolean withoutSameCreatureType = false;
|
||||
@@ -99,6 +99,7 @@ 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();
|
||||
@@ -536,6 +537,12 @@ 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;
|
||||
}
|
||||
@@ -614,13 +621,6 @@ public class TargetRestrictions {
|
||||
this.differentCMC = different;
|
||||
}
|
||||
|
||||
public boolean isDifferentNames() {
|
||||
return differentNames;
|
||||
}
|
||||
public void setDifferentNames(boolean different) {
|
||||
this.differentNames = different;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the equalToughness
|
||||
*/
|
||||
|
||||
@@ -39,20 +39,36 @@ public class StaticAbilityCantTarget {
|
||||
|
||||
static String MODE = "CantTarget";
|
||||
|
||||
public static StaticAbility cantTarget(final GameEntity entity, final SpellAbility spellAbility) {
|
||||
final Game game = entity.getGame();
|
||||
public static boolean cantTarget(final Card card, final SpellAbility spellAbility) {
|
||||
final Game game = card.getGame();
|
||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (!stAb.checkConditions(StaticAbilityMode.CantTarget)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (applyCantTargetAbility(stAb, entity, spellAbility)) {
|
||||
return stAb;
|
||||
if (applyCantTargetAbility(stAb, card, spellAbility)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean cantTarget(final Player player, final SpellAbility spellAbility) {
|
||||
final Game game = player.getGame();
|
||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||
if (!stAb.checkConditions(StaticAbilityMode.CantTarget)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (applyCantTargetAbility(stAb, player, spellAbility)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,27 +82,57 @@ public class StaticAbilityCantTarget {
|
||||
* the spell/ability
|
||||
* @return true, if successful
|
||||
*/
|
||||
public static boolean applyCantTargetAbility(final StaticAbility stAb, final GameEntity entity, final SpellAbility spellAbility) {
|
||||
if (entity instanceof Card card) {
|
||||
if (stAb.hasParam("AffectedZone")) {
|
||||
if (ZoneType.listValueOf(stAb.getParam("AffectedZone")).stream().noneMatch(zt -> card.isInZone(zt))) {
|
||||
return false;
|
||||
}
|
||||
} else if (!card.isInPlay()) { // default zone is battlefield
|
||||
return false;
|
||||
}
|
||||
Set<ZoneType> zones = stAb.getActiveZone();
|
||||
|
||||
if (zones != null && zones.contains(ZoneType.Stack)) {
|
||||
// Enthralling Hold: only works if it wasn't already cast
|
||||
if (card.getGame().getStack().getSpellMatchingHost(spellAbility.getHostCard()) != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (stAb.hasParam("AffectedZone")) {
|
||||
public static boolean applyCantTargetAbility(final StaticAbility stAb, final Card card, final SpellAbility spellAbility) {
|
||||
if (stAb.hasParam("ValidPlayer")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stAb.hasParam("AffectedZone")) {
|
||||
boolean inZone = false;
|
||||
for (final ZoneType zt : ZoneType.listValueOf(stAb.getParam("AffectedZone"))) {
|
||||
if (card.isInZone(zt)) {
|
||||
inZone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inZone) {
|
||||
return false;
|
||||
}
|
||||
} else { // default zone is battlefield
|
||||
if (!card.isInPlay()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Set<ZoneType> zones = stAb.getActiveZone();
|
||||
|
||||
if (zones != null && zones.contains(ZoneType.Stack)) {
|
||||
// Enthralling Hold: only works if it wasn't already cast
|
||||
if (card.getGame().getStack().getSpellMatchingHost(spellAbility.getHostCard()) != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stAb.matchesValidParam("ValidCard", card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return common(stAb, card, spellAbility);
|
||||
}
|
||||
|
||||
public static boolean applyCantTargetAbility(final StaticAbility stAb, final Player player, final SpellAbility spellAbility) {
|
||||
if (stAb.hasParam("ValidCard") || stAb.hasParam("AffectedZone")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stAb.matchesValidParam("ValidPlayer", player)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return common(stAb, player, spellAbility);
|
||||
}
|
||||
|
||||
protected static boolean common(final StaticAbility stAb, GameEntity entity, final SpellAbility spellAbility) {
|
||||
final Card source = spellAbility.getHostCard();
|
||||
final Player activator = spellAbility.getActivatingPlayer();
|
||||
|
||||
@@ -94,10 +140,6 @@ public class StaticAbilityCantTarget {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stAb.matchesValidParam("ValidTarget", entity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stAb.matchesValidParam("ValidSA", spellAbility)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -392,10 +392,6 @@ 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) {
|
||||
@@ -446,15 +442,7 @@ 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,6 +22,7 @@ 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;
|
||||
|
||||
@@ -59,8 +60,17 @@ public class TriggerAttackerBlocked extends Trigger {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!matchesValidParam("ValidBlocker", runParams.get(AbilityKey.Blockers))) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -108,8 +108,9 @@ public class TriggerChangesZone extends Trigger {
|
||||
}
|
||||
}
|
||||
|
||||
Card moved = (Card) runParams.get(AbilityKey.Card);
|
||||
if (hasParam("ValidCard")) {
|
||||
Card moved = (Card) runParams.get(AbilityKey.Card);
|
||||
|
||||
// CR 603.10a leaves battlefield or GY look back in time
|
||||
if ("Battlefield".equals(getParam("Origin"))
|
||||
|| ("Graveyard".equals(getParam("Origin")) && !"Battlefield".equals(getParam("Destination")))) {
|
||||
@@ -146,7 +147,8 @@ public class TriggerChangesZone extends Trigger {
|
||||
final Card host = hostCard.getGame().getCardState(hostCard);
|
||||
final String comparator = condition.length < 2 ? "GE1" : condition[1];
|
||||
final int referenceValue = AbilityUtils.calculateAmount(host, comparator.substring(2), this);
|
||||
final int actualValue = AbilityUtils.calculateAmount(moved, condition[0], this);
|
||||
final Card triggered = (Card) runParams.get(AbilityKey.Card);
|
||||
final int actualValue = AbilityUtils.calculateAmount(triggered, condition[0], this);
|
||||
if (!Expressions.compare(actualValue, comparator.substring(0, 2), referenceValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -514,6 +514,11 @@ public class TriggerHandler {
|
||||
sa.setActivatingPlayer(p);
|
||||
}
|
||||
|
||||
if (regtrig.hasParam("RememberTriggeringCard")) {
|
||||
Card triggeredCard = ((Card) sa.getTriggeringObject(AbilityKey.Card));
|
||||
host.addRemembered(triggeredCard);
|
||||
}
|
||||
|
||||
if (!sa.getActivatingPlayer().isInGame()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -331,12 +331,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
}
|
||||
}
|
||||
|
||||
if (sp instanceof AbilityStatic || (sp.isTrigger() && sp.getTrigger().getOverridingAbility() instanceof AbilityStatic)) {
|
||||
AbilityUtils.resolve(sp);
|
||||
// AbilityStatic should do nothing below
|
||||
return;
|
||||
}
|
||||
|
||||
if (si == null && sp.isActivatedAbility() && !sp.isCopied()) {
|
||||
// if not already copied use a fresh instance
|
||||
SpellAbility original = sp;
|
||||
@@ -360,6 +354,12 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
addAbilityActivatedThisTurn(sp, source);
|
||||
}
|
||||
|
||||
if (sp instanceof AbilityStatic || (sp.isTrigger() && sp.getTrigger().getOverridingAbility() instanceof AbilityStatic)) {
|
||||
AbilityUtils.resolve(sp);
|
||||
// AbilityStatic should do nothing below
|
||||
return;
|
||||
}
|
||||
|
||||
// The ability is added to stack HERE
|
||||
si = push(sp, si, id);
|
||||
|
||||
|
||||
@@ -464,7 +464,7 @@ public class TrackableTypes {
|
||||
public static final TrackableType<ColorSet> ColorSetType = new TrackableType<ColorSet>() {
|
||||
@Override
|
||||
public ColorSet getDefaultValue() {
|
||||
return ColorSet.NO_COLORS;
|
||||
return ColorSet.getNullColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,770 +0,0 @@
|
||||
{
|
||||
"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,12 +67,10 @@ 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;
|
||||
|
||||
@@ -190,26 +188,7 @@ 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);
|
||||
@@ -218,8 +197,8 @@ public class Main extends AndroidApplication {
|
||||
device.setBrand(Build.BRAND);
|
||||
device.setManufacturer(Build.MANUFACTURER);
|
||||
device.setMemorySize(memInfo.totalMem);
|
||||
device.setCpuDescription(cpu);
|
||||
device.setChipset(soc);
|
||||
device.setCpuDescription(getCpuName());
|
||||
device.setChipset(Build.HARDWARE + " " + Build.BOARD);
|
||||
// OS Info
|
||||
OperatingSystem os = new OperatingSystem();
|
||||
os.setName("Android");
|
||||
@@ -227,7 +206,7 @@ public class Main extends AndroidApplication {
|
||||
os.setBuild(Build.DISPLAY);
|
||||
os.setRawDescription(getAndroidOSName());
|
||||
|
||||
initForge(Gadapter, new HWInfo(device, os, getChipset), permissiongranted, totalMemory, isTabletDevice(getContext()));
|
||||
initForge(Gadapter, new HWInfo(device, os), 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.MagicColor;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
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.NO_COLORS;
|
||||
this.cs = ColorSet.getNullColor();
|
||||
}
|
||||
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 MagicColor.Color s : cs.getOrderedColors()) {
|
||||
CardFaceSymbols.drawManaSymbol(s.getShortName(), g, x, y);
|
||||
for (final ManaCostShard s : cs.getOrderedShards()) {
|
||||
CardFaceSymbols.drawManaSymbol(s.getImageKey(), 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::hasBasicLands));
|
||||
IterableUtil.filter(StaticData.instance().getSortedEditions(), CardEdition.Predicates.hasBasicLands));
|
||||
|
||||
private final MainPanel panel = new MainPanel();
|
||||
private final LandPanel pnlPlains = new LandPanel("Plains");
|
||||
|
||||
@@ -46,6 +46,7 @@ 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.*;
|
||||
|
||||
@@ -522,7 +523,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
else
|
||||
deck.setName(currentDeckName);
|
||||
}
|
||||
host.getDeckController().loadDeck(deck, controller.getImportBehavior() != DeckImportController.ImportBehavior.MERGE);
|
||||
host.getDeckController().loadDeck(deck, controller.getCreateNewDeck());
|
||||
processWindowEvent(new WindowEvent(DeckImport.this, WindowEvent.WINDOW_CLOSING));
|
||||
});
|
||||
|
||||
@@ -530,7 +531,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
this.createNewDeckCheckbox.setSelected(false);
|
||||
this.createNewDeckCheckbox.addActionListener(e -> {
|
||||
boolean createNewDeck = createNewDeckCheckbox.isSelected();
|
||||
controller.setImportBehavior(createNewDeck ? DeckImportController.ImportBehavior.CREATE_NEW : DeckImportController.ImportBehavior.MERGE);
|
||||
controller.setCreateNewDeck(createNewDeck);
|
||||
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;
|
||||
@@ -599,7 +600,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,
|
||||
controller.getTokenStatusMessage(token));
|
||||
getTokenStatusMessage(token));
|
||||
statusLbl.append(statusMsg);
|
||||
}
|
||||
|
||||
@@ -739,12 +740,12 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
private String toHTML(final DeckRecognizer.Token token) {
|
||||
if (token == null)
|
||||
return "";
|
||||
String tokenMsg = controller.getTokenMessage(token);
|
||||
String tokenMsg = getTokenMessage(token);
|
||||
if (tokenMsg == null)
|
||||
return "";
|
||||
String tokenStatus = controller.getTokenStatusMessage(token);
|
||||
String tokenStatus = getTokenStatusMessage(token);
|
||||
String cssClass = getTokenCSSClass(token.getType());
|
||||
if (tokenStatus.isEmpty())
|
||||
if (tokenStatus.length() == 0)
|
||||
tokenMsg = padEndWithHTMLSpaces(tokenMsg, 2*PADDING_TOKEN_MSG_LENGTH+10);
|
||||
else {
|
||||
tokenMsg = padEndWithHTMLSpaces(tokenMsg, PADDING_TOKEN_MSG_LENGTH);
|
||||
@@ -754,6 +755,11 @@ 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;
|
||||
@@ -770,6 +776,97 @@ 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:
|
||||
@@ -802,6 +899,17 @@ 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> {
|
||||
|
||||
@@ -228,9 +228,9 @@ public enum CSubmenuDraft implements ICDoc {
|
||||
return;
|
||||
}
|
||||
|
||||
final DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName());
|
||||
if (VSubmenuDraft.SINGLETON_INSTANCE.isSingleSelected()) {
|
||||
// Single opponent
|
||||
final DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName());
|
||||
int indx = 0;
|
||||
for (@SuppressWarnings("unused") Deck d : opponentDecks.getAiDecks()) {
|
||||
indx++;
|
||||
@@ -242,16 +242,10 @@ public enum CSubmenuDraft implements ICDoc {
|
||||
combo.addItem("Gauntlet");
|
||||
//combo.addItem("Tournament");
|
||||
} else {
|
||||
int size = opponentDecks.getAiDecks().size();
|
||||
combo.addItem("2");
|
||||
if (size > 2) {
|
||||
combo.addItem("3");
|
||||
}
|
||||
|
||||
if (size >= 4) {
|
||||
combo.addItem("4");
|
||||
combo.addItem("5");
|
||||
}
|
||||
combo.addItem("3");
|
||||
combo.addItem("4");
|
||||
combo.addItem("5");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,6 @@ public enum VSubmenuSealed implements IVSubmenu<CSubmenuSealed> {
|
||||
grpPanel.add(radAll, "w 200px!, h 30px!");
|
||||
radSingle.setSelected(true);
|
||||
grpPanel.add(cbOpponent, "w 200px!, h 30px!");
|
||||
pnlStart.removeAll();
|
||||
pnlStart.setLayout(new MigLayout("insets 0, gap 0, wrap 2"));
|
||||
pnlStart.setOpaque(false);
|
||||
pnlStart.add(grpPanel, "gapright 20");
|
||||
|
||||
@@ -8,7 +8,6 @@ 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;
|
||||
@@ -205,9 +204,9 @@ public class CardFaceSymbols {
|
||||
}
|
||||
|
||||
public static void drawColorSet(Graphics g, ColorSet colorSet, int x, int y, int imageSize, boolean vertical) {
|
||||
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);
|
||||
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);
|
||||
if (!vertical)
|
||||
x += imageSize;
|
||||
else
|
||||
|
||||
@@ -130,10 +130,6 @@ public class SimulateMatch {
|
||||
}
|
||||
}
|
||||
|
||||
if (params.containsKey("c")) {
|
||||
rules.setSimTimeout(Integer.parseInt(params.get("c").get(0)));
|
||||
}
|
||||
|
||||
sb.append(" - ").append(Lang.nounWithNumeral(nGames, "game")).append(" of ").append(type);
|
||||
|
||||
System.out.println(sb.toString());
|
||||
@@ -167,7 +163,6 @@ public class SimulateMatch {
|
||||
System.out.println("\tT - Type of tournament to run with all provided decks (Bracket, RoundRobin, Swiss)");
|
||||
System.out.println("\tP - Amount of players per match (used only with Tournaments, defaults to 2)");
|
||||
System.out.println("\tF - format of games, defaults to constructed");
|
||||
System.out.println("\tc - Clock flag. Set the maximum time in seconds before calling the match a draw, defaults to 120.");
|
||||
System.out.println("\tq - Quiet flag. Output just the game result, not the entire game log.");
|
||||
}
|
||||
|
||||
@@ -181,7 +176,7 @@ public class SimulateMatch {
|
||||
TimeLimitedCodeBlock.runWithTimeout(() -> {
|
||||
mc.startGame(g1);
|
||||
sw.stop();
|
||||
}, mc.getRules().getSimTimeout(), TimeUnit.SECONDS);
|
||||
}, 120, TimeUnit.SECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
System.out.println("Stopping slow match as draw");
|
||||
} catch (Exception | StackOverflowError e) {
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
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,13 +1,15 @@
|
||||
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;
|
||||
@@ -16,12 +18,21 @@ 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 extends AITest {
|
||||
public class SimulationTest {
|
||||
private static boolean initialized = false;
|
||||
|
||||
public Game resetGame() {
|
||||
// need to be done after FModel.initialize, or the Localizer isn't loaded yet
|
||||
@@ -42,6 +53,19 @@ public class SimulationTest extends AITest {
|
||||
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)) {
|
||||
@@ -51,4 +75,106 @@ public class SimulationTest extends AITest {
|
||||
}
|
||||
}, 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,11 +664,6 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
// TODO test this!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
|
||||
// test this!
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaperCard> chooseCardsYouWonToAddToDeck(List<PaperCard> losses) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
@@ -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, false);
|
||||
hw = new HWInfo(device, os);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -101,7 +101,6 @@ 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";
|
||||
@@ -151,7 +150,7 @@ public class Forge implements ApplicationListener {
|
||||
scope.getContexts().setOperatingSystem(hwInfo.os());
|
||||
});
|
||||
}
|
||||
GuiBase.setDeviceInfo(hwInfo, AndroidAPI, totalRAM, deviceAdapter.getDownloadsDir());
|
||||
GuiBase.setDeviceInfo(hwInfo, AndroidAPI, totalRAM);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
@@ -172,7 +171,18 @@ public class Forge implements ApplicationListener {
|
||||
//install our error handler
|
||||
ExceptionHandler.registerErrorHandling();
|
||||
//init hwInfo to log
|
||||
System.out.println(GuiBase.getHWInfo());
|
||||
HWInfo info = GuiBase.getHWInfo();
|
||||
if (info != null) {
|
||||
System.out.println(
|
||||
"##########################################\n" +
|
||||
"APP: Forge v." + GuiBase.getInterface().getCurrentVersion() +
|
||||
"\nDEV: " + info.device().getName() +
|
||||
"\nCPU: " + info.device().getCpuDescription() +
|
||||
"\nRAM: " + GuiBase.getDeviceRAM() + " MB" +
|
||||
"\nOS: " + info.os().getRawDescription() +
|
||||
"\n##########################################"
|
||||
);
|
||||
}
|
||||
// closeSplashScreen() is called early on non-Windows OS so it will not crash, LWJGL3 bug on AWT Splash.
|
||||
if (OperatingSystem.isWindows())
|
||||
getDeviceAdapter().closeSplashScreen();
|
||||
@@ -223,7 +233,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);
|
||||
setAltZoneTabMode(getForgePreferences().getPref(FPref.UI_ALT_PLAYERZONETABS));
|
||||
altZoneTabs = getForgePreferences().getPrefBoolean(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
|
||||
@@ -261,17 +271,6 @@ 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) {
|
||||
@@ -349,11 +348,8 @@ public class Forge implements ApplicationListener {
|
||||
GuiBase.setIsAdventureMode(true);
|
||||
advStartup = false;
|
||||
isMobileAdventureMode = 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");
|
||||
}
|
||||
if (GuiBase.isAndroid()) //force it for adventure mode
|
||||
altZoneTabs = true;
|
||||
//pixl cursor for adventure
|
||||
setCursor(null, "0");
|
||||
if (!GuiBase.isAndroid() || !getDeviceAdapter().getGamepads().isEmpty())
|
||||
@@ -770,7 +766,7 @@ public class Forge implements ApplicationListener {
|
||||
isMobileAdventureMode = false;
|
||||
GuiBase.setIsAdventureMode(false);
|
||||
setCursor(FSkin.getCursor().get(0), "0");
|
||||
setAltZoneTabMode(FModel.getPreferences().getPref(FPref.UI_ALT_PLAYERZONETABS));
|
||||
altZoneTabs = FModel.getPreferences().getPrefBoolean(FPref.UI_ALT_PLAYERZONETABS);
|
||||
Gdx.input.setInputProcessor(getInputProcessor());
|
||||
clearTransitionScreen();
|
||||
openHomeDefault();
|
||||
@@ -1483,7 +1479,7 @@ public class Forge implements ApplicationListener {
|
||||
|
||||
boolean handled;
|
||||
if (KeyInputAdapter.isShiftKeyDown()) {
|
||||
handled = pan(mouseMovedX, mouseMovedY, -Utils.AVG_FINGER_WIDTH * amountY, 0, false);
|
||||
handled = pan(mouseMovedX, mouseMovedY, -Utils.AVG_FINGER_WIDTH * amountX, 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, false);
|
||||
stage.exitDungeon(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ public class PortalActor extends EntryActor {
|
||||
}
|
||||
if (currentAnimationType == PortalAnimationTypes.Active) {
|
||||
if (targetMap == null || targetMap.isEmpty()) {
|
||||
stage.exitDungeon(false, false);
|
||||
stage.exitDungeon(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, 8);
|
||||
draft = BoosterDraft.createDraft(LimitedPoolType.Block, getCardBlock(), packConfiguration);
|
||||
registeredDeck = draft.getHumanPlayer().getDeck();
|
||||
assignPlayerNames(draft);
|
||||
}
|
||||
@@ -607,18 +607,15 @@ 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";
|
||||
|
||||
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("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,25 +1077,6 @@ 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);
|
||||
@@ -1438,7 +1419,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
|
||||
*/
|
||||
public int copyDeck() {
|
||||
for (int i = 0; i < MAX_DECK_COUNT; i++) {
|
||||
if (i >= getDeckCount()) addDeck();
|
||||
if (isEmptyDeck(i)) {
|
||||
decks.set(i, (Deck) deck.copyTo(deck.getName() + " (" + Forge.getLocalizer().getMessage("lblCopy") + ")"));
|
||||
return i;
|
||||
|
||||
@@ -27,6 +27,8 @@ 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;
|
||||
@@ -39,7 +41,6 @@ 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 {
|
||||
@@ -145,8 +146,7 @@ public class AdventureDeckEditor extends FDeckEditor {
|
||||
if(event.cardBlock != null) {
|
||||
if(event.cardBlock.getLandSet() != null)
|
||||
return List.of(event.cardBlock.getLandSet());
|
||||
List<CardEdition> eventSets = new ArrayList<>(event.cardBlock.getSets());
|
||||
eventSets.removeIf(Predicate.not(CardEdition::hasBasicLands));
|
||||
List<CardEdition> eventSets = event.cardBlock.getSets();
|
||||
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 (allowAddBasic()) {
|
||||
if (allowsAddBasic()) {
|
||||
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,6 +713,27 @@ 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())
|
||||
@@ -744,12 +765,6 @@ 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();
|
||||
@@ -760,9 +775,7 @@ public class AdventureDeckEditor extends FDeckEditor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean allowAddBasic() {
|
||||
if(getEditorConfig() instanceof DeckPreviewConfig)
|
||||
return false;
|
||||
protected boolean allowsAddBasic() {
|
||||
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, boolean isArena) {
|
||||
public void setWinner(boolean winner) {
|
||||
enable = false;
|
||||
Array<ArenaRecord> winners = new Array<>();
|
||||
Array<EnemySprite> winnersEnemies = new Array<>();
|
||||
|
||||
@@ -83,7 +83,7 @@ public class DeckSelectScene extends UIScene {
|
||||
|
||||
private void layoutDeckButtons() {
|
||||
for (int i = 0; i < AdventurePlayer.current().getDeckCount(); i++)
|
||||
addDeckButton(i);
|
||||
addDeckButton(Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1), i);
|
||||
}
|
||||
|
||||
private void addDeck(){
|
||||
@@ -144,7 +144,6 @@ public class DeckSelectScene extends UIScene {
|
||||
}
|
||||
|
||||
private void updateDeckButton(int index) {
|
||||
if (!buttons.containsKey(index)) addDeckButton(index);
|
||||
buttons.get(index).setText(Current.player().getDeck(index).getName());
|
||||
buttons.get(index).getTextraLabel().layout();
|
||||
buttons.get(index).layout();
|
||||
@@ -178,9 +177,8 @@ public class DeckSelectScene extends UIScene {
|
||||
}
|
||||
}
|
||||
|
||||
private TextraButton addDeckButton(int i) {
|
||||
private TextraButton addDeckButton(String name, int i) {
|
||||
TextraButton button = Controls.newTextButton("-");
|
||||
String name = Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1);
|
||||
button.addListener(new ClickListener() {
|
||||
@Override
|
||||
public void clicked(InputEvent event, float x, float y) {
|
||||
|
||||
@@ -142,7 +142,7 @@ public class DuelScene extends ForgeScene {
|
||||
Current.player().getStatistic().setResult(enemyName, winner);
|
||||
|
||||
if (last instanceof IAfterMatch) {
|
||||
((IAfterMatch) last).setWinner(winner, isArena);
|
||||
((IAfterMatch) last).setWinner(winner);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -192,21 +192,18 @@ public class DuelScene extends ForgeScene {
|
||||
@Override
|
||||
public void enter() {
|
||||
GameHUD.getInstance().unloadAudio();
|
||||
GameType mainGameType;
|
||||
boolean isDeckMissing = false;
|
||||
String isDeckMissingMsg = "";
|
||||
Set<GameType> appliedVariants = new HashSet<>();
|
||||
if (eventData != null && eventData.eventRules != null) {
|
||||
mainGameType = eventData.eventRules.gameType;
|
||||
appliedVariants.add(eventData.eventRules.gameType);
|
||||
} else {
|
||||
mainGameType = GameType.Adventure;
|
||||
appliedVariants.add(GameType.Adventure);
|
||||
}
|
||||
Set<GameType> appliedVariants = EnumSet.of(mainGameType);
|
||||
|
||||
AdventurePlayer advPlayer = Current.player();
|
||||
|
||||
List<RegisteredPlayer> players = new ArrayList<>();
|
||||
|
||||
applyAdventureDeckRules(mainGameType.getDeckFormat());
|
||||
applyAdventureDeckRules();
|
||||
int playerCount = 1;
|
||||
EnemyData currentEnemy = enemy.getData();
|
||||
for (int i = 0; i < 8 && currentEnemy != null; i++) {
|
||||
@@ -297,12 +294,6 @@ 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));
|
||||
@@ -376,9 +367,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 || isDeckMissing) {
|
||||
if (chaosBattle || showMessages) {
|
||||
final FBufferedImage fb = getFBEnemyAvatar();
|
||||
bossDialogue = createFOption(isDeckMissing ? isDeckMissingMsg : Forge.getLocalizer().getMessage("AdvBossIntro" + Aggregates.randomInt(1, 35)),
|
||||
bossDialogue = createFOption(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);
|
||||
@@ -402,25 +393,16 @@ public class DuelScene extends ForgeScene {
|
||||
private static final String PLACEHOLDER_ATTRACTION = "Coin-Operated Pony";
|
||||
private static final String PLACEHOLDER_CONTRAPTION = "Automatic Fidget Spinner";
|
||||
|
||||
private void applyAdventureDeckRules(DeckFormat format) {
|
||||
private void applyAdventureDeckRules() {
|
||||
//Can't just keep the player from entering a battle if their deck is invalid. So instead we'll just edit their deck.
|
||||
CardPool mainSection = playerDeck.getMain(), attractions = playerDeck.get(DeckSection.Attractions), contraptions = playerDeck.get(DeckSection.Contraptions);
|
||||
DeckFormat format = DeckFormat.Adventure;
|
||||
|
||||
removeExcessCopies(mainSection, format);
|
||||
removeExcessCopies(attractions, format);
|
||||
removeExcessCopies(contraptions, format);
|
||||
|
||||
int mainSize = mainSection.countAll();
|
||||
|
||||
int maxDeckSize = format == DeckFormat.Adventure ? Integer.MAX_VALUE : format.getMainRange().getMaximum();
|
||||
int excessCards = mainSize - maxDeckSize;
|
||||
if (excessCards > 0) {
|
||||
List<PaperCard> removals = Aggregates.random(mainSection.toFlatList(), excessCards);
|
||||
mainSection.removeAllFlat(removals);
|
||||
}
|
||||
|
||||
int minDeckSize = format == DeckFormat.Adventure ? Config.instance().getConfigData().minDeckSize : format.getMainRange().getMinimum();
|
||||
int missingCards = minDeckSize - mainSize;
|
||||
int missingCards = Config.instance().getConfigData().minDeckSize - mainSection.countAll();
|
||||
if (missingCards > 0) //Replace unknown cards for a Wastes.
|
||||
mainSection.add(PLACEHOLDER_MAIN, missingCards);
|
||||
|
||||
|
||||
@@ -541,7 +541,7 @@ public class EventScene extends MenuScene implements IAfterMatch {
|
||||
|
||||
AdventureEventData.AdventureEventMatch humanMatch = null;
|
||||
|
||||
public void setWinner(boolean winner, boolean isArena) {
|
||||
public void setWinner(boolean winner) {
|
||||
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, boolean isArena) {
|
||||
stage.setWinner(winner, isArena);
|
||||
public void setWinner(boolean winner) {
|
||||
stage.setWinner(winner);
|
||||
}
|
||||
|
||||
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, false);
|
||||
MapStage.getInstance().exitDungeon(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, false);
|
||||
MapStage.getInstance().exitDungeon(false);
|
||||
setDisabled(exitToWorldMapActor, true, "[%120][+ExitToWorldMap]", "\u2613");
|
||||
setDisabled(bookmarkActor, true, "[%120][+Bookmark]", "\u2613");
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user