mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 03:38:01 +00:00
Compare commits
136 Commits
cardTraitC
...
migrate-sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bf567a966 | ||
|
|
a0be5e25cb | ||
|
|
f2e3cdc111 | ||
|
|
7384aada40 | ||
|
|
1a8a4f63ae | ||
|
|
530d1efcd8 | ||
|
|
d8b1d76f42 | ||
|
|
830b145e88 | ||
|
|
51ed06d60a | ||
|
|
ac9b73935f | ||
|
|
fe2f49fbcc | ||
|
|
3d31494a1b | ||
|
|
ff42e730a2 | ||
|
|
a66fb8791c | ||
|
|
2ad5d20e83 | ||
|
|
4790394698 | ||
|
|
8b1c427809 | ||
|
|
c0b667c373 | ||
|
|
ca61627f5b | ||
|
|
c3e2a5b5ea | ||
|
|
0d26b499d3 | ||
|
|
bbbc5e0ee6 | ||
|
|
714d9442f1 | ||
|
|
d199765e6d | ||
|
|
e5156d8999 | ||
|
|
c0d5397541 | ||
|
|
444897d0f9 | ||
|
|
f2cb7956d3 | ||
|
|
e0c2a49c6b | ||
|
|
d64e5ebc13 | ||
|
|
9bea1bc717 | ||
|
|
bdf9573467 | ||
|
|
042eb4bf79 | ||
|
|
4b0337a836 | ||
|
|
9284775921 | ||
|
|
1671a3298e | ||
|
|
64e3193a80 | ||
|
|
3e6cd92f14 | ||
|
|
b7d4e0129e | ||
|
|
f76127dcea | ||
|
|
c7c0938936 | ||
|
|
9788a815a7 | ||
|
|
cce25a37c4 | ||
|
|
85812d819f | ||
|
|
a974352908 | ||
|
|
45e04cfef7 | ||
|
|
6a83cb89f7 | ||
|
|
494bd75572 | ||
|
|
b065e468b0 | ||
|
|
86aff59e40 | ||
|
|
748c4e1dd7 | ||
|
|
dfb776f526 | ||
|
|
ca5d2bb109 | ||
|
|
b42050a40f | ||
|
|
14220ae37f | ||
|
|
46900c3f49 | ||
|
|
cb2bf996d0 | ||
|
|
edbf6692f2 | ||
|
|
b5f16b83a0 | ||
|
|
ee4902a5ae | ||
|
|
0835817d8a | ||
|
|
15d4e1ac20 | ||
|
|
ecdce50f12 | ||
|
|
247fd7810e | ||
|
|
055cd40fb1 | ||
|
|
f1d448509e | ||
|
|
f2581855a4 | ||
|
|
1e4c7d4b00 | ||
|
|
2d80e44f70 | ||
|
|
a207bdbb51 | ||
|
|
c09ec2f410 | ||
|
|
c579df4c82 | ||
|
|
d39ab505df | ||
|
|
7756dfa5be | ||
|
|
c1b6f3126e | ||
|
|
11d45e0fa3 | ||
|
|
0504b41dbd | ||
|
|
b670cc3736 | ||
|
|
885300ba49 | ||
|
|
ff8fd51bf1 | ||
|
|
80475c6b62 | ||
|
|
88ea36523b | ||
|
|
3386d79cb1 | ||
|
|
f97429d1d4 | ||
|
|
3628d4e22a | ||
|
|
fffc9a4282 | ||
|
|
7cb32b0b25 | ||
|
|
cb13682737 | ||
|
|
9656a9fb4c | ||
|
|
2fd9d3ae14 | ||
|
|
1ea90f6655 | ||
|
|
37342910fa | ||
|
|
3a00727602 | ||
|
|
589d592997 | ||
|
|
83101f7f41 | ||
|
|
c7d24f1c01 | ||
|
|
2a678aa7e9 | ||
|
|
6a758ffc0a | ||
|
|
83421bac56 | ||
|
|
5234365cc1 | ||
|
|
76558a841c | ||
|
|
1338735000 | ||
|
|
44c33e2955 | ||
|
|
b8a5668db6 | ||
|
|
6a84a7a6f0 | ||
|
|
4811c9d726 | ||
|
|
7cea6fce1e | ||
|
|
46ba289329 | ||
|
|
2c9e5ee814 | ||
|
|
230eba687d | ||
|
|
5faf317aaf | ||
|
|
388448c94f | ||
|
|
230282b525 | ||
|
|
0f92bc18d4 | ||
|
|
dc4cee62dc | ||
|
|
ea4c8fbc72 | ||
|
|
068ea955d9 | ||
|
|
f898263bac | ||
|
|
a3147f5e26 | ||
|
|
a309eaee02 | ||
|
|
94b058ec07 | ||
|
|
46bf3348be | ||
|
|
7c4c820e92 | ||
|
|
ed3ea99e1a | ||
|
|
ec21a0c789 | ||
|
|
bfe0a78765 | ||
|
|
0abc224e1b | ||
|
|
fa3ce1c7f7 | ||
|
|
48a5046d93 | ||
|
|
9f9e68f34d | ||
|
|
d7c7aaa51e | ||
|
|
ffe883e7d9 | ||
|
|
b07a9944aa | ||
|
|
997d489a7e | ||
|
|
d181746679 | ||
|
|
693d6cd9e3 |
@@ -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);
|
||||
GuiBase.setDeviceInfo(null, 0, 0, System.getProperty("user.home") + "/Downloads/");
|
||||
new EditorMainWindow(Config.instance());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,17 +974,13 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
int pBonus = 0;
|
||||
if (ability.getApi() == ApiType.Pump) {
|
||||
if (!ability.hasParam("NumAtt")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||
if (pBonus > 0) {
|
||||
power += pBonus;
|
||||
}
|
||||
}
|
||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||
continue;
|
||||
@@ -998,12 +994,11 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
if (pBonus > 0) {
|
||||
power += pBonus;
|
||||
}
|
||||
}
|
||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
}
|
||||
|
||||
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
power += pBonus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,17 +1102,13 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
int tBonus = 0;
|
||||
if (ability.getApi() == ApiType.Pump) {
|
||||
if (!ability.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||
if (tBonus > 0) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
}
|
||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||
continue;
|
||||
@@ -1131,12 +1122,11 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
if (tBonus > 0) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
}
|
||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
}
|
||||
|
||||
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
}
|
||||
return toughness;
|
||||
@@ -1305,6 +1295,7 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
int pBonus = 0;
|
||||
if (ability.getApi() == ApiType.Pump) {
|
||||
if (!ability.hasParam("NumAtt")) {
|
||||
continue;
|
||||
@@ -1314,11 +1305,8 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!ability.getPayCosts().hasTapCost()) {
|
||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
|
||||
}
|
||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||
@@ -1333,13 +1321,14 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 (!ability.getPayCosts().hasTapCost()) {
|
||||
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
}
|
||||
}
|
||||
|
||||
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||
power += pBonus;
|
||||
}
|
||||
}
|
||||
return power;
|
||||
}
|
||||
@@ -1530,16 +1519,14 @@ 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;
|
||||
}
|
||||
|
||||
toughness += AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
||||
tBonus = 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;
|
||||
@@ -1553,10 +1540,11 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
if (tBonus > 0) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
|
||||
}
|
||||
|
||||
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
}
|
||||
return toughness;
|
||||
|
||||
@@ -291,6 +291,12 @@ 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"))
|
||||
@@ -443,7 +449,6 @@ 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()) {
|
||||
@@ -735,7 +740,8 @@ public class ComputerUtilMana {
|
||||
|
||||
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
|
||||
if (sa.getTargets() != null && sa.getTargets().contains(saPayment.getHostCard())) {
|
||||
saExcludeList.add(saPayment); // not a good idea to sac a card that you're targeting with the SA you're paying for
|
||||
// not a good idea to sac a card that you're targeting with the SA you're paying for
|
||||
saExcludeList.add(saPayment);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1496,7 +1502,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;
|
||||
}
|
||||
@@ -1587,10 +1593,8 @@ public class ComputerUtilMana {
|
||||
|
||||
// don't use abilities with dangerous drawbacks
|
||||
AbilitySub sub = m.getSubAbility();
|
||||
if (sub != null) {
|
||||
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
||||
continue;
|
||||
}
|
||||
if (sub != null && !SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
|
||||
|
||||
@@ -1347,6 +1347,11 @@ 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
|
||||
|
||||
@@ -442,11 +442,9 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
sa.addDividedAllocation(c, amount);
|
||||
return decision;
|
||||
} else {
|
||||
if (!hasSacCost) {
|
||||
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||
return decision;
|
||||
}
|
||||
} else if (!hasSacCost) {
|
||||
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
|
||||
return decision;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -609,6 +607,20 @@ 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.
|
||||
|
||||
@@ -670,14 +682,12 @@ public class CountersPutAi extends CountersAi {
|
||||
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
CardCollection list = null;
|
||||
|
||||
CardCollection list;
|
||||
if (sa.isCurse()) {
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (list.isEmpty() && isMandatoryTrigger) {
|
||||
@@ -693,9 +703,8 @@ public class CountersPutAi extends CountersAi {
|
||||
|| sa.getTargets().isEmpty()) {
|
||||
sa.resetTargets();
|
||||
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (sa.isCurse()) {
|
||||
@@ -738,8 +747,6 @@ public class CountersPutAi extends CountersAi {
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
final Card source = sa.getHostCard();
|
||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
boolean preferred = true;
|
||||
CardCollection list;
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final boolean divided = sa.isDividedAsYouChoose();
|
||||
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
@@ -761,11 +768,11 @@ public class CountersPutAi extends CountersAi {
|
||||
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
|
||||
if (decision.willingToPlay()) {
|
||||
return decision;
|
||||
} else if (mandatory) {
|
||||
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||
} else {
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
if (mandatory) {
|
||||
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
|
||||
}
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
@@ -816,19 +823,19 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.isCurse()) {
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||
}
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||
|
||||
int totalTargets = list.size();
|
||||
|
||||
sa.resetTargets();
|
||||
|
||||
Iterable<Card> filteredField;
|
||||
if (sa.isCurse()) {
|
||||
filteredField = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
filteredField = ai.getCardsIn(ZoneType.Battlefield);
|
||||
}
|
||||
CardCollection list = CardLists.getTargetableCards(filteredField, sa);
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||
int totalTargets = list.size();
|
||||
boolean preferred = true;
|
||||
|
||||
while (sa.canAddMoreTarget()) {
|
||||
if (mandatory) {
|
||||
// When things are mandatory, gotta handle a little differently
|
||||
@@ -865,27 +872,21 @@ public class CountersPutAi extends CountersAi {
|
||||
if (choice == null && mandatory) {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
} else if (type.equals("M1M1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
if (type.equals("M1M1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
} else if (preferred) {
|
||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||
choice = chooseBoonTarget(list, type);
|
||||
if (choice == null && mandatory) {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
} else if (type.equals("P1P1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
if (preferred) {
|
||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||
choice = chooseBoonTarget(list, type);
|
||||
if (choice == null && mandatory) {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
} else {
|
||||
if (type.equals("P1P1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
}
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
if (choice != null && divided) {
|
||||
int alloc = Math.max(amount / totalTargets, 1);
|
||||
@@ -1080,8 +1081,7 @@ public class CountersPutAi extends CountersAi {
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
GameEntity e = (GameEntity) params.get("Target");
|
||||
// for Card try to select not useless counter
|
||||
if (e instanceof Card) {
|
||||
Card c = (Card) e;
|
||||
if (e instanceof Card c) {
|
||||
if (c.getController().isOpponentOf(ai)) {
|
||||
if (options.contains(CounterEnumType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterEnumType.M1M1;
|
||||
@@ -1098,8 +1098,7 @@ public class CountersPutAi extends CountersAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (e instanceof Player) {
|
||||
Player p = (Player) e;
|
||||
} else if (e instanceof Player p) {
|
||||
if (p.isOpponentOf(ai)) {
|
||||
if (options.contains(CounterEnumType.POISON)) {
|
||||
return CounterEnumType.POISON;
|
||||
@@ -1233,9 +1232,8 @@ public class CountersPutAi extends CountersAi {
|
||||
if (numCtrs < optimalCMC) {
|
||||
// If the AI has less counters than the optimal CMC, it should play the ability.
|
||||
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
|
||||
} else {
|
||||
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
|
||||
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +31,8 @@ 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";
|
||||
|
||||
@@ -45,8 +45,6 @@ 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);
|
||||
@@ -303,7 +301,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
|
||||
// create faces list from rules
|
||||
for (final CardRules rule : rules.values()) {
|
||||
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
|
||||
if (filteredCards.contains(rule.getName()))
|
||||
continue;
|
||||
for (ICardFace face : rule.getAllFaces()) {
|
||||
addFaceToDbNames(face);
|
||||
@@ -501,8 +499,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
public void addCard(PaperCard paperCard) {
|
||||
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
|
||||
if (filtered.contains(paperCard.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
allCardsByName.put(paperCard.getName(), paperCard);
|
||||
|
||||
@@ -523,17 +522,6 @@ 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,6 +52,14 @@ 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,
|
||||
@@ -275,18 +283,22 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
// Booster/draft info
|
||||
private List<BoosterSlot> boosterSlots = null;
|
||||
private boolean smallSetOverride = false;
|
||||
private boolean foilAlwaysInCommonSlot = false;
|
||||
private String additionalUnlockSet = "";
|
||||
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
||||
|
||||
// Replace all of these things with booster slots
|
||||
private boolean foilAlwaysInCommonSlot = false;
|
||||
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 = "";
|
||||
private String doublePickDuringDraft = "";
|
||||
|
||||
// Draft options
|
||||
private DraftOptions draftOptions = null;
|
||||
private String[] chaosDraftThemes = new String[0];
|
||||
|
||||
private final ListMultimap<String, EditionEntry> cardMap;
|
||||
@@ -373,7 +385,6 @@ 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; }
|
||||
@@ -619,7 +630,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(
|
||||
@@ -628,7 +639,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
||||
* name - grouping #3
|
||||
* artist name - grouping #5
|
||||
*/
|
||||
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
|
||||
"(^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]*)( @(.*))?$"
|
||||
);
|
||||
|
||||
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
|
||||
@@ -808,7 +819,6 @@ 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
|
||||
@@ -816,6 +826,23 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
75
forge-core/src/main/java/forge/card/DraftOptions.java
Normal file
75
forge-core/src/main/java/forge/card/DraftOptions.java
Normal file
@@ -0,0 +1,75 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,20 @@ 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);
|
||||
|
||||
@@ -207,8 +207,6 @@ 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";
|
||||
@@ -252,6 +250,11 @@ 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);
|
||||
}
|
||||
@@ -261,6 +264,10 @@ 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);
|
||||
}
|
||||
@@ -281,8 +288,7 @@ public class ImageUtil {
|
||||
char c;
|
||||
for (int i = 0; i < in.length(); i++) {
|
||||
c = in.charAt(i);
|
||||
if ((c == '"') || (c == '/') || (c == ':') || (c == '?')) {
|
||||
} else {
|
||||
if ((c != '"') && (c != '/') && (c != ':') && (c != '?')) {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +237,8 @@ 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")) {
|
||||
|
||||
@@ -2222,6 +2222,13 @@ 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,10 +125,22 @@ 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() && !sa.isLandAbility()) {
|
||||
if (sa.isBasicSpell()) {
|
||||
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;
|
||||
@@ -166,18 +178,6 @@ 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,6 +242,7 @@ public final class GameActionUtil {
|
||||
}
|
||||
stackCopy.setLastKnownZone(game.getStackZone());
|
||||
stackCopy.setCastFrom(oldZone);
|
||||
stackCopy.setCastSA(sa);
|
||||
lkicheck = true;
|
||||
|
||||
stackCopy.clearStaticChangedCardKeywords(false);
|
||||
|
||||
@@ -15,6 +15,7 @@ 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;
|
||||
@@ -124,4 +125,12 @@ 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,6 +23,7 @@ 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;
|
||||
@@ -224,6 +225,7 @@ 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();
|
||||
@@ -288,22 +290,32 @@ public class Match {
|
||||
}
|
||||
}
|
||||
|
||||
Deck myDeck = psc.getDeck();
|
||||
player.setDraftNotes(myDeck.getDraftNotes());
|
||||
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());
|
||||
|
||||
Set<PaperCard> myRemovedAnteCards = null;
|
||||
if (!rules.useAnte()) {
|
||||
myRemovedAnteCards = getRemovedAnteCards(myDeck);
|
||||
myRemovedAnteCards = getRemovedAnteCards(myDeck.getLeft());
|
||||
for (PaperCard cp: myRemovedAnteCards) {
|
||||
for (Entry<DeckSection, CardPool> ds : myDeck) {
|
||||
for (Entry<DeckSection, CardPool> ds : myDeck.getLeft()) {
|
||||
ds.getValue().removeAll(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preparePlayerZone(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
|
||||
if (myDeck.has(DeckSection.Sideboard)) {
|
||||
preparePlayerZone(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
|
||||
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());
|
||||
|
||||
// Assign Companion
|
||||
Card companion = player.assignCompanion(game, person);
|
||||
@@ -322,7 +334,7 @@ public class Match {
|
||||
player.shuffle(null);
|
||||
|
||||
if (isFirstGame) {
|
||||
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck);
|
||||
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck.getLeft());
|
||||
if (cardsComplained != null && !cardsComplained.isEmpty()) {
|
||||
rAICards.put(player, cardsComplained);
|
||||
}
|
||||
@@ -337,6 +349,7 @@ public class Match {
|
||||
if (myRemovedAnteCards != null && !myRemovedAnteCards.isEmpty()) {
|
||||
removedAnteCards.putAll(player, myRemovedAnteCards);
|
||||
}
|
||||
unsupported.put(player, myDeck.getRight());
|
||||
}
|
||||
|
||||
final Localizer localizer = Localizer.getInstance();
|
||||
@@ -347,6 +360,10 @@ 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) {
|
||||
|
||||
@@ -383,6 +383,9 @@ 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);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ public class AlterAttributeEffect extends SpellAbilityEffect {
|
||||
boolean altered = false;
|
||||
|
||||
switch (attr.trim()) {
|
||||
case "Harnessed":
|
||||
altered = gameCard.setHarnessed(activate);
|
||||
break;
|
||||
case "Plotted":
|
||||
altered = gameCard.setPlotted(activate);
|
||||
|
||||
|
||||
@@ -969,12 +969,10 @@ 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 {
|
||||
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());
|
||||
}
|
||||
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)) {
|
||||
|
||||
@@ -34,6 +34,9 @@ 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,33 +54,29 @@ public class LifeExchangeEffect extends SpellAbilityEffect {
|
||||
|
||||
final int life1 = p1.getLife();
|
||||
final int life2 = p2.getLife();
|
||||
final int diff = Math.abs(life1 - life2);
|
||||
|
||||
if (sa.hasParam("RememberDifference")) {
|
||||
final int diff = life1 - life2;
|
||||
source.addRemembered(diff);
|
||||
if (life2 > life1) {
|
||||
// swap players
|
||||
Player tmp = p2;
|
||||
p2 = p1;
|
||||
p1 = tmp;
|
||||
}
|
||||
|
||||
final Map<Player, Integer> lossMap = Maps.newHashMap();
|
||||
if ((life1 > life2) && p1.canLoseLife() && p2.canGainLife()) {
|
||||
final int diff = life1 - life2;
|
||||
if (diff > 0 && p1.canLoseLife() && p2.canGainLife()) {
|
||||
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 (!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);
|
||||
if (sa.hasParam("RememberDifference")) {
|
||||
source.addRemembered(p1.getLife() - p2.getLife());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,10 +271,11 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
producedMana.append(abMana.produceMana(mana, p, sa));
|
||||
}
|
||||
|
||||
abMana.tapsForMana(sa.getRootAbility(), producedMana.toString());
|
||||
|
||||
// Only clear express choice after mana has been produced
|
||||
abMana.clearExpressChoice();
|
||||
|
||||
abMana.tapsForMana(sa.getRootAbility(), producedMana.toString());
|
||||
|
||||
if (sa.isKeyword(Keyword.FIREBENDING)) {
|
||||
activator.triggerElementalBend(TriggerType.Firebend);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,9 @@ 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(" ").append(source.getBasePowerString());
|
||||
sb.append(" / ").append(source.getBaseToughnessString());
|
||||
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()));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ 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;
|
||||
@@ -282,6 +281,17 @@ 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(" & ")));
|
||||
@@ -307,8 +317,6 @@ 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")) {
|
||||
@@ -394,17 +402,6 @@ 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));
|
||||
}
|
||||
@@ -494,7 +491,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), tgtCards);
|
||||
}
|
||||
|
||||
for (final Card tgtC : untargetedCards) {
|
||||
for (final Card tgtC : CardUtil.getRadiance(sa)) {
|
||||
// only pump things in PumpZone
|
||||
if (!tgtC.isInZones(pumpZones)) {
|
||||
continue;
|
||||
|
||||
@@ -203,6 +203,7 @@ 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;
|
||||
@@ -2462,10 +2463,8 @@ 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("Disguise") || keyword.startsWith("Reflect")
|
||||
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
||||
|| keyword.startsWith("Madness:")|| keyword.startsWith("Recover")
|
||||
|| keyword.startsWith("Reconfigure") || keyword.startsWith("Squad")
|
||||
@@ -2504,17 +2503,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
sbLong.append(".");
|
||||
}
|
||||
if (k.length > 3) {
|
||||
sbLong.append(". " + k[3]);
|
||||
sbLong.append(". ").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(":");
|
||||
@@ -2653,7 +2650,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("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Ripple")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[0]).append(" ").append(k[1]).append(" (").append(inst.getReminderText()).append(")");
|
||||
} else if (keyword.startsWith("Crew")) {
|
||||
@@ -2969,6 +2966,9 @@ 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");
|
||||
}
|
||||
@@ -6693,6 +6693,14 @@ 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;
|
||||
}
|
||||
@@ -6850,6 +6858,10 @@ 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;
|
||||
}
|
||||
@@ -7145,7 +7157,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return false;
|
||||
}
|
||||
|
||||
if (StaticAbilityCantTarget.cantTarget(this, sa)) {
|
||||
if (StaticAbilityCantTarget.cantTarget(this, sa) != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -376,22 +376,28 @@ public class CardFactory {
|
||||
}
|
||||
}
|
||||
|
||||
// Build English oracle and translated oracle mapping
|
||||
// Negative card Id's are for view purposes only
|
||||
if (c.getId() >= 0) {
|
||||
// Build English oracle and translated oracle mapping
|
||||
CardTranslation.buildOracleMapping(face.getName(), face.getOracleText(), variantName);
|
||||
}
|
||||
|
||||
// Name first so Senty has the Card name
|
||||
// Set name for Sentry reports to be identifiable
|
||||
c.setName(face.getName());
|
||||
|
||||
for (Entry<String, String> v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
|
||||
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 (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);
|
||||
// keywords not before variables
|
||||
c.addIntrinsicKeywords(face.getKeywords(), false);
|
||||
}
|
||||
if (face.getDraftActions() != null) {
|
||||
face.getDraftActions().forEach(c::addDraftAction);
|
||||
}
|
||||
@@ -420,7 +426,8 @@ public class CardFactory {
|
||||
|
||||
c.setAttractionLights(face.getAttractionLights());
|
||||
|
||||
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
|
||||
if (c.getId() > 0) // Set FactoryAbilities if not for view
|
||||
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,10 +2776,9 @@ public class CardFactoryUtil {
|
||||
final String cost = params[1];
|
||||
|
||||
final StringBuilder sbAttach = new StringBuilder();
|
||||
sbAttach.append("SP$ Attach | Cost$ ");
|
||||
sbAttach.append("SP$ Attach | ValidTgts$ Creature | 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();
|
||||
@@ -4113,7 +4112,7 @@ public class CardFactoryUtil {
|
||||
sbValid.append("| ").append(param).append(k[1]);
|
||||
}
|
||||
|
||||
String effect = "Mode$ CantTarget | ValidCard$ Card.Self | Secondary$ True"
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True"
|
||||
+ sbValid.toString() + " | Activator$ Opponent | Description$ "
|
||||
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
@@ -4154,7 +4153,7 @@ public class CardFactoryUtil {
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
|
||||
// Target
|
||||
effect = "Mode$ CantTarget | Protection$ True | ValidCard$ Card.Self | Secondary$ True ";
|
||||
effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidSource$ " + valid;
|
||||
}
|
||||
@@ -4162,7 +4161,7 @@ public class CardFactoryUtil {
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
|
||||
// Attach
|
||||
effect = "Mode$ CantAttach | Protection$ True | Target$ Card.Self | Secondary$ True ";
|
||||
effect = "Mode$ CantAttach | Target$ Card.Self | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidCard$ " + valid;
|
||||
}
|
||||
@@ -4183,7 +4182,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 | ValidCard$ Card.Self | Secondary$ True"
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True"
|
||||
+ " | Description$ Shroud (" + inst.getReminderText() + ")";
|
||||
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
|
||||
} else if (keyword.equals("Skulk")) {
|
||||
|
||||
@@ -1244,7 +1244,8 @@ public class CardProperty {
|
||||
if (property.contains("ControlledBy")) {
|
||||
FCollectionView<Player> p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], spellAbility);
|
||||
cards = CardLists.filterControlledBy(cards, p);
|
||||
if (!cards.contains(card)) {
|
||||
// Kraven the Hunter LTB trigger
|
||||
if (!card.isLKI() && !cards.contains(card)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1819,6 +1820,10 @@ 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")) {
|
||||
@@ -1827,6 +1832,10 @@ 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;
|
||||
|
||||
@@ -468,6 +468,9 @@ 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,5 +1,6 @@
|
||||
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;
|
||||
@@ -7,45 +8,74 @@ import forge.game.trigger.Trigger;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public record CardTraitChanges(Collection<SpellAbility> abilities, Collection<SpellAbility> removedAbilities,
|
||||
Collection<Trigger> triggers, Collection<ReplacementEffect> replacements, Collection<StaticAbility> staticAbilities,
|
||||
boolean removeAll, boolean removeNonMana) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the triggers
|
||||
*/
|
||||
public Collection<Trigger> getTriggers() {
|
||||
return Objects.requireNonNullElse(triggers, List.of());
|
||||
return triggers;
|
||||
}
|
||||
/**
|
||||
* @return the replacements
|
||||
*/
|
||||
public Collection<ReplacementEffect> getReplacements() {
|
||||
return Objects.requireNonNullElse(replacements, List.of());
|
||||
return replacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the abilities
|
||||
*/
|
||||
public Collection<SpellAbility> getAbilities() {
|
||||
return Objects.requireNonNullElse(abilities, List.of());
|
||||
return abilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the abilities
|
||||
*/
|
||||
public Collection<SpellAbility> getRemovedAbilities() {
|
||||
return Objects.requireNonNullElse(removedAbilities, List.of());
|
||||
return removedAbilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the staticAbilities
|
||||
*/
|
||||
public Collection<StaticAbility> getStaticAbilities() {
|
||||
return Objects.requireNonNullElse(staticAbilities, List.of());
|
||||
return staticAbilities;
|
||||
}
|
||||
|
||||
public boolean isRemoveAll() {
|
||||
@@ -57,30 +87,53 @@ public record CardTraitChanges(Collection<SpellAbility> abilities, Collection<Sp
|
||||
}
|
||||
|
||||
public CardTraitChanges copy(Card host, boolean lki) {
|
||||
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
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void changeText() {
|
||||
for (SpellAbility sa : this.getAbilities()) {
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
sa.changeText();
|
||||
}
|
||||
|
||||
for (Trigger tr : this.getTriggers()) {
|
||||
for (Trigger tr : this.triggers) {
|
||||
tr.changeText();
|
||||
}
|
||||
|
||||
for (ReplacementEffect re : this.getReplacements()) {
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
re.changeText();
|
||||
}
|
||||
|
||||
for (StaticAbility sa : this.getStaticAbilities()) {
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
sa.changeText();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,13 +60,12 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
|
||||
public static TrackableCollection<CardView> getCollection(Iterable<Card> cards) {
|
||||
if (cards == null) {
|
||||
return null;
|
||||
}
|
||||
TrackableCollection<CardView> collection = new TrackableCollection<>();
|
||||
for (Card c : cards) {
|
||||
if (c.getRenderForUI()) { //only add cards that match their card for UI
|
||||
collection.add(c.getView());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
return collection;
|
||||
|
||||
@@ -167,6 +167,8 @@ 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,6 +1,5 @@
|
||||
package forge.game.combat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -39,10 +38,12 @@ 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)) continue;
|
||||
defenderSpecific.add(e);
|
||||
if (e.equals(attacker)) {
|
||||
nAttackAnything++;
|
||||
} else {
|
||||
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) && !ge.hasKeyword("Creatures your opponents control attack a player other than you if able.") && canAttack(attacker, ge)) {
|
||||
if (!attacker.isGoadedBy((Player) ge) && canAttack(attacker, ge)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -233,17 +233,6 @@ 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;
|
||||
|
||||
@@ -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", KeywordWithCost.class, false, "You may cast this card from your graveyard for %s if you discarded it this turn. Timing rules still apply."),
|
||||
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."),
|
||||
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."),
|
||||
|
||||
25
forge-game/src/main/java/forge/game/keyword/Mayhem.java
Normal file
25
forge-game/src/main/java/forge/game/keyword/Mayhem.java
Normal file
@@ -0,0 +1,25 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ 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;
|
||||
@@ -454,7 +455,6 @@ 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,7 +553,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
boolean firstLost = lifeLostThisTurn == 0;
|
||||
|
||||
lifeLostThisTurn += toLose;
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
|
||||
@@ -578,9 +577,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
public final boolean payLife(final int lifePayment, final SpellAbility cause, final boolean effect) {
|
||||
return payLife(lifePayment, cause, effect, null);
|
||||
}
|
||||
public final boolean payLife(final int lifePayment, final SpellAbility cause, final boolean effect, Map<AbilityKey, Object> params) {
|
||||
// fast check for pay zero life
|
||||
if (lifePayment <= 0) {
|
||||
cause.setPaidLife(0);
|
||||
@@ -600,9 +596,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
if (cause.isReplacementAbility() && effect) {
|
||||
replaceParams.putAll(cause.getReplacingObjects());
|
||||
}
|
||||
if (params != null) {
|
||||
replaceParams.putAll(params);
|
||||
}
|
||||
switch (getGame().getReplacementHandler().run(ReplacementType.PayLife, replaceParams)) {
|
||||
case Replaced:
|
||||
return true;
|
||||
@@ -1085,7 +1078,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
// CantTarget static abilities
|
||||
if (StaticAbilityCantTarget.cantTarget(this, sa)) {
|
||||
if (StaticAbilityCantTarget.cantTarget(this, sa) != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1718,7 +1711,8 @@ 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))) {
|
||||
if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !mayPlay
|
||||
&& (landSa == null || !landSa.isAlternativeCost(AlternativeCost.Mayhem))))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1966,10 +1960,11 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
CardState speedFront = speedEffect.getState(CardStateName.Original);
|
||||
CardState speedBack = speedEffect.getState(CardStateName.Backside);
|
||||
|
||||
speedFront.setImageKey("t:speed");
|
||||
|
||||
speedFront.setImageKey(StaticData.instance().getOtherImageKey(ImageKeys.SPEED_IMAGE, CardEdition.UNKNOWN_CODE));
|
||||
speedFront.setName("Start Your Engines!");
|
||||
|
||||
speedBack.setImageKey("t:max_speed");
|
||||
speedBack.setImageKey(StaticData.instance().getOtherImageKey(ImageKeys.MAX_SPEED_IMAGE, CardEdition.UNKNOWN_CODE));
|
||||
speedBack.setName("Max Speed!");
|
||||
|
||||
String label = Localizer.getInstance().getMessage("lblSpeed", this.speed);
|
||||
|
||||
@@ -287,6 +287,8 @@ 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 | ValidPlayer$ Player.You | Secondary$ True "
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ 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 | ValidPlayer$ Player.You | Secondary$ True "
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ 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 | Protection$ True | ValidPlayer$ Player.You | EffectZone$ Command | Secondary$ True ";
|
||||
String effect = "Mode$ CantTarget | ValidTarget$ 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 | Protection$ True | Target$ Player.You | EffectZone$ Command | Secondary$ True ";
|
||||
effect = "Mode$ CantAttach | Target$ Player.You | EffectZone$ Command | Secondary$ True ";
|
||||
if (!valid.isEmpty()) {
|
||||
effect += "| ValidCard$ " + valid;
|
||||
}
|
||||
|
||||
@@ -354,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 && metConditions() && mp.meetsManaRestrictions(saPaidFor)) {
|
||||
if (mp != null && mp.meetsManaRestrictions(saPaidFor)) {
|
||||
result += amountOfManaGenerated(multiply);
|
||||
}
|
||||
result += subAbility != null ? subAbility.totalAmountOfManaGenerated(saPaidFor, multiply) : 0;
|
||||
@@ -671,6 +671,10 @@ 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);
|
||||
@@ -1500,6 +1504,22 @@ 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();
|
||||
|
||||
@@ -59,6 +59,7 @@ public class TargetRestrictions {
|
||||
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;
|
||||
@@ -621,6 +622,13 @@ public class TargetRestrictions {
|
||||
this.differentCMC = different;
|
||||
}
|
||||
|
||||
public boolean isDifferentNames() {
|
||||
return differentNames;
|
||||
}
|
||||
public void setDifferentNames(boolean different) {
|
||||
this.differentNames = different;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the equalToughness
|
||||
*/
|
||||
|
||||
@@ -39,36 +39,20 @@ public class StaticAbilityCantTarget {
|
||||
|
||||
static String MODE = "CantTarget";
|
||||
|
||||
public static boolean cantTarget(final Card card, final SpellAbility spellAbility) {
|
||||
final Game game = card.getGame();
|
||||
public static StaticAbility cantTarget(final GameEntity entity, final SpellAbility spellAbility) {
|
||||
final Game game = entity.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, card, spellAbility)) {
|
||||
return true;
|
||||
if (applyCantTargetAbility(stAb, entity, spellAbility)) {
|
||||
return stAb;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,57 +66,27 @@ public class StaticAbilityCantTarget {
|
||||
* the spell/ability
|
||||
* @return true, if successful
|
||||
*/
|
||||
public static boolean applyCantTargetAbility(final StaticAbility stAb, final Card card, final SpellAbility spellAbility) {
|
||||
if (stAb.hasParam("ValidPlayer")) {
|
||||
return false;
|
||||
}
|
||||
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 (stAb.hasParam("AffectedZone")) {
|
||||
boolean inZone = false;
|
||||
for (final ZoneType zt : ZoneType.listValueOf(stAb.getParam("AffectedZone"))) {
|
||||
if (card.isInZone(zt)) {
|
||||
inZone = true;
|
||||
break;
|
||||
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 (!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)) {
|
||||
} else if (stAb.hasParam("AffectedZone")) {
|
||||
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();
|
||||
|
||||
@@ -140,6 +94,10 @@ public class StaticAbilityCantTarget {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stAb.matchesValidParam("ValidTarget", entity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stAb.matchesValidParam("ValidSA", spellAbility)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.Map;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Localizer;
|
||||
|
||||
@@ -60,17 +59,8 @@ public class TriggerAttackerBlocked extends Trigger {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasParam("ValidBlocker")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
int count = CardLists.getValidCardCount(
|
||||
(Iterable<Card>) runParams.get(AbilityKey.Blockers),
|
||||
getParam("ValidBlocker"),
|
||||
getHostCard().getController(), getHostCard(), this
|
||||
);
|
||||
|
||||
if (count == 0) {
|
||||
return false;
|
||||
}
|
||||
if (!matchesValidParam("ValidBlocker", runParams.get(AbilityKey.Blockers))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -108,9 +108,8 @@ 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")))) {
|
||||
@@ -147,8 +146,7 @@ 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 Card triggered = (Card) runParams.get(AbilityKey.Card);
|
||||
final int actualValue = AbilityUtils.calculateAmount(triggered, condition[0], this);
|
||||
final int actualValue = AbilityUtils.calculateAmount(moved, condition[0], this);
|
||||
if (!Expressions.compare(actualValue, comparator.substring(0, 2), referenceValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -514,11 +514,6 @@ 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,6 +331,12 @@ 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;
|
||||
@@ -354,12 +360,6 @@ 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);
|
||||
|
||||
|
||||
770
forge-gui-android/assets/database.json
Normal file
770
forge-gui-android/assets/database.json
Normal file
@@ -0,0 +1,770 @@
|
||||
{
|
||||
"a32x":{
|
||||
"CPU":"2x Cortex-A76 @ 2GHz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"MediaTek Dimensity 720 (MT6853)"
|
||||
},
|
||||
"angler":{
|
||||
"CPU":"4x Cortex-A57 @ 1.95GHz 4x Cortex-A53 @ 1.55GHz",
|
||||
"SoC":"Snapdragon 810 MSM8994"
|
||||
},
|
||||
"ane":{
|
||||
"CPU":"4x Cortex-A54 @ 2.3GHz 4x Cortex-A54 @ 1.7GHz",
|
||||
"SoC":"HiSilicon Kirin 659"
|
||||
},
|
||||
"amar_row_wifi":{
|
||||
"CPU":"8x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"Mediatek MT8768"
|
||||
},
|
||||
"atlas":{
|
||||
"CPU":"-",
|
||||
"SoC":"Intel(R) Core(TM) i5-8200Y @ 1.3GHz"
|
||||
},
|
||||
"apq8084":{
|
||||
"CPU":"4x Krait 450 @ 2.65GHz",
|
||||
"SoC":"Snapdragon 805 APQ8084"
|
||||
},
|
||||
"atoll":{
|
||||
"CPU":"2x Kryo 465 Gold @ 2.3GHz 6x Kryo 465 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 720G (SM7125)"
|
||||
},
|
||||
"art-l29":{
|
||||
"CPU":"4x Cortex-A73 @ 2.2GHz 4x Cortex-A53 @ 1.7GHz",
|
||||
"SoC":"HiSilicon Kirin 710F"
|
||||
},
|
||||
"baylake":{
|
||||
"CPU":"Atom Z3745 @ 1.3GHz",
|
||||
"SoC":"Intel Atom Z3745"
|
||||
},
|
||||
"begonia":{
|
||||
"CPU":"2x Cortex-176 @ 2GHz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"MediaTek Helios G90T MT6785T"
|
||||
},
|
||||
"blueline":{
|
||||
"CPU":"4x Kryo 385 Gold @ 2.8GHz 4x Kryo 385 Silver @ 1.75GHz",
|
||||
"SoC":"Snapdragon 845"
|
||||
},
|
||||
"bullhead":{
|
||||
"CPU":"4x Cortex-A57 @ 1.8GHz 4x Cortex-A53 @ 1.44GHz",
|
||||
"SoC":"Snapdragon 808"
|
||||
},
|
||||
"capri":{
|
||||
"CPU":"2x Cortex-A9 @ 1.2GHz",
|
||||
"SoC":"Broadcom BCM28155"
|
||||
},
|
||||
"cepheus":{
|
||||
"CPU":"1x Kryo 485 Gold @ 2.8GHz 3x Kryo 485 Gold @ 2.4GHz 4x Kryo 485 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 855 SM8150"
|
||||
},
|
||||
"cheryl":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"cheryl2":{
|
||||
"CPU":"4x Kryo 385 Gold @ 2.8GHz 4x Kryo 385 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 845 SDM845"
|
||||
},
|
||||
"cheetah":{
|
||||
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor G2 (GS201)"
|
||||
},
|
||||
"comet":{
|
||||
"CPU":"1x Cortex-X4 @ 31.GHz 3x Cortex-A720 @ 2.6GHz 4x Cortex-A520 @ 1.95GHz",
|
||||
"SoC":"Google Tensor G4 (GS401)"
|
||||
},
|
||||
"eureka":{
|
||||
"CPU":"6x Cortex-A78C @ 2.3 GHz",
|
||||
"SoC":"Snapdragon XR2 Gen 2 (SM8550)"
|
||||
},
|
||||
"felix":{
|
||||
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor G2 (GS201)"
|
||||
},
|
||||
"tangorpro":{
|
||||
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor G2 (GS201)"
|
||||
},
|
||||
"codina":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"NovaThor U8500"
|
||||
},
|
||||
"clovertrail":{
|
||||
"CPU":"2x Atom Z2560 @ 1.6GHz",
|
||||
"SoC":"Intel Atom Z2560"
|
||||
},
|
||||
"clt":{
|
||||
"CPU":"4x Cortex-A73 @ 2.36GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 970"
|
||||
},
|
||||
"els":{
|
||||
"CPU":"4x Cortex-A76 @ 2.8GHz 4x Cortex-A55 @ 1.9GHz",
|
||||
"SoC":"HiSilicon Kirin 990 5G"
|
||||
},
|
||||
"dandelion":{
|
||||
"CPU":"8x Cortex-A54 @ 1.5GHz",
|
||||
"SoC":"MediaTek Helio G25 (MT6762G)"
|
||||
},
|
||||
"darcy":{
|
||||
"CPU":"4x ARM Cortex-A53 4x ARM Cortex-A57",
|
||||
"SoC":"nVIDIA Tegra X1 T210"
|
||||
},
|
||||
"db8520h":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"NovaThor U8500"
|
||||
},
|
||||
"dragon":{
|
||||
"CPU":"4x Cortex-A57 @ 1.9GHz 4x Cortex-A53 @ 1.3GHz",
|
||||
"SoC":"nVIDIA Tegra X1 T210"
|
||||
},
|
||||
"douglas":{
|
||||
"CPU":"4x Cortex-A53 @ 1.3GHz",
|
||||
"SoC":"MediaTek MT8163"
|
||||
},
|
||||
"eeepad":{
|
||||
"CPU":"4x Atom Z2520 @ 1.2GHz",
|
||||
"SoC":"Intel Atom Z2520"
|
||||
},
|
||||
"endeavoru":{
|
||||
"CPU":"4x Cortex-A9 @ 1.5GHz 1x Cortex-A9 @ 0.5GHz",
|
||||
"SoC":"nVIDIA Tegra 3 AP33"
|
||||
},
|
||||
"eml":{
|
||||
"CPU":"4x Cortex-A73 @ 2.36GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 970"
|
||||
},
|
||||
"eve":{
|
||||
"CPU":"i5-7Y56 @ 1.2GHz",
|
||||
"SoC":"Ambel Lake-Y / Kaby Lake-U/Y"
|
||||
},
|
||||
"eva-l19":{
|
||||
"CPU":"4x Cortex-A72 @ 2.5GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 955"
|
||||
},
|
||||
"exynos990":{
|
||||
"CPU":"4x Cortex-A55 @ 2GHz 2x Cortex-A76 @ 2.5GHz 2x Exynos M5 @ 2.7GHz",
|
||||
"SoC":"Exynos 990"
|
||||
},
|
||||
"exynos9611":{
|
||||
"CPU":"4x Cortex-A73 @ 2.3GHz 4x Cortex-A53 @ 1.7GHz",
|
||||
"SoC":"Exynos 7 Octa (9611)"
|
||||
},
|
||||
"exynos2100":{
|
||||
"CPU":"1x Cortex-X1 @ 2.9GHz 3x Cortex-A78 @ 2.8GHz 4x Cortex-A55 @ 2.2GHz",
|
||||
"SoC":"Exynos 2100"
|
||||
},
|
||||
"exynos9810":{
|
||||
"CPU":"4x Exynos M3 @ 2.7GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Exynos 9 (9810)"
|
||||
},
|
||||
"exynos9820":{
|
||||
"CPU":"2 Exynos M4 @ 2.7GHz 2x Cortex-A75 @ 2.3GHz 4x Cortex-A55 @ 1.9GHz",
|
||||
"SoC":"Exynos 9 (9820)"
|
||||
},
|
||||
"ford":{
|
||||
"CPU":"4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"MediaTek MT8127"
|
||||
},
|
||||
"flo":{
|
||||
"CPU":"4x Krait 300 @ 1.5GHz",
|
||||
"SoC":"Snapdragon 600 APQ8064-FLO"
|
||||
},
|
||||
"flame":{
|
||||
"CPU":"1x Kryo 485 Gold @ 2.8GHz 3x Kryo 485 Gold @ 2.4GHz 4x Kryo 485 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 855 SM8150"
|
||||
},
|
||||
"fleur":{
|
||||
"CPU":"2x Cortex-A76 @ 2GHz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"MediaTek Helio G96 (MT6781)"
|
||||
},
|
||||
"flounder":{
|
||||
"CPU":"2x nVidia Denver @ 2.5GHz",
|
||||
"SoC":"nVIDIA Tegra K1 T132"
|
||||
},
|
||||
"g3u":{
|
||||
"CPU":"2x Cortex-A5 @ 1.0GHz",
|
||||
"SoC":"Snapdragon S4 Play MSM8225"
|
||||
},
|
||||
"gee":{
|
||||
"CPU":"4x Krait 300 @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Pro APQ8064"
|
||||
},
|
||||
"grouper":{
|
||||
"CPU":"4x Cortex-A9 @ 1.3GHz 1x Cortex-A9 @ 0.5GHz",
|
||||
"SoC":"nVIDIA Tegra 3 T30L"
|
||||
},
|
||||
"hammerhead":{
|
||||
"CPU":"4x Krait 400 @ 2.26GHz",
|
||||
"SoC":"Snapdragon 800 MSM8974"
|
||||
},
|
||||
"hawaii_ss_kylepro":{
|
||||
"CPU":"2x Cortex-A9 @ 1.2GHz",
|
||||
"SoC":"Broadcom BCM21664T"
|
||||
},
|
||||
"herring":{
|
||||
"CPU":"1x Cortex-A8 @ 1.0GHz",
|
||||
"SoC":"Exynos 3 Single 3110"
|
||||
},
|
||||
"hollywood":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.4GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon XR2"
|
||||
},
|
||||
"k6853v1_64_titan":{
|
||||
"CPU":"2x Cortex-A76 @ 2Ghz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"MediaTek Dimensity 720 (MT6853)"
|
||||
},
|
||||
"kalama":{
|
||||
"CPU":"1x Cortex-X3 @ 3.3GHz 2x Cortex-A710 @ 2.8GHz 2x Cortex-A715 @ 2.8GHz 3x Cortex-A510 @ 2.0GHz",
|
||||
"SoC":"Snapdragon 8 Gen 2 (SM8550)"
|
||||
},
|
||||
"kona":{
|
||||
"CPU":"1x Cortex-A77 @ 3.1GHz 3x Cortex-A77 @ 2.4GHz 4x Kryo 585 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 865 SM8250"
|
||||
},
|
||||
"kohaku":{
|
||||
"CPU":"i5-10210U @ 1.6GHz",
|
||||
"SoC":"Comet Lake-U"
|
||||
},
|
||||
"lahaina":{
|
||||
"CPU":"1x Cortex-X1 @ 2.8GHz 3x Cortex-A78 @ 2.4GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Snapdragon 888"
|
||||
},
|
||||
"liara":{
|
||||
"CPU":"5 Compute cores 2C+3G",
|
||||
"SoC":"AMD A4-9120C Radeon R4"
|
||||
},
|
||||
"lito":{
|
||||
"CPU":"2x Cortex-A77 @ 2.1GHz 6x Kryo 560 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 690 SM6350"
|
||||
},
|
||||
"lya":{
|
||||
"CPU":"2x Cortex-A76 @ 2.6GHz 2x Cortex-A76 @ 1.9GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 980"
|
||||
},
|
||||
"lyo-l01":{
|
||||
"CPU":"4x Cortex-153 @ 1.3GHz",
|
||||
"SoC":"MediaTek MT6735"
|
||||
},
|
||||
"mako":{
|
||||
"CPU":"4x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Pro APQ8064"
|
||||
},
|
||||
"marlin":{
|
||||
"CPU":"2x Kryo HP @ 2.15GHz 2x Kryo @ 1.6GHz",
|
||||
"SoC":"Snapdragon 821 MSM8996 Pro"
|
||||
},
|
||||
"med":{
|
||||
"CPU":"8x Cortex-A54 @ 1.5GHz",
|
||||
"SoC":"MediaTek MT6762R"
|
||||
},
|
||||
"mocha":{
|
||||
"CPU":"4x Cortex-A15 @2.2GHz",
|
||||
"SoC":"nVIDIA Tegra K1 T124"
|
||||
},
|
||||
"msm8225":{
|
||||
"CPU":"2x Cortex-A5 @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 MSM8225"
|
||||
},
|
||||
"msm8226":{
|
||||
"CPU":"4x Cortex-A7 @ 1.19GHz",
|
||||
"SoC":"Snapdragon 400 MSM8226"
|
||||
},
|
||||
"msm8625":{
|
||||
"CPU":"2x Cortex-A5 @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 MSM8625"
|
||||
},
|
||||
"MSM8227":{
|
||||
"CPU":"2x Krait @ 1GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8227"
|
||||
},
|
||||
"msm8627":{
|
||||
"CPU":"2x Krait @ 1GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8627"
|
||||
},
|
||||
"apq8030":{
|
||||
"CPU":"2x Krait @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 Plus APQ8030"
|
||||
},
|
||||
"msm8230":{
|
||||
"CPU":"2x Krait @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8230"
|
||||
},
|
||||
"msm8660_surf":{
|
||||
"CPU":"2x Scorpion @ 1.5GHz",
|
||||
"SoC":"Snapdragon S3 MSM8260"
|
||||
},
|
||||
"msm8630":{
|
||||
"CPU":"2x Krait @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8630"
|
||||
},
|
||||
"msm8930":{
|
||||
"CPU":"2x Krait @ 1.2GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8930"
|
||||
},
|
||||
"msm8937":{
|
||||
"CPU":"4x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"Snapdragon 425 MSM8917"
|
||||
},
|
||||
"apq8060a":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus APQ8060A"
|
||||
},
|
||||
"msm8260a":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8260A"
|
||||
},
|
||||
"msm8660a":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8660A"
|
||||
},
|
||||
"msm8960":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8960"
|
||||
},
|
||||
"msm8260a-pro":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8260A Pro"
|
||||
},
|
||||
"msm8960t":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8960T"
|
||||
},
|
||||
"msm8960t-pro":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8960T Pro"
|
||||
},
|
||||
"msm8960ab":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8960AB"
|
||||
},
|
||||
"msm8960dt":{
|
||||
"CPU":"2x Krait 300 @ 1.7GHz",
|
||||
"SoC":"Snapdragon S4 Pro MSM8960DT"
|
||||
},
|
||||
"apq8064":{
|
||||
"CPU":"4x Krait 300 @ 1.5GHz",
|
||||
"SoC":"Snapdragon 600 APQ8064"
|
||||
},
|
||||
"msm8916":{
|
||||
"CPU":"4x Cortex-A53 @ 1.2GHz",
|
||||
"SoC":"Snapdragon 410 MSM8916"
|
||||
},
|
||||
"msm8953":{
|
||||
"CPU":"8x Cortex-A53 @ 2.0GHz",
|
||||
"SoC":"Snapdragon 625 MSM8953"
|
||||
},
|
||||
"msm8952":{
|
||||
"CPU":"8x Cortex-A53 @ 1.2GHz",
|
||||
"SoC":"Snapdragon 617 MSM8952"
|
||||
},
|
||||
"msm8956":{
|
||||
"CPU":"2x Cortex-A72 @ 1.8GHz 4x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"Snapdragon 650 MSM8956"
|
||||
},
|
||||
"msm8974":{
|
||||
"CPU":"4x Krait 400 @ 2.15GHz",
|
||||
"SoC":"Snapdragon 800 MSM8974"
|
||||
},
|
||||
"msm8974pro-ab":{
|
||||
"CPU":"4x Krait 400 @ 2.26GHz",
|
||||
"SoC":"Snapdragon 801 MSM8974PRO-AB"
|
||||
},
|
||||
"msm8974pro-ac":{
|
||||
"CPU":"4x Krait 400 @ 2.45GHz",
|
||||
"SoC":"Snapdragon 801 MSM8974AC"
|
||||
},
|
||||
"msm8976":{
|
||||
"CPU":"4x Cortex-A53 @ 1.4GHz 4x Cortex-A72 @ 1.8GHz",
|
||||
"SoC":"Snapdragon 652 MSM8976"
|
||||
},
|
||||
"msm8976pro":{
|
||||
"CPU":"4x Cortex-A53 @ 1.4GHz 4x Cortex-A72 @ 1.9GHz",
|
||||
"SoC":"Snapdragon 653 MSM8976 Pro"
|
||||
},
|
||||
"msm8992":{
|
||||
"CPU":"4x Cortex-A57 @ 1.8GHz 4x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"Snapdragon 808 MSM8992"
|
||||
},
|
||||
"msm8994":{
|
||||
"CPU":"4x Cortex-A57 @ 1.95GHz 4x Cortex-A53 @ 1.5GHz",
|
||||
"SoC":"Snapdragon 810 MSM8994"
|
||||
},
|
||||
"msm8996":{
|
||||
"CPU":"2x Kryo HP @ 1.8GHz 2x Kryo LP @ 1.36GHz",
|
||||
"SoC":"Snapdragon 820 MSM8996"
|
||||
},
|
||||
"msm8996pro":{
|
||||
"CPU":"2x Kryo HP @ 2.34GHz 2x Kryo LP @ 2.18GHz",
|
||||
"SoC":"Snapdragon 821 MSM8996 Pro"
|
||||
},
|
||||
"msm8998":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.4GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"msmnile":{
|
||||
"CPU":"1x Kryo 485 Gold @ 2.8GHz 3x Kryo 485 Gold @ 2.4GHz 4x Kryo 485 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 855 SM8150"
|
||||
},
|
||||
"mt6795t":{
|
||||
"CPU":"8x Cortex-A53 @ 2.1GHz",
|
||||
"SoC":"MediaTek Helio X10 MT6795T"
|
||||
},
|
||||
"mt6797m":{
|
||||
"CPU":"2x Cortex-A72 @ 2.1GHz 4x Cortex-A53 @ 1.85GHz 4x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"MediaTek Helio X20 MT6797M"
|
||||
},
|
||||
"mt6750t":{
|
||||
"CPU":"8x Cortex-A53 @ 1.5GHz",
|
||||
"SoC":"MediaTek MT6750T"
|
||||
},
|
||||
"mx5":{
|
||||
"CPU":"8x Cortex-A53 @ 2.1GHz",
|
||||
"SoC":"MediaTek Helio X10 MT6795T"
|
||||
},
|
||||
"mtk6575":{
|
||||
"CPU":"1x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"MediaTek MT6575"
|
||||
},
|
||||
"noh":{
|
||||
"CPU":"4x Cortex-A77 @ 3.1GHz 4x Cortex-A55 @ 2.0GHz",
|
||||
"SoC":"HiSilicon Kirin 9000"
|
||||
},
|
||||
"sdm710":{
|
||||
"CPU":"2x Kryo 385 Gold @ 2.2GHz 6x Kryo 385 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 710 SDM710"
|
||||
},
|
||||
"sdm845":{
|
||||
"CPU":"4x Kryo 385 Gold @ 2.8GHz 4x Kryo 385 Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 845 SDM845"
|
||||
},
|
||||
"monterey":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"oriole":{
|
||||
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A76 @ 2.2GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor (Whitechapel)"
|
||||
},
|
||||
"oppo6779_18073":{
|
||||
"CPU":"2x Cortex-A75 @ 2.2GHz 6x Cortex-A55 @ 2GHz",
|
||||
"SoC":"Mediatek MT6779 Helio P90"
|
||||
},
|
||||
"gt-p7510":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"nVIDIA Tegra 2 T20"
|
||||
},
|
||||
"pacific":{
|
||||
"CPU":"2x @ 2.1GHz 2x @ 1.6GHz",
|
||||
"SoC":"Snapdragon 820E Embedded"
|
||||
},
|
||||
"piranha":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"Texas Instruments OMAP4430"
|
||||
},
|
||||
"pro5":{
|
||||
"CPU":"4xCortex-56 @ 2.1GHz 4x Cortex-53 @ 1.5GHz",
|
||||
"SoC":"Exynos 7 Octa 7420"
|
||||
},
|
||||
"pro7plus":{
|
||||
"CPU":"2x Cortex-A73 @ 2.6GHz 4x Cortex-A53 @ 2.2GHz 4x Cortex-A35 @ 1.9GHz",
|
||||
"SoC":"MediaTek Helio X30 MT6799"
|
||||
},
|
||||
"pxa986":{
|
||||
"CPU":"2x Cortex-A9 @ 1.2GHz",
|
||||
"SoC":"Marvell PXA988"
|
||||
},
|
||||
"pxa19xx":{
|
||||
"CPU":"4x Cortex-A53 @ 1.25GHz",
|
||||
"SoC":"Maxvell Armada PXA1908"
|
||||
},
|
||||
"rhea_ss_corsicass":{
|
||||
"CPU":"1x Cortex-A9 @ 0.9GHz",
|
||||
"SoC":"Broadcom BCM21654"
|
||||
},
|
||||
"panther":{
|
||||
"CPU":"1x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Google Tensor G2"
|
||||
},
|
||||
"prada":{
|
||||
"CPU":"4x Cortex-A53 @ 1.4 GHz 4x Cortex-A53 @ 1.1GHz",
|
||||
"SoC":"Snapdragon 430 MSM8937"
|
||||
},
|
||||
"pro6plus":{
|
||||
"CPU":"4x Exynos M1 @ 1.97GHz 4x Cortex-A53 @ 1.48GHz",
|
||||
"SoC":"Exynos 8 Octa 8890"
|
||||
},
|
||||
"universal3110":{
|
||||
"CPU":"1x Cortex-A8 @ 1.2GHz",
|
||||
"SoC":"Exynos 3 Single 3110"
|
||||
},
|
||||
"universal9820":{
|
||||
"CPU":"2x Exynos M4 @ 2.7GHz 2x Cortex-A75 @ 2.3GHz 4x Cortex-A55 @ 1.95GHz",
|
||||
"SoC":"Exynos 9 9820"
|
||||
},
|
||||
"redfin":{
|
||||
"CPU":"2x Kryo 475 Gold @ 2.4GHz 2x Kryo 475 Gold 2.2GHz 6x Kryo 475 Silver @ 1.8GHz ",
|
||||
"SoC":"Snapdragon 765/765G"
|
||||
},
|
||||
"s5e9925":{
|
||||
"CPU":"1x Cortex-X2 @ 2.8GHZ 3x Cortex-A710 @ 2.5GHz 4x Cortex-A510 @ 1.8GHz",
|
||||
"SoC":"Exynos 2200"
|
||||
},
|
||||
"saturn":{
|
||||
"CPU":"4x Krait 450 @ 2.45GHz",
|
||||
"SoC":"Snapdragon 805 APQ8084"
|
||||
},
|
||||
"sailfish":{
|
||||
"CPU":"2x Kryo HP @ 2.15GHz 2x Kryo @ 1.6GHz",
|
||||
"SoC":"Snapdragon 821 MSM8996 Pro"
|
||||
},
|
||||
"sdm660":{
|
||||
"CPU":"4x Kryo 260 HP @ 2.2GHz 4x Kryo 260 LP @ 1.8GHz",
|
||||
"SoC":"Snapdragon 660"
|
||||
},
|
||||
"sc-02b":{
|
||||
"CPU":"1x Cortex-A8 @ 1.2GHz",
|
||||
"SoC":"Exynos 3 Single 3110"
|
||||
},
|
||||
"sch-i905":{
|
||||
"CPU":"2x Cortex-A9 @ 1.0GHz",
|
||||
"SoC":"nVIDIA Tegra 2 T20"
|
||||
},
|
||||
"sc7730s":{
|
||||
"CPU":"4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Spreadtrum SC7730S"
|
||||
},
|
||||
"shamu":{
|
||||
"CPU":"4x Krai 450 @ 2.65GHz",
|
||||
"SoC":"Snapdragon 805 APQ8084AB"
|
||||
},
|
||||
"shiba":{
|
||||
"CPU":"1x Cortex-X3 @ 2.9GHz 4x Cortex-A715 @ 2.3GHz 4x Cortex-A510 @ 1.7GHz",
|
||||
"SoC":"Google Tensor G3 (GS301)"
|
||||
},
|
||||
"sm6150":{
|
||||
"CPU":"2x Kryo 460 Gold @ 2GHz 6x 460 Kryo Silver @ 1.7GHz",
|
||||
"SoC":"Snapdragon 675 SM6150"
|
||||
},
|
||||
"smdkc110":{
|
||||
"CPU":"1x Cortex-A8 @ 1.2GHz",
|
||||
"SoC":"Exynos 3 Single 3110"
|
||||
},
|
||||
"sun":{
|
||||
"CPU":"2x Oryon @ 4.5GHz 6x Oryon @ 3.5GHz",
|
||||
"SoC":"Snapdragon 9 Elite (SM8750)"
|
||||
},
|
||||
"ums512_25c10": {
|
||||
"CPU":"2 Cortex-A75 @2GHz 6x Cortex-A55 @ 2 GHz",
|
||||
"SoC":"Unisoc Tiger T618"
|
||||
},
|
||||
"universal3250":{
|
||||
"CPU":"2x Cortex-A7 @ 1.0GHz",
|
||||
"SoC":"Exynos 2 Dual 3250"
|
||||
},
|
||||
"universal3470":{
|
||||
"CPU":"4x Cortex-A7 @ 1.4GHz",
|
||||
"SoC":"Exynos 3 Quad 3470"
|
||||
},
|
||||
"universal3475":{
|
||||
"CPU":"4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 3 Quad 3475"
|
||||
},
|
||||
"universal4210":{
|
||||
"CPU":"2x Cortex-A9 @ 1.4GHz",
|
||||
"SoC":"Exynos 4 Dual 4210"
|
||||
},
|
||||
"universal4212":{
|
||||
"CPU":"2x Cortex-A9 @ 1.5GHz",
|
||||
"SoC":"Exynos 4 Dual 4212"
|
||||
},
|
||||
"universal4412":{
|
||||
"CPU":"4x Cortex-A9 @ 1.4GHz",
|
||||
"SoC":"Exynos 4 Quad 4412"
|
||||
},
|
||||
"smdk4x12":{
|
||||
"CPU":"4x Cortex-A9 @ 1.4GHz",
|
||||
"SoC":"Exynos 4 Quad 4412"
|
||||
},
|
||||
"universal4415":{
|
||||
"CPU":"4x Cortex-A9 @ 1.5GHz",
|
||||
"SoC":"Exynos 4 Quad 4415"
|
||||
},
|
||||
"universal5250":{
|
||||
"CPU":"2x Cortex-A15 @ 1.7GHz",
|
||||
"SoC":"Exynos 5 Dual 5250"
|
||||
},
|
||||
"universal5260":{
|
||||
"CPU":"2x Cortex-A15 @ 1.7GHz 4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 5 Hexa 5260"
|
||||
},
|
||||
"universal5410":{
|
||||
"CPU":"4x Cortex-A15 @ 1.6GHz 4x Cortex-A7 @ 1.2GHz",
|
||||
"SoC":"Exynos 5 Octa 5410"
|
||||
},
|
||||
"universal5420":{
|
||||
"CPU":"4x Cortex-A15 @ 1.9GHz 4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 5 Octa 5420"
|
||||
},
|
||||
"universal5422":{
|
||||
"CPU":"4x Cortex-A15 @ 2.1GHz 4x Cortex-A7 @ 1.5GHz",
|
||||
"SoC":"Exynos 5 Octa 5422"
|
||||
},
|
||||
"universal5430":{
|
||||
"CPU":"4x Cortex-A15 @ 1.8GHz 4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 5 Octa 5430"
|
||||
},
|
||||
"universal5800":{
|
||||
"CPU":"4x Cortex-A15 @ 2.0GHz 4x Cortex-A7 @ 1.3GHz",
|
||||
"SoC":"Exynos 5 Octa 5800"
|
||||
},
|
||||
"universal5433":{
|
||||
"CPU":"4x Cortex-A57 @ 1.9GHz 4x Cortex-A53 @ 1.3GHz",
|
||||
"SoC":"Exynos 7 Octa 5433"
|
||||
},
|
||||
"universal7420":{
|
||||
"CPU":"4x Cortex-A57 @ 2.1GHz 4x Cortex-A53 @ 1.5GHz",
|
||||
"SoC":"Exynos 7 Octa 7420"
|
||||
},
|
||||
"universal7570":{
|
||||
"CPU":"8x Cortex-A53 @ 1.4GHz",
|
||||
"SoC":"Exynos 7 Quad 7570"
|
||||
},
|
||||
"universal7580":{
|
||||
"CPU":"8x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 7 Octa 7580"
|
||||
},
|
||||
"universal7870":{
|
||||
"CPU":"8x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 7 Octa 7870"
|
||||
},
|
||||
"universal7880":{
|
||||
"CPU":"8x Cortex-A53 @ 1.9GHz",
|
||||
"SoC":"Exynos 7 Octa 7880"
|
||||
},
|
||||
"universal7872":{
|
||||
"CPU":"2x Cortex-A73 @ 2.0GHz 4x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 7 Hexa 7872"
|
||||
},
|
||||
"universal7885":{
|
||||
"CPU":"2x Cortex-A73 @ 2.2GHz 6x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 7 Octa 7885"
|
||||
},
|
||||
"universal8890":{
|
||||
"CPU":"4x Cortex-A53 @ 1.6GHz 4x Samsung Exynos M1 @ 2.6GHz",
|
||||
"SoC":"Exynos 8 Octa 8890"
|
||||
},
|
||||
"universal8895":{
|
||||
"CPU":"4x Samsung Exynos M1 @ 2.3GHz 4x Cortex-A53 @ 1.6GHz",
|
||||
"SoC":"Exynos 9 Octa 8895"
|
||||
},
|
||||
"universal9810":{
|
||||
"CPU":"4x Samsung Exynos M3 @ 2.8GHz 4x Cortex-A55 @ 1.7GHz",
|
||||
"SoC":"Exynos 9 Series 9810"
|
||||
},
|
||||
"universal9825":{
|
||||
"CPU":"2x Samsung Exynos M4 @ 2.7GHz 2x Cortex-A75 @ 2.4GHz 4x Cortex-A55 @ 1.9GHz",
|
||||
"SoC":"Exynos 9 Series 9825"
|
||||
},
|
||||
"ville":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 MSM8290"
|
||||
},
|
||||
"vog":{
|
||||
"CPU":"2x Cortex-A76 @ 2.6GHz 2x Cortex-A76 @ 1.9GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 980"
|
||||
},
|
||||
"venus":{
|
||||
"CPU":"1x Cortex-X1 @ 2.8GHz 3x Cortex-A78 @ 2.4GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Snapdragon 888 SM8350"
|
||||
},
|
||||
"vtr":{
|
||||
"CPU":"4x Cortex-A73 @ 2.4GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"HiSilicon Kirin 960"
|
||||
},
|
||||
"taimen":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"tn8":{
|
||||
"CPU":"4x Cortex-A15 @2.2GHz",
|
||||
"SoC":"nVIDIA Tegra K1 T124"
|
||||
},
|
||||
"taro":{
|
||||
"CPU":"1x Cortex-X2 @ 3GHz 3x Cortex-A710 @ 2.5GHz 4x Cortex-A510 @ 1.8GHz",
|
||||
"SoC":"Snapdragon 8 Gen 1 (SM8450)"
|
||||
},
|
||||
"thebes":{
|
||||
"CPU":"2x Cortex-A15 @ 1.5GHz 2x Cortex-A7 @ 1.2GHz",
|
||||
"SoC":"MediaTek MT8135"
|
||||
},
|
||||
"trinket":{
|
||||
"CPU":"4x Kryo 260 HP @ 2GHz 4x Kryo 260 LP @ 1.8GHz",
|
||||
"SoC":"Snapdragon 665 SM6125"
|
||||
},
|
||||
"tuna":{
|
||||
"CPU":"2x Cortex-A9 @ 1.2GHz",
|
||||
"SoC":"TI OMAP 4460"
|
||||
},
|
||||
"vu2kt":{
|
||||
"CPU":"2x Krait @ 1.5GHz",
|
||||
"SoC":"Snapdragon S4 Plus MSM8960"
|
||||
},
|
||||
"walleye":{
|
||||
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
|
||||
"SoC":"Snapdragon 835 MSM8998"
|
||||
},
|
||||
"wikipad":{
|
||||
"CPU":"4x Cortex-A9 @ 1.3GHz 1x Cortex-A9 @ 0.5GHz",
|
||||
"SoC":"nVIDIA Tegra 3 T30L"
|
||||
},
|
||||
"qc_reference_phone":{
|
||||
"CPU":"2x Kryo HP @ 2.15GHz 2x Kryo LP @ 1.36GHz",
|
||||
"SoC":"Snapdragon 820 MSM8996"
|
||||
},
|
||||
"z4u":{
|
||||
"CPU":"4x Cortex-A5 @ 1.2GHz",
|
||||
"SoC":"Snapdragon 200 MSM8225Q"
|
||||
},
|
||||
"zs600kl":{
|
||||
"CPU":"4x Kryo 385 Gold @ 3GHz 4x Kryo 385 Silver @ 1.8GHz",
|
||||
"SoC":"Snapdragon 845"
|
||||
},
|
||||
"mt6765v":{
|
||||
"CPU":"4x Cortex-A53 @ 2.3GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"Mediatek MT6765G Helio G35 (12 nm)"
|
||||
},
|
||||
"k65v1_64_bsp_titan_rat":{
|
||||
"CPU":"4x Cortex-A53 @ 2.3GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"Mediatek MT6765 Helio P35 (12nm)"
|
||||
},
|
||||
"careena":{
|
||||
"CPU":"AMD 670F00h",
|
||||
"SoC":"AMD A4-9120C RADEON R4, 5 COMPUTE CORES 2C+3G"
|
||||
},
|
||||
"sion":{
|
||||
"CPU":"i3-8130U CPU 2.2GHz SoC",
|
||||
"SoC":"i3-8130U CPU 2,2 GHz soc"
|
||||
},
|
||||
"biloba":{
|
||||
"CPU":"2x Cortex-A75 @ 2.0GHz 6x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Mediatek MT6769Z Helio G85 (12nm)"
|
||||
},
|
||||
"ele":{
|
||||
"CPU":"2x Cortex-A76 @ 2.6GHz 2x Cortex-A76 @ 1.9GHz 4x Cortex-A55 @ 1.8GHz",
|
||||
"SoC":"Kirin 980 (7 nm)"
|
||||
},
|
||||
"krane":{
|
||||
"CPU":"4x Cortex-A73 @ 2.0GHz + 4x Cortex-A53 @ 2.0GHz",
|
||||
"SoC":"MediaTekHelio P60T (12nm)"
|
||||
},
|
||||
"pineapple":{
|
||||
"CPU":"1x Cortex-X4 @ 3.3GHz 3x Cortex-A720 @ 3.2GHz 2x Cortex-A720 @ 2.6GHz 4x Cortex-A520 @ 2.3GHz",
|
||||
"SoC":"Qualcomm SM8650-AB Snapdragon 8 Gen 3"
|
||||
},
|
||||
"s5e9945":{
|
||||
"CPU":"1x Cortex-X4 @ 3.2GHz 2x Cortex-A720 @ 2.9GHz 3x Cortex-A720 @ 2.6GHz 4x Cortex-A520 @ 2.0GHz",
|
||||
"SoC":"Exynos 2400"
|
||||
},
|
||||
"a12":{
|
||||
"CPU":"4x Cortex-A53 @ 2.35GHz 4x Cortex-A53 @ 1.8GHz",
|
||||
"SoC":"MediaTek Helio P35 (MT6765)"
|
||||
}
|
||||
}
|
||||
@@ -67,10 +67,12 @@ import forge.util.ThreadUtil;
|
||||
import io.sentry.protocol.Device;
|
||||
import io.sentry.protocol.OperatingSystem;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.json.JSONObject;
|
||||
import org.jupnp.DefaultUpnpServiceConfiguration;
|
||||
import org.jupnp.android.AndroidUpnpServiceConfiguration;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
@@ -188,7 +190,26 @@ public class Main extends AndroidApplication {
|
||||
|
||||
boolean permissiongranted = checkPermission();
|
||||
Gadapter = new AndroidAdapter(getContext());
|
||||
|
||||
String cpu = "";
|
||||
String soc = "";
|
||||
boolean getChipset = false;
|
||||
// database.json source: https://github.com/xTheEc0/Android-Device-Hardware-Specs-Database
|
||||
try {
|
||||
InputStream is = getAssets().open("database.json");
|
||||
int size = is.available();
|
||||
byte[] buffer = new byte[size];
|
||||
is.read(buffer);
|
||||
is.close();
|
||||
JSONObject db = new JSONObject(new String(buffer, StandardCharsets.UTF_8));
|
||||
JSONObject board = db.getJSONObject(Build.BOARD);
|
||||
cpu = board.get("CPU").toString();
|
||||
soc = board.get("SoC").toString();
|
||||
getChipset = true;
|
||||
} catch (Exception e) {
|
||||
cpu = getCpuName();
|
||||
soc = Build.BOARD;
|
||||
getChipset = false;
|
||||
}
|
||||
// Device Info
|
||||
Device device = new Device();
|
||||
device.setId(Build.ID);
|
||||
@@ -197,8 +218,8 @@ public class Main extends AndroidApplication {
|
||||
device.setBrand(Build.BRAND);
|
||||
device.setManufacturer(Build.MANUFACTURER);
|
||||
device.setMemorySize(memInfo.totalMem);
|
||||
device.setCpuDescription(getCpuName());
|
||||
device.setChipset(Build.HARDWARE + " " + Build.BOARD);
|
||||
device.setCpuDescription(cpu);
|
||||
device.setChipset(soc);
|
||||
// OS Info
|
||||
OperatingSystem os = new OperatingSystem();
|
||||
os.setName("Android");
|
||||
@@ -206,7 +227,7 @@ public class Main extends AndroidApplication {
|
||||
os.setBuild(Build.DISPLAY);
|
||||
os.setRawDescription(getAndroidOSName());
|
||||
|
||||
initForge(Gadapter, new HWInfo(device, os), permissiongranted, totalMemory, isTabletDevice(getContext()));
|
||||
initForge(Gadapter, new HWInfo(device, os, getChipset), permissiongranted, totalMemory, isTabletDevice(getContext()));
|
||||
}
|
||||
|
||||
private void crossfade(View contentView, View previousView) {
|
||||
|
||||
@@ -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,10 +242,16 @@ public enum CSubmenuDraft implements ICDoc {
|
||||
combo.addItem("Gauntlet");
|
||||
//combo.addItem("Tournament");
|
||||
} else {
|
||||
int size = opponentDecks.getAiDecks().size();
|
||||
combo.addItem("2");
|
||||
combo.addItem("3");
|
||||
combo.addItem("4");
|
||||
combo.addItem("5");
|
||||
if (size > 2) {
|
||||
combo.addItem("3");
|
||||
}
|
||||
|
||||
if (size >= 4) {
|
||||
combo.addItem("4");
|
||||
combo.addItem("5");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ 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");
|
||||
|
||||
@@ -130,6 +130,10 @@ 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());
|
||||
@@ -163,6 +167,7 @@ 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.");
|
||||
}
|
||||
|
||||
@@ -176,7 +181,7 @@ public class SimulateMatch {
|
||||
TimeLimitedCodeBlock.runWithTimeout(() -> {
|
||||
mc.startGame(g1);
|
||||
sw.stop();
|
||||
}, 120, TimeUnit.SECONDS);
|
||||
}, mc.getRules().getSimTimeout(), TimeUnit.SECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
System.out.println("Stopping slow match as draw");
|
||||
} catch (Exception | StackOverflowError e) {
|
||||
|
||||
@@ -664,6 +664,11 @@ 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);
|
||||
hw = new HWInfo(device, os, false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ public class Forge implements ApplicationListener {
|
||||
scope.getContexts().setOperatingSystem(hwInfo.os());
|
||||
});
|
||||
}
|
||||
GuiBase.setDeviceInfo(hwInfo, AndroidAPI, totalRAM);
|
||||
GuiBase.setDeviceInfo(hwInfo, AndroidAPI, totalRAM, deviceAdapter.getDownloadsDir());
|
||||
}
|
||||
return app;
|
||||
}
|
||||
@@ -171,18 +171,7 @@ public class Forge implements ApplicationListener {
|
||||
//install our error handler
|
||||
ExceptionHandler.registerErrorHandling();
|
||||
//init hwInfo to log
|
||||
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##########################################"
|
||||
);
|
||||
}
|
||||
System.out.println(GuiBase.getHWInfo());
|
||||
// closeSplashScreen() is called early on non-Windows OS so it will not crash, LWJGL3 bug on AWT Splash.
|
||||
if (OperatingSystem.isWindows())
|
||||
getDeviceAdapter().closeSplashScreen();
|
||||
|
||||
@@ -1419,6 +1419,7 @@ 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;
|
||||
|
||||
@@ -83,7 +83,7 @@ public class DeckSelectScene extends UIScene {
|
||||
|
||||
private void layoutDeckButtons() {
|
||||
for (int i = 0; i < AdventurePlayer.current().getDeckCount(); i++)
|
||||
addDeckButton(Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1), i);
|
||||
addDeckButton(i);
|
||||
}
|
||||
|
||||
private void addDeck(){
|
||||
@@ -144,6 +144,7 @@ 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();
|
||||
@@ -177,8 +178,9 @@ public class DeckSelectScene extends UIScene {
|
||||
}
|
||||
}
|
||||
|
||||
private TextraButton addDeckButton(String name, int i) {
|
||||
private TextraButton addDeckButton(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) {
|
||||
|
||||
@@ -192,18 +192,19 @@ public class DuelScene extends ForgeScene {
|
||||
@Override
|
||||
public void enter() {
|
||||
GameHUD.getInstance().unloadAudio();
|
||||
Set<GameType> appliedVariants = new HashSet<>();
|
||||
GameType mainGameType;
|
||||
if (eventData != null && eventData.eventRules != null) {
|
||||
appliedVariants.add(eventData.eventRules.gameType);
|
||||
mainGameType = eventData.eventRules.gameType;
|
||||
} else {
|
||||
appliedVariants.add(GameType.Adventure);
|
||||
mainGameType = GameType.Adventure;
|
||||
}
|
||||
Set<GameType> appliedVariants = EnumSet.of(mainGameType);
|
||||
|
||||
AdventurePlayer advPlayer = Current.player();
|
||||
|
||||
List<RegisteredPlayer> players = new ArrayList<>();
|
||||
|
||||
applyAdventureDeckRules();
|
||||
applyAdventureDeckRules(mainGameType.getDeckFormat());
|
||||
int playerCount = 1;
|
||||
EnemyData currentEnemy = enemy.getData();
|
||||
for (int i = 0; i < 8 && currentEnemy != null; i++) {
|
||||
@@ -393,16 +394,25 @@ 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() {
|
||||
private void applyAdventureDeckRules(DeckFormat format) {
|
||||
//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 missingCards = Config.instance().getConfigData().minDeckSize - mainSection.countAll();
|
||||
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;
|
||||
if (missingCards > 0) //Replace unknown cards for a Wastes.
|
||||
mainSection.add(PLACEHOLDER_MAIN, missingCards);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
captureException(e, key, subData);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +34,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -45,6 +47,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -57,6 +60,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -69,6 +73,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -81,6 +86,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -95,6 +101,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
stream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -107,6 +114,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
captureException(e, key, subData);
|
||||
}
|
||||
}
|
||||
@@ -119,6 +127,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
captureException(e, key, subData);
|
||||
}
|
||||
}
|
||||
@@ -132,6 +141,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -147,6 +157,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
|
||||
objStream.flush();
|
||||
put(key, stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
put("IOException", e.toString().getBytes());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,53 +26,50 @@ import java.util.zip.InflaterInputStream;
|
||||
/**
|
||||
* Represents everything that will be saved, like the player and the world.
|
||||
*/
|
||||
public class WorldSave {
|
||||
public class WorldSave {
|
||||
|
||||
static final public int AUTO_SAVE_SLOT =-1;
|
||||
static final public int QUICK_SAVE_SLOT =-2;
|
||||
static final public int INVALID_SAVE_SLOT =-3;
|
||||
static final WorldSave currentSave=new WorldSave();
|
||||
static final public int AUTO_SAVE_SLOT = -1;
|
||||
static final public int QUICK_SAVE_SLOT = -2;
|
||||
static final public int INVALID_SAVE_SLOT = -3;
|
||||
static final WorldSave currentSave = new WorldSave();
|
||||
public WorldSaveHeader header = new WorldSaveHeader();
|
||||
private final AdventurePlayer player=new AdventurePlayer();
|
||||
private final World world=new World();
|
||||
private final PointOfInterestChanges.Map pointOfInterestChanges= new PointOfInterestChanges.Map();
|
||||
private final AdventurePlayer player = new AdventurePlayer();
|
||||
private final World world = new World();
|
||||
private final PointOfInterestChanges.Map pointOfInterestChanges = new PointOfInterestChanges.Map();
|
||||
|
||||
|
||||
private final SignalList onLoadList=new SignalList();
|
||||
private final SignalList onLoadList = new SignalList();
|
||||
|
||||
public final World getWorld()
|
||||
{
|
||||
public final World getWorld() {
|
||||
return world;
|
||||
}
|
||||
public AdventurePlayer getPlayer()
|
||||
{
|
||||
|
||||
public AdventurePlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public void onLoad(Runnable run)
|
||||
{
|
||||
public void onLoad(Runnable run) {
|
||||
onLoadList.add(run);
|
||||
}
|
||||
public PointOfInterestChanges getPointOfInterestChanges(String id)
|
||||
{
|
||||
if(!pointOfInterestChanges.containsKey(id))
|
||||
pointOfInterestChanges.put(id,new PointOfInterestChanges());
|
||||
|
||||
public PointOfInterestChanges getPointOfInterestChanges(String id) {
|
||||
if (!pointOfInterestChanges.containsKey(id))
|
||||
pointOfInterestChanges.put(id, new PointOfInterestChanges());
|
||||
return pointOfInterestChanges.get(id);
|
||||
}
|
||||
|
||||
static public boolean load(int currentSlot) {
|
||||
|
||||
String fileName = WorldSave.getSaveFile(currentSlot);
|
||||
if(!new File(fileName).exists())
|
||||
if (!new File(fileName).exists())
|
||||
return false;
|
||||
new File(getSaveDir()).mkdirs();
|
||||
try {
|
||||
try(FileInputStream fos = new FileInputStream(fileName);
|
||||
InflaterInputStream inf = new InflaterInputStream(fos);
|
||||
ObjectInputStream oos = new ObjectInputStream(inf))
|
||||
{
|
||||
try (FileInputStream fos = new FileInputStream(fileName);
|
||||
InflaterInputStream inf = new InflaterInputStream(fos);
|
||||
ObjectInputStream oos = new ObjectInputStream(inf)) {
|
||||
currentSave.header = (WorldSaveHeader) oos.readObject();
|
||||
SaveFileData mainData=(SaveFileData)oos.readObject();
|
||||
SaveFileData mainData = (SaveFileData) oos.readObject();
|
||||
currentSave.player.load(mainData.readSubData("player"));
|
||||
GamePlayerUtil.getGuiPlayer().setName(currentSave.player.getName());
|
||||
try {
|
||||
@@ -95,9 +92,11 @@ public class WorldSave {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isSafeFile(String name) {
|
||||
return filenameToSlot(name)!= INVALID_SAVE_SLOT;
|
||||
return filenameToSlot(name) != INVALID_SAVE_SLOT;
|
||||
}
|
||||
|
||||
static public int filenameToSlot(String name) {
|
||||
if (name.equals("auto_save.sav"))
|
||||
return AUTO_SAVE_SLOT;
|
||||
@@ -131,10 +130,10 @@ public class WorldSave {
|
||||
public static WorldSave generateNewWorld(String name, boolean male, int race, int avatarIndex, ColorSet startingColorIdentity, DifficultyData diff, AdventureModes mode, int customDeckIndex, CardEdition starterEdition, long seed) {
|
||||
currentSave.world.generateNew(seed);
|
||||
currentSave.pointOfInterestChanges.clear();
|
||||
boolean chaos=mode==AdventureModes.Chaos;
|
||||
boolean custom=mode==AdventureModes.Custom;
|
||||
Deck starterDeck = Config.instance().starterDeck(startingColorIdentity,diff,mode,customDeckIndex,starterEdition);
|
||||
currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff);
|
||||
boolean chaos = mode == AdventureModes.Chaos;
|
||||
boolean custom = mode == AdventureModes.Custom;
|
||||
Deck starterDeck = Config.instance().starterDeck(startingColorIdentity, diff, mode, customDeckIndex, starterEdition);
|
||||
currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff);
|
||||
currentSave.player.setWorldPosY((int) (currentSave.world.getData().playerStartPosY * currentSave.world.getData().height * currentSave.world.getTileSize()));
|
||||
currentSave.player.setWorldPosX((int) (currentSave.world.getData().playerStartPosX * currentSave.world.getData().width * currentSave.world.getTileSize()));
|
||||
currentSave.onLoadList.emit();
|
||||
@@ -142,46 +141,103 @@ public class WorldSave {
|
||||
}
|
||||
|
||||
public boolean autoSave() {
|
||||
return save("auto save"+ SaveLoadScene.instance().getSaveFileSuffix(),AUTO_SAVE_SLOT);
|
||||
return save("auto save" + SaveLoadScene.instance().getSaveFileSuffix(), AUTO_SAVE_SLOT);
|
||||
}
|
||||
|
||||
public boolean quickSave() {
|
||||
return save("quick save"+ SaveLoadScene.instance().getSaveFileSuffix(),QUICK_SAVE_SLOT);
|
||||
return save("quick save" + SaveLoadScene.instance().getSaveFileSuffix(), QUICK_SAVE_SLOT);
|
||||
}
|
||||
|
||||
public boolean quickLoad() {
|
||||
return load(QUICK_SAVE_SLOT);
|
||||
}
|
||||
|
||||
public boolean save(String text, int currentSlot) {
|
||||
header.name = text;
|
||||
|
||||
String fileName = WorldSave.getSaveFile(currentSlot);
|
||||
String oldFileName = fileName.replace(".sav", ".old");
|
||||
new File(getSaveDir()).mkdirs();
|
||||
File currentFile = new File(fileName);
|
||||
File backupFile = new File(oldFileName);
|
||||
if (currentFile.exists())
|
||||
currentFile.renameTo(backupFile);
|
||||
|
||||
try {
|
||||
try(FileOutputStream fos = new FileOutputStream(fileName);
|
||||
DeflaterOutputStream def= new DeflaterOutputStream(fos);
|
||||
ObjectOutputStream oos = new ObjectOutputStream(def))
|
||||
{
|
||||
header.saveDate= new Date();
|
||||
oos.writeObject(header);
|
||||
SaveFileData mainData=new SaveFileData();
|
||||
mainData.store("player",currentSave.player.save());
|
||||
mainData.store("world",currentSave.world.save());
|
||||
mainData.store("worldStage", WorldStage.getInstance().save());
|
||||
mainData.store("pointOfInterestChanges",currentSave.pointOfInterestChanges.save());
|
||||
try (FileOutputStream fos = new FileOutputStream(fileName);
|
||||
DeflaterOutputStream def = new DeflaterOutputStream(fos);
|
||||
ObjectOutputStream oos = new ObjectOutputStream(def)) {
|
||||
SaveFileData player = currentSave.player.save();
|
||||
SaveFileData world = currentSave.world.save();
|
||||
SaveFileData worldStage = WorldStage.getInstance().save();
|
||||
SaveFileData poiChanges = currentSave.pointOfInterestChanges.save();
|
||||
|
||||
String message = getExceptionMessage(player, world, worldStage, poiChanges);
|
||||
if (!message.isEmpty()) {
|
||||
oos.close();
|
||||
fos.close();
|
||||
restoreBackup(oldFileName, fileName);
|
||||
announceError(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
SaveFileData mainData = new SaveFileData();
|
||||
mainData.store("player", player);
|
||||
mainData.store("world", world);
|
||||
mainData.store("worldStage", worldStage);
|
||||
mainData.store("pointOfInterestChanges", poiChanges);
|
||||
|
||||
if (mainData.readString("IOException") != null) {
|
||||
oos.close();
|
||||
fos.close();
|
||||
restoreBackup(oldFileName, fileName);
|
||||
announceError("Please check forge.log for errors.");
|
||||
return true;
|
||||
}
|
||||
|
||||
header.saveDate = new Date();
|
||||
oos.writeObject(header);
|
||||
oos.writeObject(mainData);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
restoreBackup(oldFileName, fileName);
|
||||
announceError("Please check forge.log for errors.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Config.instance().getSettingData().lastActiveSave = WorldSave.filename(currentSlot);
|
||||
Config.instance().saveSettings();
|
||||
if (backupFile.exists())
|
||||
backupFile.delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void restoreBackup(String oldFilename, String currentFilename) {
|
||||
File f = new File(currentFilename);
|
||||
if (f.exists())
|
||||
f.delete();
|
||||
File b = new File(oldFilename);
|
||||
if (b.exists())
|
||||
b.renameTo(new File(currentFilename));
|
||||
}
|
||||
|
||||
public String getExceptionMessage(SaveFileData... datas) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
|
||||
for (SaveFileData data : datas) {
|
||||
String s = data.readString("IOException");
|
||||
if (s != null)
|
||||
message.append(s).append("\n");
|
||||
}
|
||||
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
private void announceError(String message) {
|
||||
currentSave.player.getCurrentGameStage().setExtraAnnouncement("Error Saving File!\n" + message);
|
||||
}
|
||||
|
||||
public void clearChanges() {
|
||||
pointOfInterestChanges.clear();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,10 @@ public class BugReportDialog extends FScreen { //use screen rather than dialog s
|
||||
BugReporter.sendSentry();
|
||||
Forge.back();
|
||||
});
|
||||
btnSave.setCommand(e -> BugReporter.saveToFile(tvDetails.text));
|
||||
btnSave.setCommand(e -> {
|
||||
BugReporter.saveToFile(tvDetails.text);
|
||||
Forge.back();
|
||||
});
|
||||
btnDiscard.setCommand(e -> Forge.back());
|
||||
if (showExitAppBtn) {
|
||||
btnExit.setCommand(e -> Forge.exit(true));
|
||||
|
||||
@@ -915,7 +915,7 @@ public abstract class ItemManager<T extends InventoryItem> extends FContainer im
|
||||
|
||||
Iterable<Entry<T, Integer>> items = pool;
|
||||
if (useFilter) {
|
||||
Predicate<Entry<T, Integer>> pred = x -> filterPredicate.test(x.getKey());
|
||||
Predicate<Entry<T, Integer>> pred = x -> x != null && filterPredicate.test(x.getKey());
|
||||
items = IterableUtil.filter(pool, pred);
|
||||
}
|
||||
model.addItems(items);
|
||||
|
||||
@@ -78,6 +78,59 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
private Supplier<List<Group>> groups = Suppliers.memoize(ArrayList::new);
|
||||
private Function<Entry<? extends InventoryItem, Integer>, ?> fnIsFavorite = ColumnDef.FAVORITE.fnDisplay, fnPrice = null;
|
||||
|
||||
private class SafeList<T> {
|
||||
private final List<T> internalList;
|
||||
private final Object lock = new Object(); // Object for synchronization
|
||||
|
||||
private SafeList() {
|
||||
this.internalList = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void add(T element) {
|
||||
synchronized (lock) {
|
||||
internalList.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
private T get(int index) {
|
||||
synchronized (lock) {
|
||||
return internalList.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
private T remove(int index) {
|
||||
synchronized (lock) {
|
||||
return internalList.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
private int size() {
|
||||
synchronized (lock) {
|
||||
return internalList.size();
|
||||
}
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
synchronized (lock) {
|
||||
internalList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
synchronized (lock) {
|
||||
return internalList.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean addAll(Collection c) {
|
||||
synchronized (lock) {
|
||||
return internalList.addAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
// Add other list operations as needed, ensuring synchronization
|
||||
}
|
||||
|
||||
private class ExpandCollapseButton extends FLabel {
|
||||
private boolean isAllCollapsed;
|
||||
|
||||
@@ -330,11 +383,17 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
if (group.getBottom() < visibleTop) {
|
||||
continue;
|
||||
}
|
||||
for (Pile pile : group.piles) {
|
||||
for (int i = 0; i < group.piles.size(); i++) {
|
||||
Pile pile = group.piles.get(i);
|
||||
if (pile == null)
|
||||
continue;
|
||||
if (group.getBottom() < visibleTop) {
|
||||
continue;
|
||||
}
|
||||
for (ItemInfo item : pile.items) {
|
||||
for (int j = 0; j < pile.items.size(); j++) {
|
||||
ItemInfo item = pile.items.get(j);
|
||||
if (item == null)
|
||||
continue;
|
||||
if (item.getTop() >= visibleTop) {
|
||||
return item;
|
||||
}
|
||||
@@ -454,7 +513,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
//use TreeMap to build pile set so iterating below sorts on key
|
||||
ColumnDef groupPileBy = groupBy == null ? pileBy : groupBy.getGroupPileBy(i, pileBy);
|
||||
Map<Comparable<?>, Pile> piles = new TreeMap<>();
|
||||
for (ItemInfo itemInfo : group.items) {
|
||||
for (int j = 0; j < group.items.size(); j++) {
|
||||
ItemInfo itemInfo = group.items.get(j);
|
||||
if (itemInfo == null)
|
||||
continue;
|
||||
Comparable<?> key = groupPileBy.fnSort.apply(itemInfo);
|
||||
if (key != null && !piles.containsKey(key)) {
|
||||
piles.put(key, new Pile());
|
||||
@@ -490,7 +552,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
Pile pile = new Pile();
|
||||
x = 0;
|
||||
|
||||
for (ItemInfo itemInfo : group.items) {
|
||||
for (int j = 0; j < group.items.size(); j++) {
|
||||
ItemInfo itemInfo = group.items.get(j);
|
||||
if (itemInfo == null)
|
||||
continue;
|
||||
itemInfo.pos = CardStackPosition.Top;
|
||||
|
||||
if (pile.items.size() == columnCount) {
|
||||
@@ -517,7 +582,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
for (int j = 0; j < group.piles.size(); j++) {
|
||||
Pile pile = group.piles.get(j);
|
||||
y = pileY;
|
||||
for (ItemInfo itemInfo : pile.items) {
|
||||
for (int k = 0; k < pile.items.size(); k++) {
|
||||
ItemInfo itemInfo = pile.items.get(k);
|
||||
if (itemInfo == null)
|
||||
continue;
|
||||
itemInfo.pos = CardStackPosition.BehindVert;
|
||||
itemInfo.setBounds(x, y, itemWidth, itemHeight);
|
||||
y += dy;
|
||||
@@ -549,15 +617,24 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
|
||||
if (group.isCollapsed && pileBy == null) {
|
||||
//Piles won't have been generated in this case.
|
||||
for(ItemInfo itemInfo : group.items) {
|
||||
for (int i = 0; i < group.items.size(); i++) {
|
||||
ItemInfo itemInfo = group.items.get(i);
|
||||
if (itemInfo == null)
|
||||
continue;
|
||||
itemInfo.index = index++;
|
||||
orderedItems.get().add(itemInfo);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Pile pile : group.piles) {
|
||||
for (ItemInfo itemInfo : pile.items) {
|
||||
for (int i = 0; i < group.piles.size(); i++) {
|
||||
Pile pile = group.piles.get(i);
|
||||
if (pile == null)
|
||||
continue;
|
||||
for (int j = 0; j < pile.items.size(); j++) {
|
||||
ItemInfo itemInfo = pile.items.get(j);
|
||||
if (itemInfo == null)
|
||||
continue;
|
||||
itemInfo.index = index++;
|
||||
orderedItems.get().add(itemInfo);
|
||||
}
|
||||
@@ -655,7 +732,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
@Override
|
||||
public int getIndexOfItem(T item) {
|
||||
for (Group group : groups.get()) {
|
||||
for (ItemInfo itemInfo : group.items) {
|
||||
for (int i = 0; i < group.items.size(); i++) {
|
||||
ItemInfo itemInfo = group.items.get(i);
|
||||
if (itemInfo == null)
|
||||
continue;
|
||||
if (itemInfo.item == item) {
|
||||
//if group containing item is collapsed, expand it so the item can be selected and has a valid index
|
||||
if (group.isCollapsed) {
|
||||
@@ -838,8 +918,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
}
|
||||
|
||||
private class Group extends FScrollPane {
|
||||
private final List<ItemInfo> items = new ArrayList<>();
|
||||
private final List<Pile> piles = new ArrayList<>();
|
||||
private final SafeList<ItemInfo> items = new SafeList<>();
|
||||
private final SafeList<Pile> piles = new SafeList<>();
|
||||
private final String name;
|
||||
private boolean isCollapsed;
|
||||
private float scrollWidth;
|
||||
@@ -896,7 +976,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
|
||||
float visibleLeft = getScrollLeft();
|
||||
float visibleRight = visibleLeft + getWidth();
|
||||
for (Pile pile : piles) {
|
||||
for (int i = 0; i < piles.size(); i++) {
|
||||
Pile pile = piles.get(i);
|
||||
if (pile == null)
|
||||
continue;
|
||||
if (pile.getRight() < visibleLeft) {
|
||||
@@ -964,7 +1045,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
}
|
||||
|
||||
private class Pile extends FDisplayObject {
|
||||
private final List<ItemInfo> items = new ArrayList<>();
|
||||
private final SafeList<ItemInfo> items = new SafeList<>();
|
||||
|
||||
@Override
|
||||
public void draw(Graphics g) {
|
||||
@@ -972,7 +1053,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
|
||||
final float visibleBottom = visibleTop + getScroller().getHeight();
|
||||
|
||||
ItemInfo skippedItem = null;
|
||||
for (ItemInfo itemInfo : items) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
ItemInfo itemInfo = items.get(i);
|
||||
if (itemInfo == null)
|
||||
continue;
|
||||
if (itemInfo.getBottom() < visibleTop) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ public final class ItemListView<T extends InventoryItem> extends ItemView<T> {
|
||||
|
||||
@Override
|
||||
protected void onRefresh() {
|
||||
list.setListData(model.getOrderedList());
|
||||
list.setListData(new ArrayList<>(model.getOrderedList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,66 +1,237 @@
|
||||
0nderzeeboot
|
||||
Adajone
|
||||
add-le
|
||||
Agetian
|
||||
allentiak
|
||||
Alumi
|
||||
Alwayssnarky
|
||||
amang876
|
||||
amikano
|
||||
Ampersandnz
|
||||
antoniomartinelli
|
||||
Aoki-Kujo
|
||||
apantel
|
||||
Arguas
|
||||
ArtjomsPorss
|
||||
asvitkine
|
||||
austeregrim
|
||||
Austinio7116
|
||||
autumnmyst
|
||||
AvacynAngel
|
||||
Ayora29
|
||||
beemaisonp
|
||||
Benjamin
|
||||
BigCrunch22
|
||||
BlindGuyNW
|
||||
bountygiver
|
||||
brngl
|
||||
brockfanning
|
||||
CCTV-1
|
||||
cdietschrun
|
||||
ChristopherLoper
|
||||
Churrufli
|
||||
cicoa
|
||||
CodeOfTheRing
|
||||
CollinJ
|
||||
Compterra
|
||||
CTimmerman
|
||||
Cyantime
|
||||
datuilabs
|
||||
davidjdiebold
|
||||
davison-cf
|
||||
dennisfriedrichsen
|
||||
dennisvlahos
|
||||
DerpaholicRex
|
||||
dev-id
|
||||
Dnaynu
|
||||
DorkmasterFlek
|
||||
Dracontes
|
||||
drdemp72
|
||||
DrDev
|
||||
Drecon84
|
||||
earno12
|
||||
edg444
|
||||
EfourC
|
||||
EldritchBimbo
|
||||
Ellios77
|
||||
Elwin
|
||||
entrailmix
|
||||
Eradev
|
||||
EvanMurawski
|
||||
excessum
|
||||
fernandomfgomes
|
||||
FLAREdirector-mse
|
||||
Flair
|
||||
Fregnor
|
||||
Fulgur14
|
||||
gabrielmop
|
||||
Gadget2013
|
||||
Glorax
|
||||
Gos
|
||||
Grimm
|
||||
GroundThing
|
||||
gsonnier333
|
||||
guytrash
|
||||
Hanmac
|
||||
HassanSky81
|
||||
HeitorBittenc
|
||||
Heitorpayao
|
||||
henges
|
||||
hovergoat
|
||||
hungriestPigeon
|
||||
hypercross
|
||||
Hythonia
|
||||
IceBen4444
|
||||
icy-
|
||||
imakunee
|
||||
Indigo Dragon
|
||||
invalidCards
|
||||
isaacbutterfield
|
||||
Ivniinvi
|
||||
JakeLoustone
|
||||
Jamin Collins
|
||||
jcb936
|
||||
jeb886
|
||||
Jefik37
|
||||
Jetz72
|
||||
jjayers99
|
||||
jkarlsson
|
||||
jochemvanthull-cpu
|
||||
John
|
||||
JohnWilliams77
|
||||
joongiealexkim
|
||||
Jorilx
|
||||
jpvorenk
|
||||
jumpinjackie
|
||||
junytang
|
||||
jyockey
|
||||
karlek
|
||||
keineahnungking
|
||||
kenizl86
|
||||
Kev-inBacon
|
||||
kevlahnota
|
||||
klaxnek
|
||||
Klisz
|
||||
kms70847
|
||||
krafczyk
|
||||
KrazyTheFox
|
||||
kvn1338
|
||||
LAHardman
|
||||
LargeGutSalesman
|
||||
lemtom
|
||||
leriomaggio
|
||||
loud1990
|
||||
Luke
|
||||
lukeway
|
||||
Lykrast
|
||||
MAC-GH
|
||||
maforget
|
||||
Magpie
|
||||
magpie514
|
||||
magusnebula
|
||||
MarcoFazioRandom
|
||||
Marek14
|
||||
marthinwurer
|
||||
Marvel
|
||||
maulet218
|
||||
mcrawford
|
||||
mctubbies
|
||||
MD200210
|
||||
medusa
|
||||
Meerkov
|
||||
mewtwo15026
|
||||
MIC132
|
||||
MikeS-NZ
|
||||
misha-colbourne
|
||||
MisterVitoPro
|
||||
mmw125
|
||||
Monkey Gland Sauce
|
||||
Morgenmvffel
|
||||
mousep
|
||||
mtwilliams14
|
||||
Myrd
|
||||
myyk
|
||||
NCat39
|
||||
nefigah
|
||||
neoFuzz
|
||||
NicolasCunha
|
||||
NikolayXHD
|
||||
NimSpork
|
||||
Nionios
|
||||
NishacIroCode
|
||||
Northmoc
|
||||
nshcat
|
||||
nthoron
|
||||
NuWiSan
|
||||
OgreBattlecruiser
|
||||
pakoito
|
||||
paulsnoops
|
||||
pduran5
|
||||
petersul
|
||||
pferreir
|
||||
pfirpfel
|
||||
pfps
|
||||
pochiel
|
||||
pvishalkeerthan
|
||||
rawbeans
|
||||
remggo
|
||||
rikimbo
|
||||
riku4470
|
||||
Ral
|
||||
Robbatog
|
||||
rpg2014
|
||||
rsnively
|
||||
Ryan1729
|
||||
ryanehamil
|
||||
SapphiCat
|
||||
schnautzr
|
||||
Seravy
|
||||
SeravySensei
|
||||
SethMilliken
|
||||
Shedletsky
|
||||
shenshinoman
|
||||
Simisays
|
||||
Sirspud
|
||||
SladeWilson
|
||||
Sloth
|
||||
slyfox7777777
|
||||
Snoops
|
||||
Sol
|
||||
SprinkleMeTimbers
|
||||
squee1968
|
||||
stubobis1
|
||||
Sunnovah
|
||||
Swordshine
|
||||
Swordshinehjy
|
||||
Suthro
|
||||
Svaldan
|
||||
t-w-o-s-a-t
|
||||
tehdiplomat
|
||||
The Cheese Stands Alone
|
||||
The-Wolverine28
|
||||
TheBlackMarvel
|
||||
thedevnull
|
||||
TheLastNarwhal
|
||||
thenobletheif
|
||||
Tillerino
|
||||
timmermac
|
||||
TimothyWright95
|
||||
tjtillman
|
||||
tojammot
|
||||
tool4ever
|
||||
torridus
|
||||
TrueFuFLeaderG
|
||||
TwentyToedToad
|
||||
twosat
|
||||
Valensior
|
||||
verifiedtm
|
||||
wcoldren
|
||||
Wild-W
|
||||
xanxer6rB
|
||||
XavierMD
|
||||
Xyx
|
||||
ZacharyDeganutti
|
||||
Zimtente
|
||||
Zuchinni
|
||||
XavierMD
|
||||
|
||||
(If you think your name should be on this list, add it with your next contribution)
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:4
|
||||
Types:Creature Avatar
|
||||
PT:3/4
|
||||
K:Vigilance
|
||||
A:AB$ CopySpellAbility | Cost$ 1 T | TgtPrompt$ Select target activated or triggered ability you control from a colorless source | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Card.Colorless | AILogic$ AlwaysCopyActivatedAbilities | MayChooseTarget$ True | SpellDescription$ Copy target activated or triggered ability you control from a colorless source. You may choose new targets for the copy. (Mana abilities can't be targeted.)
|
||||
A:AB$ CopySpellAbility | Cost$ 1 T | TgtPrompt$ Select target activated or triggered ability you control from a colorless source | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Card.Colorless,Emblem | AILogic$ AlwaysCopyActivatedAbilities | MayChooseTarget$ True | SpellDescription$ Copy target activated or triggered ability you control from a colorless source. You may choose new targets for the copy. (Mana abilities can't be targeted.)
|
||||
DeckHints:Type$Artifact|Eldrazi
|
||||
Oracle:Vigilance\n{1}, {T}: Copy target activated or triggered ability you control from a colorless source. You may choose new targets for the copy. (Mana abilities can't be targeted.)
|
||||
|
||||
10
forge-gui/res/cardsfolder/a/agent_venom.txt
Normal file
10
forge-gui/res/cardsfolder/a/agent_venom.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Name:Agent Venom
|
||||
ManaCost:2 B
|
||||
Types:Legendary Creature Symbiote Soldier Hero
|
||||
PT:2/3
|
||||
K:Flash
|
||||
K:Menace
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.Other+!token+YouCtrl | Execute$ TrigDraw | TriggerDescription$ Whenever another nontoken creature you control dies, you draw a card and lose 1 life.
|
||||
SVar:TrigDraw:DB$ Draw | SubAbility$ DBLoseLife
|
||||
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
|
||||
Oracle:Flash\nMenace\nWhenever another nontoken creature you control dies, you draw a card and lose 1 life.
|
||||
9
forge-gui/res/cardsfolder/a/alien_symbiosis.txt
Normal file
9
forge-gui/res/cardsfolder/a/alien_symbiosis.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Name:Alien Symbiosis
|
||||
ManaCost:1 B
|
||||
Types:Enchantment Aura
|
||||
K:Enchant:Creature
|
||||
SVar:AttachAILogic:Pump
|
||||
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | AddToughness$ 1 | AddKeyword$ Menace | AddType$ Symbiote | Description$ Enchanted creature gets +1/+1, has menace, and is a Symbiote in addition to its other types.
|
||||
S:Mode$ Continuous | Affected$ Card.Self | MayPlay$ True | AffectedZone$ Graveyard | EffectZone$ Graveyard | RaiseCost$ Discard<1/Card> | Description$ You may cast this card from your graveyard by discarding a card in addition to paying its other costs.
|
||||
DeckHas:Ability$Graveyard|Discard
|
||||
Oracle:Enchant creature\nEnchanted creature gets +1/+1, has menace, and is a Symbiote in addition to its other types.\nYou may cast this card from your graveyard by discarding a card in addition to paying its other costs.
|
||||
7
forge-gui/res/cardsfolder/a/amazing_acrobatics.txt
Normal file
7
forge-gui/res/cardsfolder/a/amazing_acrobatics.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Name:Amazing Acrobatics
|
||||
ManaCost:1 U U
|
||||
Types:Instant
|
||||
A:SP$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBCounter,DBTap
|
||||
SVar:DBCounter:DB$ Counter | TargetType$ Spell | ValidTgts$ Card | TgtPrompt$ Counter target spell | SpellDescription$ Counter target spell.
|
||||
SVar:DBTap:DB$ Tap | TargetMin$ 1 | TargetMax$ 2 | ValidTgts$ Creature | TgtPrompt$ Choose one or two target creatures | SpellDescription$ Tap one or two target creatures.
|
||||
Oracle:Choose one or both —\n• Counter target spell.\n• Tap one or two target creatures.
|
||||
11
forge-gui/res/cardsfolder/a/ambassador_of_evendo.txt
Normal file
11
forge-gui/res/cardsfolder/a/ambassador_of_evendo.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Name:Ambassador of Evendo
|
||||
ManaCost:G U
|
||||
Types:Creature Insect Advisor
|
||||
PT:1/3
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigRandom | TriggerDescription$ Landfall — Whenever a land you control enters, a random land card in your library perpetually gains "Whenever this land becomes tapped, draw a card."
|
||||
SVar:TrigRandom:DB$ ChooseCard | Choices$ Land.YouOwn | ChoiceZone$ Library | AtRandom$ True | Amount$ 1 | SubAbility$ DBAnimate
|
||||
SVar:DBAnimate:DB$ Animate | Defined$ ChosenCard | Triggers$ LandTapDraw | Duration$ Perpetual | SubAbility$ DBCleanup
|
||||
SVar:LandTapDraw:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever this land becomes tapped, draw a card.
|
||||
SVar:TrigDraw:DB$ Draw
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
|
||||
Oracle:Landfall — Whenever a land you control enters, a random land card in your library perpetually gains "Whenever this land becomes tapped, draw a card."
|
||||
@@ -3,6 +3,6 @@ ManaCost:2 U
|
||||
Types:Enchantment Aura
|
||||
K:Enchant:Creature
|
||||
SVar:AttachAILogic:Pump
|
||||
S:Mode$ CantTarget | ValidCard$ Creature.EnchantedBy | ValidSA$ Spell | Description$ Enchanted creature can't be the target of spells.
|
||||
S:Mode$ CantTarget | ValidTarget$ Creature.EnchantedBy | ValidSA$ Spell | Description$ Enchanted creature can't be the target of spells.
|
||||
S:Mode$ CantAttach | ValidCard$ Aura.Other | Target$ Creature.EnchantedBy | Description$ Enchanted creature can't be enchanted by other Auras.
|
||||
Oracle:Enchant creature\nEnchanted creature can't be the target of spells and can't be enchanted by other Auras.
|
||||
|
||||
12
forge-gui/res/cardsfolder/a/arachne_psionic_weaver.txt
Normal file
12
forge-gui/res/cardsfolder/a/arachne_psionic_weaver.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
Name:Arachne, Psionic Weaver
|
||||
ManaCost:2 W
|
||||
Types:Legendary Creature Spider Human Hero
|
||||
PT:3/3
|
||||
K:Web-slinging:W
|
||||
K:ETBReplacement:Other:ChoosePlayer
|
||||
SVar:ChoosePlayer:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ChoiceTitle$ Choose an opponent to look at their hand | SubAbility$ DBLook | SpellDescription$ As NICKNAME enters, look at an opponent's hand, then choose a card type other than creature.
|
||||
SVar:DBLook:DB$ RevealHand | Defined$ ChosenPlayer | Look$ True | SubAbility$ DBChooseCardType
|
||||
SVar:DBChooseCardType:DB$ ChooseType | Defined$ You | Type$ Card | InvalidTypes$ Creature | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearChosenPlayer$ True
|
||||
S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.ChosenType | Type$ Spell | Amount$ 1 | Activator$ Player | Description$ Spells of the chosen type cost {1} more to cast.
|
||||
Oracle:Web-slinging {W} (You may cast this spell for {W} if you also return a tapped creature you control to its owner's hand.)\nAs Arachne enters, look at an opponent's hand, then choose a card type other than creature.\nSpells of the chosen type cost {1} more to cast.
|
||||
@@ -3,6 +3,7 @@ ManaCost:5 G
|
||||
Types:Creature Spider
|
||||
PT:5/7
|
||||
K:Reach
|
||||
A:AB$ ChangeZone | Cost$ tapXType<1/Spider> | Hidden$ True | Origin$ Library | OriginAlternative$ Graveyard | Destination$ Battlefield | ChangeType$ Card.YouOwn+namedArachnus Web | SpellDescription$ Search your graveyard and/or library for a card named Arachnus Web and put it onto the battlefield attached to target creature. If you search your library this way, shuffle.
|
||||
A:AB$ Pump | Cost$ tapXType<1/Spider> | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | SubAbility$ DBChange
|
||||
SVar:DBChange:DB$ ChangeZone | Hidden$ True | Origin$ Library | OriginAlternative$ Graveyard | Destination$ Battlefield | ChangeType$ Card.YouOwn+namedArachnus Web | AttachedTo$ ParentTarget | SpellDescription$ Search your graveyard and/or library for a card named Arachnus Web and put it onto the battlefield attached to target creature. If you search your library this way, shuffle.
|
||||
DeckHints:Name$Arachnus Web & Type$Spider
|
||||
Oracle:Reach\nTap an untapped Spider you control: Search your graveyard and/or library for a card named Arachnus Web and put it onto the battlefield attached to target creature. If you search your library this way, shuffle.
|
||||
|
||||
15
forge-gui/res/cardsfolder/a/arana_heart_of_the_spider.txt
Normal file
15
forge-gui/res/cardsfolder/a/arana_heart_of_the_spider.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
Name:Araña, Heart of the Spider
|
||||
ManaCost:1 R W
|
||||
Types:Legendary Creature Spider Human Hero
|
||||
PT:3/3
|
||||
T:Mode$ AttackersDeclared | AttackingPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack, put a +1/+1 counter on target attacking creature.
|
||||
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature
|
||||
T:Mode$ DamageDone | ValidSource$ Creature.modified+YouCtrl | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigExile | TriggerZones$ Battlefield | TriggerDescription$ Whenever a modified creature you control deals combat damage to a player, exile the top card of your library. You may play that card this turn. (Equipment, Auras you control, and counters are modifications.)
|
||||
SVar:TrigExile:DB$ Dig | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect
|
||||
SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | ExileOnMoved$ Exile | RememberObjects$ Remembered | SubAbility$ DBCleanup
|
||||
SVar:STPlay:Mode$ Continuous | MayPlay$ True | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ Exile the top card of your library. You may play that card this turn.
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:PlayMain1:TRUE
|
||||
DeckHas:Ability$Counters
|
||||
DeckHints:Type$Aura|Equipment
|
||||
Oracle:Whenever you attack, put a +1/+1 counter on target attacking creature.\nWhenever a modified creature you control deals combat damage to a player, exile the top card of your library. You may play that card this turn. (Equipment, Auras you control, and counters are modifications.)
|
||||
@@ -4,7 +4,7 @@ Types:Creature Phoenix
|
||||
PT:3/2
|
||||
K:Flying
|
||||
K:Haste
|
||||
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Graveyard | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigReturn | OptionalDecider$ You | TriggerDescription$ At the beginning of combat on your turn, if you've cast three or more instant and sorcery spells this turn, return CARDNAME from your graveyard to the battlefield.
|
||||
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Graveyard | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigReturn | TriggerDescription$ At the beginning of combat on your turn, if you've cast three or more instant and sorcery spells this turn, return CARDNAME from your graveyard to the battlefield.
|
||||
SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield
|
||||
SVar:X:Count$ThisTurnCast_Instant.YouCtrl,Sorcery.YouCtrl
|
||||
DeckNeeds:Type$Instant|Sorcery
|
||||
|
||||
@@ -5,5 +5,5 @@ K:Enchant:Creature
|
||||
SVar:AttachAILogic:Pump
|
||||
S:Mode$ CantBlockBy | ValidAttacker$ Creature.EnchantedBy | ValidBlocker$ Creature.Artifact | Description$ Enchanted creature can't be blocked by artifact creatures.
|
||||
R:Event$ DamageDone | Prevent$ True | ActiveZones$ Battlefield | ValidTarget$ Creature.EnchantedBy | ValidSource$ Artifact | Description$ Prevent all damage that would be dealt to enchanted creature by artifact sources.
|
||||
S:Mode$ CantTarget | ValidCard$ Card.EnchantedBy | ValidSource$ Artifact | Description$ Enchanted creature can't be the target of abilities from artifact sources.
|
||||
S:Mode$ CantTarget | ValidTarget$ Card.EnchantedBy | ValidSource$ Artifact | Description$ Enchanted creature can't be the target of abilities from artifact sources.
|
||||
Oracle:Enchant creature\nEnchanted creature can't be blocked by artifact creatures.\nPrevent all damage that would be dealt to enchanted creature by artifact sources.\nEnchanted creature can't be the target of abilities from artifact sources.
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:G
|
||||
Types:Instant
|
||||
A:SP$ Effect | ReplacementEffects$ AntiMagic | StaticAbilities$ STCantBeTarget | SpellDescription$ Spells you control can't be countered by blue or black spells this turn, and creatures you control can't be the targets of blue or black spells this turn.
|
||||
SVar:AntiMagic:Event$ Counter | ValidSA$ Spell.YouCtrl | ValidCause$ Spell.Blue,Spell.Black | Layer$ CantHappen | Description$ Spells you control can't be countered by blue or black spells this turn.
|
||||
SVar:STCantBeTarget:Mode$ CantTarget | ValidCard$ Creature.YouCtrl | ValidSource$ Card.Blue,Card.Black | ValidSA$ Spell | Description$ Creatures you control can't be the targets of blue or black spells this turn.
|
||||
SVar:STCantBeTarget:Mode$ CantTarget | ValidTarget$ Creature.YouCtrl | ValidSource$ Card.Blue,Card.Black | ValidSA$ Spell | Description$ Creatures you control can't be the targets of blue or black spells this turn.
|
||||
AI:RemoveDeck:All
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:Spells you control can't be countered by blue or black spells this turn, and creatures you control can't be the targets of blue or black spells this turn.
|
||||
|
||||
10
forge-gui/res/cardsfolder/a/axavar_fate_thief.txt
Normal file
10
forge-gui/res/cardsfolder/a/axavar_fate_thief.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Name:Axavar, Fate Thief
|
||||
ManaCost:2 B R
|
||||
Types:Legendary Creature Drix Pirate
|
||||
PT:4/3
|
||||
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | Execute$ TrigDiscard | TriggerDescription$ Void — At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, discard a card, then heist target opponent's library.
|
||||
SVar:TrigDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | SubAbility$ DBHeist
|
||||
SVar:DBHeist:DB$ Heist | ValidTgts$ Opponent
|
||||
SVar:X:Count$Void.1.0
|
||||
K:Warp:BR
|
||||
Oracle:Void — At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, discard a card, then heist target opponent's library.\nWarp {B/R}
|
||||
8
forge-gui/res/cardsfolder/b/bagel_and_schmear.txt
Normal file
8
forge-gui/res/cardsfolder/b/bagel_and_schmear.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Name:Bagel and Schmear
|
||||
ManaCost:1
|
||||
Types:Artifact Food
|
||||
A:AB$ PutCounter | PreCostDesc$ Share — | Cost$ W T Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw | SorcerySpeed$ True | SpellDescription$ Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery.
|
||||
A:AB$ GainLife | PreCostDesc$ Nosh — | Cost$ 2 T Sac<1/CARDNAME> | LifeAmount$ 3 | SubAbility$ DBDraw | SpellDescription$ You gain 3 life and draw a card.
|
||||
SVar:DBDraw:DB$ Draw
|
||||
DeckHas:Ability$LifeGain|Sacrifice|Counters
|
||||
Oracle:Share — {W}, {T}, Sacrifice this artifact: Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery.\nNosh — {2}, {T}, Sacrifice this artifact: You gain 3 life and draw a card.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user